11 Commits

Author SHA1 Message Date
68a21ec355 Se Cambia tipo de impuesto en linea de factura 2023-10-24 16:24:21 -05:00
bit4bit
b6aa9e08b4 se retira variable si uso
FossilOrigin-Name: 5c883a7668e7381d2d57c4813184c97fba867b87c80a4c8c5e3defe106a6c582
2022-10-14 22:04:36 +00:00
bit4bit
2171da658a se mueve asignacion de ProfileID a objetos de factura
FossilOrigin-Name: 495e73349c8479f846c628397eea242912b982b83c4a294d8682b35c762f61f2
2022-10-14 21:36:26 +00:00
bit4bit
b00eadb9e5 se renombran metodos SOAPService
FossilOrigin-Name: 1f0d5dd62941fb6afaabdc723cb75673e484ee1851e52e20ad955a5ecf5c43a7
2022-10-14 18:48:16 +00:00
bit4bit
a9625addf8 se remueve codigo sin uso
FossilOrigin-Name: 57a6715f8b4cbcc8b71f176b636820e310a9efd1c20d505e07f6b83f452ea1ce
2022-10-14 18:44:44 +00:00
bit4bit
55d611397e se adiciona Caja_de_herramientas_Factura_Electronica_Validacion_Previa-09-02-2021.zip
FossilOrigin-Name: 95b971e5f9d19286c25d4b538aad6ca24e85b3f92e9f23a3f2d52fd9159d2059
2022-10-14 18:06:13 +00:00
bit4bit
23322d6ec8 se adiciona anexo tecnico de 09-02-2021 vr 1.8
FossilOrigin-Name: a63cbe87a10d8e4d32a470d66f0e738240ff0764aab4537d82409a133fbca419
2022-10-14 17:49:12 +00:00
bit4bit
6642b118af se adiciona pruebas de python 3.9 y 3.10 en tox
FossilOrigin-Name: 9f144ac3bbaf83e41d086a116d75aa0445add612a4631bfc828bb5161324576d
2022-10-14 16:49:51 +00:00
bit4bit
3c8742e330 se adiciona requirements_dev.txt para tox
FossilOrigin-Name: 87445cdaae06d57e1ba913605341c7f5c13ca443abce709a3fd515d912fdb62d
2022-10-14 16:21:46 +00:00
bit4bit
7156102a4a se elimina soporte python 3.6
FossilOrigin-Name: 0f05bddb22c6ecac02d9bb6cc00b4d5c8a22f88ed921ad54cb0863b4609a49b4
2022-10-14 16:20:26 +00:00
bit4bit
d5a96ea07d facho-signer: autoconf a version 2.69 compatible con PureOS
FossilOrigin-Name: e75ffb9d0e4f62680595617e69ef7890c33691be39df8cd04595dd0d7eee5deb
2022-10-14 15:47:17 +00:00
48 changed files with 2524 additions and 2927 deletions

1
.gitignore vendored
View File

@@ -215,4 +215,3 @@ tags
pyvenv.cfg
.venv
pip-selfcheck.json
invoice.xml

View File

@@ -57,6 +57,17 @@ If you are proposing a feature:
Get Started!
------------
Using docker
------------
1. make -f Makefile.dev dev-setup
1. make -f Makefile.dev dev-shell
2. make -f Makefile.dev test
3. make -f Makefile.dev tox
From Source Code
-----------
Ready to contribute? Here's how to set up `facho` for local development.
1. Fork the `facho` repo .
@@ -94,13 +105,6 @@ Ready to contribute? Here's how to set up `facho` for local development.
7. Submit a pull request through the GitHub website.
Using docker
------------
1. make -f Makefile.dev build
2. make -f Makefile.dev dev-shell
3. make -f Makefile.dev python3.8 setup.py develop
4. make -f Makefile.dev python3.8 setup.py test
Pull Request Guidelines
-----------------------

View File

@@ -1,5 +1,5 @@
# DERIVADO DE https://alextereshenkov.github.io/run-python-tests-with-tox-in-docker.html
FROM ubuntu:24.04
FROM ubuntu:18.04
RUN apt-get -qq update
@@ -7,18 +7,18 @@ RUN apt install software-properties-common -y \
&& add-apt-repository ppa:deadsnakes/ppa
RUN apt-get install -y --no-install-recommends \
python3.7 python3.7-distutils python3.7-dev \
python3.8 python3.8-distutils python3.8-dev \
python3.9 python3.9-distutils python3.9-dev \
python3.10 python3.10-distutils python3.10-dev \
python3.11 python3.11-distutils python3.11-dev \
python3.12 python3-setuptools python3.12-dev \
wget \
ca-certificates
RUN wget https://bootstrap.pypa.io/get-pip.py \
&& python3.9 get-pip.py pip==23.2.1 --break-system-packages \
&& python3.10 get-pip.py pip==23.2.1 --break-system-packages \
&& python3.11 get-pip.py pip==23.2.1 --break-system-packages \
&& python3.12 get-pip.py pip==23.2.1 --break-system-packages \
&& python3.7 get-pip.py pip==22.2.2 \
&& python3.8 get-pip.py pip==22.2.2 \
&& python3.9 get-pip.py pip==22.2.2 \
&& python3.10 get-pip.py pip==22.2.2 \
&& rm get-pip.py
RUN apt-get install -y --no-install-recommends \
@@ -27,14 +27,14 @@ RUN apt-get install -y --no-install-recommends \
build-essential \
zip
RUN python3.7 --version
RUN python3.8 --version
RUN python3.9 --version
RUN python3.10 --version
RUN python3.11 --version
RUN python3.12 --version
RUN pip3.7 install setuptools setuptools-rust
RUN pip3.8 install setuptools setuptools-rust
RUN pip3.9 install setuptools setuptools-rust
RUN pip3.10 install setuptools setuptools-rust
RUN pip3.11 install setuptools setuptools-rust --break-system-packages
RUN pip3.12 install setuptools setuptools-rust --break-system-packages
RUN pip3 install tox pytest --break-system-packages
RUN pip3 install tox pytest

View File

@@ -15,7 +15,7 @@ dev-shell:
docker run --rm -ti -v "$(PWD):/app" -w /app --name facho-cli facho bash
test:
docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.12 setup.py test'
docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.7 setup.py test'
tox:
docker run -it -v $(PWD)/:/app -w /app facho tox

Binary file not shown.

View File

@@ -14,7 +14,7 @@ from facho.fe import fe
from datetime import datetime, date
# Datos del fomulario del SET de pruebas
INVOICE_AUTHORIZATION = '181360000001' # Número suministrado por la Dian en el momento de la creación del SET de Pruebas
INVOICE_AUTHORIZATION = '181360000001' #Número suministrado por la Dian en el momento de la creación del SET de Pruebas
ID_SOFTWARE = '57bcb6d1-c591-5a90-b80a-cb030ec91440' #Id suministrado por la Dian en el momento de la creación del SET de Pruebas
PIN = '19642' #Número creado por la empresa para poder crear el SET de pruebas
CLAVE_TECNICA = 'fc9eac422eba16e21ffd8c5f94b3f30a6e38162d' ##Id suministrado por la Dian en el momento de la creación del SET de Pruebas
@@ -36,7 +36,6 @@ def extensions(inv):
'SETP', 990000000, 995000000)#del SET de pruebas
return [security_code, authorization_provider, cufe, software_provider, inv_authorization]
def invoice():
# factura de venta nacional
inv = form.Invoice('01')
@@ -50,17 +49,16 @@ def invoice():
inv.set_operation_type('10')
inv.set_supplier(form.Party(
legal_name = 'Nombre registrado de la empresa',
name='Nombre comercial o él mismo nombre registrado',
ident=form.PartyIdentification(
'nit_empresa', 'digito_verificación', '31'),
name = 'Nombre comercial o él mismo nombre registrado',
ident = form.PartyIdentification('nit_empresa', 'digito_verificación', '31'),
# obligaciones del contribuyente ver DIAN:FAK26
responsability_code=form.Responsability(['ZZ', 'O-14', 'O-48']),
responsability_code = form.Responsability(['O-07', 'O-14', 'O-48']),
# ver DIAN:FAJ28
responsability_regime_code='48',
responsability_regime_code = '48',
# tipo de organizacion juridica ver DIAN:6.2.3
organization_code='1',
email="correoempresa@correoempresa.correo",
address=form.Address(
organization_code = '1',
email = "correoempresa@correoempresa.correo",
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia')),
@@ -78,43 +76,42 @@ def invoice():
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia')),
# tax_scheme = form.TaxScheme('01', 'IVA')
#tax_scheme = form.TaxScheme('01', 'IVA')
))
# asignar metodo de pago
inv.set_payment_mean(form.PaymentMean(
# metodo de pago ver DIAN:3.4.1
id='1',
# codigocorrespondientealmediodepagoverDIAN:3.4.2
code='20',
# fechadevencimientodelafactura
due_at=datetime.now(),
# identificadornumerico
payment_id='2'
id = '1',
# codigo correspondiente al medio de pago ver DIAN:3.4.2
code = '20',
# fecha de vencimiento de la factura
due_at = datetime.now(),
# identificador numerico
payment_id = '2'
))
# adicionar una linea al documento
inv.add_invoice_line(
form.InvoiceLine(
quantity=form.Quantity(int(20.5), '94'),
# item general de codigo 999
description='productO3',
sitem=form.StandardItem('test', 9999),
price=form.Price(
# precio base del item (sin iva)
amount=form.Amount(200.00),
# ver DIAN:6.3.5.1
type_code='01',
type='x'
),
tax=form.TaxTotal(
subtotals=[
form.TaxSubTotal(
percent=19.00,
scheme=form.TaxScheme('01')
)]
)
))
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(int(20.5), '94'),
# item general de codigo 999
description = 'productO3',
item = form.StandardItem('test', 9999),
price = form.Price(
# precio base del item (sin iva)
amount = form.Amount(200.00),
# ver DIAN:6.3.5.1
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.00,
scheme=form.TaxScheme('01')
)
]
)
))
return inv
def document_xml():
return form_xml.DIANInvoiceXML

View File

@@ -1,127 +1,109 @@
# importar libreria de modelos
from facho import fe, form_xml
import facho.fe.form as form
import datetime
PRIVATE_KEY_PATH = 'ruta a mi llave privada'
PRIVATE_PASSPHRASE = 'clave de la llave privada'
import facho.fe.form_xml
PRIVATE_KEY_PATH='ruta a mi llave privada'
PRIVATE_PASSPHRASE='clave de la llave privada'
# consultar las extensiones necesarias
def extensions(inv):
security_code = fe.DianXMLExtensionSoftwareSecurityCode(
'id software', 'pin', inv.invoice_ident)
security_code = fe.DianXMLExtensionSoftwareSecurityCode('id software', 'pin', inv.invoice_ident)
authorization_provider = fe.DianXMLExtensionAuthorizationProvider()
cufe = fe.DianXMLExtensionCUFE(
inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS,
'clave tecnica')
cufe = fe.DianXMLExtensionCUFE(inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS,
'clave tecnica')
nit = form.PartyIdentification('nit', '5', '31')
software_provider = fe.DianXMLExtensionSoftwareProvider(
nit, nit.dv, 'id software')
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization(
'invoice autorization',
datetime(2019, 1, 19),
datetime(2030, 1, 19),
'SETP', 990000001, 995000000)
return [
security_code,
authorization_provider,
cufe, software_provider,
inv_authorization
]
software_provider = fe.DianXMLExtensionSoftwareProvider(nit, nit.dv, 'id software')
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization('invoice autorization',
datetime(2019, 1, 19),
datetime(2030, 1, 19),
'SETP', 990000001, 995000000)
return [security_code, authorization_provider, cufe, software_provider, inv_authorization]
# generar documento desde modelo a ruta indicada
def generate_document(invoice, filepath):
xml = form_xml.DIANInvoiceXML(invoice)
for extension in extensions(invoice):
xml.add_extension(extension)
form_xml.utils.DIANWriteSigned(
xml, filepath, PRIVATE_KEY_PATH, PRIVATE_PASSPHRASE, True)
form_xml.utils.DIANWriteSigned(xml, filepath, PRIVATE_KEY_PATH, PRIVATE_PASSPHRASE, True)
# Modelars las facturas
# ...
# factura de venta nacional
inv = form.NationalSalesInvoice()
# asignar periodo de facturacion
inv.set_period(datetime.now(), datetime.now())
# asignar fecha de emision de la factura
inv.set_issue(datetime.now())
# asignar prefijo y numero del documento
inv.set_ident('SETP990003033')
# asignar tipo de operacion ver DIAN:6.1.5
inv.set_operation_type('10')
# asignar proveedor
inv.set_supplier(form.Party(
legal_name='FACHOSOS',
name='FACHOSOS',
ident=form.PartyIdentification('900579212', '5', '31'),
legal_name = 'FACHO SOS',
name = 'FACHO SOS',
ident = form.PartyIdentification('900579212', '5', '31'),
# obligaciones del contribuyente ver DIAN:FAK26
responsability_code=form.Responsability(['ZZ', 'O-09', 'O-14', 'O-48']),
responsability_code = form.Responsability(['O-07', 'O-09', 'O-14', 'O-48']),
# ver DIAN:FAJ28
responsability_regime_code='48',
responsability_regime_code = '48',
# tipo de organizacion juridica ver DIAN:6.2.3
organization_code='1',
email="sdds@sd.com",
address=form.Address(
name='',
street='',
city=form.City('05001', 'Medellín'),
country=form.Country('CO', 'Colombia'),
countrysubentity=form.CountrySubentity('05', 'Antioquia'))
organization_code = '1',
email = "sdds@sd.com",
address = form.Address(
name = '',
street = '',
city = form.City('05001', 'Medellín'),
country = form.Country('CO', 'Colombia'),
countrysubentity = form.CountrySubentity('05', 'Antioquia'))
))
inv.set_customer(form.Party(
legal_name='facho-customer',
name='facho-customer',
ident=form.PartyIdentification('999999999', '', '13'),
responsability_code=form.Responsability(['R-99-PN']),
responsability_regime_code='49',
organization_code='2',
email="sdds@sd.com",
address=form.Address(
name='',
street='',
city=form.City('05001', 'Medellín'),
country=form.Country('CO', 'Colombia'),
countrysubentity=form.CountrySubentity('05', 'Antioquia'))
legal_name = 'facho-customer',
name = 'facho-customer',
ident = form.PartyIdentification('999999999', '', '13'),
responsability_code = form.Responsability(['R-99-PN']),
responsability_regime_code = '49',
organization_code = '2',
email = "sdds@sd.com",
address = form.Address(
name = '',
street = '',
city = form.City('05001', 'Medellín'),
country = form.Country('CO', 'Colombia'),
countrysubentity = form.CountrySubentity('05', 'Antioquia'))
))
# asignar metodo de pago
inv.set_payment_mean(form.PaymentMean(
# metodo de pago ver DIAN:3.4.1
id='1',
id = '1',
# codigo correspondiente al medio de pago ver DIAN:3.4.2
code='10',
code = '10',
# fecha de vencimiento de la factura
due_at=datetime.now(),
due_at = datetime.now(),
# identificador numerico
payment_id='1'
payment_id = '1'
))
# adicionar una linea al documento
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto facho',
quantity = form.Quantity(1, '94'),
description = 'producto facho',
# item general de codigo 999
item=form.StandardItem('test', 9999),
price=form.Price(
item = form.StandardItem('test', 9999),
price = form.Price(
# precio base del tiem
amount=form.Amount(100.00),
amount = form.Amount(100.00),
# ver DIAN:6.3.5.1
type_code='01',
type='x'
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.00,
)]
percent = 19.00,
)
]
)
))

View File

@@ -1,7 +1,7 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.71])
AC_PREREQ([2.69])
AC_INIT([facho-signer], [0.0.1], [bit4bit@riseup.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/facho_signer.c])

View File

@@ -76,7 +76,7 @@
<cac:PartyTaxScheme>
<cbc:RegistrationName>NEUROTEC TECNOLOGIA S.A.S</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195" schemeID="5" schemeName="31">900579212</cbc:CompanyID>
<cbc:TaxLevelCode listName="48">ZZ;O-09;O-14;O-48</cbc:TaxLevelCode>
<cbc:TaxLevelCode listName="48">O-07;O-09;O-14;O-48</cbc:TaxLevelCode>
<cac:TaxScheme/>
</cac:PartyTaxScheme>
<cac:Contact>

View File

@@ -259,14 +259,14 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
spec.loader.exec_module(module)
import facho.fe.form as form
from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned, DIANWrite, DIANSupportDocumentXML
from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned,DIANWrite
from facho import fe
try:
invoice_xml = module.document_xml()
except AttributeError:
#invoice_xml = DIANInvoiceXML
invoice_xml = DIANSupportDocumentXML
invoice_xml = DIANInvoiceXML
print("Using document xml:", invoice_xml)
invoice = module.invoice()
invoice.calculate()

View File

@@ -1,11 +1,12 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from lxml import etree
from lxml.etree import Element, tostring
from lxml.etree import Element, SubElement, tostring
import re
from collections import defaultdict
from copy import deepcopy
from pprint import pprint
class FachoValueInvalid(Exception):
def __init__(self, xpath):
@@ -31,10 +32,7 @@ class LXMLBuilder:
def __init__(self, nsmap):
self.nsmap = nsmap
self._re_node_expr = \
re.compile(
r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))'
r'(?P<attrs>\[.+\])?')
self._re_node_expr = re.compile(r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))(?P<attrs>\[.+\])?')
self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?')
def match_expression(self, node_expr):
@@ -123,7 +121,7 @@ class LXMLBuilder:
elem.attrib[key] = value
@classmethod
def remove_attributes(cls, elem, keys, exclude=[]):
def remove_attributes(cls, elem, keys, exclude = []):
for key in keys:
if key in exclude:
continue
@@ -145,8 +143,7 @@ class LXMLBuilder:
self.remove_attributes(el, keys, exclude=['facho_optional'])
is_optional = el.get('facho_optional', 'False') == 'True'
if is_optional and el.getchildren() == [] and el.keys() == [
'facho_optional']:
if is_optional and el.getchildren() == [] and el.keys() == ['facho_optional']:
el.getparent().remove(el)
return tostring(elem, **attrs).decode('utf-8')
@@ -156,15 +153,14 @@ class FachoXML:
"""
Decora XML con funciones de consulta XPATH de un solo elemento
"""
def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',
fragment_root_element=None):
def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',fragment_root_element=None):
if builder is None:
self.builder = LXMLBuilder(nsmap)
else:
self.builder = builder
self.nsmap = nsmap
if isinstance(root, str):
self.root = self.builder.build_element_from_string(root, nsmap)
else:
@@ -184,19 +180,14 @@ class FachoXML:
def root_namespace(self):
return etree.QName(self.root).namespace
def root_localname(self):
return etree.QName(self.root).localname
def append_element(self, elem, new_elem):
# elem = self.find_or_create_element(xpath, append=append)
# self.builder.append(elem, new_elem)
self.builder.append(elem, new_elem)
def add_extension(self, extension):
extension.build(self)
def fragment(
self, xpath, append=False, append_not_exists=False):
def fragment(self, xpath, append=False, append_not_exists=False):
nodes = xpath.split('/')
nodes.pop()
root_prefix = '/'.join(nodes)
@@ -206,9 +197,7 @@ class FachoXML:
if parent is None:
parent = self.find_or_create_element(xpath, append=append)
return FachoXML(
parent, nsmap=self.nsmap, fragment_prefix=root_prefix,
fragment_root_element=self.root)
return FachoXML(parent, nsmap=self.nsmap, fragment_prefix=root_prefix, fragment_root_element=self.root)
def register_alias_xpath(self, alias, xpath):
self.xpath_for[alias] = xpath
@@ -244,8 +233,7 @@ class FachoXML:
"""
xpath = self._path_xpath_for(xpath)
node_paths = xpath.split('/')
# remove empty /
node_paths.pop(0)
node_paths.pop(0) #remove empty /
root_tag = node_paths.pop(0)
root_node = self.builder.build_from_expression(root_tag)
@@ -253,10 +241,10 @@ class FachoXML:
# restaurar ya que no es la raiz y asignar actual como raiz
node_paths.insert(0, root_tag)
root_node = self.root
if not self.builder.same_tag(root_node.tag, self.root.tag):
raise ValueError('xpath %s must be absolute to /%s' % (
xpath, self.root.tag))
raise ValueError('xpath %s must be absolute to /%s' % (xpath, self.root.tag))
# crea jerarquia segun xpath indicado
parent = None
@@ -266,8 +254,8 @@ class FachoXML:
for node_path in node_paths:
node_expr = self.builder.match_expression(node_path)
node = self.builder.build_from_expression(node_path)
child = self.builder.find_relative(
current_elem, node_expr['path'], self.nsmap)
child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap)
parent = current_elem
if child is not None:
@@ -278,12 +266,11 @@ class FachoXML:
node_expr = self.builder.match_expression(node_tag)
node = self.builder.build_from_expression(node_tag)
child = self.builder.find_relative(
current_elem, node_expr['path'], self.nsmap)
child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap)
parent = current_elem
if child is not None:
current_elem = child
if parent == current_elem:
self.builder.append(parent, node)
return node
@@ -300,10 +287,9 @@ class FachoXML:
self.builder.append(parent, node)
return node
if self.builder.is_attribute(
last_slibing, 'facho_placeholder', 'True'):
if self.builder.is_attribute(last_slibing, 'facho_placeholder', 'True'):
self._remove_facho_attributes(last_slibing)
return last_slibing
return last_slibing
self.builder.append_next(last_slibing, node)
return node
@@ -314,8 +300,7 @@ class FachoXML:
self._remove_facho_attributes(current_elem)
return current_elem
def set_element_validator(
self, xpath, validator=False):
def set_element_validator(self, xpath, validator = False):
"""
validador al asignar contenido a xpath indicado
@@ -328,9 +313,8 @@ class FachoXML:
self._validators[key] = lambda v, attrs: True
else:
self._validators[key] = validator
def set_element(
self, xpath, content, **attrs):
def set_element(self, xpath, content, **attrs):
"""
asigna contenido ubicado por ruta tipo XPATH.
@param xpath ruta tipo XPATH
@@ -372,8 +356,7 @@ class FachoXML:
self.builder.set_attribute(elem, k, str(v))
return self
def get_element_attribute(
self, xpath, attribute, multiple=False):
def get_element_attribute(self, xpath, attribute, multiple=False):
elem = self.get_element(xpath, multiple=multiple)
if elem is None:
@@ -410,16 +393,14 @@ class FachoXML:
return None
return format_(text)
def get_element_text_or_attribute(
self, xpath, default=None, multiple=False, raise_on_fail=False):
def get_element_text_or_attribute(self, xpath, default=None, multiple=False, raise_on_fail=False):
parts = xpath.split('/')
is_attribute = parts[-1].startswith('@')
is_attribute = parts[-1].startswith('@')
if is_attribute:
attribute_name = parts.pop(-1).lstrip('@')
element_path = "/".join(parts)
try:
val = self.get_element_attribute(
element_path, attribute_name, multiple=multiple)
val = self.get_element_attribute(element_path, attribute_name, multiple=multiple)
if val is None:
return default
return val
@@ -452,8 +433,7 @@ class FachoXML:
if isinstance(xpath, tuple):
val = xpath[0]
else:
val = self.get_element_text_or_attribute(
xpath, raise_on_fail=raise_on_fail)
val = self.get_element_text_or_attribute(xpath, raise_on_fail=raise_on_fail)
vals.append(val)
return vals
@@ -475,8 +455,7 @@ class FachoXML:
return True
def _remove_facho_attributes(self, elem):
self.builder.remove_attributes(
elem, ['facho_optional', 'facho_placeholder'])
self.builder.remove_attributes(elem, ['facho_optional', 'facho_placeholder'])
def tostring(self, **kw):
return self.builder.tostring(self.root, **kw)
@@ -488,17 +467,15 @@ class FachoXML:
root = self.root
if self.fragment_root_element is not None:
root = self.fragment_root_element
if isinstance(self.nsmap, dict):
nsmap = dict(map(reversed, self.nsmap.items()))
ns = nsmap[etree.QName(root).namespace] + ':'
if self.fragment_root_element is not None:
new_xpath = '/' + ns + etree.QName(root).localname + '/' + \
etree.QName(self.root).localname + '/' + xpath.lstrip('/')
new_xpath = '/' + ns + etree.QName(root).localname + '/' + etree.QName(self.root).localname + '/' + xpath.lstrip('/')
else:
new_xpath = '/' + ns + etree.QName(root).localname + '/' + \
xpath.lstrip('/')
new_xpath = '/' + ns + etree.QName(root).localname + '/' + xpath.lstrip('/')
return new_xpath
def __str__(self):

View File

@@ -5,7 +5,6 @@ from .fe import DianXMLExtensionSigner
from .fe import DianXMLExtensionSoftwareSecurityCode
from .fe import DianXMLExtensionCUFE
from .fe import DianXMLExtensionCUDE
from .fe import DianXMLExtensionCUDS
from .fe import DianXMLExtensionInvoiceAuthorization
from .fe import DianXMLExtensionSoftwareProvider
from .fe import DianXMLExtensionAuthorizationProvider

View File

@@ -21,10 +21,10 @@ __all__ = ['DianClient',
class SOAPService:
def get_wsdl(self):
def wsdl(self):
raise NotImplementedError()
def get_service(self):
def service(self):
raise NotImplementedError()
def build_response(self, as_dict):
@@ -63,10 +63,10 @@ class GetNumberingRange(SOAPService):
accountCodeT: str
softwareCode: str
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'GetNumberingRange'
def build_response(self, as_dict):
@@ -78,10 +78,10 @@ class SendBillAsync(SOAPService):
fileName: str
contentFile: str
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'SendBillAsync'
def build_response(self, as_dict):
@@ -106,10 +106,10 @@ class SendTestSetAsync(SOAPService):
contentFile: str
testSetId: str = ''
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'SendTestSetAsync'
def build_response(self, as_dict):
@@ -120,10 +120,10 @@ class SendBillSync(SOAPService):
fileName: str
contentFile: bytes
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'SendBillSync'
def build_response(self, as_dict):
@@ -153,10 +153,10 @@ class GetStatusResponse:
class GetStatus(SOAPService):
trackId: bytes
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'GetStatus'
def build_response(self, as_dict):
@@ -166,10 +166,10 @@ class GetStatus(SOAPService):
class GetStatusZip(SOAPService):
trackId: bytes
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'GetStatusZip'
def build_response(self, as_dict):
@@ -179,10 +179,10 @@ class GetStatusZip(SOAPService):
class SendNominaSync(SOAPService):
contentFile: bytes
def get_wsdl(self):
def wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
def service(self):
return 'SendNominaSync'
def build_response(self, as_dict):
@@ -193,31 +193,31 @@ class Habilitacion:
WSDL = 'https://vpfe-hab.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
class GetNumberingRange(GetNumberingRange):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class SendBillAsync(SendBillAsync):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class SendBillSync(SendBillSync):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class SendTestSetAsync(SendTestSetAsync):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class GetStatus(GetStatus):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class GetStatusZip(GetStatusZip):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class SendNominaSync(SendNominaSync):
def get_wsdl(self):
def wsdl(self):
return Habilitacion.WSDL
class DianGateway:
@@ -226,7 +226,7 @@ class DianGateway:
raise NotImplementedError()
def _remote_service(self, conn, service):
return conn.service[service.get_service()]
return conn.service[service.service()]
def _close(self, conn):
return
@@ -250,7 +250,7 @@ class DianClient(DianGateway):
self._password = password
def _open(self, service):
return zeep.Client(service.get_wsdl(), wsse=UsernameToken(self._username, self._password))
return zeep.Client(service.wsdl(), wsse=UsernameToken(self._username, self._password))
class DianSignatureClient(DianGateway):
@@ -264,7 +264,7 @@ class DianSignatureClient(DianGateway):
# RESOLUCCION 0004: pagina 756
from zeep.wsse import utils
client = zeep.Client(service.get_wsdl(), wsse=
client = zeep.Client(service.wsdl(), wsse=
BinarySignature(
self.private_key_path, self.public_key_path, self.password,
signature_method=xmlsec.Transform.RSA_SHA256,

View File

@@ -1,61 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de validacion :: Ultima modificación 18-02-2019 - evb-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TarifaImpuestos</ShortName>
<LongName xml:lang="es">Tarifas por Impuesto</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TarifaImpuestos-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<LongName xml:lang="es">Codigo Comun</LongName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Name</ShortName>
<LongName xml:lang="es">Nombre</LongName>
<Data Type="string"/>
</Column>
<Column Id="description" Use="required">
<ShortName>Description</ShortName>
<LongName xml:lang="es">Descripcion</LongName>
<Data Type="string"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>15.00</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>100.00</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>
<?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de validacion :: Ultima modificación 18-02-2019 - evb-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TarifaImpuestos</ShortName>
<LongName xml:lang="es">Tarifas por Impuesto</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TarifaImpuestos-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<LongName xml:lang="es">Codigo Comun</LongName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Name</ShortName>
<LongName xml:lang="es">Nombre</LongName>
<Data Type="string"/>
</Column>
<Column Id="description" Use="required">
<ShortName>Description</ShortName>
<LongName xml:lang="es">Descripcion</LongName>
<Data Type="string"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>15.00</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@@ -1,100 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de valores: Ultima modificación 03-04-2022 - wcbr-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoDocumento</ShortName>
<LongName xml:lang="es">Tipo de Documento</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoDocumento-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<LongName xml:lang="es">Codigo Comun</LongName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Name</ShortName>
<LongName xml:lang="es">Nombre</LongName>
<Data Type="string"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura electrónica de Venta</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipos de factura</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura electrónica de venta con propósito de exportación</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipos de factura</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>03</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura de talonario o papel con numeración de contingencia.</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipos de factura</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>04</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura electrónica de Venta por Contingencia DIAN</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipos de factura</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>91</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nota Crédito</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Exclusivo en referencias a documentos (elementos DocumentReference)</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>92</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nota Débito</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Exclusivo en referencias a documentos (elementos DocumentReference)</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
<?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de valores:: Ultima modificación 18-02-2019 - evb-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoDocumento</ShortName>
<LongName xml:lang="es">Tipo de Documento</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoDocumento-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<LongName xml:lang="es">Codigo Comun</LongName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Name</ShortName>
<LongName xml:lang="es">Nombre</LongName>
<Data Type="string"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura de Venta Nacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura de Exportación </SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>03</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factura de Contingencia</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>91</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nota Crédito</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>92</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nota Débito</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@@ -1,171 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de valores:: Ultima modificación 18-02-2019 - evb-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoImpuesto</ShortName>
<LongName xml:lang="es">Tipo de Tributos</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoImpuesto-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<LongName xml:lang="es">Codigo Comun</LongName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Name</ShortName>
<LongName xml:lang="es">Nombre</LongName>
<Data Type="string"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>IVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>IC</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>03</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ICA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>04</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>INC</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>05</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>06</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteRenta</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>07</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteICA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>08</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteCREE</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>20</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>FtoHorticultura</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>21</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Timbre</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>22</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Bolsas</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>23</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>INCarbono</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>24</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>INCombustibles</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>25</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sobretasa Combustibles</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>26</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sordicom</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>30</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Impuesto al Consumo de Datos</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>ZZ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nombre de la figura tributaria</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
<?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de valores:: Ultima modificación 18-02-2019 - evb-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoImpuesto</ShortName>
<LongName xml:lang="es">Tipo de Tributos</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoImpuesto-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<LongName xml:lang="es">Codigo Comun</LongName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Name</ShortName>
<LongName xml:lang="es">Nombre</LongName>
<Data Type="string"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>IVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>IC</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>03</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ICA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>04</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>INC</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>05</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteIVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>06</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteFuente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>07</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteICA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>08</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>ReteCREE</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>20</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>FtoHorticultura</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>21</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Timbre</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>22</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Bolsas</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>23</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>INCarbono</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>24</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>INCombustibles</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>25</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sobretasa Combustibles</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>26</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sordicom</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>ZZ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nombre de la figura tributaria</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoOperacion</ShortName>
<LongName xml:lang="es">Tipo de operacion</LongName>
<Version>1</Version>
<CanonicalUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoOperacion</CanonicalUri>
<CanonicalVersionUri>urn:dian:names:especificacion:ubl:listacodigos:gc:TipoOperacion-2.1</CanonicalVersionUri>
<LocationUri>http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoOperacion-2.1.gc</LocationUri>
<Agency>
<LongName xml:lang="es">DIAN (Dirección de Impuestos y Aduanas Nacionales)</LongName>
<Identifier>195</Identifier>
</Agency>
</Identification>
<ColumnSet>
<Column Id="code" Use="required">
<ShortName>Code</ShortName>
<Data Type="normalizedString"/>
</Column>
<Column Id="name" Use="required">
<ShortName>Nombre</ShortName>
<Data Type="normalizedString"/>
</Column>
<Key Id="codeKey">
<ShortName>CodeKey</ShortName>
<ColumnRef Ref="code"/>
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>10</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Residente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>11</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No Residente</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@@ -30,6 +30,46 @@
</Key>
</ColumnSet>
<SimpleCodeList>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-99</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro tipo de obligado</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-06</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Ingresos y patrimonio</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-07</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Retención en la fuente a título de renta</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-08</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Retención timbre nacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-09</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Retención en la fuente en el impuesto sobre las ventas</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-13</SimpleValue>
@@ -38,6 +78,14 @@
<SimpleValue>Gran contribuyente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-14</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Informante de exógena</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-15</SimpleValue>
@@ -46,6 +94,38 @@
<SimpleValue>Autorretenedor</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-16</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Obligación de facturar por ingresos de bienes y/o servicios excluidos</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-17</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Profesionales de compra y venta de divisas</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-19</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Productor y/o exportador de bienes exentos</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-22</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Obligado a cumplir deberes formales a nombre de terceros</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-23</SimpleValue>
@@ -54,6 +134,62 @@
<SimpleValue>Agente de retención en el impuesto sobre las ventas</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-32</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Impuesto Nacional a la Gasolina y al ACPM</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-33</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Impuesto Nacional al consumo</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-34</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Régimen simplificado impuesto nacional consumo rest y bares</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-36</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Establecimiento Permanente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-37</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Obligado a Facturar Electrónicamente Modelo 2242</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-38</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Facturación Electrónica Voluntaria Modelo 2242</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-39</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Proveedor de Servicios Tecnológicos PST Modelo 2242</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-47</SimpleValue>
@@ -78,6 +214,782 @@
<SimpleValue>No responsable de IVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-52</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Facturador electrónico</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-99</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro tipo de obligado</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-00-PN</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Clientes del Exterior</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-12-PN</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factor PN</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-16-PN</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Mandatario</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-25-PN</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agente Interventor</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-99-PN</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No responsable</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-06-PJ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Apoderado especial</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-07-PJ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Apoderado general</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-12-PJ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Factor</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-16-PJ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Mandatario</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-99-PJ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro tipo de responsable</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agente de carga internacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agente marítimo</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-03</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Almacén general de depósito</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-04</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Comercializadora internacional (C.I.)</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-05</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Comerciante de la zona aduanera especial de Inírida, Puerto Carreño, Cumaribo y Primavera</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-06</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Comerciantes de la zona de régimen aduanero especial de Leticia</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-07</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Comerciantes de la zona de régimen aduanero especial de Maicao, Uribia y Manaure</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-08</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Comerciantes de la zona de régimen aduanero especial de Urabá, Tumaco y Guapí</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-09</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Comerciantes del puerto libre de San Andrés, Providencia y Santa Catalina</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-10</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito público de apoyo logístico internacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-11</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado para procesamiento industrial</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-12</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado de transformación o ensamble</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-13</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito franco</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-14</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado aeronáutico</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-15</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado para distribución internacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-16</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado de provisiones de a bordo para consumo y para llevar</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-17</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado para envíos urgentes</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-18</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito privado</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-19</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito público</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-20</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósito público para distribución internacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-21</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Exportador de café</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-22</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Exportador</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-23</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Importador</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-24</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Intermediario de tráfico postal y envíos urgentes</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-25</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Operador de transporte multimodal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-26</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sociedad de intermediación aduanera</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-27</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Titular de puertos y muelles de servicio público o privado</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-28</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportador 263nfor régimen de importación y/o exportación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-29</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportista nacional para operaciones del régimen de tránsito aduanero</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-30</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario comercial zona franca</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-32</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario industrial de bienes zona franca</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-34</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario industrial de servicios zona franca</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-36</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario operador de zona franca</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-37</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario aduanero permanente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-38</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario altamente exportador</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-39</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario de zonas económicas especiales de exportación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-40</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Deposito privado de instalaciones industriales</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-41</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Beneficiarios de programas especiales de exportación PEX</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-42</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Depósitos privados para mercancías en tránsito San Andrés</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-43</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Observadores de las operaciones de importación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-44</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuarios sistemas especiales Importación exportación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-46</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportador 263nformac régimen de importación y/o exportación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-47</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportador terrestre régimen de importación y/o exportación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-48</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Aeropuerto de servicio publico o privado</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-49</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportador fluvial régimen de importación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-50</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario industrial zona franca especial</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-53</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agencias de aduanas 1</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-54</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Usuario Operador Zona Franca Especial</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-55</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agencias de aduanas 2</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-56</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agencias de aduanas 3</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-57</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agencias de aduanas 4</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-58</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportador aéreo nacional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-60</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Transportador aéreo, marítimo o fluvial modalidad Cabotaje</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-61</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Importador de alimentos de consumo humano y animal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-62</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Importador Ocasional</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-63</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Importador de maquinaría y sus partes Decreto 2261 de 2012</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-64</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Beneficiario Programa de Fomento Industria Automotriz-PROFIA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>A-99</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro tipo de agente aduanero</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agencia</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Establecimiento de comercio</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-03</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Centro de explotación agrícola</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-04</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Centro de explotación animal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-05</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Centro de explotación minera</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-06</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Centro de explotación de transformación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-07</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Centro de explotación de servicios</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-08</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Oficina</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-09</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sede</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-10</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Sucursal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-11</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Consultorio</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-12</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Administraciones</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-13</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Seccionales</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-14</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Regionales</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-15</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Intendencias</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-16</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Local o negocio</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-17</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Punto de venta</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-18</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Fábrica</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-19</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Taller</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-20</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Cantera</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-21</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Pozo de Petróleo y Gas</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-22</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro lug de tipo de extrac explotación de recursos naturales</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>E-99</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro tipo de establecimiento</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-13</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Gran contribuyente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-15</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Autorretenedor</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-23</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Agente de retención IVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-47</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Régimen simple de tributación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-99-PN</SimpleValue>

View File

@@ -62,13 +62,5 @@
<SimpleValue>Régimen simple de tributación</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>ZZ</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No aplica</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@@ -82,16 +82,11 @@ TipoAmbiente = CodeList(path_for_codelist('TipoAmbiente-2.1.gc'), 'code', 'name'
TipoDocumento = CodeList(path_for_codelist('TipoDocumento-2.1.gc'), 'code', 'name')
TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TipoImpuesto-2.1.custom.gc'), 'code', 'name'))
TarifaImpuesto = CodeList(path_for_codelist('TarifaImpuestoINC-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TarifaImpuestoIVA-2.1.gc'), 'code', 'name'))\
.update(CodeList(path_for_codelist('TarifaImpuestoReteIVA-2.1.gc'), 'code', 'name'))\
.update(CodeList(path_for_codelist('TarifaImpuestoReteRenta-2.1.gc'), 'code', 'name'))
CodigoPrecioReferencia = CodeList(path_for_codelist('CodigoPrecioReferencia-2.1.gc'), 'code', 'name')
MediosPago = CodeList(path_for_codelist('MediosPago-2.1.gc'), 'code', 'name')
FormasPago = CodeList(path_for_codelist('FormasPago-2.1.gc'), 'code', 'name')
RegimenFiscal = CodeList(path_for_codelist('RegimenFiscal-2.1.custom.gc'), 'code', 'name')
TipoOperacionNC = CodeList(path_for_codelist('TipoOperacionNC-2.1.gc'), 'code', 'name')
TipoOperacionNCDS = CodeList(path_for_codelist('TipoOperacionNCDS-2.1.gc'), 'code', 'name')
TipoOperacionND = CodeList(path_for_codelist('TipoOperacionND-2.1 - copia.gc'), 'code', 'name')
TipoOperacionF = CodeList(path_for_codelist('TipoOperacionF-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name'))

View File

@@ -1,5 +1,6 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder
import uuid
import xmlsig
@@ -7,16 +8,13 @@ import xades
from datetime import datetime
import OpenSSL
import zipfile
# import warnings
import warnings
import hashlib
from contextlib import contextmanager
from .data.dian import codelist
from . import form
from collections import defaultdict
# from pathlib import Path
from dateutil import tz
from cryptography.hazmat.primitives.serialization import pkcs12
from pathlib import Path
AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code']
AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code']
@@ -32,49 +30,33 @@ SCHEME_AGENCY_ATTRS = {
POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf'
POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.'
Bogota = tz.gettz('America/Bogota')
# NAMESPACES = {
# 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
# 'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual',
# 'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste',
# 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
# 'xs': 'http://www.w3.org/2001/XMLSchema-instance',
# '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',
# 'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001',
# 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003',
# 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2',
# 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2',
# 'sts': 'dian:gov:co:facturaelectronica:Structures-2-1',
# 'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
# 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
# 'xades': 'http://uri.etsi.org/01903/v1.3.2#',
# 'xades141': 'http://uri.etsi.org/01903/v1.4.1#',
# 'ds': 'http://www.w3.org/2000/09/xmldsig#',
# 'sig': 'http://www.w3.org/2000/09/xmldsig#',
# }
NAMESPACES = {
'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual',
'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste',
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
'xs': 'http://www.w3.org/2001/XMLSchema-instance',
'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',
'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001',
'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003',
'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2',
'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2',
'sts': 'dian:gov:co:facturaelectronica:Structures-2-1',
'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'ds': 'http://www.w3.org/2000/09/xmldsig#',
'xades': 'http://uri.etsi.org/01903/v1.3.2#',
'xades141': 'http://uri.etsi.org/01903/v1.4.1#',
'ds': 'http://www.w3.org/2000/09/xmldsig#',
'sig': 'http://www.w3.org/2000/09/xmldsig#',
}
def fe_from_string(document: str) -> FachoXML:
return FeXML.from_string(document)
# from contextlib import contextmanager
from contextlib import contextmanager
@contextmanager
def mock_xades_policy():
from mock import patch
@@ -92,35 +74,30 @@ def mock_xades_policy():
mock.return_value = UrllibPolicyMock()
yield
class FeXML(FachoXML):
def __init__(self, root, namespace):
# raise Exception(namespace)
super().__init__("{%s}%s" % (namespace, root),
nsmap=NAMESPACES)
@classmethod
def from_string(cls, document: str) -> 'FeXML':
return super().from_string(document, namespaces=NAMESPACES)
def tostring(self, **kw):
# MACHETE(bit4bit) la DIAN espera que la etiqueta raiz no este en un namespace
root_namespace = self.root_namespace()
root_localname = self.root_localname()
xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace]
if root_localname == 'Invoice':
urn_oasis = 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
if root_localname == 'CreditNote':
urn_oasis = 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'
return super().tostring(**kw)\
.replace(xmlns_name + ':', '')\
.replace('xmlns:'+xmlns_name, 'xmlns')\
.replace(root_namespace, urn_oasis)
.replace(xmlns_name + ':', '')\
.replace('xmlns:'+xmlns_name, 'xmlns')\
.replace('schemaLocation', 'xsi:schemaLocation')
class DianXMLExtensionCUDFE(FachoXMLExtension):
def __init__(self, invoice, tipo_ambiente=AMBIENTE_PRUEBAS):
def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.invoice = invoice
@@ -146,24 +123,10 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
schemeID=self.tipo_ambiente,
schemeName=self.schemeName())
if self.schemeName() == "CUDS-SHA384":
if fachoxml.tag_document() == 'Invoice':
fachoxml.set_element('./cbc:ProfileID',
'DIAN 2.1: documento soporte en adquisiciones efectuadas a no obligados a facturar.')
else:
fachoxml.set_element('./cbc:ProfileID',
'DIAN 2.1: Nota de ajuste al documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente')
else:
fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta')
# #DIAN 1.8.-2021: FAD03
# fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta')
fachoxml.set_element(
'./cbc:ProfileExecutionID', self._tipo_ambiente_int())
fachoxml.set_element('./cbc:ProfileExecutionID', self._tipo_ambiente_int())
#DIAN 1.7.-2020: FAB36
fachoxml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode',
self._get_qrcode(cufe))
fachoxml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode',
self._get_qrcode(cufe))
def issue_time(self, datetime_):
return datetime_.strftime('%H:%M:%S-05:00')
@@ -179,8 +142,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue)
# PAG 601
build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount
build_vars['ValorTotalPagar'
] = invoice.invoice_legal_monetary_total.payable_amount
build_vars['ValorTotalPagar'] = invoice.invoice_legal_monetary_total.payable_amount
ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0))
build_vars['CodImpuesto1'] = '01'
build_vars['CodImpuesto2'] = '04'
@@ -210,8 +172,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
class DianXMLExtensionCUFE(DianXMLExtensionCUDFE):
def __init__(
self, invoice, clave_tecnica='', tipo_ambiente=AMBIENTE_PRUEBAS):
def __init__(self, invoice, clave_tecnica = '', tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.clave_tecnica = clave_tecnica
self.invoice = invoice
@@ -247,7 +208,6 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE):
'%d' % build_vars['TipoAmb'],
]
class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
@@ -285,41 +245,6 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
'%d' % build_vars['TipoAmb'],
]
class DianXMLExtensionCUDS(DianXMLExtensionCUDFE):
def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.software_pin = software_pin
self.invoice = invoice
def schemeName(self):
return 'CUDS-SHA384'
def buildVars(self):
build_vars = super().buildVars()
build_vars['Software-PIN'] = str(self.software_pin)
return build_vars
def formatVars(self):
build_vars = self.buildVars()
CodImpuesto1 = build_vars['CodImpuesto1']
CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3']
return [
'%s' % build_vars['NumFac'],
'%s' % build_vars['FecFac'],
'%s' % build_vars['HoraFac'],
form.Amount(build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' % build_vars['NitOFE'],
'%s' % build_vars['NumAdq'],
'%s' % build_vars['Software-PIN'],
'%d' % build_vars['TipoAmb'],
]
class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
# RESOLUCION 0004: pagina 108
@@ -329,8 +254,7 @@ class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
self.id_software = id_software
def build(self, fexml):
software_provider = fexml.fragment(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider')
software_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider')
provider_id_attrs = SCHEME_AGENCY_ATTRS.copy()
provider_id_attrs.update({'schemeID': self.dv})
#DIAN 1.7.-2020: FAB23
@@ -360,6 +284,7 @@ class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension):
class DianXMLExtensionSigner:
def __init__(self, pkcs12_path, passphrase=None, localpolicy=True):
self._pkcs12_data = open(pkcs12_path, 'rb').read()
self._passphrase = None
@@ -370,18 +295,17 @@ class DianXMLExtensionSigner:
@classmethod
def from_bytes(cls, data, passphrase=None, localpolicy=True):
self = cls.__new__(cls)
self._pkcs12_data = data
self._passphrase = None
self._localpolicy = localpolicy
if passphrase:
self._passphrase = passphrase.encode('utf-8')
return self
def _element_extension_content(self, fachoxml):
return fachoxml.builder.xpath(
fachoxml.root,
'./ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent')
return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent')
def sign_xml_string(self, document):
xml = LXMLBuilder.from_string(document)
@@ -403,6 +327,7 @@ class DianXMLExtensionSigner:
)
xml.append(signature)
ref = xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="", name="xmldsig-%s-ref0" % (id_uuid)
)
@@ -410,16 +335,14 @@ class DianXMLExtensionSigner:
id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid)
xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="#%s" % (
id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid),
signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid),
)
ki = xmlsig.template.ensure_key_info(signature, name=id_keyinfo)
data = xmlsig.template.add_x509_data(ki)
xmlsig.template.x509_data_add_certificate(data)
xmlsig.template.add_key_value(ki)
qualifying = xades.template.create_qualifying_properties(
signature, 'XadesObjects', 'xades')
qualifying = xades.template.create_qualifying_properties(signature, 'XadesObjects', 'xades')
xades.utils.ensure_id(qualifying)
id_props = "xmldsig-%s-signedprops" % (id_uuid)
@@ -427,12 +350,10 @@ class DianXMLExtensionSigner:
signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props),
uri_type="http://uri.etsi.org/01903#SignedProperties"
)
xmlsig.template.add_transform(
props_ref, xmlsig.constants.TransformInclC14N)
xmlsig.template.add_transform(props_ref, xmlsig.constants.TransformInclC14N)
# TODO assert with http://www.sic.gov.co/hora-legal-colombiana
props = xades.template.create_signed_properties(
qualifying, name=id_props, datetime=datetime.now(tz=Bogota))
props = xades.template.create_signed_properties(qualifying, name=id_props, datetime=datetime.now())
xades.template.add_claimed_role(props, "supplier")
policy = xades.policy.GenericPolicyId(
@@ -440,13 +361,9 @@ class DianXMLExtensionSigner:
POLICY_NAME,
xmlsig.constants.TransformSha256)
ctx = xades.XAdESContext(policy)
ctx.load_pkcs12(pkcs12.load_key_and_certificates(
self._pkcs12_data,
self._passphrase))
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(self._pkcs12_data,
self._passphrase))
# ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(
# self._pkcs12_data,
# self._passphrase))
if self._localpolicy:
with mock_xades_policy():
ctx.sign(signature)
@@ -454,7 +371,7 @@ class DianXMLExtensionSigner:
else:
ctx.sign(signature)
ctx.verify(signature)
# xmlsig take parent root
#xmlsig take parent root
xml.remove(signature)
return signature
@@ -463,28 +380,29 @@ class DianXMLExtensionSigner:
extcontent = self._element_extension_content(fachoxml)
fachoxml.append_element(extcontent, signature)
class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
# RESOLUCION 0004: pagina 176
def build(self, fexml):
attrs = {'schemeID': '4', 'schemeName': '31'}
attrs.update(SCHEME_AGENCY_ATTRS)
authorization_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider')
authorization_provider.set_element('./sts:AuthorizationProviderID',
'800197268',
**attrs)
class DianXMLExtensionInvoiceSource(FachoXMLExtension):
# CAB13
def build(self, fexml):
dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode'
fexml.set_element(
dian_path, 'CO',
listAgencyID="6",
listAgencyName="United Nations Economic Commission for Europe",
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1")
fexml.set_element(dian_path, 'CO',
listAgencyID="6",
listAgencyName="United Nations Economic Commission for Europe",
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1")
class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
@@ -514,15 +432,16 @@ class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To',
self.to)
fexml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode',
'CO',
# DIAN 1.7.-2020: FAB15
listAgencyID="6",
# DIAN 1.7.-2020: FAB16
listAgencyName="United Nations Economic Commission for Europe",
# DIAN 1.7.-2020: FAB17
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1")
fexml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode',
'CO',
#DIAN 1.7.-2020: FAB15
listAgencyID="6",
#DIAN 1.7.-2020: FAB16
listAgencyName="United Nations Economic Commission for Europe",
#DIAN 1.7.-2020: FAB17
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1"
)
class DianZIP:
@@ -531,8 +450,7 @@ class DianZIP:
MAX_FILES = 50
def __init__(self, file_like):
self.zipfile = zipfile.ZipFile(
file_like, mode='w', compression=zipfile.ZIP_DEFLATED)
self.zipfile = zipfile.ZipFile(file_like, mode='w', compression=zipfile.ZIP_DEFLATED)
self.num_files = 0
def add_xml(self, name, xml_data):
@@ -553,6 +471,7 @@ class DianZIP:
def __enter__(self):
"""
Facilita el uso de esta manera:
f = open('xxx', 'rb')
with DianZIP(f) as zip:
zip.add_invoice_xml('name', 'data xml')
@@ -575,7 +494,7 @@ class DianXMLExtensionSignerVerifier:
def verify_string(self, document):
# Obtener FachoXML
xml = LXMLBuilder.from_string(document)
fachoxml = FachoXML(xml, nsmap=NAMESPACES)
fachoxml = FachoXML(xml,nsmap=NAMESPACES)
# Obtener Signature
signature = fachoxml.builder.xpath(fachoxml.root, '//ds:Signature')

View File

@@ -1,26 +1,24 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
# import hashlib
# from functools import reduce
# import copy
import hashlib
from functools import reduce
import copy
import dataclasses
from dataclasses import dataclass, field
from dataclasses import dataclass
from datetime import datetime, date
# from collections import defaultdict
from collections import defaultdict
import decimal
from decimal import Decimal
import typing
from ..data.dian import codelist
DECIMAL_PRECISION = 6
class AmountCurrencyError(TypeError):
pass
@dataclass
class Currency:
code: str
@@ -31,7 +29,6 @@ class Currency:
def __str__(self):
return self.code
class Collection:
def __init__(self, array):
@@ -48,7 +45,6 @@ class Collection:
def sum(self):
return sum(self.array)
class AmountCollection(Collection):
def sum(self):
@@ -57,13 +53,10 @@ class AmountCollection(Collection):
total += v
return total
class Amount:
def __init__(
self, amount: typing.Union[int, float, str, "Amount"],
currency: Currency = Currency('COP')):
def __init__(self, amount: int or float or str or Amount, currency: Currency = Currency('COP')):
# DIAN 1.7.-2020: 1.2.3.1
#DIAN 1.7.-2020: 1.2.3.1
if isinstance(amount, Amount):
if amount < Amount(0.0):
raise ValueError('amount must be positive >= 0')
@@ -74,16 +67,14 @@ class Amount:
if float(amount) < 0:
raise ValueError('amount must be positive >= 0')
self.amount = Decimal(
amount, decimal.Context(
prec=DECIMAL_PRECISION,
# DIAN 1.7.-2020: 1.2.1.1
rounding=decimal.ROUND_HALF_EVEN))
self.amount = Decimal(amount, decimal.Context(prec=DECIMAL_PRECISION,
#DIAN 1.7.-2020: 1.2.1.1
rounding=decimal.ROUND_HALF_EVEN ))
self.currency = currency
def fromNumber(self, val):
return Amount(val, currency=self.currency)
def round(self, prec):
return Amount(round(self.amount, prec), currency=self.currency)
@@ -101,8 +92,7 @@ class Amount:
def __eq__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return round(self.amount, DECIMAL_PRECISION) == round(
other.amount, DECIMAL_PRECISION)
return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION)
def _cast(self, val):
if type(val) in [int, float]:
@@ -110,7 +100,7 @@ class Amount:
if isinstance(val, Amount):
return val
raise TypeError("cant cast to amount")
def __add__(self, rother):
other = self._cast(rother)
if not self.is_same_currency(other):
@@ -134,14 +124,14 @@ class Amount:
def truncate_as_string(self, prec):
parts = str(self.float()).split('.', 1)
return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec, '0'))
return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec,'0'))
def float(self):
return float(round(self.amount, DECIMAL_PRECISION))
class Quantity:
def __init__(self, val, code):
if type(val) not in [float, int]:
raise ValueError('val expected int or float')
@@ -163,7 +153,6 @@ class Quantity:
def __repr__(self):
return str(self)
@dataclass
class Item:
scheme_name: str
@@ -174,10 +163,10 @@ class Item:
class StandardItem(Item):
def __init__(self, id_: str, description: str = '', name: str = ''):
def __init__(self, id_: str, description: str = ''):
super().__init__(id=id_,
description=description,
scheme_name=name,
scheme_name='',
scheme_id='999',
scheme_agency_id='')
@@ -188,9 +177,9 @@ class UNSPSCItem(Item):
description=description,
scheme_name='UNSPSC',
scheme_id='001',
scheme_agency_id='10')
scheme_agency_id='10')
@dataclass
class Country:
code: str
@@ -201,7 +190,6 @@ class Country:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Paises[self.code]['name']
@dataclass
class CountrySubentity:
code: str
@@ -212,7 +200,6 @@ class CountrySubentity:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Departamento[self.code]['name']
@dataclass
class City:
code: str
@@ -223,22 +210,13 @@ class City:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Municipio[self.code]['name']
@dataclass
class PostalZone:
code: str = ''
@dataclass
class Address:
name: str
street: str = ''
city: City = field(default_factory=lambda: City('05001'))
country: Country = field(default_factory=lambda: Country('CO'))
countrysubentity: CountrySubentity = field(
default_factory=lambda: CountrySubentity('05'))
postalzone: PostalZone = field(default_factory=lambda: PostalZone(''))
city: City = City('05001')
country: Country = Country('CO')
countrysubentity: CountrySubentity = CountrySubentity('05')
@dataclass
class PartyIdentification:
@@ -259,7 +237,6 @@ class PartyIdentification:
if self.type_fiscal not in codelist.TipoIdFiscal:
raise ValueError("type_fiscal [%s] not found" % (self.type_fiscal))
@dataclass
class Responsability:
codes: list
@@ -284,12 +261,12 @@ class TaxScheme:
code: 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
class Party:
name: str
@@ -297,10 +274,10 @@ class Party:
responsability_code: typing.List[Responsability]
responsability_regime_code: str
organization_code: str
tax_scheme: TaxScheme = field(default_factory=lambda: TaxScheme('01'))
tax_scheme: TaxScheme = TaxScheme('01')
phone: str = ''
address: Address = field(default_factory=lambda: Address(''))
address: Address = Address('')
email: str = ''
legal_name: str = ''
legal_company_ident: str = ''
@@ -328,7 +305,7 @@ class TaxScheme:
class TaxSubTotal:
percent: float
scheme: typing.Optional[TaxScheme] = None
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_amount: Amount = Amount(0.0)
def calculate(self, invline):
if self.percent is not None:
@@ -338,11 +315,12 @@ class TaxSubTotal:
@dataclass
class TaxTotal:
subtotals: list
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
taxable_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_amount: Amount = Amount(0.0)
taxable_amount: Amount = Amount(0.0)
def calculate(self, invline):
self.taxable_amount = invline.total_amount
for subtax in self.subtotals:
subtax.calculate(invline)
self.tax_amount += subtax.tax_amount
@@ -355,40 +333,6 @@ class TaxTotalOmit(TaxTotal):
def calculate(self, invline):
pass
@dataclass
class WithholdingTaxSubTotal:
percent: float
scheme: typing.Optional[TaxScheme] = None
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
def calculate(self, invline):
if self.percent is not None:
self.tax_amount = invline.total_amount * Amount(self.percent / 100)
@dataclass
class WithholdingTaxTotal:
subtotals: list
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
taxable_amount: Amount = field(default_factory=lambda: Amount(0.0))
def calculate(self, invline):
self.taxable_amount = invline.total_amount
for subtax in self.subtotals:
subtax.calculate(invline)
self.tax_amount += subtax.tax_amount
class WithholdingTaxTotalOmit(WithholdingTaxTotal):
def __init__(self):
super().__init__([])
def calculate(self, invline):
pass
@dataclass
class Price:
amount: Amount
@@ -404,7 +348,6 @@ class Price:
self.amount *= self.quantity
@dataclass
class PaymentMean:
DEBIT = '01'
@@ -422,24 +365,8 @@ class PaymentMean:
@dataclass
class PrePaidPayment:
# DIAN 1.7.-2020: FBD03
paid_amount: Amount = field(default_factory=lambda: Amount(0.0))
@dataclass
class BillingResponse:
id: str
code: str
description: str
class SupportDocumentCreditNoteResponse(BillingResponse):
"""
ReferenceID: Identifica la sección del Documento
Soporte original a la cual se aplica la corrección.
ResponseCode: Código de descripción de la corrección.
Description: Descripción de la naturaleza de la corrección.
"""
#DIAN 1.7.-2020: FBD03
paid_amount: Amount = Amount(0.0)
@dataclass
@@ -448,7 +375,6 @@ class BillingReference:
uuid: str
date: date
class CreditNoteDocumentReference(BillingReference):
"""
ident: Prefijo + Numero de la factura relacionada
@@ -464,7 +390,6 @@ class DebitNoteDocumentReference(BillingReference):
date: fecha de emision de la factura relacionada
"""
class InvoiceDocumentReference(BillingReference):
"""
ident: Prefijo + Numero de la nota credito relacionada
@@ -472,7 +397,6 @@ class InvoiceDocumentReference(BillingReference):
date: fecha de emision de la nota credito relacionada
"""
@dataclass
class AllowanceChargeReason:
code: str
@@ -485,26 +409,22 @@ class AllowanceChargeReason:
@dataclass
class AllowanceCharge:
# DIAN 1.7.-2020: FAQ03
#DIAN 1.7.-2020: FAQ03
charge_indicator: bool = True
amount: Amount = field(default_factory=lambda: Amount(0.0))
amount: Amount = Amount(0.0)
reason: AllowanceChargeReason = None
# Valor Base para calcular el descuento o el cargo
base_amount: typing.Optional[Amount] = field(
default_factory=lambda: Amount(0.0))
#Valor Base para calcular el descuento o el cargo
base_amount: typing.Optional[Amount] = Amount(0.0)
# Porcentaje: Porcentaje que aplicar.
multiplier_factor_numeric: Amount = field(
default_factory=lambda: Amount(1.0))
multiplier_factor_numeric: Amount = Amount(1.0)
def isCharge(self):
charge_indicator = self.charge_indicator is True
return charge_indicator
return self.charge_indicator == True
def isDiscount(self):
charge_indicator = self.charge_indicator is False
return charge_indicator
return self.charge_indicator == False
def asCharge(self):
self.charge_indicator = True
@@ -518,13 +438,11 @@ class AllowanceCharge:
def set_base_amount(self, amount):
self.base_amount = amount
class AllowanceChargeAsDiscount(AllowanceCharge):
def __init__(self, amount: Amount = Amount(0.0)):
self.charge_indicator = False
self.amount = amount
@dataclass
class InvoiceLine:
# RESOLUCION 0004: pagina 155
@@ -537,9 +455,8 @@ class InvoiceLine:
# la factura y el percent es unico por type_code
# de subtotal
tax: typing.Optional[TaxTotal]
withholding: typing.Optional[WithholdingTaxTotal]
allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(
default_factory=list)
allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list)
def add_allowance_charge(self, charge):
if not isinstance(charge, AllowanceCharge):
@@ -550,7 +467,7 @@ class InvoiceLine:
@property
def total_amount_without_charge(self):
return (self.quantity * self.price.amount)
@property
def total_amount(self):
charge = AmountCollection(self.allowance_charge)\
@@ -582,17 +499,8 @@ class InvoiceLine:
def taxable_amount(self):
return self.tax.taxable_amount
@property
def withholding_amount(self):
return self.withholding.tax_amount
@property
def withholding_taxable_amount(self):
return self.withholding.taxable_amount
def calculate(self):
self.tax.calculate(self)
self.withholding.calculate(self)
def __post_init__(self):
if not isinstance(self.quantity, Quantity):
@@ -601,22 +509,18 @@ class InvoiceLine:
if self.tax is None:
self.tax = TaxTotalOmit()
if self.withholding is None:
self.withholding = WithholdingTaxTotalOmit()
@dataclass
class LegalMonetaryTotal:
line_extension_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_exclusive_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_inclusive_amount: Amount = field(default_factory=lambda: Amount(0.0))
charge_total_amount: Amount = field(default_factory=lambda: Amount(0.0))
allowance_total_amount: Amount = field(default_factory=lambda: Amount(0.0))
payable_amount: Amount = field(default_factory=lambda: Amount(0.0))
prepaid_amount: Amount = field(default_factory=lambda: Amount(0.0))
line_extension_amount: Amount = Amount(0.0)
tax_exclusive_amount: Amount = Amount(0.0)
tax_inclusive_amount: Amount = Amount(0.0)
charge_total_amount: Amount = Amount(0.0)
allowance_total_amount: Amount = Amount(0.0)
payable_amount: Amount = Amount(0.0)
prepaid_amount: Amount = Amount(0.0)
def calculate(self):
# DIAN 1.7.-2020: FAU14
#DIAN 1.7.-2020: FAU14
self.payable_amount = \
self.tax_inclusive_amount \
+ self.allowance_total_amount \
@@ -624,29 +528,22 @@ class LegalMonetaryTotal:
- self.prepaid_amount
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 CreditNoteSupportDocumentType(str):
def __str__(self):
return '95'
class Invoice:
def __init__(self, type_code: str):
if str(type_code) not in codelist.TipoDocumento:
@@ -666,7 +563,6 @@ class Invoice:
self.invoice_allowance_charge = []
self.invoice_prepaid_payment = []
self.invoice_billing_reference = None
self.invoice_discrepancy_response = None
self.invoice_type_code = str(type_code)
self.invoice_ident_prefix = None
@@ -692,8 +588,7 @@ class Invoice:
if len(prefix) <= 4:
self.invoice_ident_prefix = prefix
else:
raise ValueError(
'ident prefix failed to get, expected 0 to 4 chars')
raise ValueError('ident prefix failed to get, expected 0 to 4 chars')
def set_ident(self, ident: str):
"""
@@ -724,7 +619,7 @@ class Invoice:
def _get_codelist_tipo_operacion(self):
return codelist.TipoOperacionF
def set_operation_type(self, operation):
if operation not in self._get_codelist_tipo_operacion():
raise ValueError("operation not found")
@@ -743,9 +638,6 @@ class Invoice:
def set_billing_reference(self, billing_reference: BillingReference):
self.invoice_billing_reference = billing_reference
def set_discrepancy_response(self, billing_response: BillingResponse):
self.invoice_discrepancy_response = billing_response
def accept(self, visitor):
visitor.visit_payment_mean(self.invoice_payment_mean)
visitor.visit_customer(self.invoice_customer)
@@ -757,34 +649,29 @@ class Invoice:
def _calculate_legal_monetary_total(self):
for invline in self.invoice_lines:
self.invoice_legal_monetary_total.line_extension_amount +=\
invline.total_amount
self.invoice_legal_monetary_total.tax_exclusive_amount +=\
invline.total_tax_exclusive_amount
# DIAN 1.7.-2020: FAU6
self.invoice_legal_monetary_total.tax_inclusive_amount +=\
invline.total_tax_inclusive_amount
self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount
self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_tax_exclusive_amount
#DIAN 1.7.-2020: FAU6
self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount
# DIAN 1.7.-2020: FAU08
self.invoice_legal_monetary_total.allowance_total_amount =\
AmountCollection(self.invoice_allowance_charge)\
#DIAN 1.7.-2020: FAU08
self.invoice_legal_monetary_total.allowance_total_amount = AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isDiscount())\
.map(lambda charge: charge.amount)\
.sum()
# DIAN 1.7.-2020: FAU10
self.invoice_legal_monetary_total.charge_total_amount =\
AmountCollection(self.invoice_allowance_charge)\
#DIAN 1.7.-2020: FAU10
self.invoice_legal_monetary_total.charge_total_amount = AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isCharge())\
.map(lambda charge: charge.amount)\
.sum()
# DIAN 1.7.-2020: FAU12
self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(
self.invoice_prepaid_payment).map(
lambda paid: paid.paid_amount).sum()
#DIAN 1.7.-2020: FAU12
self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(self.invoice_prepaid_payment)\
.map(lambda paid: paid.paid_amount)\
.sum()
# DIAN 1.7.-2020: FAU14
#DIAN 1.7.-2020: FAU14
self.invoice_legal_monetary_total.calculate()
def _refresh_charges_base_amount(self):
@@ -792,21 +679,18 @@ class Invoice:
for invline in self.invoice_lines:
if invline.allowance_charge:
# TODO actualmente solo uno de los cargos es permitido
raise ValueError(
'allowance charge in invoice exclude invoice line')
raise ValueError('allowance charge in invoice exclude invoice line')
# cargos a nivel de factura
for charge in self.invoice_allowance_charge:
charge.set_base_amount(
self.invoice_legal_monetary_total.line_extension_amount)
charge.set_base_amount(self.invoice_legal_monetary_total.line_extension_amount)
def calculate(self):
for invline in self.invoice_lines:
invline.calculate()
self._calculate_legal_monetary_total()
self._refresh_charges_base_amount()
class NationalSalesInvoice(Invoice):
def __init__(self):
super().__init__(NationalSalesInvoiceDocumentType())
@@ -822,7 +706,7 @@ class CreditNote(Invoice):
def _get_codelist_tipo_operacion(self):
return codelist.TipoOperacionNC
def _check_ident_prefix(self, prefix):
if len(prefix) != 6:
raise ValueError('prefix must be 6 length')
@@ -851,30 +735,3 @@ class DebitNote(Invoice):
if not self.invoice_ident_prefix:
self.invoice_ident_prefix = self.invoice_ident[0:6]
class SupportDocument(Invoice):
pass
class SupportDocumentCreditNote(SupportDocument):
def __init__(
self, invoice_document_reference: BillingReference,
invoice_discrepancy_response: BillingResponse):
super().__init__(CreditNoteSupportDocumentType())
if not isinstance(invoice_document_reference, BillingReference):
raise TypeError('invoice_document_reference invalid type')
self.invoice_billing_reference = invoice_document_reference
self.invoice_discrepancy_response = invoice_discrepancy_response
def _get_codelist_tipo_operacion(self):
return codelist.TipoOperacionNCDS
def _check_ident_prefix(self, prefix):
if len(prefix) != 6:
raise ValueError('prefix must be 6 length')
def _set_ident_prefix_automatic(self):
if not self.invoice_ident_prefix:
self.invoice_ident_prefix = self.invoice_ident[0:6]
pass

View File

@@ -3,5 +3,3 @@ from .credit_note import *
from .debit_note import *
from .utils import *
from .attached_document import *
from .support_document import *
from .support_document_credit_note import *

View File

@@ -2,14 +2,13 @@ from .. import fe
__all__ = ['AttachedDocument']
class AttachedDocument():
def __init__(self, id):
schema =\
'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2'
schema = 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2'
self.fexml = fe.FeXML('AttachedDocument', schema)
self.fexml.set_element('./cbc:ID', id)
def toFachoXML(self):
return self.fexml

View File

@@ -1,10 +1,9 @@
# from .. import fe
# from ..form import *
from .. import fe
from ..form import *
from .invoice import DIANInvoiceXML
__all__ = ['DIANCreditNoteXML']
class DIANCreditNoteXML(DIANInvoiceXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
@@ -19,3 +18,6 @@ class DIANCreditNoteXML(DIANInvoiceXML):
def tag_document_concilied(fexml):
return 'Credited'
def post_attach_invoice(fexml, invoice):
fexml.set_element('./cbc:ProfileID', 'DIAN 2.1: Nota Crédito de Factura Electrónica de Venta')

View File

@@ -1,10 +1,9 @@
# from .. import fe
# from ..form import *
from .. import fe
from ..form import *
from .invoice import DIANInvoiceXML
__all__ = ['DIANDebitNoteXML']
class DIANDebitNoteXML(DIANInvoiceXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
@@ -14,30 +13,28 @@ class DIANDebitNoteXML(DIANInvoiceXML):
def __init__(self, invoice):
super().__init__(invoice, 'DebitNote')
def post_attach_invoice(fexml, invoice):
fexml.set_element('./cbc:ProfileID', 'DIAN 2.1 Nota Débito de Factura Electrónica de Venta')
def tag_document(fexml):
return 'DebitNote'
def tag_document_concilied(fexml):
return 'Debited'
# DIAN 1.7.-2020: DAU03
#DIAN 1.7.-2020: DAU03
def set_legal_monetary(fexml, invoice):
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)

View File

@@ -1,6 +1,5 @@
from .. import fe
from ..form import *
from collections import defaultdict
__all__ = ['DIANInvoiceXML']
@@ -22,6 +21,7 @@ class DIANInvoiceXML(fe.FeXML):
ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True)
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice)
self.post_attach_invoice(invoice)
def set_supplier(fexml, invoice):
fexml.placeholder_for('./cac:AccountingSupplierParty')
@@ -148,6 +148,7 @@ class DIANInvoiceXML(fe.FeXML):
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
#DIAN 1.7.-2020: CAJ41
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
@@ -415,13 +416,11 @@ class DIANInvoiceXML(fe.FeXML):
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: Amount(0.0)))
withholding_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0)))
percent_for = defaultdict(lambda: None)
#requeridos para CUFE
@@ -434,7 +433,6 @@ class DIANInvoiceXML(fe.FeXML):
#tax_amount_for['03']['taxable_amount'] += 0.0
total_tax_amount = Amount(0.0)
total_withholding_amount = Amount(0.0)
for invoice_line in invoice.invoice_lines:
for subtotal in invoice_line.tax.subtotals:
@@ -448,28 +446,17 @@ class DIANInvoiceXML(fe.FeXML):
total_tax_amount += subtotal.tax_amount
for subtotal_withholding in invoice_line.withholding.subtotals:
if subtotal_withholding.scheme is not None:
withholding_amount_for[subtotal_withholding.scheme.code]['tax_amount'] += subtotal_withholding.tax_amount
withholding_amount_for[subtotal_withholding.scheme.code]['taxable_amount'] += invoice_line.withholding_taxable_amount
# MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal_withholding.scheme.code] = subtotal_withholding.percent
total_withholding_amount += subtotal_withholding.tax_amount
if total_tax_amount != Amount(0.0):
fexml.placeholder_for('./cac:TaxTotal')
fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount',
total_tax_amount)
for index, item in enumerate(tax_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
#DIAN 1.7.-2020: FAS01
line = fexml.fragment('./cac:TaxTotal', append=next_append)
line = fexml.fragment('./cac:TaxTotal', append=True)
#DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line,
@@ -500,44 +487,7 @@ class DIANInvoiceXML(fe.FeXML):
cod_impuesto)
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
amount_of['name'])
for index, item in enumerate(withholding_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
#DIAN 1.7.-2020: FAS01
line = fexml.fragment('./cac:WithholdingTaxTotal', append=next_append)
#DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line,
'/cac:WithholdingTaxTotal/cbc:TaxAmount',
tax_amount)
#DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(line,
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount'])
#DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(line,
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount'])
#DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]:
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto])
if percent_for[cod_impuesto]:
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto])
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto)
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
'ReteRenta')
# abstract method
def tag_document(fexml):
return 'Invoice'
@@ -565,29 +515,7 @@ class DIANInvoiceXML(fe.FeXML):
#DIAN 1.7.-2020: FAX15
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code)
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name)
def set_invoice_line_withholding(fexml, line, invoice_line):
fexml.set_element_amount_for(line,
'./cac:WithholdingTaxTotal/cbc:TaxAmount',
invoice_line.withholding_amount)
#DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(line,
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.withholding_taxable_amount)
for subtotal in invoice_line.withholding.subtotals:
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP')
if subtotal.percent is not None:
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2))
if subtotal.scheme is not None:
#DIAN 1.7.-2020: FAX15
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code)
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name)
def set_invoice_lines(fexml, invoice):
next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines):
@@ -603,9 +531,6 @@ class DIANInvoiceXML(fe.FeXML):
if not isinstance(invoice_line.tax, TaxTotalOmit):
fexml.set_invoice_line_tax(line, invoice_line)
if not isinstance(invoice_line.withholding, WithholdingTaxTotalOmit):
fexml.set_invoice_line_withholding(line, invoice_line)
line.set_element('./cac:Item/cbc:Description', invoice_line.item.description)
line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID',
@@ -623,32 +548,28 @@ class DIANInvoiceXML(fe.FeXML):
for idx, charge in enumerate(invoice_line.allowance_charge):
next_append_charge = idx > 0
fexml.append_allowance_charge(line, index + 1, charge, append=next_append_charge)
def set_allowance_charge(fexml, invoice):
for idx, charge in enumerate(invoice.invoice_allowance_charge):
next_append = idx > 0
fexml.append_allowance_charge(
fexml, idx + 1, charge, append=next_append)
fexml.append_allowance_charge(fexml, idx + 1, charge, append=next_append)
def append_allowance_charge(fexml, parent, idx, charge, append=False):
line = parent.fragment('./cac:AllowanceCharge', append=append)
# DIAN 1.7.-2020: FAQ02
line.set_element('./cbc:ID', idx)
# DIAN 1.7.-2020: FAQ03
line.set_element('./cbc:ChargeIndicator', str(
charge.charge_indicator).lower())
if charge.reason:
line.set_element(
'./cbc:AllowanceChargeReasonCode', charge.reason.code)
line.set_element(
'./cbc:allowanceChargeReason', charge.reason.reason)
line.set_element(
'./cbc:MultiplierFactorNumeric', str(
round(charge.multiplier_factor_numeric, 2)))
fexml.set_element_amount_for(
line, './cbc:Amount', charge.amount)
fexml.set_element_amount_for(
line, './cbc:BaseAmount', charge.base_amount)
line = parent.fragment('./cac:AllowanceCharge', append=append)
#DIAN 1.7.-2020: FAQ02
line.set_element('./cbc:ID', idx)
#DIAN 1.7.-2020: FAQ03
line.set_element('./cbc:ChargeIndicator', str(charge.charge_indicator).lower())
if charge.reason:
line.set_element('./cbc:AllowanceChargeReasonCode', charge.reason.code)
line.set_element('./cbc:allowanceChargeReason', charge.reason.reason)
line.set_element('./cbc:MultiplierFactorNumeric', str(round(charge.multiplier_factor_numeric, 2)))
fexml.set_element_amount_for(line, './cbc:Amount', charge.amount)
fexml.set_element_amount_for(line, './cbc:BaseAmount', charge.base_amount)
def post_attach_invoice(fexml, invoice):
#DIAN 1.8.-2021: FAD03
fexml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta')
def attach_invoice(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
@@ -661,6 +582,7 @@ class DIANInvoiceXML(fe.FeXML):
fexml.placeholder_for('./cbc:ProfileExecutionID')
fexml.set_element('./cbc:ID', invoice.invoice_ident)
fexml.placeholder_for('./cbc:UUID')
fexml.set_element('./cbc:DocumentCurrencyCode', 'COP')
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'))
@@ -669,25 +591,24 @@ class DIANInvoiceXML(fe.FeXML):
listAgencyID='195',
listAgencyName='No matching global declaration available for the validation root',
listURI='http://www.dian.gov.co')
fexml.set_element('./cbc:DocumentCurrencyCode', 'COP')
fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines))
if fexml.tag_document() == 'Invoice':
fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (
fexml.tag_document()),
invoice.invoice_period_start.strftime('%Y-%m-%d'))
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'))
fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (
fexml.tag_document()),
invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.set_billing_reference(invoice)
fexml.customize(invoice)
fexml.set_supplier(invoice)
fexml.set_customer(invoice)
fexml.set_payment_mean(invoice)
fexml.set_invoice_totals(invoice)
fexml.set_legal_monetary(invoice)
fexml.set_invoice_totals(invoice)
fexml.set_invoice_lines(invoice)
fexml.set_payment_mean(invoice)
fexml.set_allowance_charge(invoice)
fexml.set_billing_reference(invoice)
return fexml
def customize(fexml, invoice):

View File

@@ -1,647 +0,0 @@
from .. import fe
from ..form import (
Amount, DebitNoteDocumentReference, CreditNoteDocumentReference,
InvoiceDocumentReference, TaxTotalOmit, WithholdingTaxTotalOmit
)
from collections import defaultdict
from datetime import datetime
# from .attached_document import *
__all__ = ['DIANSupportDocumentXML']
class DIANSupportDocumentXML(fe.FeXML):
"""
DianSupportDocumentXML mapea objeto form.Invoice a XML segun
lo indicado para él Documento soporte en adquisiciones efectuadas con sujetos no obligados a expedir factura de venta o documento equivalente.
"""
def __init__(self, invoice, tag_document='Invoice'):
super().__init__(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
# DIAN 1.1.-2021: DSAB03
# DIAN 1.1.-2021: NSAB03
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl')
# DIAN 1.1.-2021: DSAB13
# DIAN 1.1.-2021: NSAB13
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource')
# DIAN 1.1.-2021: DSAB18
# DIAN 1.1.-2021: NSAB18
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider')
# DIAN 1.1.-2021: DSAB27
# DIAN 1.1.-2021: NSAB27
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode')
# DIAN 1.1.-2021: DSAB30 DSAB31
# DIAN 1.1.-2021: NSAB30 NSAB31
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID')
# ZE02 se requiere existencia para firmar
# DIAN 1.1.-2021: DSAA02 DSAB01
# DIAN 1.1.-2021: NSAA02 NSAB01
ublextension = self.fragment(
'./ext:UBLExtensions/ext:UBLExtension', append=True)
# DIAN 1.1.-2021: DSAB02
# DIAN 1.1.-2021: NSAB02
extcontent = ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice)
def set_supplier(fexml, invoice):
# DIAN 1.1.-2021: DSAJ01
# DIAN 1.1.-2021: NSAB01
fexml.placeholder_for('./cac:AccountingSupplierParty')
# DIAN 1.1.-2021: DSAJ02
# DIAN 1.1.-2021: NSAJ02
fexml.set_element(
'./cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code)
# DIAN 1.1.-2021: DSAJ07 DSAJ08
# DIAN 1.1.-2021: NSAJ07 NSAJ08
fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address')
# DIAN 1.1.-2021: DSAJ09
# DIAN 1.1.-2021: NSAJ09
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_supplier.address.city.code)
# DIAN 1.1.-2021: DSAJ10
# DIAN 1.1.-2021: NSAJ10
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName',
invoice.invoice_supplier.address.city.name)
# DIAN 1.1.-2021: DSAJ73
# DIAN 1.1.-2021: NSAJ73
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:PostalZone',
invoice.invoice_supplier.address.postalzone.code)
# DIAN 1.1.-2021: DSAJ11
# DIAN 1.1.-2021: NSAJ11
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.1.-2021: DSAJ12
# DIAN 1.1.-2021: NSAJ12
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.1.-2021: NSAJ13 NSAJ14
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
# DIAN 1.1.-2021: DSAJ15 DSAJ16
# DIAN 1.1.-2021: NSAJ15 NSAJ16
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
# DIAN 1.1.-2021: DSAJ17
# DIAN 1.1.-2021: NSAJ17
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
# DIAN 1.1.-2021: DSAJ18
# # DIAN 1.1.-2021: NSAJ18
languageID='es')
supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
supplier_company_id_attrs.update(
{
'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal})
# DIAN 1.1.-2021: DSAJ19
# DIAN 1.1.-2021: NSAJ19
fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.1.-2021: DSAJ20
# DIAN 1.1.-2021: NSAJ20
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
# DIAN 1.1.-2021: DSAJ21
# DIAN 1.1.-2021: NSAJ21
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_supplier.ident,
# DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25
# DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25
**supplier_company_id_attrs)
# DIAN 1.1.-2021: DSAJ26
# DIAN 1.1.-2021: NSAJ26
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
invoice.invoice_supplier.responsability_code,
listName=invoice.invoice_supplier.responsability_regime_code)
# DIAN 1.1.-2021: DSAJ39
# DIAN 1.1.-2021: NSAJ39
fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.1.-2021: DSAJ40
# DIAN 1.1.-2021: NSAJ40
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
# DIAN 1.1.-2021: DSAJ41
# DIAN 1.1.-2021: NSAJ41
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
def set_customer(fexml, invoice):
# DIAN 1.1.-2021: DSAK01
# DIAN 1.1.-2021: NSAK01
fexml.placeholder_for('./cac:AccountingCustomerParty')
# DIAN 1.1.-2021: DSAK02
# DIAN 1.1.-2021: NSAK02
fexml.set_element(
'./cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code)
# DIAN 1.1.-2021: DSAK03
# DIAN 1.1.-2021: NSAK03
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party')
# DIAN 1.1.-2021: DSAK19
# DIAN 1.1.-2021: NSAK19
fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.1.-2021: DSAK20
# DIAN 1.1.-2021: NSAK20
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
customer_company_id_attrs.update(
{
'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal})
# DIAN 1.1.-2021: DSAK21
# DIAN 1.1.-2021: NSAK21
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_customer.ident,
# DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25
# DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25
**customer_company_id_attrs)
# DIAN 1.1.-2021: DSAK26
# DIAN 1.1.-2021: NSAK26
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
invoice.invoice_customer.responsability_code)
# DIAN 1.1.-2021: DSAK39
# DIAN 1.1.-2021: NSAK39
fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.1.-2021: DSAK40
# DIAN 1.1.-2021: NSAK40
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
# DIAN 1.1.-2021: DSAK41
# DIAN 1.1.-2021: NSAK41
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
def set_payment_mean(fexml, invoice):
payment_mean = invoice.invoice_payment_mean
# DIAN 1.1.-2021: DSAN01 DSAN02
# DIAN 1.1.-2021: NSAN02 NSAN02
fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id)
# DIAN 1.1.-2021: DSAN03
# DIAN 1.1.-2021: NSAN03
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentMeansCode',
payment_mean.code)
# DIAN 1.1.-2021: DSAN04
# DIAN 1.1.-2021: NSAN04
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentDueDate',
payment_mean.due_at.strftime('%Y-%m-%d'))
# DIAN 1.1.-2021: DSAN05
# DIAN 1.1.-2021: NSAN05
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentID',
payment_mean.payment_id)
def set_element_amount_for(fexml, xml, xpath, amount):
if not isinstance(amount, Amount):
raise TypeError("amount not is Amount")
xml.set_element(xpath, amount, currencyID=amount.currency.code)
def set_element_amount(fexml, xpath, amount):
if not isinstance(amount, Amount):
raise TypeError("amount not is Amount")
fexml.set_element(xpath, amount, currencyID=amount.currency.code)
def set_legal_monetary(fexml, invoice):
# DIAN 1.1.-2021: DSAU01 DSAU02 DSAU03
# DIAN 1.1.-2021: NSAU01 NSAU02 NSAU03
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
# DIAN 1.1.-2021: DSAU04 DSAU05
# DIAN 1.1.-2021: NSAU04 NSAU05
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
# DIAN 1.1.-2021: DSAU06 DSAU07
# DIAN 1.1.-2021: NSAU06 DSAU07
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
# DIAN 1.1.-2021: DSAU10 DSAU11
# DIAN 1.1.-2021: NSAU10 DSAU11
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
# DIAN 1.1.-2021: DSAU14 DSAU15
# DIAN 1.1.-2021: NSAU14 DSAU15
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)
def _set_invoice_document_reference(fexml, reference):
fexml._do_set_billing_reference(
reference, 'cac:InvoiceDocumentReference')
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):
if tag_document == 'Invoice':
schemeName = 'CUFE-SHA384'
else:
schemeName = 'CUDS-SHA384'
fexml.set_element('./cac:BillingReference/%s/cbc:ID' % (tag_document),
reference.ident)
fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID',
reference.uuid,
schemeName=schemeName)
fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate',
reference.date.strftime("%Y-%m-%d"))
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_discrepancy_response(fexml, invoice):
reference = invoice.invoice_discrepancy_response
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)
fexml.set_element('./cac:DiscrepancyResponse/cbc:ReferenceID',
reference.id)
fexml.set_element('./cac:DiscrepancyResponse/cbc:ResponseCode',
reference.code)
fexml.set_element('./cac:DiscrepancyResponse/cbc:Description',
reference.description)
def set_invoice_totals(fexml, invoice):
tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0)))
percent_for = defaultdict(lambda: None)
total_tax_amount = Amount(0.0)
for invoice_line in invoice.invoice_lines:
for subtotal in invoice_line.tax.subtotals:
if subtotal.scheme is not None:
tax_amount_for[
subtotal.scheme.code][
'tax_amount'] += subtotal.tax_amount
tax_amount_for[subtotal.scheme.code][
'taxable_amount'] += invoice_line.taxable_amount
# MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal.scheme.code] = subtotal.percent
total_tax_amount += subtotal.tax_amount
if total_tax_amount != Amount(0.0):
fexml.placeholder_for('./cac:TaxTotal')
fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount',
total_tax_amount)
for index, item in enumerate(tax_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
# DIAN 1.7.-2020: FAS01
line = fexml.fragment('./cac:TaxTotal', append=next_append)
# DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cbc:TaxAmount',
tax_amount)
# DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(
line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount'])
# DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(
line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount'])
# DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto])
if percent_for[cod_impuesto]:
line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto])
line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto)
line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', 'IVA')
# abstract method
def tag_document(fexml):
return 'Invoice'
# abstract method
def tag_document_concilied(fexml):
return 'Invoiced'
def set_invoice_line_withholding(fexml, line, invoice_line):
fexml.set_element_amount_for(line,
'./cac:WithholdingTaxTotal/cbc:TaxAmount',
invoice_line.withholding_amount)
# DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(
line,
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.withholding_taxable_amount)
for subtotal in invoice_line.withholding.subtotals:
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
subtotal.tax_amount,
currencyID='COP')
if subtotal.percent is not None:
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
'%0.2f' %
round(
subtotal.percent,
2))
if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
subtotal.scheme.code)
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
subtotal.scheme.name)
def set_invoice_line_tax(fexml, line, invoice_line):
fexml.set_element_amount_for(line,
'./cac:TaxTotal/cbc:TaxAmount',
invoice_line.tax_amount)
# DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(
line,
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.taxable_amount)
for subtotal in invoice_line.tax.subtotals:
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
subtotal.tax_amount,
currencyID='COP')
if subtotal.percent is not None:
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
'%0.2f' %
round(
subtotal.percent,
2))
if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
subtotal.scheme.code)
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
subtotal.scheme.name)
def set_invoice_lines(fexml, invoice):
next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines):
line = fexml.fragment(
'./cac:%sLine' %
(fexml.tag_document()),
append=next_append)
next_append = True
line.set_element('./cbc:ID', index + 1)
line.set_element(
'./cbc:%sQuantity' %
(fexml.tag_document_concilied()),
invoice_line.quantity,
unitCode='NAR')
fexml.set_element_amount_for(line,
'./cbc:LineExtensionAmount',
invoice_line.total_amount)
period = line.fragment('./cac:InvoicePeriod')
period.set_element('./cbc:StartDate',
datetime.now().strftime('%Y-%m-%d'))
period.set_element(
'./cbc:DescriptionCode', '1')
period.set_element('./cbc:Description',
'Por operación')
if not isinstance(invoice_line.tax, TaxTotalOmit):
fexml.set_invoice_line_tax(line, invoice_line)
if not isinstance(
invoice_line.withholding,
WithholdingTaxTotalOmit):
fexml.set_invoice_line_withholding(line, invoice_line)
line.set_element(
'./cac:Item/cbc:Description',
invoice_line.item.description)
line.set_element(
'./cac:Item/cac:StandardItemIdentification/cbc:ID',
invoice_line.item.id,
schemeID=invoice_line.item.scheme_id,
schemeName=invoice_line.item.scheme_name,
schemeAgencyID=invoice_line.item.scheme_agency_id)
line.set_element(
'./cac:Price/cbc:PriceAmount',
invoice_line.price.amount,
currencyID=invoice_line.price.amount.currency.code)
# DIAN 1.7.-2020: FBB04
line.set_element('./cac:Price/cbc:BaseQuantity',
invoice_line.quantity,
unitCode=invoice_line.quantity.code)
for idx, charge in enumerate(invoice_line.allowance_charge):
next_append_charge = idx > 0
fexml.append_allowance_charge(
line, index + 1, charge, append=next_append_charge)
def set_allowance_charge(fexml, invoice):
for idx, charge in enumerate(invoice.invoice_allowance_charge):
next_append = idx > 0
fexml.append_allowance_charge(
fexml, idx + 1, charge, append=next_append)
def append_allowance_charge(fexml, parent, idx, charge, append=False):
line = parent.fragment('./cac:AllowanceCharge', append=append)
# DIAN 1.7.-2020: FAQ02
line.set_element('./cbc:ID', idx)
# DIAN 1.7.-2020: FAQ03
line.set_element('./cbc:ChargeIndicator',
str(charge.charge_indicator).lower())
if charge.reason:
line.set_element(
'./cbc:AllowanceChargeReasonCode',
charge.reason.code)
line.set_element(
'./cbc:allowanceChargeReason',
charge.reason.reason)
line.set_element('./cbc:MultiplierFactorNumeric',
str(round(charge.multiplier_factor_numeric, 2)))
fexml.set_element_amount_for(line, './cbc:Amount', charge.amount)
fexml.set_element_amount_for(
line, './cbc:BaseAmount', charge.base_amount)
def attach_invoice(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
en caso de fallar validacion retorna None"""
fexml.placeholder_for('./ext:UBLExtensions')
fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1')
fexml.set_element(
'./cbc:CustomizationID',
invoice.invoice_operation_type)
fexml.placeholder_for('./cbc:ProfileID')
fexml.placeholder_for('./cbc:ProfileExecutionID')
fexml.set_element('./cbc:ID', invoice.invoice_ident)
fexml.placeholder_for('./cbc:UUID')
fexml.set_element('./cbc:DocumentCurrencyCode', 'COP')
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()),
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'))
fexml.customize(invoice)
fexml.set_supplier(invoice)
fexml.set_customer(invoice)
fexml.set_legal_monetary(invoice)
fexml.set_invoice_totals(invoice)
fexml.set_invoice_lines(invoice)
fexml.set_payment_mean(invoice)
fexml.set_allowance_charge(invoice)
fexml.set_discrepancy_response(invoice)
fexml.set_billing_reference(invoice)
return fexml
def customize(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
en caso de fallar validacion retorna None"""

View File

@@ -1,25 +0,0 @@
# from .. import fe
# from ..form import *
from .support_document import DIANSupportDocumentXML
__all__ = ['DIANSupportDocumentCreditNoteXML']
class DIANSupportDocumentCreditNoteXML(DIANSupportDocumentXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
lo indicado para la facturacion electronica.
"""
def __init__(self, invoice):
super(
DIANSupportDocumentCreditNoteXML,
self).__init__(
invoice,
'CreditNote')
def tag_document(fexml):
return 'CreditNote'
def tag_document_concilied(fexml):
return 'Credited'

View File

@@ -2,30 +2,18 @@ from .. import fe
__all__ = ['DIANWrite', 'DIANWriteSigned']
def DIANWrite(xml, filename):
document = xml.tostring(xml_declaration=True, encoding='UTF-8')
with open(filename, 'w') as f:
f.write(document)
def DIANWriteSigned(
xml,
filename,
private_key,
passphrase,
use_cache_policy=False,
dian_signer=None):
document = xml.tostring(
xml_declaration=True,
encoding='UTF-8').encode('utf-8')
def DIANWriteSigned(xml, filename, private_key, passphrase, use_cache_policy=False, dian_signer=None):
document = xml.tostring(xml_declaration=True, encoding='UTF-8').encode('utf-8')
if dian_signer is None:
dian_signer = fe.DianXMLExtensionSigner
signer = dian_signer(
private_key,
passphrase=passphrase,
localpolicy=use_cache_policy)
signer = dian_signer(private_key, passphrase=passphrase, localpolicy=use_cache_policy)
with open(filename, 'w') as f:
f.write(signer.sign_xml_string(document))

View File

@@ -1,4 +1,4 @@
from dataclasses import dataclass, field
from dataclasses import dataclass
from ..amount import Amount
@@ -29,7 +29,7 @@ class Trabajador:
codigo_trabajador: str = None
otros_nombres: str = None
sub_tipo: SubTipoTrabajador = field(default_factory=lambda: SubTipoTrabajador(code='00'))
sub_tipo: SubTipoTrabajador = SubTipoTrabajador(code='00')
def apply(self, fragment):
fragment.set_attributes('./Trabajador',

View File

@@ -22,4 +22,4 @@ exclude = docs
test = pytest
[tool:pytest]
addopts = --ignore=setup.py
collect_ignore = ['setup.py']

View File

@@ -13,22 +13,6 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = ['Click>=8.1.7',
'zeep==4.2.1',
'lxml==5.2.2',
'cryptography==3.3.2',
'pyOpenSSL==20.0.1',
'xmlsig==0.1.7',
'xades==1.0.0',
'xmlsec==1.3.14',
'python-dateutil==2.9.0.post0',
# usamos esta dependencia en runtime
# para forzar uso de policy_id de archivo local
'mock>=5.1.0',
'xmlschema>=3.0.0']
"""
Listado de Versiones Anteriores
requirements = ['Click>=6.0',
'zeep==4.0.0',
'lxml==4.6.3',
@@ -42,8 +26,6 @@ requirements = ['Click>=6.0',
'mock>=2.0.0',
'xmlschema>=1.8']
"""
setup_requirements = ['pytest-runner', ]
test_requirements = ['pytest', ]
@@ -57,10 +39,10 @@ setup(
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
description="Facturacion Electronica Colombia",
entry_points={

View File

@@ -1 +0,0 @@
907e4444decc9e59c160a2fb3b6659b33dc5b632a5008922b9a62f83f757b1c448e47f5867f2b50dbdb96f48c7681168

View File

View File

@@ -4,32 +4,30 @@ from datetime import datetime
@pytest.fixture
def simple_debit_note_without_lines():
inv = form.DebitNote(form.InvoiceDocumentReference(
'1234', 'xx', datetime.now()))
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('30')
inv.set_payment_mean(form.PaymentMean(
form.PaymentMean.DEBIT, '41', datetime.now(), '1234'))
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(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
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(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
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'))
@@ -47,7 +45,7 @@ def simple_credit_note_without_lines():
inv.set_supplier(form.Party(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['ZZ']),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
@@ -58,7 +56,7 @@ def simple_credit_note_without_lines():
inv.set_customer(form.Party(
name = 'facho-customer',
ident = form.PartyIdentification('321', '', '31'),
responsability_code = form.Responsability(['ZZ']),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
@@ -79,7 +77,7 @@ def simple_invoice_without_lines():
inv.set_supplier(form.Party(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['ZZ']),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
@@ -90,7 +88,7 @@ def simple_invoice_without_lines():
inv.set_customer(form.Party(
name = 'facho-customer',
ident = form.PartyIdentification('321', '', '31'),
responsability_code = form.Responsability(['ZZ']),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
@@ -100,7 +98,6 @@ def simple_invoice_without_lines():
))
return inv
@pytest.fixture
def simple_invoice():
inv = form.NationalSalesInvoice()
@@ -112,7 +109,7 @@ def simple_invoice():
inv.set_supplier(form.Party(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['ZZ']),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
@@ -123,7 +120,7 @@ def simple_invoice():
inv.set_customer(form.Party(
name = 'facho-customer',
ident = form.PartyIdentification('321','', '31'),
responsability_code = form.Responsability(['ZZ']),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
@@ -131,20 +128,19 @@ def simple_invoice():
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(form.Amount(100.0),'01',''),
tax=form.TaxTotal(
tax_amount=form.Amount(0.0),
taxable_amount=form.Amount(0.0),
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem( 9999),
price = form.Price(form.Amount(100.0), '01', ''),
tax = form.TaxTotal(
tax_amount = form.Amount(0.0),
taxable_amount = form.Amount(0.0),
subtotals = [
form.TaxSubTotal(
percent=19.0,
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
return inv

View File

@@ -2,15 +2,16 @@
# -*- coding: utf-8 -*-
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
# from datetime import datetime
from datetime import datetime
# import pytest
# from facho.fe import form_xml
import pytest
from facho.fe import form_xml
# import helpers
import helpers
def test_xml_with_required_elements():
doc = form_xml.AttachedDocument(id='123')
# def test_xml_with_required_elements():
# doc = form_xml.AttachedDocument(id='123')
# xml = doc.toFachoXML()
# assert xml.get_element_text('/atd:AttachedDocument/cbc:ID') == '123'
xml = doc.toFachoXML()
assert xml.get_element_text('/atd:AttachedDocument/cbc:ID') == '123'

View File

@@ -4,26 +4,21 @@
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
from facho.fe.data.dian import codelist
import pytest
from facho.fe.data.dian import codelist
def test_tiporesponsabilidad():
assert codelist.TipoResponsabilidad.short_name == 'TipoResponsabilidad'
assert codelist.TipoResponsabilidad.by_name(
'Autorretenedor')['name'] == 'Autorretenedor'
assert codelist.TipoResponsabilidad.by_name('Autorretenedor')['name'] == 'Autorretenedor'
def test_tipoorganizacion():
assert codelist.TipoOrganizacion.short_name == 'TipoOrganizacion'
assert codelist.TipoOrganizacion.by_name(
'Persona Natural')['name'] == 'Persona Natural'
assert codelist.TipoOrganizacion.by_name('Persona Natural')['name'] == 'Persona Natural'
def test_tipodocumento():
assert codelist.TipoDocumento.short_name == 'TipoDocumento'
assert codelist.TipoDocumento.by_name(
'Factura electrónica de Venta')['code'] == '01'
assert codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'] == '01'
def test_departamento():
assert codelist.Departamento['05']['name'] == 'Antioquia'

View File

@@ -5,15 +5,15 @@
from datetime import datetime
import pytest
from facho import fe
import helpers
def test_xmlsigned_build(monkeypatch):
# openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1 -newkey rsa:2048 -keyout example.key -out example.pem
# openssl pkcs12 -export -out example.p12 -inkey example.key -in example.pem
#openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1 -newkey rsa:2048 -keyout example.key -out example.pem
#openssl pkcs12 -export -out example.p12 -inkey example.key -in example.pem
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
xml = fe.FeXML('Invoice',
@@ -116,20 +116,3 @@ def test_xml_sign_dian_using_bytes(monkeypatch):
xmlsigned = signer.sign_xml_string(xmlstring)
assert "Signature" in xmlsigned
def test_xml_signature_timestamp(monkeypatch):
xml = fe.FeXML(
'Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.find_or_create_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
ublextension = xml.fragment(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True)
ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
xmlstring = xml.tostring()
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
xmlsigned = signer.sign_xml_string(xmlstring)
with open('invoice.xml', 'w') as file_:
file_.write(xmlsigned)

View File

@@ -5,47 +5,24 @@
"""Tests for `facho` package."""
import pytest
from datetime import datetime
import io
import zipfile
import facho.fe.form as form
from facho import fe
from facho.fe.form_xml import (
DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML)
from fixtures import (
simple_invoice,
simple_invoice_without_lines,
simple_credit_note_without_lines,
simple_debit_note_without_lines)
try:
CUDE_ = open("./tests/cude.txt", 'r').read().strip()
except FileNotFoundError:
raise Exception("Archivo Cude No encontrado")
CUFE_ = (
'8bb918b19ba22a694f1da'
'11c643b5e9de39adf60311c'
'f179179e9b33381030bcd4c3c'
'3f156c506ed5908f9276f5bd9b4')
simple_invoice = simple_invoice
simple_invoice_without_lines = simple_invoice_without_lines
simple_credit_note_without_lines = simple_credit_note_without_lines
simple_debit_note_without_lines = simple_debit_note_without_lines
from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML
from fixtures import *
def test_invoicesimple_build(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')
assert supplier_name == simple_invoice.invoice_supplier.name
customer_name = xml.get_element_text(
'/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name')
customer_name = xml.get_element_text('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name')
assert customer_name == simple_invoice.invoice_customer.name
@@ -56,7 +33,24 @@ def test_invoicesimple_build_with_cufe(simple_invoice):
cufe = xml.get_element_text('/fe:Invoice/cbc:UUID')
assert cufe != ''
def test_invoice_profile_id(simple_invoice):
xml = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml.add_extension(cufe_extension)
assert xml.get_element_text('/fe:Invoice/cbc:ProfileID') == 'DIAN 2.1: Factura Electrónica de Venta'
def test_debit_note_profile_id(simple_invoice):
xml = DIANDebitNoteXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml.add_extension(cufe_extension)
assert xml.get_element_text('/fe:DebitNote/cbc:ProfileID') == 'DIAN 2.1 Nota Débito de Factura Electrónica de Venta'
def test_credit_note_profile_id(simple_invoice):
xml = DIANCreditNoteXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml.add_extension(cufe_extension)
assert xml.get_element_text('/fe:CreditNote/cbc:ProfileID') == 'DIAN 2.1: Nota Crédito de Factura Electrónica de Venta'
def test_invoicesimple_xml_signed(monkeypatch, simple_invoice):
xml = DIANInvoiceXML(simple_invoice)
@@ -65,11 +59,9 @@ def test_invoicesimple_xml_signed(monkeypatch, simple_invoice):
print(xml.tostring())
xml.add_extension(signer)
elem = xml.get_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent/ds:Signature')
elem = xml.get_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent/ds:Signature')
assert elem.text is not None
def test_invoicesimple_zip(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
@@ -91,34 +83,26 @@ def test_invoicesimple_zip(simple_invoice):
def test_bug_cbcid_empty_on_invoice_line(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
cbc_id = xml_invoice.get_element_text(
'/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int)
cbc_id = xml_invoice.get_element_text('/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int)
assert cbc_id == 1
def test_invoice_line_count_numeric(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
count = xml_invoice.get_element_text(
'/fe:Invoice/cbc:LineCountNumeric', format_=int)
count = xml_invoice.get_element_text('/fe:Invoice/cbc:LineCountNumeric', format_=int)
assert count == len(simple_invoice.invoice_lines)
def test_invoice_profileexecutionid(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml_invoice.add_extension(cufe_extension)
id_ = xml_invoice.get_element_text(
'/fe:Invoice/cbc:ProfileExecutionID', format_=int)
id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int)
assert id_ == 2
def test_invoice_invoice_type_code(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
id_ = xml_invoice.get_element_text(
'/fe:Invoice/cbc:InvoiceTypeCode', format_=int)
id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:InvoiceTypeCode', format_=int)
assert id_ == 1
def test_invoice_totals(simple_invoice_without_lines):
simple_invoice = simple_invoice_without_lines
simple_invoice.invoice_ident = '323200000129'
@@ -126,50 +110,39 @@ def test_invoice_totals(simple_invoice_without_lines):
simple_invoice.invoice_supplier.ident = '700085371'
simple_invoice.invoice_customer.ident = '800199436'
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto',
item=form.StandardItem(9999),
price=form.Price(form.Amount(1_500_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto',
item = form.StandardItem(9999),
price = form.Price(form.Amount(1_500_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('01'),
percent=19.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('01'),
percent = 19.0
)])
))
simple_invoice.calculate()
assert 1 == len(simple_invoice.invoice_lines)
assert form.Amount(1_500_000) == (
simple_invoice.invoice_legal_monetary_total.line_extension_amount)
assert form.Amount(1_785_000) == (
simple_invoice.invoice_legal_monetary_total.payable_amount)
assert form.Amount(1_500_000) == simple_invoice.invoice_legal_monetary_total.line_extension_amount
assert form.Amount(1_785_000) == simple_invoice.invoice_legal_monetary_total.payable_amount
def test_invoice_cufe(simple_invoice_without_lines):
simple_invoice = simple_invoice_without_lines
simple_invoice.invoice_ident = '323200000129'
simple_invoice.invoice_issue = datetime.strptime(
'2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification(
'700085371', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification(
'800199436', '5', '31')
simple_invoice.invoice_issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification('700085371', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification('800199436', '5', '31')
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(
1.00, '94'),
description='producto',
item=form.StandardItem(111),
price=form.Price(form.Amount(1_500_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1.00, '94'),
description = 'producto',
item = form.StandardItem(111),
price = form.Price(form.Amount(1_500_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('01'),
percent=19.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('01'),
percent = 19.0
)])
))
simple_invoice.calculate()
@@ -177,86 +150,65 @@ def test_invoice_cufe(simple_invoice_without_lines):
cufe_extension = fe.DianXMLExtensionCUFE(
simple_invoice,
tipo_ambiente=fe.AMBIENTE_PRODUCCION,
clave_tecnica='693ff6f2a553c3646a063436fd4dd9ded0311471'
tipo_ambiente = fe.AMBIENTE_PRODUCCION,
clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471'
)
formatVars = cufe_extension.formatVars()
# NumFac
#NumFac
assert formatVars[0] == '323200000129', "NumFac"
# FecFac
#FecFac
assert formatVars[1] == '2019-01-16', "FecFac"
# HoraFac
#HoraFac
assert formatVars[2] == '10:53:10-05:00', "HoraFac"
# ValorBruto
#ValorBruto
assert formatVars[3] == '1500000.00', "ValorBruto"
# CodImpuesto1
#CodImpuesto1
assert formatVars[4] == '01', "CodImpuesto1"
# ValorImpuesto1
#ValorImpuesto1
assert formatVars[5] == '285000.00', "ValorImpuesto1"
# CodImpuesto2
#CodImpuesto2
assert formatVars[6] == '04', "CodImpuesto2"
# ValorImpuesto2
#ValorImpuesto2
assert formatVars[7] == '0.00', "ValorImpuesto2"
# CodImpuesto3
#CodImpuesto3
assert formatVars[8] == '03', "CodImpuesto3"
# ValorImpuesto3
#ValorImpuesto3
assert formatVars[9] == '0.00', "ValorImpuesto3"
# ValTotFac
#ValTotFac
assert formatVars[10] == '1785000.00', "ValTotFac"
# NitOFE
#NitOFE
assert formatVars[11] == '700085371', "NitOFE"
# NumAdq
#NumAdq
assert formatVars[12] == '800199436', "NumAdq"
# ClTec
#ClTec
assert formatVars[13] == '693ff6f2a553c3646a063436fd4dd9ded0311471', "ClTec"
# TipoAmbiente
#TipoAmbiente
assert formatVars[14] == '1', "TipoAmbiente"
xml_invoice.add_extension(cufe_extension)
cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID')
# RESOLUCION 004: pagina 689
assert cufe == CUFE_
assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'
def test_credit_note_cude(simple_credit_note_without_lines):
simple_invoice = simple_credit_note_without_lines
simple_invoice.invoice_ident = '8110007871'
simple_invoice.invoice_issue = datetime.strptime(
'2019-01-12 07:00:00-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification(
'900373076', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification(
'8355990', '5', '31')
simple_invoice.invoice_issue = datetime.strptime('2019-01-12 07:00:00-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification('900373076', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification('8355990', '5', '31')
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(
1, '94'),
description='producto',
item=form.StandardItem(111),
price=form.Price(
form.Amount(5_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto',
item = form.StandardItem(111),
price = form.Price(form.Amount(5_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('01'),
percent=19.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('01'),
percent = 19.0
)])
))
simple_invoice.calculate()
@@ -265,39 +217,33 @@ def test_credit_note_cude(simple_credit_note_without_lines):
cude_extension = fe.DianXMLExtensionCUDE(
simple_invoice,
'12301',
tipo_ambiente=fe.AMBIENTE_PRODUCCION,
tipo_ambiente = fe.AMBIENTE_PRODUCCION,
)
xml_invoice.add_extension(cude_extension)
cude = xml_invoice.get_element_text('/fe:CreditNote/cbc:UUID')
# pag 612
assert cude == CUDE_
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.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=form.Quantity(1, '94'),
description='producto',
item=form.StandardItem(111),
price=form.Price(form.Amount(30_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto',
item = form.StandardItem(111),
price = form.Price(form.Amount(30_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('04'),
percent=8.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('04'),
percent = 8.0
)])
))
simple_invoice.calculate()
@@ -306,7 +252,7 @@ def test_debit_note_cude(simple_debit_note_without_lines):
cude_extension = fe.DianXMLExtensionCUDE(
simple_invoice,
'10201',
tipo_ambiente=fe.AMBIENTE_PRUEBAS,
tipo_ambiente = fe.AMBIENTE_PRUEBAS,
)
build_vars = cude_extension.buildVars()
assert build_vars['NumFac'] == 'ND1001'
@@ -322,7 +268,8 @@ def test_debit_note_cude(simple_debit_note_without_lines):
assert build_vars['Software-PIN'] == '10201'
assert build_vars['TipoAmb'] == 2
cude_composicion = "".join(cude_extension.formatVars())
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)

View File

@@ -6,117 +6,91 @@
"""Tests for `facho` package."""
import pytest
# from datetime import datetime
# import io
# import zipfile
from datetime import datetime
import io
import zipfile
import facho.fe.form as form
# from facho import fe
from fixtures import (
simple_invoice,
simple_invoice_without_lines,
simple_credit_note_without_lines,
simple_debit_note_without_lines)
simple_invoice = simple_invoice
simple_invoice_without_lines = simple_invoice_without_lines
simple_credit_note_without_lines = simple_credit_note_without_lines
simple_debit_note_without_lines = simple_debit_note_without_lines
from facho import fe
def test_invoice_legalmonetary():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto facho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.0,
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == (
form.Amount(119.0))
assert inv.invoice_legal_monetary_total.charge_total_amount == (
form.Amount(0.0))
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0)
def test_allowancecharge_as_discount():
discount = form.AllowanceChargeAsDiscount(amount=form.Amount(1000.0))
assert discount.isDiscount()
assert discount.isDiscount() == True
def test_FAU10():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.0)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == (
form.Amount(119.0))
assert inv.invoice_legal_monetary_total.charge_total_amount == (
form.Amount(19.0))
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(19.0)
def test_FAU14():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.0,
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
inv.add_allowance_charge(form.AllowanceCharge(
amount=form.Amount(19.0)))
inv.add_prepaid_payment(form.PrePaidPayment(
paid_amount=form.Amount(50.0)))
inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.add_prepaid_payment(form.PrePaidPayment(paid_amount = form.Amount(50.0)))
inv.calculate()
wants = form.Amount(119.0 + 19.0 - 50.0)
@@ -126,42 +100,38 @@ def test_FAU14():
def test_invalid_tipo_operacion_nota_debito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.DebitNote(reference)
with pytest.raises(ValueError):
inv.set_operation_type(22)
def test_valid_tipo_operacion_nota_debito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.DebitNote(reference)
inv.set_operation_type('30')
def test_invalid_tipo_operacion_nota_credito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.DebitNote(reference)
with pytest.raises(ValueError):
inv.set_operation_type('990')
def test_valid_tipo_operacion_nota_credito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.CreditNote(reference)
inv.set_operation_type('20')
@@ -171,52 +141,41 @@ def test_quantity():
quantity1 = form.Quantity(10, '94')
assert quantity1 * form.Amount(3) == form.Amount(30)
def test_invoice_line_quantity_without_taxes():
line = form.InvoiceLine(
quantity=form.Quantity(10, '94'),
description='',
item=form.StandardItem('test', 9999),
price=form.Price(
amount=form.Amount(30.00),
type_code='01',
type='x'),
tax=form.TaxTotal(subtotals=[]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
)
quantity = form.Quantity(10, '94'),
description = '',
item = form.StandardItem('test', 9999),
price = form.Price(
amount = form.Amount(30.00),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(subtotals=[]))
line.calculate()
assert line.total_amount == form.Amount(300)
assert line.tax_amount == form.Amount(0)
def test_invoice_legalmonetary_with_taxes():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(subtotals=[]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
tax = form.TaxTotal(subtotals=[])
))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.charge_total_amount == (
form.Amount(0.0))
assert inv.invoice_legal_monetary_total.payable_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0)
assert inv.invoice_legal_monetary_total.payable_amount == form.Amount(100.0)
def test_invoice_ident_prefix_automatic_invalid():
@@ -224,7 +183,6 @@ def test_invoice_ident_prefix_automatic_invalid():
with pytest.raises(ValueError):
inv.set_ident('SETPQJQJ1234567')
def test_invoice_ident_prefix_automatic():
inv = form.NationalSalesInvoice()
inv.set_ident('SETP1234567')
@@ -242,15 +200,13 @@ def test_invoice_ident_prefix_automatic():
inv.set_ident('1234567')
assert inv.invoice_ident_prefix == ''
def test_invoice_ident_prefix_manual():
inv = form.NationalSalesInvoice()
inv.set_ident('SETP1234567')
inv.set_ident_prefix('SETA')
assert inv.invoice_ident_prefix == 'SETA'
def test_invoice_ident_prefix_automatic_debit():
inv = form.DebitNote(form.BillingReference('', '', ''))
inv = form.DebitNote(form.BillingReference('','',''))
inv.set_ident('ABCDEF1234567')
assert inv.invoice_ident_prefix == 'ABCDEF'

View File

@@ -6,24 +6,13 @@
"""Tests for `facho` package."""
import pytest
# from datetime import datetime
from datetime import datetime
import copy
from facho.fe import form
from facho.fe import form_xml
# from fixtures import *
from fixtures import (
simple_invoice,
simple_invoice_without_lines,
simple_credit_note_without_lines,
simple_debit_note_without_lines)
simple_invoice = simple_invoice
simple_invoice_without_lines = simple_invoice_without_lines
simple_credit_note_without_lines = simple_credit_note_without_lines
simple_debit_note_without_lines = simple_debit_note_without_lines
from fixtures import *
def test_import_DIANInvoiceXML():
try:
@@ -38,82 +27,70 @@ def test_import_DIANDebitNoteXML():
except AttributeError:
pytest.fail("unexpected not found")
def test_import_DIANCreditNoteXML():
try:
form_xml.DIANCreditNoteXML
except AttributeError:
pytest.fail("unexpected not found")
def test_allowance_charge_in_invoice(simple_invoice_without_lines):
inv = copy.copy(simple_invoice_without_lines)
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.0,
)
]
)
))
inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.calculate()
xml = form_xml.DIANInvoiceXML(inv)
assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1'
assert xml.get_element_text('./cac:AllowanceCharge/cbc:ChargeIndicator') == 'true'
assert xml.get_element_text('./cac:AllowanceCharge/cbc:Amount') == '19.0'
assert xml.get_element_text('./cac:AllowanceCharge/cbc:BaseAmount') == '100.0'
# def test_allowance_charge_in_invoice(simple_invoice_without_lines):
# inv = copy.copy(simple_invoice_without_lines)
# inv.add_invoice_line(form.InvoiceLine(
# quantity=form.Quantity(1, '94'),
# description='productofacho',
# item=form.StandardItem(9999),
# price=form.Price(
# amount=form.Amount(100.0),
# type_code='01',
# type='x'
# ),
# tax=form.TaxTotal(
# subtotals=[
# form.TaxSubTotal(
# percent=19.0,
# )]),
# withholding=form.WithholdingTaxTotal(
# subtotals=[])
# ))
def test_allowance_charge_in_invoice_line(simple_invoice_without_lines):
inv = copy.copy(simple_invoice_without_lines)
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.0,
)
]
),
allowance_charge = [
form.AllowanceChargeAsDiscount(amount=form.Amount(10.0))
]
))
inv.calculate()
# inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
# inv.calculate()
# se aplico descuento
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(90.0)
xml = form_xml.DIANInvoiceXML(inv)
# xml = form_xml.DIANInvoiceXML(inv)
# assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1'
# assert xml.get_element_text(
# './cac:AllowanceCharge/cbc:ChargeIndicator') == 'true'
# assert xml.get_element_text(
# './cac:AllowanceCharge/cbc:Amount') == '19.0'
# assert xml.get_element_text(
# './cac:AllowanceCharge/cbc:BaseAmount') == '100.0'
# def test_allowance_charge_in_invoice_line(simple_invoice_without_lines):
# inv = copy.copy(simple_invoice_without_lines)
# inv.add_invoice_line(form.InvoiceLine(
# quantity=form.Quantity(1, '94'),
# description='producto facho',
# item=form.StandardItem(9999),
# price=form.Price(
# amount=form.Amount(100.0),
# type_code='01',
# type='x'
# ),
# tax=form.TaxTotal(
# subtotals=[
# form.TaxSubTotal(
# percent=19.0,
# )]),
# withholding=form.WithholdingTaxTotal(
# subtotals=[]),
# allowance_charge=[
# form.AllowanceChargeAsDiscount(amount=form.Amount(10.0))
# ]
# ))
# inv.calculate()
# # se aplico descuento
# assert inv.invoice_legal_monetary_total.line_extension_amount == (
# form.Amount(90.0))
# xml = form_xml.DIANInvoiceXML(inv)
# with pytest.raises(AttributeError):
# assert xml.get_element_text(
# '/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1'
# xml.get_element_text(
# '/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1'
# xml.get_element_text(
# '/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount'
# ) == '100.0'
with pytest.raises(AttributeError):
assert xml.get_element_text('/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1'
xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1'
xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount') == '100.0'

View File

@@ -4,467 +4,467 @@
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
# import re
import re
# import pytest
import pytest
# from facho import fe
from facho import fe
# import helpers
import helpers
# def assert_error(errors, msg):
# for error in errors:
# if str(error) == msg:
# return True
def assert_error(errors, msg):
for error in errors:
if str(error) == msg:
return True
# raise "wants error: %s" % (msg)
raise "wants error: %s" % (msg)
# def test_adicionar_devengado_Basico():
# nomina = fe.nomina.DIANNominaIndividual()
def test_adicionar_devengado_Basico():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 30,
# sueldo_trabajado = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
# assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30'
# assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') == '1000000.00'
xml = nomina.toFachoXML()
assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30'
assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') == '1000000.00'
# def test_adicionar_devengado_transporte():
# nomina = fe.nomina.DIANNominaIndividual()
def test_adicionar_devengado_transporte():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
# auxilio_transporte = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
auxilio_transporte = fe.nomina.Amount(2_000_000)
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Transporte', 'AuxilioTransporte') == '2000000.0'
assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Transporte', 'AuxilioTransporte') == '2000000.0'
# def test_adicionar_devengado_comprobante_total():
# nomina = fe.nomina.DIANNominaIndividual()
def test_adicionar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(2_000_000)
))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
# assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '1000000.00'
# def test_adicionar_devengado_comprobante_total_cero():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(1_000_000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '0.00'
# def test_adicionar_devengado_transporte_muchos():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
# auxilio_transporte = fe.nomina.Amount(2_000_000)
# ))
# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
# auxilio_transporte = fe.nomina.Amount(3_000_000)
# ))
# xml = nomina.toFachoXML()
# print(xml)
# assert xml.get_element_text('/nomina:NominaIndividual/DevengadosTotal') == '5000000.00'
# def test_adicionar_deduccion_salud():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(1000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1000)
# ))
# xml = nomina.toFachoXML()
# print(xml)
# assert xml.get_element_text('/nomina:NominaIndividual/DeduccionesTotal') == '1000.00'
# def test_nomina_obligatorios_segun_anexo_tecnico():
# nomina = fe.nomina.DIANNominaIndividual()
# errors = nomina.validate()
# assert_error(errors, 'se requiere Periodo')
# assert_error(errors, 'se requiere DevengadoBasico')
# assert_error(errors, 'se requiere DeduccionSalud')
# assert_error(errors, 'se requiere DeduccionFondoPension')
# def test_nomina_xml():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.asignar_metadata(fe.nomina.Metadata(
# novedad=fe.nomina.Novedad(
# activa = True,
# cune = "N0111"
# ),
# secuencia=fe.nomina.NumeroSecuencia(
# prefijo = 'N',
# consecutivo='00001'
# ),
# lugar_generacion=fe.nomina.Lugar(
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# ),
# proveedor=fe.nomina.Proveedor(
# nit='999999',
# dv=2,
# software_id='xx',
# software_pin='12',
# razon_social='facho'
# )
# ))
# nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
# fecha_generacion = '2020-01-16',
# hora_generacion = '1053:10-05:00',
# tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
# software_pin = '693',
# tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_NORMAL,
# periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
# tipo_moneda = fe.nomina.TipoMoneda(code='COP')
# ))
# nomina.asignar_empleador(fe.nomina.Empleador(
# razon_social='facho',
# nit = '700085371',
# dv = '1',
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# direccion = 'calle etrivial'
# ))
# nomina.asignar_trabajador(fe.nomina.Trabajador(
# tipo_contrato = fe.nomina.TipoContrato(
# code = '1'
# ),
# alto_riesgo = False,
# tipo_documento = fe.nomina.TipoDocumento(
# code = '11'
# ),
# primer_apellido = 'gnu',
# segundo_apellido = 'emacs',
# primer_nombre = 'facho',
# lugar_trabajo = fe.nomina.LugarTrabajo(
# pais = fe.nomina.Pais(code='CO'),
# departamento = fe.nomina.Departamento(code='05'),
# municipio = fe.nomina.Municipio(code='05001'),
# direccion = 'calle facho'
# ),
# numero_documento = '800199436',
# tipo = fe.nomina.TipoTrabajador(
# code = '01'
# ),
# salario_integral = True,
# sueldo = fe.nomina.Amount(1_500_000)
# ))
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(3_500_000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab'
# assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune
# assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'TipoXML') == '102'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@DepartamentoEstado') == '05'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@MunicipioCiudad') == '05001'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@NIT') == '999999'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@DV') == '2'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareID') == 'xx'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareSC') is not None
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/CodigoQR') == f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={expected_cune}"
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Empleador/@NIT') == '700085371'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Trabajador/@NumeroDocumento') == '800199436'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad') == 'True'
# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad/@CUNENov') == 'N0111'
# # confirmar el namespace
# assert 'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"' in xml.tostring()
# def test_asignar_pago():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.asignar_pago(fe.nomina.Pago(
# forma = fe.nomina.FormaPago(code='1'),
# metodo = fe.nomina.MetodoPago(code='1')
# ))
# def test_nomina_xmlsign(monkeypatch):
# nomina = fe.nomina.DIANNominaIndividual()
# xml = nomina.toFachoXML()
# signer = fe.nomina.DianXMLExtensionSigner('./tests/example.p12')
# xml.add_extension(signer)
# print(xml.tostring())
# elem = xml.get_element('/nomina:NominaIndividual/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature')
# assert elem is not None
# def test_nomina_devengado_horas_extras_nocturnas():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnas(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENs/HEN', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_recargo_nocturno():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturno(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNs/HRN', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_extras_diarias_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiariasDominicalesYFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDDFs/HEDDF', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_recargo_diarias_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoDiariasDominicalesYFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRDDFs/HRDDF', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_extras_nocturnas_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnasDominicalesYFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENDFs/HENDF', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_recargo_nocturno_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturnoDominicalesYFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNDFs/HRNDF', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_fecha_validacion():
# with pytest.raises(ValueError) as e:
# fe.nomina.Fecha('535-35-3')
xml = nomina.toFachoXML()
assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '1000000.00'
def test_adicionar_devengado_comprobante_total_cero():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(1_000_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
xml = nomina.toFachoXML()
assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '0.00'
def test_adicionar_devengado_transporte_muchos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
auxilio_transporte = fe.nomina.Amount(2_000_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
auxilio_transporte = fe.nomina.Amount(3_000_000)
))
xml = nomina.toFachoXML()
print(xml)
assert xml.get_element_text('/nomina:NominaIndividual/DevengadosTotal') == '5000000.00'
def test_adicionar_deduccion_salud():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(1000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1000)
))
xml = nomina.toFachoXML()
print(xml)
assert xml.get_element_text('/nomina:NominaIndividual/DeduccionesTotal') == '1000.00'
def test_nomina_obligatorios_segun_anexo_tecnico():
nomina = fe.nomina.DIANNominaIndividual()
errors = nomina.validate()
assert_error(errors, 'se requiere Periodo')
assert_error(errors, 'se requiere DevengadoBasico')
assert_error(errors, 'se requiere DeduccionSalud')
assert_error(errors, 'se requiere DeduccionFondoPension')
def test_nomina_xml():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(
activa = True,
cune = "N0111"
),
secuencia=fe.nomina.NumeroSecuencia(
prefijo = 'N',
consecutivo='00001'
),
lugar_generacion=fe.nomina.Lugar(
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
),
proveedor=fe.nomina.Proveedor(
nit='999999',
dv=2,
software_id='xx',
software_pin='12',
razon_social='facho'
)
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2020-01-16',
hora_generacion = '1053:10-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin = '693',
tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_NORMAL,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='facho',
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion = 'calle etrivial'
))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(3_500_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
xml = nomina.toFachoXML()
expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab'
assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune
assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'TipoXML') == '102'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@DepartamentoEstado') == '05'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@MunicipioCiudad') == '05001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@NIT') == '999999'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@DV') == '2'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareID') == 'xx'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareSC') is not None
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/CodigoQR') == f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={expected_cune}"
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Empleador/@NIT') == '700085371'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Trabajador/@NumeroDocumento') == '800199436'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad') == 'True'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad/@CUNENov') == 'N0111'
# confirmar el namespace
assert 'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"' in xml.tostring()
def test_asignar_pago():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_pago(fe.nomina.Pago(
forma = fe.nomina.FormaPago(code='1'),
metodo = fe.nomina.MetodoPago(code='1')
))
def test_nomina_xmlsign(monkeypatch):
nomina = fe.nomina.DIANNominaIndividual()
xml = nomina.toFachoXML()
signer = fe.nomina.DianXMLExtensionSigner('./tests/example.p12')
xml.add_extension(signer)
print(xml.tostring())
elem = xml.get_element('/nomina:NominaIndividual/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature')
assert elem is not None
def test_nomina_devengado_horas_extras_nocturnas():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnas(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENs/HEN', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_recargo_nocturno():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturno(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNs/HRN', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_extras_diarias_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiariasDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDDFs/HEDDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_recargo_diarias_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoDiariasDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRDDFs/HRDDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_extras_nocturnas_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnasDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENDFs/HENDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_recargo_nocturno_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturnoDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNDFs/HRNDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_fecha_validacion():
with pytest.raises(ValueError) as e:
fe.nomina.Fecha('535-35-3')

View File

@@ -4,233 +4,232 @@
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
# import re
import re
# import pytest
import pytest
# from facho import fe
from facho import fe
# import helpers
import helpers
# def atest_nomina_ajuste_reemplazar():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def atest_nomina_ajuste_reemplazar():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# xml = nomina.toFachoXML()
# print(xml)
# assert False
xml = nomina.toFachoXML()
print(xml)
assert False
# def test_nomina_ajuste_reemplazar_asignacion_tipo_xml():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# nomina.asignar_metadata(fe.nomina.Metadata(
# novedad=fe.nomina.Novedad(
# activa = True,
# cune = "N0111"
# ),
# secuencia=fe.nomina.NumeroSecuencia(
# prefijo = 'N',
# consecutivo='00001'
# ),
# lugar_generacion=fe.nomina.Lugar(
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# ),
# proveedor=fe.nomina.Proveedor(
# nit='999999',
# dv=2,
# software_id='xx',
# software_pin='12',
# razon_social='facho'
# )
# ))
# nomina.asignar_empleador(fe.nomina.Empleador(
# razon_social='facho',
# nit = '700085371',
# dv = '1',
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# direccion = 'calle etrivial'
# ))
def test_nomina_ajuste_reemplazar_asignacion_tipo_xml():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(
activa = True,
cune = "N0111"
),
secuencia=fe.nomina.NumeroSecuencia(
prefijo = 'N',
consecutivo='00001'
),
lugar_generacion=fe.nomina.Lugar(
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
),
proveedor=fe.nomina.Proveedor(
nit='999999',
dv=2,
software_id='xx',
software_pin='12',
razon_social='facho'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='facho',
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion = 'calle etrivial'
))
# nomina.asignar_trabajador(fe.nomina.Trabajador(
# tipo_contrato = fe.nomina.TipoContrato(
# code = '1'
# ),
# alto_riesgo = False,
# tipo_documento = fe.nomina.TipoDocumento(
# code = '11'
# ),
# primer_apellido = 'gnu',
# segundo_apellido = 'emacs',
# primer_nombre = 'facho',
# lugar_trabajo = fe.nomina.LugarTrabajo(
# pais = fe.nomina.Pais(code='CO'),
# departamento = fe.nomina.Departamento(code='05'),
# municipio = fe.nomina.Municipio(code='05001'),
# direccion = 'calle facho'
# ),
# numero_documento = '800199436',
# tipo = fe.nomina.TipoTrabajador(
# code = '01'
# ),
# salario_integral = True,
# sueldo = fe.nomina.Amount(1_500_000)
# ))
# nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
# fecha_generacion = '2020-01-16',
# hora_generacion = '1053:10-05:00',
# tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
# software_pin = '693',
# tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_AJUSTES,
# periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
# tipo_moneda = fe.nomina.TipoMoneda(code='COP')
# ))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2020-01-16',
hora_generacion = '1053:10-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin = '693',
tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_AJUSTES,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/InformacionGeneral', 'TipoXML') == '103'
assert xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/InformacionGeneral', 'TipoXML') == '103'
# def test_adicionar_reemplazar_devengado_comprobante_total():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def test_adicionar_reemplazar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(2_000_000)
))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00'
assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00'
# def test_adicionar_reemplazar_asignar_predecesor():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def test_adicionar_reemplazar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456'
# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456'
# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16'
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16'
# def test_adicionar_reemplazar_eliminar_predecesor_opcional():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def test_adicionar_reemplazar_eliminar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
xml = nomina.toFachoXML()
print(xml.tostring())
# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None
# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None
# def test_adicionar_eliminar_reemplazar_predecesor_opcional():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
def test_adicionar_eliminar_reemplazar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None
# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None
# def test_adicionar_eliminar_devengado_comprobante_total():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
def test_adicionar_eliminar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(2_000_000)
))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00'
assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00'
# def test_adicionar_eliminar_asignar_predecesor():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
def test_adicionar_eliminar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456'
# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456'
# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16'
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16'
# def test_nomina_devengado_horas_extras_diarias():
# nomina = fe.nomina.DIANNominaIndividual()
def test_nomina_devengado_horas_extras_diarias():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
# xml = nomina.toFachoXML()
# extras = xml.get_element(
# '/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'

View File

@@ -3,28 +3,22 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
# import pytest
import pytest
import facho.fe.form as form
from facho import fe
from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML
from facho.fe.form_xml import DIANInvoiceXML
# from facho.fe.form_xml import (
# DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML)
from fixtures import simple_invoice
from fixtures import *
from facho.fe.form import query
simple_invoice = simple_invoice
def test_query_billing_reference(simple_invoice):
xml = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml.add_extension(cufe_extension)
out = xml.tostring()
reference = query.billing_reference(out, form.BillingReference)
assert isinstance(reference, form.BillingReference)
assert reference.ident != ''

11
tox.ini
View File

@@ -1,17 +1,12 @@
[tox]
envlist = py39, py310, py311, py312, flake8
envlist = py37, py38, py39, py310
[travis]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
[testenv:flake8]
basepython = python
deps = flake8
commands = flake8 facho
[testenv]
setenv =