mas pruebas y algunos pequenos cambios
FossilOrigin-Name: 02bc90719bb17216a749568086887a7d27878fdbd81febfb88a9fb1e68aa8205
This commit is contained in:
parent
64b312a432
commit
efe93ecc3c
@ -257,6 +257,9 @@ class FachoXML:
|
|||||||
def get_element_text(self, xpath, format_=str):
|
def get_element_text(self, xpath, format_=str):
|
||||||
xpath = self.fragment_prefix + self._path_xpath_for(xpath)
|
xpath = self.fragment_prefix + self._path_xpath_for(xpath)
|
||||||
elem = self.builder.xpath(self.root, xpath)
|
elem = self.builder.xpath(self.root, xpath)
|
||||||
|
if elem is None:
|
||||||
|
raise ValueError('xpath %s invalid' % (xpath))
|
||||||
|
|
||||||
text = self.builder.get_text(elem)
|
text = self.builder.get_text(elem)
|
||||||
return format_(text)
|
return format_(text)
|
||||||
|
|
||||||
|
@ -227,15 +227,18 @@ class LegalMonetaryTotal(model.Model):
|
|||||||
self.payable_amount = self.tax_inclusive_amount + self.charge_total_amount
|
self.payable_amount = self.tax_inclusive_amount + self.charge_total_amount
|
||||||
|
|
||||||
|
|
||||||
|
class DIANExtensionContent(model.Model):
|
||||||
|
__name__ = 'ExtensionContent'
|
||||||
|
|
||||||
|
dian = fields.Many2One(dian.DianExtensions, name='DianExtensions', namespace='sts')
|
||||||
|
|
||||||
class DIANExtension(model.Model):
|
class DIANExtension(model.Model):
|
||||||
__name__ = 'UBLExtension'
|
__name__ = 'UBLExtension'
|
||||||
|
|
||||||
_content = fields.Many2One(Element, name='ExtensionContent', namespace='ext')
|
content = fields.Many2One(DIANExtensionContent, namespace='ext')
|
||||||
|
|
||||||
dian = fields.Many2One(dian.DianExtensions, name='DianExtensions', namespace='sts')
|
|
||||||
|
|
||||||
def __default_get__(self, name, value):
|
def __default_get__(self, name, value):
|
||||||
return self.dian
|
return self.content.dian
|
||||||
|
|
||||||
class UBLExtension(model.Model):
|
class UBLExtension(model.Model):
|
||||||
__name__ = 'UBLExtension'
|
__name__ = 'UBLExtension'
|
||||||
@ -250,16 +253,7 @@ class UBLExtensions(model.Model):
|
|||||||
|
|
||||||
class Invoice(model.Model):
|
class Invoice(model.Model):
|
||||||
__name__ = 'Invoice'
|
__name__ = 'Invoice'
|
||||||
__namespace__ = {
|
__namespace__ = fe.NAMESPACES
|
||||||
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
|
|
||||||
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
|
|
||||||
'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1',
|
|
||||||
'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2',
|
|
||||||
'sts': 'http://www.dian.gov.co/contratos/facturaelectronica/v1/Structures',
|
|
||||||
'xades': 'http://uri.etsi.org/01903/v1.3.2#',
|
|
||||||
'ds': 'http://www.w3.org/2000/09/xmldsig#'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_ubl_extensions = fields.Many2One(UBLExtensions, namespace='ext')
|
_ubl_extensions = fields.Many2One(UBLExtensions, namespace='ext')
|
||||||
dian = fields.Virtual(getter='get_dian_extension')
|
dian = fields.Virtual(getter='get_dian_extension')
|
||||||
@ -284,6 +278,7 @@ class Invoice(model.Model):
|
|||||||
taxtotal_03 = fields.Many2One(TaxTotal)
|
taxtotal_03 = fields.Many2One(TaxTotal)
|
||||||
|
|
||||||
def __setup__(self):
|
def __setup__(self):
|
||||||
|
self._namespace_prefix = 'fe'
|
||||||
# Se requieren minimo estos impuestos para
|
# Se requieren minimo estos impuestos para
|
||||||
# validar el cufe
|
# validar el cufe
|
||||||
self._subtotal_01 = self.taxtotal_01.subtotals.create()
|
self._subtotal_01 = self.taxtotal_01.subtotals.create()
|
||||||
@ -351,3 +346,10 @@ class Invoice(model.Model):
|
|||||||
|
|
||||||
def get_dian_extension(self, name, _value):
|
def get_dian_extension(self, name, _value):
|
||||||
return self._ubl_extensions.dian
|
return self._ubl_extensions.dian
|
||||||
|
|
||||||
|
def to_xml(self, **kw):
|
||||||
|
# al generar documento el namespace
|
||||||
|
# se hace respecto a la raiz
|
||||||
|
return super().to_xml(**kw)\
|
||||||
|
.replace("fe:", "")\
|
||||||
|
.replace("xmlns:fe", "xmlns")
|
||||||
|
@ -2,6 +2,19 @@ import facho.model as model
|
|||||||
import facho.model.fields as fields
|
import facho.model.fields as fields
|
||||||
from .common import *
|
from .common import *
|
||||||
|
|
||||||
|
class DIANElement(Element):
|
||||||
|
"""
|
||||||
|
Elemento que contiene atributos por defecto.
|
||||||
|
|
||||||
|
Puede extender esta clase y modificar los atributos nuevamente
|
||||||
|
"""
|
||||||
|
__name__ = 'DIANElement'
|
||||||
|
|
||||||
|
scheme_id = fields.Attribute('schemeID', default='4')
|
||||||
|
scheme_name = fields.Attribute('schemeName', default='31')
|
||||||
|
scheme_agency_name = fields.Attribute('schemeAgencyName', default='CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)')
|
||||||
|
scheme_agency_id = fields.Attribute('schemeAgencyID', default='195')
|
||||||
|
|
||||||
class SoftwareProvider(model.Model):
|
class SoftwareProvider(model.Model):
|
||||||
__name__ = 'SoftwareProvider'
|
__name__ = 'SoftwareProvider'
|
||||||
|
|
||||||
@ -26,10 +39,18 @@ class InvoiceControl(model.Model):
|
|||||||
authorization = fields.Many2One(Element, name='InvoiceAuthorization', namespace='sts')
|
authorization = fields.Many2One(Element, name='InvoiceAuthorization', namespace='sts')
|
||||||
period = fields.Many2One(Period, name='AuthorizationPeriod', namespace='sts')
|
period = fields.Many2One(Period, name='AuthorizationPeriod', namespace='sts')
|
||||||
invoices = fields.Many2One(AuthorizedInvoices, namespace='sts')
|
invoices = fields.Many2One(AuthorizedInvoices, namespace='sts')
|
||||||
|
|
||||||
|
class AuthorizationProvider(model.Model):
|
||||||
|
__name__ = 'AuthorizationProvider'
|
||||||
|
|
||||||
|
|
||||||
|
id = fields.Many2One(DIANElement, name='AuthorizationProviderID', namespace='sts', default='800197268')
|
||||||
|
|
||||||
class DianExtensions(model.Model):
|
class DianExtensions(model.Model):
|
||||||
__name__ = 'DianExtensions'
|
__name__ = 'DianExtensions'
|
||||||
|
|
||||||
|
authorization_provider = fields.Many2One(AuthorizationProvider, namespace='sts', create=True)
|
||||||
|
|
||||||
software_security_code = fields.Many2One(Element, name='SoftwareSecurityCode', namespace='sts')
|
software_security_code = fields.Many2One(Element, name='SoftwareSecurityCode', namespace='sts')
|
||||||
software_provider = fields.Many2One(SoftwareProvider, namespace='sts')
|
software_provider = fields.Many2One(SoftwareProvider, namespace='sts')
|
||||||
source = fields.Many2One(InvoiceSource, namespace='sts')
|
source = fields.Many2One(InvoiceSource, namespace='sts')
|
||||||
|
@ -13,7 +13,9 @@ import facho.fe.model as model
|
|||||||
import facho.fe.form as form
|
import facho.fe.form as form
|
||||||
from facho import fe
|
from facho import fe
|
||||||
|
|
||||||
def test_simple_invoice():
|
import helpers
|
||||||
|
|
||||||
|
def simple_invoice():
|
||||||
invoice = model.Invoice()
|
invoice = model.Invoice()
|
||||||
invoice.dian.software_security_code = '12345'
|
invoice.dian.software_security_code = '12345'
|
||||||
invoice.dian.software_provider.provider_id = 'provider-id'
|
invoice.dian.software_provider.provider_id = 'provider-id'
|
||||||
@ -21,25 +23,6 @@ def test_simple_invoice():
|
|||||||
invoice.dian.control.prefix = 'SETP'
|
invoice.dian.control.prefix = 'SETP'
|
||||||
invoice.dian.control.from_range = '1000'
|
invoice.dian.control.from_range = '1000'
|
||||||
invoice.dian.control.to_range = '1000'
|
invoice.dian.control.to_range = '1000'
|
||||||
|
|
||||||
invoice.id = '323200000129'
|
|
||||||
invoice.issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
|
|
||||||
invoice.supplier.party.id = '700085371'
|
|
||||||
invoice.customer.party.id = '800199436'
|
|
||||||
|
|
||||||
line = invoice.lines.create()
|
|
||||||
line.quantity = 1
|
|
||||||
line.price = form.Amount(5_000)
|
|
||||||
subtotal = line.taxtotal.subtotals.create()
|
|
||||||
subtotal.percent = 19.0
|
|
||||||
assert '<Invoice><ID>323200000129</ID><IssueDate>2019-01-16T10:53:10-05:00</IssueDate><IssueTime>10:5310-05:00</IssueTime><AccountingSupplierParty><Party><ID>700085371</ID></Party></AccountingSupplierParty><AccountingCustomerParty><Party><ID>800199436</ID></Party></AccountingCustomerParty><InvoiceLine><InvoicedQuantity unitCode="NAR">1</InvoicedQuantity><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><Percent>19.0</Percent><TaxCategory><Percent>19.0</Percent></TaxCategory></TaxSubTotal></TaxTotal><Price><PriceAmount currencyID="COP">5000.0</PriceAmount>5000.0</Price><LineExtensionAmount currencyID="COP">5000.0</LineExtensionAmount></InvoiceLine><LegalMonetaryTotal><LineExtensionAmount currencyID="COP">0.0</LineExtensionAmount></LegalMonetaryTotal><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><Percent>19.0</Percent><TaxCategory><Percent>19.0</Percent><TaxScheme><ID>01</ID></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><TaxCategory><TaxScheme><ID>04</ID></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><TaxCategory><TaxScheme><ID>03</ID></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal></Invoice>' == invoice.to_xml()
|
|
||||||
|
|
||||||
|
|
||||||
def test_simple_invoice_cufe():
|
|
||||||
token = '693ff6f2a553c3646a063436fd4dd9ded0311471'
|
|
||||||
environment = fe.AMBIENTE_PRODUCCION
|
|
||||||
|
|
||||||
invoice = model.Invoice()
|
|
||||||
invoice.id = '323200000129'
|
invoice.id = '323200000129'
|
||||||
invoice.issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
|
invoice.issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
|
||||||
invoice.supplier.party.id = '700085371'
|
invoice.supplier.party.id = '700085371'
|
||||||
@ -54,4 +37,34 @@ def test_simple_invoice_cufe():
|
|||||||
line.quantity = 1
|
line.quantity = 1
|
||||||
line.price = 1_500_000
|
line.price = 1_500_000
|
||||||
|
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
def test_simple_invoice_cufe():
|
||||||
|
token = '693ff6f2a553c3646a063436fd4dd9ded0311471'
|
||||||
|
environment = fe.AMBIENTE_PRODUCCION
|
||||||
|
invoice = simple_invoice()
|
||||||
assert invoice.cufe(token, environment) == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'
|
assert invoice.cufe(token, environment) == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'
|
||||||
|
|
||||||
|
def test_simple_invoice_sign_dian(monkeypatch):
|
||||||
|
invoice = simple_invoice()
|
||||||
|
|
||||||
|
xmlstring = invoice.to_xml()
|
||||||
|
p12_data = open('./tests/example.p12', 'rb').read()
|
||||||
|
signer = fe.DianXMLExtensionSigner.from_bytes(p12_data)
|
||||||
|
|
||||||
|
with monkeypatch.context() as m:
|
||||||
|
helpers.mock_urlopen(m)
|
||||||
|
xmlsigned = signer.sign_xml_string(xmlstring)
|
||||||
|
assert "Signature" in xmlsigned
|
||||||
|
|
||||||
|
|
||||||
|
def test_dian_extension_authorization_provider():
|
||||||
|
invoice = simple_invoice()
|
||||||
|
xml = fe.FeXML.from_string(invoice.to_xml())
|
||||||
|
provider_id = xml.get_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID')
|
||||||
|
|
||||||
|
assert provider_id.attrib['schemeID'] == '4'
|
||||||
|
assert provider_id.attrib['schemeName'] == '31'
|
||||||
|
assert provider_id.attrib['schemeAgencyName'] == 'CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)'
|
||||||
|
assert provider_id.attrib['schemeAgencyID'] == '195'
|
||||||
|
assert provider_id.text == '800197268'
|
||||||
|
Loading…
Reference in New Issue
Block a user