se retira validador y se sustituye por asserciones de precondicion
FossilOrigin-Name: 1c360b4b29bb4fa1909ca1daae5b18cb01a56212cd84d71e7562cf700a5d7006
This commit is contained in:
		| @@ -259,15 +259,6 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr | |||||||
|     invoice = module.invoice() |     invoice = module.invoice() | ||||||
|     invoice.calculate() |     invoice.calculate() | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         validator = module.validator() |  | ||||||
|     except AttributeError: |  | ||||||
|         validator = form.DianResolucion0001Validator() |  | ||||||
|  |  | ||||||
|     if not validator.validate(invoice): |  | ||||||
|         for error in validator.errors: |  | ||||||
|             print("ERROR:", error) |  | ||||||
|  |  | ||||||
|     if generate: |     if generate: | ||||||
|         xml = invoice_xml(invoice) |         xml = invoice_xml(invoice) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -93,3 +93,4 @@ TipoOperacionF = CodeList(path_for_codelist('TipoOperacionF-2.1.gc'), 'code', 'n | |||||||
|     .update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name')) |     .update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name')) | ||||||
| Municipio = CodeList(path_for_codelist('Municipio-2.1.gc'), 'code', 'name') | Municipio = CodeList(path_for_codelist('Municipio-2.1.gc'), 'code', 'name') | ||||||
| Departamento = CodeList(path_for_codelist('Departamentos-2.1.gc'), 'code', 'name') | Departamento = CodeList(path_for_codelist('Departamentos-2.1.gc'), 'code', 'name') | ||||||
|  | Paises = CodeList(path_for_codelist('Paises-2.1.gc'), 'code', 'name') | ||||||
|   | |||||||
							
								
								
									
										137
									
								
								facho/fe/form.py
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								facho/fe/form.py
									
									
									
									
									
								
							| @@ -9,7 +9,7 @@ from datetime import datetime | |||||||
| from collections import defaultdict | from collections import defaultdict | ||||||
| import decimal | import decimal | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
|  | import typing | ||||||
|  |  | ||||||
| from .data.dian import codelist | from .data.dian import codelist | ||||||
|  |  | ||||||
| @@ -105,25 +105,40 @@ class StandardItem(Item): | |||||||
| @dataclass | @dataclass | ||||||
| class Country: | class Country: | ||||||
|     code: str |     code: str | ||||||
|     name: str |     name: str = '' | ||||||
|  |  | ||||||
|  |     def __post_init__(self): | ||||||
|  |         if self.code not in codelist.Paises: | ||||||
|  |             raise ValueError("code [%s] not found" % (self.code)) | ||||||
|  |         self.name = codelist.Paises[self.code]['name'] | ||||||
|          |          | ||||||
| @dataclass | @dataclass | ||||||
| class CountrySubentity: | class CountrySubentity: | ||||||
|     code: str |     code: str | ||||||
|     name: str |     name: str = '' | ||||||
|  |  | ||||||
|  |     def __post_init__(self): | ||||||
|  |         if self.code not in codelist.Departamento: | ||||||
|  |             raise ValueError("code [%s] not found" % (self.code)) | ||||||
|  |         self.name = codelist.Departamento[self.code]['name'] | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class City: | class City: | ||||||
|     code: str |     code: str | ||||||
|     name: str |     name: str = '' | ||||||
|  |  | ||||||
|  |     def __post_init__(self): | ||||||
|  |         if self.code not in codelist.Municipio: | ||||||
|  |             raise ValueError("code [%s] not found" % (self.code)) | ||||||
|  |         self.name = codelist.Municipio[self.code]['name'] | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class Address: | class Address: | ||||||
|     name: str |     name: str | ||||||
|     street: str = '' |     street: str = '' | ||||||
|     city: City = City('', '') |     city: City = City('05001') | ||||||
|     country: Country = Country('CO', 'Colombia') |     country: Country = Country('CO') | ||||||
|     countrysubentity: CountrySubentity = CountrySubentity('', '') |     countrysubentity: CountrySubentity = CountrySubentity('05') | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class PartyIdentification: | class PartyIdentification: | ||||||
| @@ -159,14 +174,19 @@ class TaxScheme: | |||||||
|     name: str = ''    |     name: str = ''    | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def __post_init__(self): | ||||||
|  |         if self.code not in codelist.TipoImpuesto: | ||||||
|  |             raise ValueError("code not found") | ||||||
|  |         self.name = codelist.TipoImpuesto[self.code]['name'] | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class Party: | class Party: | ||||||
|     name: str |     name: str | ||||||
|     ident: str |     ident: str | ||||||
|     responsability_code: str |     responsability_code: typing.List[Responsability] | ||||||
|     responsability_regime_code: str |     responsability_regime_code: str | ||||||
|     organization_code: str |     organization_code: str | ||||||
|     tax_scheme: TaxScheme = TaxScheme('') |     tax_scheme: TaxScheme = TaxScheme('01') | ||||||
|  |  | ||||||
|     phone: str = '' |     phone: str = '' | ||||||
|     address: Address = Address('') |     address: Address = Address('') | ||||||
| @@ -175,6 +195,13 @@ class Party: | |||||||
|     legal_company_ident: str = '' |     legal_company_ident: str = '' | ||||||
|     legal_address: str = '' |     legal_address: str = '' | ||||||
|  |  | ||||||
|  |     def __post_init__(self): | ||||||
|  |         if self.organization_code not in codelist.TipoOrganizacion: | ||||||
|  |             raise ValueError("organization_code not found") | ||||||
|  |  | ||||||
|  |         for code in self.responsability_code: | ||||||
|  |             if code not in codelist.TipoResponsabilidad: | ||||||
|  |                 raise ValueError("responsability_code %s not found" % (code)) | ||||||
|          |          | ||||||
| @dataclass | @dataclass | ||||||
| class TaxSubTotal: | class TaxSubTotal: | ||||||
| @@ -209,6 +236,10 @@ class Price: | |||||||
|     type_code: str |     type_code: str | ||||||
|     type: str |     type: str | ||||||
|  |  | ||||||
|  |     def __post_init__(self): | ||||||
|  |         if self.type_code not in codelist.CodigoPrecioReferencia: | ||||||
|  |             raise ValueError("type_code [%s] not found" % (self.type_code)) | ||||||
|  |  | ||||||
|          |          | ||||||
| @dataclass | @dataclass | ||||||
| class PaymentMean: | class PaymentMean: | ||||||
| @@ -216,6 +247,9 @@ class PaymentMean: | |||||||
|     CREDIT = '02' |     CREDIT = '02' | ||||||
|  |  | ||||||
|     def __init__(self, id: str, code: str, due_at: datetime, payment_id: str): |     def __init__(self, id: str, code: str, due_at: datetime, payment_id: str): | ||||||
|  |         if code not in codelist.MediosPago: | ||||||
|  |             raise ValueError("code not found") | ||||||
|  |          | ||||||
|         self.id = id |         self.id = id | ||||||
|         self.code = code |         self.code = code | ||||||
|         self.due_at = due_at |         self.due_at = due_at | ||||||
| @@ -325,6 +359,9 @@ class Invoice: | |||||||
|         self.invoice_period_end = enddate |         self.invoice_period_end = enddate | ||||||
|  |  | ||||||
|     def set_issue(self, dtime: datetime): |     def set_issue(self, dtime: datetime): | ||||||
|  |         if dtime.tzname() not in ['UTC-05:00', '-05', None]: | ||||||
|  |             raise ValueError("dtime must be UTC-05:00") | ||||||
|  |  | ||||||
|         self.invoice_issue = dtime |         self.invoice_issue = dtime | ||||||
|  |  | ||||||
|     def set_ident(self, ident: str): |     def set_ident(self, ident: str): | ||||||
| @@ -340,6 +377,9 @@ class Invoice: | |||||||
|         self.invoice_payment_mean = payment_mean |         self.invoice_payment_mean = payment_mean | ||||||
|  |  | ||||||
|     def set_operation_type(self, operation): |     def set_operation_type(self, operation): | ||||||
|  |         if operation not in codelist.TipoOperacionF: | ||||||
|  |             raise ValueError("operation not found") | ||||||
|  |          | ||||||
|         self.invoice_operation_type = operation |         self.invoice_operation_type = operation | ||||||
|  |  | ||||||
|     def add_allownace_charge(self, charge: AllowanceCharge): |     def add_allownace_charge(self, charge: AllowanceCharge): | ||||||
| @@ -399,82 +439,3 @@ class Invoice: | |||||||
|         for invline in self.invoice_lines: |         for invline in self.invoice_lines: | ||||||
|             invline.calculate() |             invline.calculate() | ||||||
|         self._calculate_legal_monetary_total() |         self._calculate_legal_monetary_total() | ||||||
|  |  | ||||||
|  |  | ||||||
| class DianResolucion0001Validator: |  | ||||||
|  |  | ||||||
|     def __init__(self): |  | ||||||
|         self.errors = [] |  | ||||||
|  |  | ||||||
|     def _validate_party(self, model, party): |  | ||||||
|         for code in party.responsability_code: |  | ||||||
|             if code not in codelist.TipoResponsabilidad: |  | ||||||
|                 self.errors.append((model, |  | ||||||
|                                     'responsability_code', |  | ||||||
|                                     'not found %s' % (code))) |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             codelist.TipoOrganizacion[party.organization_code] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append((model, 'organization_code' , |  | ||||||
|                                 'not found %s' % (party.organization_code))) |  | ||||||
|         try: |  | ||||||
|             if isinstance(party.tax_scheme, (str, str)): |  | ||||||
|                 codelist.TipoImpuesto[party.tax_scheme.code] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append((model , 'tax_scheme' , |  | ||||||
|                                 'not found %s' % (party.tax_scheme))) |  | ||||||
|         try: |  | ||||||
|             codelist.Departamento[party.address.countrysubentity.code] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append((model, 'countrysubentity_code', |  | ||||||
|                                 'not found %s' % (party.address.countrysubentity.code))) |  | ||||||
|         try: |  | ||||||
|             codelist.Municipio[party.address.city.code] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append((model, 'city_code', |  | ||||||
|                                 'not found %s' % (party.address.city.code))) |  | ||||||
|  |  | ||||||
|     def _validate_invoice(self, invoice): |  | ||||||
|         try: |  | ||||||
|             codelist.TipoOperacionF[invoice.invoice_operation_type] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append(('invoice', 'operation_type', |  | ||||||
|                                 'not found %s' % (invoice.invoice_operation_type))) |  | ||||||
|  |  | ||||||
|         # MACHETE se espera en zona horario colombia |  | ||||||
|         if invoice.invoice_issue.tzname() not in ['UTC-05:00', '-05', None]: |  | ||||||
|             self.errors.append(('invoice', 'invoice_issue', |  | ||||||
|                                 'expected timezone UTC-05:00 or -05 or empty got %s' % (invoice.invoice_issue.tzname()))) |  | ||||||
|  |  | ||||||
|     def validate(self, invoice): |  | ||||||
|         invoice.accept(self) |  | ||||||
|         self._validate_invoice(invoice) |  | ||||||
|  |  | ||||||
|         return not self.errors |  | ||||||
|  |  | ||||||
|     def visit_payment_mean(self, mean): |  | ||||||
|         try: |  | ||||||
|             codelist.MediosPago[mean.code] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append(('payment_mean', 'code', |  | ||||||
|                                 'not found %s' % (mean.code))) |  | ||||||
|  |  | ||||||
|     def visit_customer(self, customer): |  | ||||||
|         self._validate_party('customer', customer) |  | ||||||
|  |  | ||||||
|     def visit_supplier(self, supplier): |  | ||||||
|         self._validate_party('supplier', supplier) |  | ||||||
|  |  | ||||||
|     def visit_payment(self, payment): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def visit_invoice_line(self, line): |  | ||||||
|         try: |  | ||||||
|             codelist.CodigoPrecioReferencia[line.price.type_code] |  | ||||||
|         except KeyError: |  | ||||||
|             self.errors.append(('invoice_line', 'line.price', |  | ||||||
|                                'not found %s' % (line.price.type_code))) |  | ||||||
|  |  | ||||||
|     def valid(self): |  | ||||||
|         return not self.errors |  | ||||||
|   | |||||||
| @@ -19,3 +19,6 @@ def test_tipoorganizacion(): | |||||||
| def test_tipodocumento(): | def test_tipodocumento(): | ||||||
|     assert codelist.TipoDocumento.short_name == 'TipoDocumento' |     assert codelist.TipoDocumento.short_name == 'TipoDocumento' | ||||||
|     assert codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'] == '01' |     assert codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'] == '01' | ||||||
|  |  | ||||||
|  | def test_departamento(): | ||||||
|  |     assert codelist.Departamento['05']['name'] == 'Antioquia' | ||||||
|   | |||||||
| @@ -95,10 +95,6 @@ def simple_invoice(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_invoicesimple_build(simple_invoice): | def test_invoicesimple_build(simple_invoice): | ||||||
|     invoice_validator = form.DianResolucion0001Validator() |  | ||||||
|  |  | ||||||
|     invoice_validator.validate(simple_invoice) |  | ||||||
|     assert invoice_validator.errors == [] |  | ||||||
|     xml = DIANInvoiceXML(simple_invoice) |     xml = DIANInvoiceXML(simple_invoice) | ||||||
|  |  | ||||||
|     supplier_name = xml.get_element_text('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name') |     supplier_name = xml.get_element_text('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name') | ||||||
| @@ -109,8 +105,6 @@ def test_invoicesimple_build(simple_invoice): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_invoicesimple_build_with_cufe(simple_invoice): | def test_invoicesimple_build_with_cufe(simple_invoice): | ||||||
|     invoice_validator = form.DianResolucion0001Validator() |  | ||||||
|     assert invoice_validator.validate(simple_invoice) == True |  | ||||||
|     xml = DIANInvoiceXML(simple_invoice) |     xml = DIANInvoiceXML(simple_invoice) | ||||||
|     cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice) |     cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice) | ||||||
|     xml.add_extension(cufe_extension) |     xml.add_extension(cufe_extension) | ||||||
| @@ -119,8 +113,6 @@ def test_invoicesimple_build_with_cufe(simple_invoice): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_invoicesimple_xml_signed(monkeypatch, simple_invoice): | def test_invoicesimple_xml_signed(monkeypatch, simple_invoice): | ||||||
|     invoice_validator = form.DianResolucion0001Validator() |  | ||||||
|     assert invoice_validator.validate(simple_invoice) == True |  | ||||||
|     xml = DIANInvoiceXML(simple_invoice) |     xml = DIANInvoiceXML(simple_invoice) | ||||||
|  |  | ||||||
|     signer = fe.DianXMLExtensionSigner('./tests/example.p12') |     signer = fe.DianXMLExtensionSigner('./tests/example.p12') | ||||||
| @@ -178,7 +170,7 @@ def test_invoice_totals(simple_invoice_without_lines): | |||||||
|         quantity = 1, |         quantity = 1, | ||||||
|         description = 'producto', |         description = 'producto', | ||||||
|         item = form.StandardItem('test', 9999), |         item = form.StandardItem('test', 9999), | ||||||
|         price = form.Price(form.Amount(1_500_000), '', ''), |         price = form.Price(form.Amount(1_500_000), '01', ''), | ||||||
|         tax = form.TaxTotal( |         tax = form.TaxTotal( | ||||||
|             subtotals = [ |             subtotals = [ | ||||||
|                 form.TaxSubTotal( |                 form.TaxSubTotal( | ||||||
| @@ -201,7 +193,7 @@ def test_invoice_cufe(simple_invoice_without_lines): | |||||||
|         quantity = 1, |         quantity = 1, | ||||||
|         description = 'producto', |         description = 'producto', | ||||||
|         item = form.StandardItem('test', 111), |         item = form.StandardItem('test', 111), | ||||||
|         price = form.Price(form.Amount(1_500_000), '', ''), |         price = form.Price(form.Amount(1_500_000), '01', ''), | ||||||
|         tax = form.TaxTotal( |         tax = form.TaxTotal( | ||||||
|             subtotals = [ |             subtotals = [ | ||||||
|                 form.TaxSubTotal( |                 form.TaxSubTotal( | ||||||
| @@ -254,8 +246,3 @@ def test_invoice_cufe(simple_invoice_without_lines): | |||||||
|     cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID') |     cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID') | ||||||
|     # RESOLUCION 004: pagina 689 |     # RESOLUCION 004: pagina 689 | ||||||
|     assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' |     assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_invoice_payment_mean(monkeypatch, simple_invoice): |  | ||||||
|     invoice_validator = form.DianResolucion0001Validator() |  | ||||||
|     assert invoice_validator.validate(simple_invoice) == True |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user