se retira validador y se sustituye por asserciones de precondicion
FossilOrigin-Name: 1c360b4b29bb4fa1909ca1daae5b18cb01a56212cd84d71e7562cf700a5d7006
This commit is contained in:
parent
b31e467bc0
commit
2aa0107579
@ -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')
|
||||||
|
139
facho/fe/form.py
139
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:
|
||||||
@ -158,15 +173,20 @@ class TaxScheme:
|
|||||||
code: str
|
code: str
|
||||||
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,7 +195,14 @@ 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:
|
||||||
percent: float
|
percent: float
|
||||||
@ -209,13 +236,20 @@ 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:
|
||||||
DEBIT = '01'
|
DEBIT = '01'
|
||||||
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
|
|
||||||
|
Loading…
Reference in New Issue
Block a user