diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 64e05b7..b523e80 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -13,6 +13,7 @@ import hashlib from contextlib import contextmanager from .data.dian import codelist from . import form +from collections import defaultdict AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code'] AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code'] @@ -97,7 +98,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): raise NotImplementedError() def build(self, fachoxml): - cufe = self._generate_cufe(fachoxml) + cufe = self._generate_cufe() fachoxml.set_element('./cbc:UUID', cufe, schemeID=self.tipo_ambiente, schemeName=self.schemeName()) @@ -122,7 +123,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): # PAG 601 build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount build_vars['ValorTotalPagar'] = invoice.invoice_legal_monetary_total.payable_amount - ValorImpuestoPara = {} + ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0)) build_vars['CodImpuesto1'] = 1 build_vars['CodImpuesto2'] = 4 build_vars['CodImpuesto3'] = 3 @@ -139,9 +140,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): return build_vars - def _generate_cufe(self, fachoxml): - formatVars = self.formatVars() - cufe = "".join(formatVars) + def _generate_cufe(self): + cufe = "".join(self.formatVars()) # crear hash... h = hashlib.sha384() diff --git a/facho/fe/form.py b/facho/fe/form.py index dab1fda..b5960f4 100644 --- a/facho/fe/form.py +++ b/facho/fe/form.py @@ -91,6 +91,7 @@ class Amount: def is_same_currency(self, other): return self.currency == other.currency + @dataclass class Item: description: str @@ -348,12 +349,19 @@ class AllowanceCharge: class NationalSalesInvoiceDocumentType(str): def __str__(self): + # 6.1.3 return '01' class CreditNoteDocumentType(str): def __str__(self): + # 6.1.3 return '91' +class DebitNoteDocumentType(str): + def __str__(self): + # 6.1.3 + return '92' + class Invoice: def __init__(self, type_code: str): if str(type_code) not in codelist.TipoDocumento: @@ -475,3 +483,12 @@ class CreditNote(Invoice): if not isinstance(invoice_document_reference, BillingReference): raise TypeError('invoice_document_reference invalid type') self.invoice_billing_reference = invoice_document_reference + + +class DebitNote(Invoice): + def __init__(self, invoice_document_reference: BillingReference): + super().__init__(DebitNoteDocumentType()) + + if not isinstance(invoice_document_reference, BillingReference): + raise TypeError('invoice_document_reference invalid type') + self.invoice_billing_reference = invoice_document_reference diff --git a/facho/fe/form_xml/debit_note.py b/facho/fe/form_xml/debit_note.py index 3bf367a..c537f2a 100644 --- a/facho/fe/form_xml/debit_note.py +++ b/facho/fe/form_xml/debit_note.py @@ -17,7 +17,7 @@ class DIANDebitNoteXML(fe.FeXML): ublextension = self.fragment('/fe:DebitNote/ext:UBLExtensions/ext:UBLExtension', append=True) extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent') self.attach_invoice(invoice) - + def set_supplier(fexml, invoice): fexml.placeholder_for('/fe:DebitNote/cac:AccountingSupplierParty') #DIAN 1.7.-2020: DAJ02 @@ -101,7 +101,7 @@ class DIANDebitNoteXML(fe.FeXML): invoice.invoice_customer.tax_scheme.code) #DIAN 1.7.-2020: DAJ41 fexml.set_element('/fe:DebitNote/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - invoice.invoice_customer.tax_scheme.name) + invoice.invoice_customer.tax_scheme.name) #DIAN 1.7.-2020: DAJ42 fexml.placeholder_for('/fe:DebitNote/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity') #DIAN 1.7.-2020: DAJ43 @@ -229,7 +229,7 @@ class DIANDebitNoteXML(fe.FeXML): #DIAN 1.7.-2020: CAK51, CAK55 fexml.set_element('/fe:DebitNote/cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail', invoice.invoice_customer.email) - + def set_payment_mean(fexml, invoice): payment_mean = invoice.invoice_payment_mean @@ -275,10 +275,10 @@ class DIANDebitNoteXML(fe.FeXML): schemeName='CUFE-SHA384') fexml.set_element('/fe:DebitNote/cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate', invoice.invoice_billing_reference.date) - - + + def set_invoice_totals(fexml, invoice): - tax_amount_for = defaultdict(lambda: defaultdict(lambda: 0.0)) + tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) percent_for = defaultdict(lambda: None) #requeridos para CUDE diff --git a/facho/fe/form_xml/invoice.py b/facho/fe/form_xml/invoice.py index 3fb05db..63c4fd3 100644 --- a/facho/fe/form_xml/invoice.py +++ b/facho/fe/form_xml/invoice.py @@ -416,7 +416,7 @@ class DIANInvoiceXML(fe.FeXML): return fexml._set_invoice_document_reference(reference) def set_invoice_totals(fexml, invoice): - tax_amount_for = defaultdict(lambda: defaultdict(lambda: 0.0)) + tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) percent_for = defaultdict(lambda: None) #requeridos para CUFE diff --git a/tests/test_fe_form.py b/tests/test_fe_form.py index 060ced3..15a6c13 100644 --- a/tests/test_fe_form.py +++ b/tests/test_fe_form.py @@ -12,7 +12,39 @@ import zipfile import facho.fe.form as form from facho import fe -from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML +from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML + +@pytest.fixture +def simple_debit_note_without_lines(): + inv = form.DebitNote(form.InvoiceDocumentReference('1234', 'xx', datetime.now())) + inv.set_period(datetime.now(), datetime.now()) + inv.set_issue(datetime.now()) + inv.set_ident('ABC123') + inv.set_operation_type('10') + inv.set_payment_mean(form.PaymentMean(form.PaymentMean.DEBIT, '41', datetime.now(), '1234')) + inv.set_supplier(form.Party( + name = 'facho-supplier', + ident = form.PartyIdentification('123','', '31'), + responsability_code = form.Responsability(['O-07']), + responsability_regime_code = '48', + organization_code = '1', + address = form.Address( + '', '', form.City('05001', 'Medellín'), + form.Country('CO', 'Colombia'), + form.CountrySubentity('05', 'Antioquia')) + )) + inv.set_customer(form.Party( + name = 'facho-customer', + ident = form.PartyIdentification('321', '', '31'), + responsability_code = form.Responsability(['O-07']), + responsability_regime_code = '48', + organization_code = '1', + address = form.Address( + '', '', form.City('05001', 'Medellín'), + form.Country('CO', 'Colombia'), + form.CountrySubentity('05', 'Antioquia')) + )) + return inv @pytest.fixture def simple_credit_note_without_lines(): @@ -312,3 +344,54 @@ def test_credit_note_cude(simple_credit_note_without_lines): cude = xml_invoice.get_element_text('/fe:CreditNote/cbc:UUID') # pag 612 assert cude == '907e4444decc9e59c160a2fb3b6659b33dc5b632a5008922b9a62f83f757b1c448e47f5867f2b50dbdb96f48c7681168' + + +# pag 614 +def test_debit_note_cude(simple_debit_note_without_lines): + simple_invoice = simple_debit_note_without_lines + simple_invoice.invoice_ident = 'ND1001' + simple_invoice.invoice_issue = datetime.strptime('2019-01-18 10:58:00-05:00', '%Y-%m-%d %H:%M:%S%z') + simple_invoice.invoice_supplier.ident = form.PartyIdentification('900197264', '5', '31') + simple_invoice.invoice_customer.ident = form.PartyIdentification('10254102', '5', '31') + simple_invoice.add_invoice_line(form.InvoiceLine( + quantity = 1, + description = 'producto', + item = form.StandardItem('test', 111), + price = form.Price(form.Amount(30_000), '01', ''), + tax = form.TaxTotal( + subtotals = [ + form.TaxSubTotal( + tax_scheme_ident = '04', + percent = 8.0 + )]) + )) + + simple_invoice.calculate() + xml_invoice = DIANDebitNoteXML(simple_invoice) + + cude_extension = fe.DianXMLExtensionCUDE( + simple_invoice, + '10201', + tipo_ambiente = fe.AMBIENTE_PRUEBAS, + ) + build_vars = cude_extension.buildVars() + assert build_vars['NumFac'] == 'ND1001' + assert build_vars['FecFac'] == '2019-01-18' + assert build_vars['HoraFac'] == '10:58:00-05:00' + assert build_vars['ValorBruto'] == form.Amount(30_000) + assert build_vars['NitOFE'] == '900197264' + assert build_vars['NumAdq'] == '10254102' + assert build_vars['ValorImpuestoPara'][1] == form.Amount(0) + assert build_vars['ValorImpuestoPara'][4] == form.Amount(2400) + assert build_vars['ValorImpuestoPara'][3] == form.Amount(0) + assert build_vars['ValorTotalPagar'] == form.Amount(32400) + assert build_vars['Software-PIN'] == '10201' + assert build_vars['TipoAmb'] == 2 + + + cude_composicion = "".join(cude_extension.formatVars()) + assert cude_composicion == 'ND10012019-01-1810:58:00-05:0030000.00010.00042400.00030.0032400.0090019726410254102102012' + + xml_invoice.add_extension(cude_extension) + cude = xml_invoice.get_element_text('/fe:DebitNote/cbc:UUID') + assert cude == 'b9483dc2a17167feedf37b6bd67c4204e7b601933e0e389cffbd545e4d0ec370b403cbb41ff656776cb6cb5d8348ecd4'