diff --git a/facho/fe/form.py b/facho/fe/form.py index b14e3c5..e7f3651 100644 --- a/facho/fe/form.py +++ b/facho/fe/form.py @@ -271,6 +271,14 @@ class BillingReference: self.uuid = uuid self.date = date +class CreditNoteDocumentReference(BillingReference): + pass + +class DebitNoteDocumentReference(BillingReference): + pass + +class InvoiceDocumentReference(BillingReference): + pass @dataclass class InvoiceLine: @@ -338,9 +346,19 @@ class AllowanceCharge: self.charge_indicator = False +class NationalSalesInvoiceDocumentType(str): + def __str__(self): + return '01' + +class CreditNoteDocumentType(str): + def __str__(self): + return '91' class Invoice: - def __init__(self): + def __init__(self, type_code: str): + if str(type_code) not in codelist.TipoDocumento: + raise ValueError("type_code [%s] not found") + self.invoice_period_start = None self.invoice_period_end = None self.invoice_issue = None @@ -355,6 +373,8 @@ class Invoice: self.invoice_allowance_charge = [] self.invoice_prepaid_payment = [] self.invoice_billing_reference = None + self.invoice_type_code = str(type_code) + def set_period(self, startdate, enddate): self.invoice_period_start = startdate @@ -441,3 +461,15 @@ class Invoice: for invline in self.invoice_lines: invline.calculate() self._calculate_legal_monetary_total() + + +class NationalSalesInvoice(Invoice): + def __init__(self): + super().__init__(NationalSalesInvoiceDocumentType()) + + +class CreditNote(Invoice): + def __init__(self, invoice_document_reference: BillingReference): + 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/credit_note.py b/facho/fe/form_xml/credit_note.py index 85063d1..89820c0 100644 --- a/facho/fe/form_xml/credit_note.py +++ b/facho/fe/form_xml/credit_note.py @@ -15,9 +15,6 @@ class DIANCreditNoteXML(DIANInvoiceXML): def tag_document(fexml): return 'CreditNote' - + def tag_document_concilied(fexml): return 'Credited' - - def document_type_code(fexml): - return codelist.TipoDocumento.by_name('Nota Crédito')['code'] diff --git a/facho/fe/form_xml/invoice.py b/facho/fe/form_xml/invoice.py index 5955526..3fb05db 100644 --- a/facho/fe/form_xml/invoice.py +++ b/facho/fe/form_xml/invoice.py @@ -30,11 +30,11 @@ class DIANInvoiceXML(fe.FeXML): #DIAN 1.7.-2020: FAJ06 fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name', invoice.invoice_supplier.name) - + #DIAN 1.7.-2020: CAJ07, CAJ08 #DIAN 1.7.-2020: FAJ07 fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') - + #DIAN 1.7.-2020: FAJ08 #DIAN 1.7.-2020: CAJ09 fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', @@ -47,7 +47,7 @@ class DIANInvoiceXML(fe.FeXML): #DIAN 1.7.-2020: CAJ11 fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', invoice.invoice_supplier.address.countrysubentity.name) - + #DIAN 1.7.-2020: FAJ12 #DIAN 1.7.-2020: CAJ12 fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', @@ -384,19 +384,36 @@ class DIANInvoiceXML(fe.FeXML): invoice.invoice_legal_monetary_total.payable_amount) - def set_billing_reference(fexml, invoice): - if invoice.invoice_billing_reference is None: - return + def _set_invoice_document_reference(fexml, reference): + fexml._do_set_billing_reference(reference, 'cac:InvoiceDocumentReference') - fexml.placeholder_for('./cac:BillingReference') - fexml.placeholder_for('./cac:BillingReference/cac:InvoiceDocumentReference') - fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:ID', - invoice.invoice_billing_reference.ident) + 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): + fexml.set_element('./cac:BillingReference/%s/cbc:ID' %(tag_document), + reference.ident) fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID', - invoice.invoice_billing_reference.uuid, + reference.uuid, schemeName='CUFE-SHA384') fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate', - invoice.invoice_billing_reference.date) + reference.date) + + 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_invoice_totals(fexml, invoice): tax_amount_for = defaultdict(lambda: defaultdict(lambda: 0.0)) @@ -469,10 +486,6 @@ class DIANInvoiceXML(fe.FeXML): def tag_document_concilied(fexml): return 'Invoiced' - # abstract method - def document_type_code(fexml): - return codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'] - def set_invoice_lines(fexml, invoice): next_append = False for index, invoice_line in enumerate(invoice.invoice_lines): @@ -519,14 +532,15 @@ class DIANInvoiceXML(fe.FeXML): 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()), fexml.document_type_code(), - listAgencyID='195', - listAgencyName='No matching global declaration available for the validation root', - listURI='http://www.dian.gov.co') + 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')) @@ -545,10 +559,3 @@ class DIANInvoiceXML(fe.FeXML): def customize(fexml, invoice): """adiciona etiquetas a FEXML y retorna FEXML en caso de fallar validacion retorna None""" - - - - - - - diff --git a/tests/test_fe_form.py b/tests/test_fe_form.py index 843f154..6bf2fbc 100644 --- a/tests/test_fe_form.py +++ b/tests/test_fe_form.py @@ -16,7 +16,7 @@ from facho.fe.form_xml import DIANInvoiceXML @pytest.fixture def simple_invoice_without_lines(): - inv = form.Invoice() + inv = form.NationalSalesInvoice() inv.set_period(datetime.now(), datetime.now()) inv.set_issue(datetime.now()) inv.set_ident('ABC123') @@ -48,7 +48,7 @@ def simple_invoice_without_lines(): @pytest.fixture def simple_invoice(): - inv = form.Invoice() + inv = form.NationalSalesInvoice() inv.set_period(datetime.now(), datetime.now()) inv.set_issue(datetime.now()) inv.set_ident('ABC123') diff --git a/tests/test_form.py b/tests/test_form.py index fed69e3..51a9c21 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -14,7 +14,7 @@ import facho.fe.form as form from facho import fe def test_invoice_legalmonetary(): - inv = form.Invoice() + inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( quantity = 1, description = 'producto facho', @@ -40,7 +40,7 @@ def test_invoice_legalmonetary(): def test_FAU10(): - inv = form.Invoice() + inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( quantity = 1, description = 'producto facho', @@ -68,7 +68,7 @@ def test_FAU10(): def test_FAU14(): - inv = form.Invoice() + inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( quantity = 1, description = 'producto facho',