diff --git a/facho/facho.py b/facho/facho.py index cce1a80..752af73 100644 --- a/facho/facho.py +++ b/facho/facho.py @@ -125,6 +125,11 @@ class FachoXML: self.xpath_for = {} self.extensions = [] + def append_element(self, elem, new_elem): + #elem = self.find_or_create_element(xpath, append=append) + #self.builder.append(elem, new_elem) + self.builder.append(elem, new_elem) + def add_extension(self, extension): root_tag = self.builder.get_tag(self.root) diff --git a/facho/fe/fe.py b/facho/fe/fe.py index c12e2e6..6a79eac 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -54,7 +54,7 @@ class DianXMLExtensionCUFE(FachoXMLExtension): def build(self, fachoxml): cufe = self._generate_cufe(self.invoice, fachoxml) - fachoxml.set_element('/fe:Invoice/cbc:UUID[schemaName="CUFE-SHA384"]', cufe) + fachoxml.set_element('/fe:Invoice/cbc:UUID', cufe, schemeName='CUFE-SHA384') fachoxml.set_element('/fe:Invoice/cbc:ProfileID', 'DIAN 2.1') fachoxml.set_element('/fe:Invoice/cbc:ProfileExecutionID', self._tipo_ambiente()) return '', [] @@ -161,8 +161,6 @@ class DianXMLExtensionSigner(FachoXMLExtension): # return (xpath, xml.Element) def build(self, fachoxml): - dian_path = '/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent' - signature = xmlsig.template.create( xmlsig.constants.TransformInclC14N, xmlsig.constants.TransformRsaSha256, @@ -227,8 +225,13 @@ class DianXMLExtensionSigner(FachoXMLExtension): ctx.verify(signature) #xmlsig take parent root fachoxml.root.remove(signature) + + + ublextension = fachoxml.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True) + extcontent = ublextension.find_or_create_element('/ext:UBLExtension:/ext:ExtensionContent') + fachoxml.append_element(extcontent, signature) - return (dian_path, [signature]) + return '', [] class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension): diff --git a/facho/fe/form.py b/facho/fe/form.py index 00706ab..84b0eac 100644 --- a/facho/fe/form.py +++ b/facho/fe/form.py @@ -56,7 +56,8 @@ class Party: class TaxSubTotal: percent: float tax_scheme_ident: str = '01' - + tax_scheme_name: str = 'IVA' + tax_amount: float = 0.0 taxable_amount: float = 0.0 @@ -99,6 +100,14 @@ class InvoiceLine: def total_tax_exclusive_amount(self): return self.tax.taxable_amount + @property + def tax_amount(self): + return self.tax.tax_amount + + @property + def taxable_amount(self): + return self.tax.taxable_amount + def calculate(self): self.tax.calculate(self) @@ -207,93 +216,120 @@ class DIANInvoiceXML(fe.FeXML): super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') self.attach_invoice(invoice) - def attach_invoice(self, invoice): + def placeholder_for(fexml, xpath): + fexml.find_or_create_element(xpath) + + def set_supplier(fexml, invoice): + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cbc:AdditionalAccountID', + invoice.invoice_supplier.organization_code) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name', + invoice.invoice_supplier.name) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', + invoice.invoice_supplier.address.street) + + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', + invoice.invoice_supplier.ident) + + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', + invoice.invoice_supplier.responsability_code) + + fexml.find_or_create_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', + invoice.invoice_supplier.legal_name) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:AddressLine/cbc:Line', invoice.invoice_supplier.address.street) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:AddressLine/cbc:Line', invoice.invoice_supplier.address.street) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', invoice.invoice_supplier.address.country.code) + fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_supplier.address.country.name) + + def set_customer(fexml, invoice): + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cbc:AdditionalAccountID', + invoice.invoice_customer.organization_code) + + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name', + invoice.invoice_customer.name) + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', + invoice.invoice_customer.address.street) + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', + invoice.invoice_customer.ident) + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', + invoice.invoice_customer.responsability_code) + fexml.find_or_create_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') + + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', + invoice.invoice_customer.legal_name) + + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cbc:CityName', invoice.invoice_customer.address.city) + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:AddressLine/cbc:Line', invoice.invoice_customer.address.street) + + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:AddressLine/cbc:Line', invoice.invoice_customer.address.street) + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', invoice.invoice_customer.address.country.code) + fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name) + + def set_legal_monetary(fexml, invoice): + fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:LineExtensionAmount', + invoice.invoice_legal_monetary_total.line_extension_amount, + currencyID='COP') + fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', + invoice.invoice_legal_monetary_total.tax_exclusive_amount, + currencyID='COP') + fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', + invoice.invoice_legal_monetary_total.tax_inclusive_amount, + currencyID='COP') + fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:ChargeTotalAmount', + invoice.invoice_legal_monetary_total.charge_total_amount, + currencyID='COP') + fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount', + invoice.invoice_legal_monetary_total.payable_amount, + currencyID='COP') + + def set_invoice_lines(fexml, invoice): + next_append = False + for index, invoice_line in enumerate(invoice.invoice_lines): + line = fexml.fragment('/fe:Invoice/cac:InvoiceLine', append=next_append) + next_append = True + + line.set_element('/cac:InvoiceLine/cbc:ID', index + 1) + line.set_element('/cac:InvoiceLine/cbc:InvoicedQuantity', invoice_line.quantity, unitCode = 'NAR') + line.set_element('/cac:InvoiceLine/cbc:LineExtensionAmount', invoice_line.total_amount, currencyID="COP") + line.set_element('/cac:InvoiceLine/cac:TaxTotal/cbc:TaxAmount', invoice_line.tax_amount, currencyID='COP') + for subtotal in invoice_line.tax.subtotals: + line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', subtotal.taxable_amount, currencyID='COP') + line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') + line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', subtotal.percent) + line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.tax_scheme_ident) + line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.tax_scheme_name) + line.set_element('/cac:InvoiceLine/cac:Item/cbc:Description', invoice_line.item.description) + # TODO + line.set_element('/cac:InvoiceLine/cac:Item/cac:StandardItemIdentification/cbc:ID', invoice_line.item.id) + line.set_element('/cac:InvoiceLine/cac:Price/cbc:PriceAmount', invoice_line.price_amount, currencyID="COP") + + def attach_invoice(fexml, invoice): """adiciona etiquetas a FEXML y retorna FEXML en caso de fallar validacion retorna None""" - fexml = self invoice.calculate() - + fexml.placeholder_for('/fe:Invoice/ext:UBLExtensions') + fexml.set_element('/fe:Invoice/cbc:UBLVersionID', 'UBL 2.1') + fexml.placeholder_for('/fe:Invoice/cbc:ProfileID') + fexml.placeholder_for('/fe:Invoice/cbc:ProfileExecutionID') + fexml.set_element('/fe:Invoice/cbc:ID', invoice.invoice_ident) + fexml.placeholder_for('/fe:Invoice/cbc:UUID') + + fexml.set_element('/fe:Invoice/cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) + fexml.set_element('/fe:Invoice/cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S%z')) fexml.set_element('/fe:Invoice/cbc:InvoiceTypeCode', codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'], listAgencyID='195', listAgencyName='No matching global declaration available for the validation root', listURI='http://www.dian.gov.co') - fexml.set_element('/fe:Invoice/cbc:UBLVersionID', 'UBL 2.1') - fexml.set_element('/fe:Invoice/cbc:ID', invoice.invoice_ident) - fexml.set_element('/fe:Invoice/cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) - fexml.set_element('/fe:Invoice/cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S%z')) + fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines)) fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:StartDate', invoice.invoice_period_start.strftime('%Y-%m-%d')) fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:EndDate', invoice.invoice_period_end.strftime('%Y-%m-%d')) - fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines)) + fexml.set_supplier(invoice) + fexml.set_customer(invoice) + fexml.set_legal_monetary(invoice) + fexml.set_invoice_lines(invoice) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/cac:PartyIdentification/cbc:ID', - invoice.invoice_supplier.ident) - fexml.set_element('/Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', - invoice.invoice_supplier.ident) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyTaxScheme/cbc:TaxLevelCode', - invoice.invoice_supplier.responsability_code) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/cbc:AdditionalAccountID', - invoice.invoice_supplier.organization_code) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/cac:PartyName/cbc:Name', - invoice.invoice_supplier.name) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationName', - invoice.invoice_supplier.legal_name) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:AddressLine/cbc:Line', invoice.invoice_supplier.address.street) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:Country/cbc:IdentificationCode', invoice.invoice_supplier.address.country.code) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:Country/cbc:name', invoice.invoice_supplier.address.country.name) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:Country/cac:AddressLine/cbc:Line', invoice.invoice_supplier.address.street) - fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PhysicalLocation/fe:Address/cac:AddressLine/cbc:Line', - invoice.invoice_supplier.address.street) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/cac:PartyIdentification/cbc:ID', - invoice.invoice_customer.ident) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyTaxScheme/cbc:TaxLevelCode', - invoice.invoice_customer.responsability_code) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/cbc:AdditionalAccountID', - invoice.invoice_customer.organization_code) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/cac:PartyName/cbc:Name', - invoice.invoice_customer.name) - fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', - invoice.invoice_customer.ident) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationName', - invoice.invoice_customer.legal_name) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cbc:CityName', invoice.invoice_customer.address.city) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:Country/cbc:IdentificationCode', invoice.invoice_customer.address.country.code) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:AddressLine/cbc:Line', invoice.invoice_customer.address.street) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PhysicalLocation/fe:Address/cac:AddressLine/cbc:Line', - invoice.invoice_customer.address.street) - fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationAddress/cac:Country/cac:AddressLine/cbc:Line', invoice.invoice_customer.address.street) - - fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:LineExtensionAmount', - invoice.invoice_legal_monetary_total.line_extension_amount, - currencyID='COP') - fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:TaxExclusiveAmount', - invoice.invoice_legal_monetary_total.tax_exclusive_amount, - currencyID='COP') - fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:TaxInclusiveAmount', - invoice.invoice_legal_monetary_total.tax_inclusive_amount, - currencyID='COP') - fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:ChargeTotalAmount', - invoice.invoice_legal_monetary_total.charge_total_amount, - currencyID='COP') - fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:PayableAmount', - invoice.invoice_legal_monetary_total.payable_amount, - currencyID='COP') - - fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines)) - next_append = False - for index, invoice_line in enumerate(invoice.invoice_lines): - line = fexml.fragment('/fe:Invoice/fe:InvoiceLine', append=next_append) - next_append = True - - line.set_element('/fe:InvoiceLine/cbc:ID', index + 1) - line.set_element('/fe:InvoiceLine/cbc:InvoicedQuantity', invoice_line.quantity, unitCode = 'NAR') - line.set_element('/fe:InvoiceLine/cbc:LineExtensionAmount', invoice_line.total_amount, currencyID="COP") - line.set_element('/fe:InvoiceLine/fe:Price/cbc:PriceAmount', invoice_line.price_amount, currencyID="COP") - line.set_element('/fe:InvoiceLine/fe:Item/cbc:Description', invoice_line.item.description) - # TODO - line.set_element('/fe:InvoiceLine/fe:Item/cac:StandardItemIdentification/cbc:ID', invoice_line.item.id) return fexml