def validate(self): # check company settigs company_address = get_primary_address(target_name=self.company, target_type="Company") if (not company_address or not company_address.address_line1 or not company_address.pincode or not company_address.city): frappe.throw(_("Company address missing or incomplete.")) if self.pay_from_account: payment_account = frappe.get_doc('Account', self.pay_from_account) if not payment_account.iban: frappe.throw(_("IBAN missing in pay from account.")) # perform some checks to improve file quality/stability for purchase_invoice in self.purchase_invoices: pinv = frappe.get_doc("Purchase Invoice", purchase_invoice.purchase_invoice) # check addresses (mandatory in ISO 20022 if not pinv.supplier_address: frappe.throw( _("Address missing for purchase invoice <a href=\"/desk#Form/Purchase Invoice/{0}\">{0}</a>" ).format(pinv.name)) # check target account info if purchase_invoice.payment_type == "ESR": if not purchase_invoice.esr_reference or not purchase_invoice.esr_participation_number: frappe.throw( _("ESR: missing transaction information (participant number or reference) in <a href=\"/desk#Form/Purchase Invoice/{0}\">{0}</a>" ).format(pinv.name)) else: supl = frappe.get_doc("Supplier", pinv.supplier) if not supl.iban: frappe.throw( _("Missing IBAN for purchase invoice <a href=\"/desk#Form/Purchase Invoice/{0}\">{0}</a>" ).format(pinv.name)) # check expense records for expense_claim in self.expenses: emp = frappe.get_doc("Employee", expense_claim.employee) if not emp.bank_ac_no: frappe.throw( _("Employee <a href=\"/desk#Form/Employee/{0}\">{0}</a> has no bank account number." ).format(emp.name)) return
def create_bank_file(self): data = {} data['xml_version'] = frappe.get_value("ERPNextSwiss Settings", "ERPNextSwiss Settings", "xml_version") data['xml_region'] = frappe.get_value("ERPNextSwiss Settings", "ERPNextSwiss Settings", "banking_region") data['msgid'] = "MSG-" + time.strftime( "%Y%m%d%H%M%S") # message ID (unique, SWIFT-characters only) data['date'] = time.strftime( "%Y-%m-%dT%H:%M:%S" ) # creation date and time ( e.g. 2010-02-15T07:30:00 ) # number of transactions in the file transaction_count = 0 # total amount of all transactions ( e.g. 15850.00 ) (sum of all amounts) control_sum = 0.0 # define company address data['company'] = {'name': self.company} company_address = get_primary_address(target_name=self.company, target_type="Company") if company_address: data['company']['address_line1'] = cgi.escape( company_address.address_line1) data['company']['address_line2'] = "{0} {1}".format( cgi.escape(company_address.pincode), cgi.escape(company_address.city)) data['company']['country_code'] = company_address['country_code'] # crop lines if required (length limitation) data['company']['address_line1'] = data['company'][ 'address_line1'][:35] data['company']['address_line2'] = data['company'][ 'address_line2'][:35] ### Payment Information (PmtInf, B-Level) # payment information records (1 .. 99'999) payment_account = frappe.get_doc('Account', self.pay_from_account) if not payment_account: frappe.throw( _("{0}: no account IBAN found ({1})".format( payment.references, self.pay_from_account))) data['company']['iban'] = "{0}".format( payment_account.iban.replace(" ", "")) data['company']['bic'] = payment_account.bic data['payments'] = [] for payment in self.payments: payment_content = "" payment_record = { 'id': "PMTINF-{0}-{1}".format( self.name, transaction_count ), # unique (in this file) identification for the payment ( e.g. PMTINF-01, PMTINF-PE-00005 ) 'method': "TRF", # payment method (TRF or TRA, no impact in Switzerland) 'batch': "true", # batch booking (true or false; recommended true) 'required_execution_date': "{0}".format( payment.execution_date.split(" ")[0] ), # Requested Execution Date (e.g. 2010-02-22, remove time element) 'debtor': { # debitor (technically ignored, but recommended) 'name': cgi.escape(self.company), 'account': "{0}".format(payment_account.iban.replace(" ", "")), 'bic': "{0}".format(payment_account.bic) }, 'instruction_id': "INSTRID-{0}-{1}".format( self.name, transaction_count), # instruction identification 'end_to_end_id': "{0}".format( (payment.reference[:33] + '..' ) if len(payment.reference) > 35 else payment.reference ), # end-to-end identification (should be used and unique within B-level; payment entry name) 'currency': payment.currency, 'amount': round(payment.amount, 2), 'creditor': { 'name': cgi.escape(payment.receiver), 'address_line1': cgi.escape(payment.receiver_address_line1[:35]), 'address_line2': cgi.escape(payment.receiver_address_line2[:35]), 'country_code': frappe.get_value("Country", payment.receiver_country, "code").upper() } } if payment.payment_type == "SEPA": # service level code (e.g. SEPA) payment_record['service_level'] = "SEPA" payment_record['iban'] = payment.iban.replace(" ", "") payment_record['reference'] = payment.reference elif payment.payment_type == "ESR": # proprietary (nothing or CH01 for ESR) payment_record['local_instrument'] = "CH01" payment_record[ 'service_level'] = "ESR" # only internal information payment_record[ 'esr_participation_number'] = payment.esr_participation_number payment_record[ 'esr_reference'] = payment.esr_reference.replace(" ", "") else: payment_record['service_level'] = "IBAN" payment_record['iban'] = payment.iban.replace(" ", "") payment_record['reference'] = payment.reference # once the payment is extracted for payment, submit the record transaction_count += 1 control_sum += round(payment.amount, 2) data['payments'].append(payment_record) data['transaction_count'] = transaction_count data['control_sum'] = control_sum # render file content = frappe.render_template( 'erpnextswiss/erpnextswiss/doctype/payment_proposal/pain-001.html', data) return {'content': content}
def create_zugferd_xml(sales_invoice, verify=True): try: # get original document sinv = frappe.get_doc("Sales Invoice", sales_invoice) company = frappe.get_doc("Company", sinv.company) # compile notes notes = [] if sinv.terms: notes.append({ 'text': cgi.escape(BeautifulSoup(sinv.terms, "lxml").text or "") }) if hasattr(sinv, 'eingangstext') and sinv.eingangstext: notes.append({ 'text': cgi.escape( BeautifulSoup(sinv.eingangstext, "lxml").text or "") }) if len(notes) == 0: notes.append({ 'text': cgi.escape("Sales Invoice {title} ({number}), {date}".format( title=sinv.title, number=sinv.name, date=sinv.posting_date)) }) # compile xml content data = { 'name': cgi.escape(sinv.name), 'issue_date': "{year:04d}{month:02d}{day:02d}".format( year=sinv.posting_date.year, month=sinv.posting_date.month, day=sinv.posting_date.day), 'notes': notes, 'company': cgi.escape(sinv.company), 'tax_id': cgi.escape(company.tax_id or ""), 'customer': cgi.escape(sinv.customer), 'customer_name': cgi.escape(sinv.customer_name), 'currency': sinv.currency, 'payment_terms': cgi.escape(sinv.payment_terms_template), 'due_date': "{year:04d}{month:02d}{day:02d}".format(year=sinv.due_date.year, month=sinv.due_date.month, day=sinv.due_date.day), 'total': sinv.total, 'discount': (sinv.total - sinv.net_total), 'net_total': sinv.net_total, 'total_tax': sinv.total_taxes_and_charges, 'grand_total': (sinv.rounded_total or sinv.grand_total), 'prepaid_amount': ((sinv.rounded_total or sinv.grand_total) - sinv.outstanding_amount), 'outstanding_amount': sinv.outstanding_amount } data['items'] = [] for item in sinv.items: item_data = { 'idx': item.idx, 'item_code': cgi.escape(item.item_code), 'item_name': cgi.escape(item.item_name), 'barcode': item.barcode, 'price_list_rate': item.price_list_rate, 'rate': item.rate, 'unit_code': get_unit_code(item.uom), 'qty': item.qty, 'amount': item.amount } data['items'].append(item_data) if sinv.taxes and sinv.taxes[0].rate: data['overall_tax_rate_percent'] = sinv.taxes[0].rate data['taxes'] = [] for tax in sinv.taxes: tax_data = { 'tax_amount': tax.tax_amount, 'net_amount': (tax.total - tax.tax_amount), 'rate': tax.rate } data['taxes'].append(tax_data) else: data['overall_tax_rate_percent'] = 0 company_address = get_primary_address(target_name=sinv.company, target_type="Company") if company_address: data['company_address'] = { 'address_line1': cgi.escape(company_address.address_line1 or ""), 'address_line2': cgi.escape(company_address.address_line2 or ""), 'pincode': cgi.escape(company_address.pincode or ""), 'city': cgi.escape(company_address.city or ""), 'country_code': company_address['country_code'] or "CH" } else: data['company_address'] = { 'address_line1': "", 'address_line2': "", 'pincode': "", 'city': "", 'country_code': "CH" } customer_address = frappe.get_doc("Address", sinv.customer_address) if customer_address: customer_country_code = frappe.get_value("Country", customer_address.country, "code").upper() data['customer_address'] = { 'address_line1': cgi.escape(customer_address.address_line1 or ""), 'address_line2': cgi.escape(customer_address.address_line2 or ""), 'pincode': cgi.escape(customer_address.pincode or ""), 'city': cgi.escape(customer_address.city or ""), 'country_code': customer_country_code or "CH" } else: data['customer_address'] = { 'address_line1': "", 'address_line2': "", 'pincode': "", 'city': "", 'country_code': "CH" } xml = frappe.render_template( 'erpnextswiss/erpnextswiss/zugferd/en16931.html', data) # verify the generated xml if verify: try: if not check_facturx_xsd(facturx_xml=xml.encode('utf-8')): frappe.log_error( _("XML validation failed for {0}").format( sales_invoice), "ZUGFeRD") return None except Exception as err: frappe.log_error( "XML validation error ({2}): {0}\n{1}".format( err, xml, sales_invoice), "ZUGFeRD XSD validation") return xml except Exception as err: frappe.log_error( "Failure during XML generation for {1}: {0}".format( err, sales_invoice), "ZUGFeRD") return None