Example #1
0
 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
Example #2
0
    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}
Example #3
0
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