From bd0fe70f33f22ceb965aefd3df63c62684b68037 Mon Sep 17 00:00:00 2001 From: sinergia <> Date: Sat, 27 Aug 2022 22:43:16 +0000 Subject: [PATCH] xml support document FossilOrigin-Name: 4d1f4e81f2527a0c4ba735f4f5edc8bc8dfebff2365e8e2ee91d4cd435acecf8 --- .../data/dian/codelist/TipoDocumento-2.1.gc | 194 ++++--- facho/fe/fe.py | 43 +- facho/fe/form/__init__.py | 7 + facho/fe/form_xml/__init__.py | 1 + facho/fe/form_xml/support_document.py | 509 ++++++++++++++++++ 5 files changed, 679 insertions(+), 75 deletions(-) diff --git a/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc b/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc index 80c7b96..c420bba 100644 --- a/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc +++ b/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc @@ -1,74 +1,122 @@ - - - - - TipoDocumento - Tipo de Documento - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoDocumento-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - Codigo Comun - - - - Name - Nombre - - - - CodeKey - - - - - - - 01 - - - Factura de Venta Nacional - - - - - 02 - - - Factura de Exportación - - - - - 03 - - - Factura de Contingencia - - - - - 91 - - - Nota Crédito - - - - - 92 - - - Nota Débito - - - + + + + + TipoDocumento + Tipo de Documento + 1 + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento-2.1 + http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoDocumento-2.1.gc + + DIAN (Dirección de Impuestos y Aduanas Nacionales) + 195 + + + + + Code + Codigo Comun + + + + Name + Nombre + + + + CodeKey + + + + + + + 01 + + + Factura electrónica de Venta + + + Tipos de factura + + + + + 02 + + + Factura electrónica de venta con propósito de exportación + + + Tipos de factura + + + + + 03 + + + Factura de talonario o papel con numeración de contingencia. + + + Tipos de factura + + + + + 04 + + + Factura electrónica de Venta por Contingencia DIAN + + + Tipos de factura + + + + + 91 + + + Nota Crédito + + + Exclusivo en referencias a documentos (elementos DocumentReference) + + + + + 92 + + + Nota Débito + + + Exclusivo en referencias a documentos (elementos DocumentReference) + + + + + 05 + + + Documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente + + + Tipo de documento + + + + + 95 + + + Nota de ajuste al documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente + + + Exclusivo en referencias a documentos (elementos DocumentReference) + + + diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 0e15c53..3f7afdd 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -122,8 +122,14 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): fachoxml.set_element('./cbc:UUID', cufe, schemeID=self.tipo_ambiente, schemeName=self.schemeName()) - #DIAN 1.8.-2021: FAD03 - fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') + + if self.schemeName() == "CUDS-SHA384": + fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: documento soporte en adquisiciones efectuadas a no obligados a facturar.') + else: + fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') + + # #DIAN 1.8.-2021: FAD03 + # fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') fachoxml.set_element('./cbc:ProfileExecutionID', self._tipo_ambiente_int()) #DIAN 1.7.-2020: FAB36 fachoxml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', @@ -246,6 +252,39 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): '%d' % build_vars['TipoAmb'], ] +class DianXMLExtensionCUDS(DianXMLExtensionCUDFE): + def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS): + self.tipo_ambiente = tipo_ambiente + self.software_pin = software_pin + self.invoice = invoice + + def schemeName(self): + return 'CUDS-SHA384' + + def buildVars(self): + build_vars = super().buildVars() + build_vars['Software-PIN'] = str(self.software_pin) + return build_vars + + def formatVars(self): + build_vars = self.buildVars() + CodImpuesto1 = build_vars['CodImpuesto1'] + CodImpuesto2 = build_vars['CodImpuesto2'] + CodImpuesto3 = build_vars['CodImpuesto3'] + return [ + '%s' % build_vars['NumFac'], + '%s' % build_vars['FecFac'], + '%s' % build_vars['HoraFac'], + form.Amount(build_vars['ValorBruto']).truncate_as_string(2), + CodImpuesto1, + form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2), + form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2), + '%s' % build_vars['NitOFE'], + '%s' % build_vars['NumAdq'], + '%s' % build_vars['Software-PIN'], + '%d' % build_vars['TipoAmb'], + ] + class DianXMLExtensionSoftwareProvider(FachoXMLExtension): # RESOLUCION 0004: pagina 108 diff --git a/facho/fe/form/__init__.py b/facho/fe/form/__init__.py index 0aa6318..e6838a6 100644 --- a/facho/fe/form/__init__.py +++ b/facho/fe/form/__init__.py @@ -210,6 +210,10 @@ class City: raise ValueError("code [%s] not found" % (self.code)) self.name = codelist.Municipio[self.code]['name'] +@dataclass +class PostalZone: + code: str = '' + @dataclass class Address: name: str @@ -217,6 +221,7 @@ class Address: city: City = City('05001') country: Country = Country('CO') countrysubentity: CountrySubentity = CountrySubentity('05') + postalzone: PostalZone = PostalZone('') @dataclass class PartyIdentification: @@ -735,3 +740,5 @@ class DebitNote(Invoice): if not self.invoice_ident_prefix: self.invoice_ident_prefix = self.invoice_ident[0:6] +class SupportDocument(Invoice): + pass diff --git a/facho/fe/form_xml/__init__.py b/facho/fe/form_xml/__init__.py index b7530fa..2dac750 100644 --- a/facho/fe/form_xml/__init__.py +++ b/facho/fe/form_xml/__init__.py @@ -3,3 +3,4 @@ from .credit_note import * from .debit_note import * from .utils import * from .attached_document import * +from .support_document import * diff --git a/facho/fe/form_xml/support_document.py b/facho/fe/form_xml/support_document.py index e69de29..ae0aba2 100644 --- a/facho/fe/form_xml/support_document.py +++ b/facho/fe/form_xml/support_document.py @@ -0,0 +1,509 @@ +from .. import fe +from ..form import * +from datetime import datetime, date +from .attached_document import * + +__all__ = ['DIANInvoiceSupportDocumentXML'] + +class DIANInvoiceSupportDocumentXML(fe.FeXML): + """ + DianSupportDocumentXML mapea objeto form.Invoice a XML segun + lo indicado para él Documento soporte en adquisiciones efectuadas con sujetos no obligados a expedir factura de venta o documento equivalente. + """ + + def __init__(self, invoice, tag_document = 'Invoice'): + super().__init__(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1') + + #DIAN 1.1.-2021: DSAB03 + #DIAN 1.1.-2021: NSAB03 + self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl') + + #DIAN 1.1.-2021: DSAB13 + #DIAN 1.1.-2021: NSAB13 + self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource') + + #DIAN 1.1.-2021: DSAB18 + #DIAN 1.1.-2021: NSAB18 + self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') + + #DIAN 1.1.-2021: DSAB27 + #DIAN 1.1.-2021: NSAB27 + self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode') + + #DIAN 1.1.-2021: DSAB30 DSAB31 + #DIAN 1.1.-2021: NSAB30 NSAB31 + self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID') + + #ZE02 se requiere existencia para firmar + #DIAN 1.1.-2021: DSAA02 DSAB01 + #DIAN 1.1.-2021: NSAA02 NSAB01 + ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True) + #DIAN 1.1.-2021: DSAB02 + #DIAN 1.1.-2021: NSAB02 + extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent') + self.attach_invoice(invoice) + + def set_supplier(fexml, invoice): + #DIAN 1.1.-2021: DSAJ01 + #DIAN 1.1.-2021: NSAB01 + fexml.placeholder_for('./cac:AccountingSupplierParty') + + #DIAN 1.1.-2021: DSAJ02 + #DIAN 1.1.-2021: NSAJ02 + fexml.set_element('./cac:AccountingSupplierParty/cbc:AdditionalAccountID', + invoice.invoice_supplier.organization_code) + + #DIAN 1.1.-2021: DSAJ07 DSAJ08 + #DIAN 1.1.-2021: NSAJ07 NSAJ08 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') + + #DIAN 1.1.-2021: DSAJ09 + #DIAN 1.1.-2021: NSAJ09 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', + invoice.invoice_supplier.address.city.code) + + #DIAN 1.1.-2021: DSAJ10 + #DIAN 1.1.-2021: NSAJ10 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', + invoice.invoice_supplier.address.city.name) + + #DIAN 1.1.-2021: DSAJ73 + #DIAN 1.1.-2021: NSAJ73 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:PostalZone', + invoice.invoice_supplier.address.postalzone.code) + + #DIAN 1.1.-2021: DSAJ11 + #DIAN 1.1.-2021: NSAJ11 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', + invoice.invoice_supplier.address.countrysubentity.name) + + #DIAN 1.1.-2021: DSAJ12 + #DIAN 1.1.-2021: NSAJ12 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', + invoice.invoice_supplier.address.countrysubentity.code) + + #DIAN 1.1.-2021: DSAJ13 DSAJ14 + #DIAN 1.1.-2021: NSAJ13 NSAJ14 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', + invoice.invoice_supplier.address.street) + + #DIAN 1.1.-2021: DSAJ15 DSAJ16 + #DIAN 1.1.-2021: NSAJ15 NSAJ16 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', + invoice.invoice_supplier.address.country.code) + + #DIAN 1.1.-2021: DSAJ17 + #DIAN 1.1.-2021: NSAJ17 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', + invoice.invoice_supplier.address.country.name, + #DIAN 1.1.-2021: DSAJ18 + #DIAN 1.1.-2021: NSAJ18 + languageID = 'es') + + + supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() + supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv, + 'schemeName': invoice.invoice_supplier.ident.type_fiscal}) + + #DIAN 1.1.-2021: DSAJ19 + #DIAN 1.1.-2021: NSAJ19 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme') + + #DIAN 1.1.-2021: DSAJ20 + #DIAN 1.1.-2021: NSAJ20 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', + invoice.invoice_supplier.legal_name) + + #DIAN 1.1.-2021: DSAJ21 + #DIAN 1.1.-2021: NSAJ21 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', + invoice.invoice_supplier.ident, + #DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25 + #DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25 + **supplier_company_id_attrs) + + #DIAN 1.1.-2021: DSAJ26 + #DIAN 1.1.-2021: NSAJ26 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', + invoice.invoice_supplier.responsability_code, + listName=invoice.invoice_supplier.responsability_regime_code) + + #DIAN 1.1.-2021: DSAJ39 + #DIAN 1.1.-2021: NSAJ39 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') + + #DIAN 1.1.-2021: DSAJ40 + #DIAN 1.1.-2021: NSAJ40 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', + invoice.invoice_customer.tax_scheme.code) + + #DIAN 1.1.-2021: DSAJ41 + #DIAN 1.1.-2021: NSAJ41 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', + invoice.invoice_customer.tax_scheme.name) + + + def set_customer(fexml, invoice): + #DIAN 1.1.-2021: DSAK01 + #DIAN 1.1.-2021: NSAK01 + fexml.placeholder_for('./cac:AccountingCustomerParty') + + #DIAN 1.1.-2021: DSAK02 + #DIAN 1.1.-2021: NSAK02 + fexml.set_element('./cac:AccountingCustomerParty/cbc:AdditionalAccountID', + invoice.invoice_customer.organization_code) + + #DIAN 1.1.-2021: DSAK03 + #DIAN 1.1.-2021: NSAK03 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party') + + #DIAN 1.1.-2021: DSAK19 + #DIAN 1.1.-2021: NSAK19 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme') + + #DIAN 1.1.-2021: DSAK20 + #DIAN 1.1.-2021: NSAK20 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', + invoice.invoice_customer.legal_name) + + customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() + customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv, + 'schemeName': invoice.invoice_customer.ident.type_fiscal}) + + #DIAN 1.1.-2021: DSAK21 + #DIAN 1.1.-2021: NSAK21 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', + invoice.invoice_customer.ident, + #DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25 + #DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25 + **customer_company_id_attrs) + + #DIAN 1.1.-2021: DSAK26 + #DIAN 1.1.-2021: NSAK26 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', + invoice.invoice_customer.responsability_code) + + #DIAN 1.1.-2021: DSAK39 + #DIAN 1.1.-2021: NSAK39 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') + + #DIAN 1.1.-2021: DSAK40 + #DIAN 1.1.-2021: NSAK40 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', + invoice.invoice_customer.tax_scheme.code) + + #DIAN 1.1.-2021: DSAK41 + #DIAN 1.1.-2021: NSAK41 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', + invoice.invoice_customer.tax_scheme.name) + + + def set_payment_mean(fexml, invoice): + payment_mean = invoice.invoice_payment_mean + + #DIAN 1.1.-2021: DSAN01 DSAN02 + #DIAN 1.1.-2021: NSAN02 NSAN02 + fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id) + + #DIAN 1.1.-2021: DSAN03 + #DIAN 1.1.-2021: NSAN03 + fexml.set_element('./cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code) + + #DIAN 1.1.-2021: DSAN04 + #DIAN 1.1.-2021: NSAN04 + fexml.set_element('./cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d')) + + #DIAN 1.1.-2021: DSAN05 + #DIAN 1.1.-2021: NSAN05 + fexml.set_element('./cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id) + + def set_element_amount_for(fexml, xml, xpath, amount): + if not isinstance(amount, Amount): + raise TypeError("amount not is Amount") + + xml.set_element(xpath, amount, currencyID=amount.currency.code) + + def set_element_amount(fexml, xpath, amount): + if not isinstance(amount, Amount): + raise TypeError("amount not is Amount") + + fexml.set_element(xpath, amount, currencyID=amount.currency.code) + + def set_legal_monetary(fexml, invoice): + #DIAN 1.1.-2021: DSAU01 DSAU02 DSAU03 + #DIAN 1.1.-2021: NSAU01 NSAU02 NSAU03 + fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:LineExtensionAmount', + invoice.invoice_legal_monetary_total.line_extension_amount) + + #DIAN 1.1.-2021: DSAU04 DSAU05 + #DIAN 1.1.-2021: NSAU04 NSAU05 + fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', + invoice.invoice_legal_monetary_total.tax_exclusive_amount) + + #DIAN 1.1.-2021: DSAU06 DSAU07 + #DIAN 1.1.-2021: NSAU06 DSAU07 + fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', + invoice.invoice_legal_monetary_total.tax_inclusive_amount) + + #DIAN 1.1.-2021: DSAU10 DSAU11 + #DIAN 1.1.-2021: NSAU10 DSAU11 + fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:ChargeTotalAmount', + invoice.invoice_legal_monetary_total.charge_total_amount) + + #DIAN 1.1.-2021: DSAU14 DSAU15 + #DIAN 1.1.-2021: NSAU14 DSAU15 + fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:PayableAmount', + invoice.invoice_legal_monetary_total.payable_amount) + + + def _set_invoice_document_reference(fexml, reference): + fexml._do_set_billing_reference(reference, 'cac:InvoiceDocumentReference') + + def _set_credit_note_document_reference(fexml, reference): + fexml._do_set_billing_reference(reference, 'cac:CreditNoteDocumentReference') + + def _set_debit_note_document_reference(fexml, reference): + fexml._do_set_billing_reference(reference, 'cac:DebitNoteDocumentReference') + + def _do_set_billing_reference(fexml, reference, tag_document): + + if tag_document == 'Invoice': + schemeName = 'CUFE-SHA384' + else: + schemeName = 'CUDS-SHA384' + + fexml.set_element('./cac:BillingReference/%s/cbc:ID' %(tag_document), + reference.ident) + fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID', + reference.uuid, + schemeName=schemeName) + fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate', + reference.date.strftime("%Y-%m-%d")) + + def set_billing_reference(fexml, invoice): + reference = invoice.invoice_billing_reference + if reference is None: + return + + if isinstance(reference, DebitNoteDocumentReference): + return fexml._set_debit_note_document_reference(reference) + if isinstance(reference, CreditNoteDocumentReference): + return fexml._set_credit_note_document_reference(reference) + + if isinstance(reference, InvoiceDocumentReference): + return fexml._set_invoice_document_reference(reference) + + def set_discrepancy_response(fexml, invoice): + reference = invoice.invoice_discrepancy_response + if reference is None: + return + + if isinstance(reference, DebitNoteDocumentReference): + return fexml._set_debit_note_document_reference(reference) + if isinstance(reference, CreditNoteDocumentReference): + return fexml._set_credit_note_document_reference(reference) + + if isinstance(reference, InvoiceDocumentReference): + return fexml._set_invoice_document_reference(reference) + + fexml.set_element('./cac:DiscrepancyResponse/cbc:ReferenceID', + reference.ident) + fexml.set_element('./cac:DiscrepancyResponse/cbc:ResponseCode:UUID', + '1') + fexml.set_element('./cac:DiscrepancyResponse/cbc:Description', + 'Test') + + def set_invoice_totals(fexml, invoice): + tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) + percent_for = defaultdict(lambda: None) + + total_tax_amount = Amount(0.0) + + for invoice_line in invoice.invoice_lines: + for subtotal in invoice_line.tax.subtotals: + if subtotal.scheme is not None: + tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount + tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount + + # MACHETE ojo InvoiceLine.tax pasar a Invoice + percent_for[subtotal.scheme.code] = subtotal.percent + + total_tax_amount += subtotal.tax_amount + + if total_tax_amount != Amount(0.0): + fexml.placeholder_for('./cac:TaxTotal') + fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', + total_tax_amount) + + + for index, item in enumerate(tax_amount_for.items()): + cod_impuesto, amount_of = item + next_append = index > 0 + + #DIAN 1.7.-2020: FAS01 + line = fexml.fragment('./cac:TaxTotal', append=next_append) + #DIAN 1.7.-2020: FAU06 + tax_amount = amount_of['tax_amount'] + fexml.set_element_amount_for(line, + '/cac:TaxTotal/cbc:TaxAmount', + tax_amount) + + #DIAN 1.7.-2020: FAS05 + fexml.set_element_amount_for(line, + '/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', + amount_of['taxable_amount']) + + #DIAN 1.7.-2020: FAU06 + fexml.set_element_amount_for(line, + '/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', + amount_of['tax_amount']) + + #DIAN 1.7.-2020: FAS07 + if percent_for[cod_impuesto]: + line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent', + percent_for[cod_impuesto]) + + + if percent_for[cod_impuesto]: + line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', + percent_for[cod_impuesto]) + + line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', + cod_impuesto) + line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', + 'IVA') + + # abstract method + def tag_document(fexml): + return 'Invoice' + + # abstract method + def tag_document_concilied(fexml): + return 'Invoiced' + + def set_invoice_line_tax(fexml, line, invoice_line): + fexml.set_element_amount_for(line, + './cac:TaxTotal/cbc:TaxAmount', + invoice_line.tax_amount) + + #DIAN 1.7.-2020: FAX05 + fexml.set_element_amount_for(line, + './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', + invoice_line.taxable_amount) + for subtotal in invoice_line.tax.subtotals: + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') + + if subtotal.percent is not None: + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2)) + + if subtotal.scheme is not None: + #DIAN 1.7.-2020: FAX15 + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) + + def set_invoice_lines(fexml, invoice): + next_append = False + for index, invoice_line in enumerate(invoice.invoice_lines): + line = fexml.fragment('./cac:%sLine' % (fexml.tag_document()), append=next_append) + next_append = True + + line.set_element('./cbc:ID', index + 1) + line.set_element('./cbc:%sQuantity' % (fexml.tag_document_concilied()), invoice_line.quantity, unitCode = 'NAR') + fexml.set_element_amount_for(line, + './cbc:LineExtensionAmount', + invoice_line.total_amount) + + period = line.fragment('./cac:InvoicePeriod') + period.set_element('./cbc:StartDate', + datetime.now().strftime('%Y-%m-%d')) + period.set_element('./cbc:DescriptionCode', + '1') + period.set_element('./cbc:Description', + 'Por operación') + + if not isinstance(invoice_line.tax, TaxTotalOmit): + fexml.set_invoice_line_tax(line, invoice_line) + + line.set_element('./cac:Item/cbc:Description', invoice_line.item.description) + + line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', + invoice_line.item.id, + schemeID=invoice_line.item.scheme_id, + schemeName=invoice_line.item.scheme_name, + schemeAgencyID=invoice_line.item.scheme_agency_id) + + line.set_element('./cac:Price/cbc:PriceAmount', invoice_line.price.amount, currencyID=invoice_line.price.amount.currency.code) + #DIAN 1.7.-2020: FBB04 + line.set_element('./cac:Price/cbc:BaseQuantity', + invoice_line.price.quantity, + unitCode=invoice_line.quantity.code) + + for idx, charge in enumerate(invoice_line.allowance_charge): + next_append_charge = idx > 0 + fexml.append_allowance_charge(line, index + 1, charge, append=next_append_charge) + + def set_allowance_charge(fexml, invoice): + for idx, charge in enumerate(invoice.invoice_allowance_charge): + next_append = idx > 0 + fexml.append_allowance_charge(fexml, idx + 1, charge, append=next_append) + + def append_allowance_charge(fexml, parent, idx, charge, append=False): + line = parent.fragment('./cac:AllowanceCharge', append=append) + #DIAN 1.7.-2020: FAQ02 + line.set_element('./cbc:ID', idx) + #DIAN 1.7.-2020: FAQ03 + line.set_element('./cbc:ChargeIndicator', str(charge.charge_indicator).lower()) + if charge.reason: + line.set_element('./cbc:AllowanceChargeReasonCode', charge.reason.code) + line.set_element('./cbc:allowanceChargeReason', charge.reason.reason) + line.set_element('./cbc:MultiplierFactorNumeric', str(round(charge.multiplier_factor_numeric, 2))) + fexml.set_element_amount_for(line, './cbc:Amount', charge.amount) + fexml.set_element_amount_for(line, './cbc:BaseAmount', charge.base_amount) + + def attach_invoice(fexml, invoice): + """adiciona etiquetas a FEXML y retorna FEXML + en caso de fallar validacion retorna None""" + + fexml.placeholder_for('./ext:UBLExtensions') + fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1') + fexml.set_element('./cbc:CustomizationID', invoice.invoice_operation_type) + fexml.placeholder_for('./cbc:ProfileID') + fexml.placeholder_for('./cbc:ProfileExecutionID') + fexml.set_element('./cbc:ID', invoice.invoice_ident) + fexml.placeholder_for('./cbc:UUID') + fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') + fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) + #DIAN 1.7.-2020: FAD10 + fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) + fexml.set_element('./cbc:%sTypeCode' % (fexml.tag_document()), + invoice.invoice_type_code, + listAgencyID='195', + listAgencyName='No matching global declaration available for the validation root', + listURI='http://www.dian.gov.co') + fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) + fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (fexml.tag_document()), + invoice.invoice_period_start.strftime('%Y-%m-%d')) + + fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (fexml.tag_document()), + invoice.invoice_period_end.strftime('%Y-%m-%d')) + + fexml.customize(invoice) + + fexml.set_supplier(invoice) + fexml.set_customer(invoice) + fexml.set_legal_monetary(invoice) + fexml.set_invoice_totals(invoice) + fexml.set_invoice_lines(invoice) + fexml.set_payment_mean(invoice) + fexml.set_allowance_charge(invoice) + fexml.set_discrepancy_response(invoice) + fexml.set_billing_reference(invoice) + cosa = AttachedDocument(id=1, xml=fexml) + print(cosa.toFachoXML()) + + return fexml + + def customize(fexml, invoice): + + """adiciona etiquetas a FEXML y retorna FEXML + en caso de fallar validacion retorna None"""