4 Commits

85 changed files with 4616 additions and 5842 deletions

View File

@@ -94,14 +94,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,24 +1,17 @@
# 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
RUN apt install software-properties-common -y \
&& add-apt-repository ppa:deadsnakes/ppa
RUN apt-get install -y --no-install-recommends \
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 \
python3.7 python3.7-distutils python3.7-dev \
python3.8 python3.8-distutils python3.8-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 get-pip.py pip==21.3 \
&& python3.7 get-pip.py pip==21.3 \
&& python3.8 get-pip.py pip==21.3 \
&& rm get-pip.py
RUN apt-get install -y --no-install-recommends \
@@ -27,14 +20,12 @@ RUN apt-get install -y --no-install-recommends \
build-essential \
zip
RUN python3.9 --version
RUN python3.10 --version
RUN python3.11 --version
RUN python3.12 --version
RUN python3.6 --version
RUN python3.7 --version
RUN python3.8 --version
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.6 install setuptools setuptools-rust
RUN pip3.7 install setuptools setuptools-rust
RUN pip3.8 install setuptools setuptools-rust
RUN pip3 install tox pytest --break-system-packages
RUN pip3 install tox pytest

View File

@@ -3,4 +3,4 @@ History
=======
* 0.2.1 version usada en produccion.
* First release on PyPI.

View File

@@ -11,11 +11,14 @@
dev-setup:
docker build -t facho .
py-develop:
docker run -t -v $(PWD):/app -w /app facho sh -c 'python3.7 setup.py develop --user'
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

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

@@ -0,0 +1,123 @@
from facho import fe
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_fecha_pago('2024-04-30')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(value='false'),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='N',
consecutivo='000001'
),
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(
razon_social='BICI PIZZA S.A.S',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '2022-01-05',
fecha_liquidacion_inicio='2024-04-01',
fecha_liquidacion_fin='2024-04-30',
fecha_generacion='2024-04-30'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2024-04-30',
hora_generacion = '08:01:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='BICI PIZZA S.A.S',
nit = NIT,
dv = DV,
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 = 'GONZALEZ',
segundo_apellido = '',
primer_nombre = 'JUAN',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'CL 35C 102-17 BL 7 AP 101'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(2400000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(2400000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(96000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(96000)
))
return nomina

View File

@@ -0,0 +1,89 @@
<?xml version='1.0' encoding='UTF-8'?>
<NominaIndividual xmlns:atd="urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2" xmlns="dian:gov:co:facturaelectronica:NominaIndividual" xmlns:fe="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#" SchemaLocation="" xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"><ext:UBLExtensions><ext:UBLExtension><ext:ExtensionContent><ds:Signature Id="xmlsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Id="xmldsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0-ref0" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>HS3rmJgPKyCNLNO3TXfP0s1zIGvJTjq40JfMm/muHLA=</ds:DigestValue>
</ds:Reference>
<ds:Reference Id="xmldsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0-ref1" URI="#xmldsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0-KeyInfo">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>88R+0zjb6NIUiHhT8EE1ale8bO+TR6SSQBtVQUYkZ6c=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#xmldsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0-signedprops" Type="http://uri.etsi.org/01903#SignedProperties">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>QF0d+RznylwnbFaGclqPWLndUo35HVDouEYoS1wfVO8=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>0JBFsBhyo4r/9m5vuBXbKirHOgniDNeGW3RNXpCE/sgXvrV3E4K8/M4HeO0VYtM0
G4bHvvkP2NCkdrKgZ9gB+XG8E/rEmJnwKpHm2kaNnpM1pYGoqBNWpqCa5FnUsEk4
c4BmBaKjmBSUrz+2JoOg1RKaaJumqGWm83IjWWJq9b4l40imL0XIgoTmWUtIGOA0
swm5rokLFldHv9T9CQvuYqCQIb6GAb2HB/GzNcPVO6quCqNVI6K9+Zh6vNpWpnCW
crP8iCn4T3jo2PxU56dwA63xOtkTdYWphaGL4J63flsZysPQ817uuHV/XLu1VwHE
cBpcTWyXL9LB7rgqrVSIzw==</ds:SignatureValue>
<ds:KeyInfo Id="xmldsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>5J1O+4ZedrSBUsb+9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5
/wra04OU57cS1/+/yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/Pbs
wnrKukFU2acxISwTLu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SI
iqqPwHKh+6EDIooujo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891
KBA9Yebdw7m/+ufu+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWns
pvDK97D8YG0MeifPYveIPw==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object><xades:QualifyingProperties Target="#xmlsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0" Id="XadesObjects"><xades:SignedProperties Id="xmldsig-2e62ccd5-82d2-4922-90a0-31af30fc44c0-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>2025-03-16T16:49:57.777363</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>YcIPspAVFcNg+B/galYrdCLYvLIEwFI4KWdSzcuupPY=</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>C=CO, L=Bogota D.C., O=Andes SCD, OU=Division de certificacion entidad final, CN=CA ANDES SCD S.A. Clase II v3, OID.1.2.840.113549.1.9.1=info@andesscd.com.co</ds:X509IssuerName><ds:X509SerialNumber>4834208642831502931</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate><xades:SignaturePolicyIdentifier><xades:SignaturePolicyId><xades:SigPolicyId><xades:Identifier>https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf</xades:Identifier><xades:Description>Política de firma para facturas electrónicas de la República de Colombia.</xades:Description></xades:SigPolicyId><xades:SigPolicyHash><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>dMoMvtcG5aIzgYo0tIsSQeVJBDnUnfSOfBpxXrmor0Y=</ds:DigestValue></xades:SigPolicyHash></xades:SignaturePolicyId></xades:SignaturePolicyIdentifier><xades:SignerRole><xades:ClaimedRoles><xades:ClaimedRole>supplier</xades:ClaimedRole></xades:ClaimedRoles></xades:SignerRole></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions><Novedad CUNENov="false">false</Novedad><Periodo FechaIngreso="2022-01-05" FechaLiquidacionInicio="2024-04-01" FechaLiquidacionFin="2024-04-30" TiempoLaborado="1" FechaGen="2024-04-30"/><NumeroSecuenciaXML Prefijo="N" Consecutivo="000001" Numero="N000001"/><LugarGeneracionXML Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Idioma="es"/><ProveedorXML NIT="901575528" DV="2" SoftwareID="100b0d10-0ca0-4ad4-a894-b704a568cbf3" RazonSocial="BICI PIZZA S.A.S" SoftwareSC="277c710304542320e15609646371219dc9ac3edd9c73adc0a278a355fbef97609bb7e451b92900b517044443f6ae1838"/><CodigoQR>https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=f498037db4d2f7c42224fbdc958d031927f17f0ad4e4c1f4a3e85d6363d68f3f951860873e6899937c080f03b0bd0ceb</CodigoQR><InformacionGeneral Version="V1.0: Documento Soporte de Pago de Nómina Electrónica" Ambiente="2" TipoXML="102" EncripCUNE="CUNE-SHA384" FechaGen="2024-04-30" HoraGen="08:01:00-05:00" PeriodoNomina="1" TipoMoneda="COP" TRM="0" CUNE="f498037db4d2f7c42224fbdc958d031927f17f0ad4e4c1f4a3e85d6363d68f3f951860873e6899937c080f03b0bd0ceb"/><Empleador NIT="901575528" DV="2" Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Direccion="calle etrivial" RazonSocial="BICI PIZZA S.A.S"/><Trabajador TipoTrabajador="01" SubTipoTrabajador="00" AltoRiesgoPension="false" TipoDocumento="11" NumeroDocumento="901575528" PrimerApellido="GONZALEZ" SegundoApellido="" PrimerNombre="JUAN" LugarTrabajoPais="CO" LugarTrabajoDepartamentoEstado="05" LugarTrabajoMunicipioCiudad="05001" LugarTrabajoDireccion="CL 35C 102-17 BL 7 AP 101" SalarioIntegral="false" TipoContrato="1" Sueldo="2400000.0"/><Pago Forma="1" Metodo="10"/><FechasPagos><FechaPago>2024-04-30</FechaPago></FechasPagos><Devengados><Basico DiasTrabajados="30" SueldoTrabajado="2400000.00"/></Devengados><Deducciones><Salud Porcentaje="4.00" Deduccion="96000.0"/><FondoPension Porcentaje="4.00" Deduccion="96000.0"/></Deducciones><Redondeo>0</Redondeo><DevengadosTotal>2400000.00</DevengadosTotal><DeduccionesTotal>192000.00</DeduccionesTotal><ComprobanteTotal>2208000.00</ComprobanteTotal></NominaIndividual>

Binary file not shown.

View File

@@ -0,0 +1,123 @@
from facho import fe
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_fecha_pago('2024-04-30')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(value='false'),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='N',
consecutivo='000002'
),
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(
razon_social='BICI PIZZA S.A.S',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '2022-09-05',
fecha_liquidacion_inicio='2024-04-01',
fecha_liquidacion_fin='2024-04-30',
fecha_generacion='2024-04-30'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2024-04-30',
hora_generacion = '08:01:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='BICI PIZZA S.A.S',
nit = NIT,
dv = DV,
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 = 'GIRALDO',
segundo_apellido = '',
primer_nombre = 'VIVIANA',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'CL 35C 102-17 BL 7 AP 101'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(1160000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(1160000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(46400)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(46400)
))
return nomina

View File

@@ -0,0 +1,89 @@
<?xml version='1.0' encoding='UTF-8'?>
<NominaIndividual xmlns:atd="urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2" xmlns="dian:gov:co:facturaelectronica:NominaIndividual" xmlns:fe="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#" SchemaLocation="" xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"><ext:UBLExtensions><ext:UBLExtension><ext:ExtensionContent><ds:Signature Id="xmlsig-bd7246f2-0f63-4a0a-822b-f59be349b7df">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Id="xmldsig-bd7246f2-0f63-4a0a-822b-f59be349b7df-ref0" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>OZ66ZARE1QPKfYYk+BdiEnO0675e7p00fGwhb/Iyd5s=</ds:DigestValue>
</ds:Reference>
<ds:Reference Id="xmldsig-bd7246f2-0f63-4a0a-822b-f59be349b7df-ref1" URI="#xmldsig-bd7246f2-0f63-4a0a-822b-f59be349b7df-KeyInfo">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>jZRqaYdQrLDYoqJmrKrLoO2ZHlGX1Xo5IpGFAznaQnI=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#xmldsig-bd7246f2-0f63-4a0a-822b-f59be349b7df-signedprops" Type="http://uri.etsi.org/01903#SignedProperties">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>XNMT1fgTLgeuUDfYH//8aY/TqzhsRcScSySL/6nKwxE=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>fHBH/GVqHYi8iFbtTUwLUaVerZjfoj64Toq5jE+qi/HLmwhtDCAK2V/ISn0VDxpJ
3QrAqWw/VjTFEqWmRV7mCxybM98A/+5yeWbvDkG2eY5fUOUHin9+oomysY8cwLjA
joXsvjEJlM1YIndzWG/Dryr14Hcax6ItVSaopA+1BpUoFn/5jWwZXsbmq0jHDjsI
Vyk1WpZzYwmM/aGZg7gj/pR7kZZOnv4d3L/MnFP6vJRdRNtRwO73wBd5lYGU1KaB
iTimrV2gBwg0+o5bqloSaQEtqV87adJG0h3eosSiIhrG9rc9rOpvqqq7tG2nAxRx
NyPM8gwuKoaoU9ogvRsqHQ==</ds:SignatureValue>
<ds:KeyInfo Id="xmldsig-bd7246f2-0f63-4a0a-822b-f59be349b7df-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>5J1O+4ZedrSBUsb+9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5
/wra04OU57cS1/+/yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/Pbs
wnrKukFU2acxISwTLu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SI
iqqPwHKh+6EDIooujo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891
KBA9Yebdw7m/+ufu+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWns
pvDK97D8YG0MeifPYveIPw==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object><xades:QualifyingProperties Target="#xmlsig-bd7246f2-0f63-4a0a-822b-f59be349b7df" Id="XadesObjects"><xades:SignedProperties Id="xmldsig-bd7246f2-0f63-4a0a-822b-f59be349b7df-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>2025-03-16T16:50:01.250573</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>YcIPspAVFcNg+B/galYrdCLYvLIEwFI4KWdSzcuupPY=</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>C=CO, L=Bogota D.C., O=Andes SCD, OU=Division de certificacion entidad final, CN=CA ANDES SCD S.A. Clase II v3, OID.1.2.840.113549.1.9.1=info@andesscd.com.co</ds:X509IssuerName><ds:X509SerialNumber>4834208642831502931</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate><xades:SignaturePolicyIdentifier><xades:SignaturePolicyId><xades:SigPolicyId><xades:Identifier>https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf</xades:Identifier><xades:Description>Política de firma para facturas electrónicas de la República de Colombia.</xades:Description></xades:SigPolicyId><xades:SigPolicyHash><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>dMoMvtcG5aIzgYo0tIsSQeVJBDnUnfSOfBpxXrmor0Y=</ds:DigestValue></xades:SigPolicyHash></xades:SignaturePolicyId></xades:SignaturePolicyIdentifier><xades:SignerRole><xades:ClaimedRoles><xades:ClaimedRole>supplier</xades:ClaimedRole></xades:ClaimedRoles></xades:SignerRole></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions><Novedad CUNENov="false">false</Novedad><Periodo FechaIngreso="2022-09-05" FechaLiquidacionInicio="2024-04-01" FechaLiquidacionFin="2024-04-30" TiempoLaborado="1" FechaGen="2024-04-30"/><NumeroSecuenciaXML Prefijo="N" Consecutivo="000002" Numero="N000002"/><LugarGeneracionXML Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Idioma="es"/><ProveedorXML NIT="901575528" DV="2" SoftwareID="100b0d10-0ca0-4ad4-a894-b704a568cbf3" RazonSocial="BICI PIZZA S.A.S" SoftwareSC="067c1069842576f1f954d3540d9f75a214e2a63219f36f4f25465e26958c10d2c9e222076635379bd7f61ca0459c87e9"/><CodigoQR>https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=cb2e6968a811c661ebdc8b8cd19a323ca05471c2fe3c3b1cfb5275379d7df94d6c22f9405d732295e805095e366a99fa</CodigoQR><InformacionGeneral Version="V1.0: Documento Soporte de Pago de Nómina Electrónica" Ambiente="2" TipoXML="102" EncripCUNE="CUNE-SHA384" FechaGen="2024-04-30" HoraGen="08:01:00-05:00" PeriodoNomina="1" TipoMoneda="COP" TRM="0" CUNE="cb2e6968a811c661ebdc8b8cd19a323ca05471c2fe3c3b1cfb5275379d7df94d6c22f9405d732295e805095e366a99fa"/><Empleador NIT="901575528" DV="2" Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Direccion="calle etrivial" RazonSocial="BICI PIZZA S.A.S"/><Trabajador TipoTrabajador="01" SubTipoTrabajador="00" AltoRiesgoPension="false" TipoDocumento="11" NumeroDocumento="901575528" PrimerApellido="GIRALDO" SegundoApellido="" PrimerNombre="VIVIANA" LugarTrabajoPais="CO" LugarTrabajoDepartamentoEstado="05" LugarTrabajoMunicipioCiudad="05001" LugarTrabajoDireccion="CL 35C 102-17 BL 7 AP 101" SalarioIntegral="false" TipoContrato="1" Sueldo="1160000.0"/><Pago Forma="1" Metodo="10"/><FechasPagos><FechaPago>2024-04-30</FechaPago></FechasPagos><Devengados><Basico DiasTrabajados="30" SueldoTrabajado="1160000.00"/></Devengados><Deducciones><Salud Porcentaje="4.00" Deduccion="46400.0"/><FondoPension Porcentaje="4.00" Deduccion="46400.0"/></Deducciones><Redondeo>0</Redondeo><DevengadosTotal>1160000.00</DevengadosTotal><DeduccionesTotal>92800.00</DeduccionesTotal><ComprobanteTotal>1067200.00</ComprobanteTotal></NominaIndividual>

Binary file not shown.

View File

@@ -0,0 +1,123 @@
from facho import fe
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_fecha_pago('2024-04-30')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(value='false'),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='N',
consecutivo='000003'
),
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(
razon_social='BICI PIZZA S.A.S',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '2022-01-05',
fecha_liquidacion_inicio='2024-04-01',
fecha_liquidacion_fin='2024-04-30',
fecha_generacion='2024-04-30'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2024-04-30',
hora_generacion = '08:01:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='BICI PIZZA S.A.S',
nit = NIT,
dv = DV,
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 = 'GONZALEZ',
segundo_apellido = '',
primer_nombre = 'JUAN',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'CL 35C 102-17 BL 7 AP 101'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(2400000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(2400000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(96000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(96000)
))
return nomina

View File

@@ -0,0 +1,89 @@
<?xml version='1.0' encoding='UTF-8'?>
<NominaIndividual xmlns:atd="urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2" xmlns="dian:gov:co:facturaelectronica:NominaIndividual" xmlns:fe="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#" SchemaLocation="" xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"><ext:UBLExtensions><ext:UBLExtension><ext:ExtensionContent><ds:Signature Id="xmlsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Id="xmldsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b-ref0" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>mf3prH7SIB1Df0Z/nOPdRGG0DKXP8oCra41918zaCyg=</ds:DigestValue>
</ds:Reference>
<ds:Reference Id="xmldsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b-ref1" URI="#xmldsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b-KeyInfo">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>H3dF/siCNdG6iSy15jUQct1k4qswXBZToWJH6tsptpk=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#xmldsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b-signedprops" Type="http://uri.etsi.org/01903#SignedProperties">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>IqiqJKHTI0OAWva1xZ+ZOk9ooGkzEa4hReQCr4/W778=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Nv0nFvZ4Y3KIaPODEiOFgThDYb+DNKlJrRUAOrIxcNvtotpK7Y91JnYbUgFvl28b
Z5V/catUMyOclGDjPzslTBcHXExleb5eIgUB8URdNvdrB78iXFUOfPc+ESFomru8
jyPX9cZw24ZYBmQqqusvqIkf/tIARFGZ+7AzXkp28dBRFyWtgjESn60DIrFjm9x5
Pthv1K95gytjrrpwvdk77KN4uumlCbGoc2Pizbj+ufijbnxEPDS7fS7RR0Qv9uz/
2X7znR10TK8+gwMz7AVRYQi90ZbhOadxf3j+HVAlHtw+JHbrPSC+7xBOiPPStY6o
6TkQU+ZMpz2IUovjqREXow==</ds:SignatureValue>
<ds:KeyInfo Id="xmldsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>5J1O+4ZedrSBUsb+9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5
/wra04OU57cS1/+/yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/Pbs
wnrKukFU2acxISwTLu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SI
iqqPwHKh+6EDIooujo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891
KBA9Yebdw7m/+ufu+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWns
pvDK97D8YG0MeifPYveIPw==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object><xades:QualifyingProperties Target="#xmlsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b" Id="XadesObjects"><xades:SignedProperties Id="xmldsig-135b3cc1-1d1c-4ddd-a4c0-704a880a352b-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>2025-03-16T16:51:23.354553</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>YcIPspAVFcNg+B/galYrdCLYvLIEwFI4KWdSzcuupPY=</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>C=CO, L=Bogota D.C., O=Andes SCD, OU=Division de certificacion entidad final, CN=CA ANDES SCD S.A. Clase II v3, OID.1.2.840.113549.1.9.1=info@andesscd.com.co</ds:X509IssuerName><ds:X509SerialNumber>4834208642831502931</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate><xades:SignaturePolicyIdentifier><xades:SignaturePolicyId><xades:SigPolicyId><xades:Identifier>https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf</xades:Identifier><xades:Description>Política de firma para facturas electrónicas de la República de Colombia.</xades:Description></xades:SigPolicyId><xades:SigPolicyHash><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>dMoMvtcG5aIzgYo0tIsSQeVJBDnUnfSOfBpxXrmor0Y=</ds:DigestValue></xades:SigPolicyHash></xades:SignaturePolicyId></xades:SignaturePolicyIdentifier><xades:SignerRole><xades:ClaimedRoles><xades:ClaimedRole>supplier</xades:ClaimedRole></xades:ClaimedRoles></xades:SignerRole></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions><Novedad CUNENov="false">false</Novedad><Periodo FechaIngreso="2022-01-05" FechaLiquidacionInicio="2024-04-01" FechaLiquidacionFin="2024-04-30" TiempoLaborado="1" FechaGen="2024-04-30"/><NumeroSecuenciaXML Prefijo="N" Consecutivo="000003" Numero="N000003"/><LugarGeneracionXML Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Idioma="es"/><ProveedorXML NIT="901575528" DV="2" SoftwareID="100b0d10-0ca0-4ad4-a894-b704a568cbf3" RazonSocial="BICI PIZZA S.A.S" SoftwareSC="23bd3a3e925c4cdf6c4b99c1eda9e64846526241a263b86f3ffc9673d91020625812ef6abdcf93b4c326b7b65999d6c1"/><CodigoQR>https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=beae68d136b80593b01b5cd7594bc86cc2f6ffd17d0238e5842e29ef6dfde62d26cb959764b6bf0943e73149596bfec0</CodigoQR><InformacionGeneral Version="V1.0: Documento Soporte de Pago de Nómina Electrónica" Ambiente="2" TipoXML="102" EncripCUNE="CUNE-SHA384" FechaGen="2024-04-30" HoraGen="08:01:00-05:00" PeriodoNomina="1" TipoMoneda="COP" TRM="0" CUNE="beae68d136b80593b01b5cd7594bc86cc2f6ffd17d0238e5842e29ef6dfde62d26cb959764b6bf0943e73149596bfec0"/><Empleador NIT="901575528" DV="2" Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Direccion="calle etrivial" RazonSocial="BICI PIZZA S.A.S"/><Trabajador TipoTrabajador="01" SubTipoTrabajador="00" AltoRiesgoPension="false" TipoDocumento="11" NumeroDocumento="901575528" PrimerApellido="GONZALEZ" SegundoApellido="" PrimerNombre="JUAN" LugarTrabajoPais="CO" LugarTrabajoDepartamentoEstado="05" LugarTrabajoMunicipioCiudad="05001" LugarTrabajoDireccion="CL 35C 102-17 BL 7 AP 101" SalarioIntegral="false" TipoContrato="1" Sueldo="2400000.0"/><Pago Forma="1" Metodo="10"/><FechasPagos><FechaPago>2024-04-30</FechaPago></FechasPagos><Devengados><Basico DiasTrabajados="30" SueldoTrabajado="2400000.00"/></Devengados><Deducciones><Salud Porcentaje="4.00" Deduccion="96000.0"/><FondoPension Porcentaje="4.00" Deduccion="96000.0"/></Deducciones><Redondeo>0</Redondeo><DevengadosTotal>2400000.00</DevengadosTotal><DeduccionesTotal>192000.00</DeduccionesTotal><ComprobanteTotal>2208000.00</ComprobanteTotal></NominaIndividual>

Binary file not shown.

View File

@@ -0,0 +1,123 @@
from facho import fe
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_fecha_pago('2024-04-30')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(value='false'),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='N',
consecutivo='000004'
),
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(
razon_social='BICI PIZZA S.A.S',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '2022-09-05',
fecha_liquidacion_inicio='2024-04-01',
fecha_liquidacion_fin='2024-04-30',
fecha_generacion='2024-04-30'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2024-04-30',
hora_generacion = '08:01:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='BICI PIZZA S.A.S',
nit = NIT,
dv = DV,
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 = 'GIRALDO',
segundo_apellido = '',
primer_nombre = 'VIVIANA',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'CL 35C 102-17 BL 7 AP 101'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(1160000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(1160000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(46400)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(46400)
))
return nomina

View File

@@ -0,0 +1,89 @@
<?xml version='1.0' encoding='UTF-8'?>
<NominaIndividual xmlns:atd="urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2" xmlns="dian:gov:co:facturaelectronica:NominaIndividual" xmlns:fe="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#" SchemaLocation="" xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"><ext:UBLExtensions><ext:UBLExtension><ext:ExtensionContent><ds:Signature Id="xmlsig-bf43fedd-6780-438f-afe3-ac062d8b8072">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Id="xmldsig-bf43fedd-6780-438f-afe3-ac062d8b8072-ref0" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>ZAE71pLQXXnN5+540pnkxH5ALNBQcecMdLWFKTKby0g=</ds:DigestValue>
</ds:Reference>
<ds:Reference Id="xmldsig-bf43fedd-6780-438f-afe3-ac062d8b8072-ref1" URI="#xmldsig-bf43fedd-6780-438f-afe3-ac062d8b8072-KeyInfo">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>kNyh65TH4KfF6wqRoy1duEawgkvOlr2rYLCNfdTfK1c=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#xmldsig-bf43fedd-6780-438f-afe3-ac062d8b8072-signedprops" Type="http://uri.etsi.org/01903#SignedProperties">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>BoPBi5C0tXfV04cf74M4jktBxf9eVsXk5IWqGdeOSHw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>pw5WDlbd4GhnSAJKlTEDjsb1+VAVtyyXWA/isvGz4LHY5n1Fm4hiN7fD3JIrfOBj
z/tUfROLzbbywUnVklfaDri6txZlFFwa9SY8MO38bgE0HUxGq5khb/4uzktGRInp
H3i0TG5i42YB83PQItSLiEgPWqGVolj5sWfUqD81Ck4CvgtKuZgnn9JPLgP0sjjT
BUjKcMt4K+ZLvXumehKKCzEbtoaa5DHKUrpuCU2giGMMPElwaTulFA5kNLBnAk2n
HBCiIq+YZ2C3BqE+WQptcQDIObmEnRURvvUTB64gCh4Tigmq6vnv/dMPWmQvohmp
ETIZDwU0/xdGcUK7Ujyl4g==</ds:SignatureValue>
<ds:KeyInfo Id="xmldsig-bf43fedd-6780-438f-afe3-ac062d8b8072-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>5J1O+4ZedrSBUsb+9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5
/wra04OU57cS1/+/yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/Pbs
wnrKukFU2acxISwTLu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SI
iqqPwHKh+6EDIooujo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891
KBA9Yebdw7m/+ufu+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWns
pvDK97D8YG0MeifPYveIPw==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object><xades:QualifyingProperties Target="#xmlsig-bf43fedd-6780-438f-afe3-ac062d8b8072" Id="XadesObjects"><xades:SignedProperties Id="xmldsig-bf43fedd-6780-438f-afe3-ac062d8b8072-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>2025-03-16T16:51:26.699846</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>YcIPspAVFcNg+B/galYrdCLYvLIEwFI4KWdSzcuupPY=</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>C=CO, L=Bogota D.C., O=Andes SCD, OU=Division de certificacion entidad final, CN=CA ANDES SCD S.A. Clase II v3, OID.1.2.840.113549.1.9.1=info@andesscd.com.co</ds:X509IssuerName><ds:X509SerialNumber>4834208642831502931</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate><xades:SignaturePolicyIdentifier><xades:SignaturePolicyId><xades:SigPolicyId><xades:Identifier>https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf</xades:Identifier><xades:Description>Política de firma para facturas electrónicas de la República de Colombia.</xades:Description></xades:SigPolicyId><xades:SigPolicyHash><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>dMoMvtcG5aIzgYo0tIsSQeVJBDnUnfSOfBpxXrmor0Y=</ds:DigestValue></xades:SigPolicyHash></xades:SignaturePolicyId></xades:SignaturePolicyIdentifier><xades:SignerRole><xades:ClaimedRoles><xades:ClaimedRole>supplier</xades:ClaimedRole></xades:ClaimedRoles></xades:SignerRole></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions><Novedad CUNENov="false">false</Novedad><Periodo FechaIngreso="2022-09-05" FechaLiquidacionInicio="2024-04-01" FechaLiquidacionFin="2024-04-30" TiempoLaborado="1" FechaGen="2024-04-30"/><NumeroSecuenciaXML Prefijo="N" Consecutivo="000004" Numero="N000004"/><LugarGeneracionXML Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Idioma="es"/><ProveedorXML NIT="901575528" DV="2" SoftwareID="100b0d10-0ca0-4ad4-a894-b704a568cbf3" RazonSocial="BICI PIZZA S.A.S" SoftwareSC="61d2d18fc61f70cff4f5c5c2a5e17e5c904bf42179a8118242e994b40c22ef203741568ee51d94b81a7fbde4ea6a79aa"/><CodigoQR>https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=40d20d7af2d7ee471909e62fcb4beef7de82494e6c8df4e8349ca33a1246dea601b4cc9035e18972e853ecd435799eaa</CodigoQR><InformacionGeneral Version="V1.0: Documento Soporte de Pago de Nómina Electrónica" Ambiente="2" TipoXML="102" EncripCUNE="CUNE-SHA384" FechaGen="2024-04-30" HoraGen="08:01:00-05:00" PeriodoNomina="1" TipoMoneda="COP" TRM="0" CUNE="40d20d7af2d7ee471909e62fcb4beef7de82494e6c8df4e8349ca33a1246dea601b4cc9035e18972e853ecd435799eaa"/><Empleador NIT="901575528" DV="2" Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Direccion="calle etrivial" RazonSocial="BICI PIZZA S.A.S"/><Trabajador TipoTrabajador="01" SubTipoTrabajador="00" AltoRiesgoPension="false" TipoDocumento="11" NumeroDocumento="901575528" PrimerApellido="GIRALDO" SegundoApellido="" PrimerNombre="VIVIANA" LugarTrabajoPais="CO" LugarTrabajoDepartamentoEstado="05" LugarTrabajoMunicipioCiudad="05001" LugarTrabajoDireccion="CL 35C 102-17 BL 7 AP 101" SalarioIntegral="false" TipoContrato="1" Sueldo="1160000.0"/><Pago Forma="1" Metodo="10"/><FechasPagos><FechaPago>2024-04-30</FechaPago></FechasPagos><Devengados><Basico DiasTrabajados="30" SueldoTrabajado="1160000.00"/></Devengados><Deducciones><Salud Porcentaje="4.00" Deduccion="46400.0"/><FondoPension Porcentaje="4.00" Deduccion="46400.0"/></Deducciones><Redondeo>0</Redondeo><DevengadosTotal>1160000.00</DevengadosTotal><DeduccionesTotal>92800.00</DeduccionesTotal><ComprobanteTotal>1067200.00</ComprobanteTotal></NominaIndividual>

Binary file not shown.

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

@@ -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:
@@ -181,22 +177,16 @@ class FachoXML:
xml = LXMLBuilder.from_string(document)
return FachoXML(xml, nsmap=namespaces)
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)
#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 +196,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 +232,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 +240,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 +253,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 +265,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 +286,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 +299,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 +312,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 +355,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:
@@ -389,12 +371,90 @@ class FachoXML:
def get_element(self, xpath, multiple=False):
xpath = self.fragment_prefix + self._path_xpath_for(xpath)
elem = self.builder.xpath(self.root, xpath)
if elem is None:
raise AttributeError('xpath %s invalid' % (xpath))
return self.builder.xpath(self.root, xpath, multiple=multiple)
text = self.builder.get_text(elem)
return str(text)
def get_element_text(self, xpath, format_=str, multiple=False):
xpath = self.fragment_prefix + self._path_xpath_for(xpath)
# MACHETE(bit4bit) al usar ./ queda ../
xpath = re.sub(r'^\.\.+', '.', xpath)
elem = self.builder.xpath(self.root, xpath, multiple=multiple)
if multiple:
vals = []
for e in elem:
text = self.builder.get_text(e)
if text is not None:
vals.append(format_(text))
return vals
else:
text = self.builder.get_text(elem)
if text is None:
return None
return format_(text)
def get_element_text_or_attribute(self, xpath, default=None, multiple=False, raise_on_fail=False):
parts = xpath.split('/')
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)
if val is None:
return default
return val
except KeyError as e:
if raise_on_fail:
raise e
return default
except ValueError as e:
if raise_on_fail:
raise e
return default
else:
try:
val = self.get_element_text(xpath, multiple=multiple)
if val is None:
return default
return val
except ValueError as e:
if raise_on_fail:
raise e
return default
def get_elements_text_or_attributes(self, xpaths, raise_on_fail=True):
"""
returna el contenido o attributos de un conjunto de XPATHS
si algun XPATH es una tupla se retorna el primer elemento del mismo.
"""
vals = []
for xpath in xpaths:
if isinstance(xpath, tuple):
val = xpath[0]
else:
val = self.get_element_text_or_attribute(xpath, raise_on_fail=raise_on_fail)
vals.append(val)
return vals
def exist_element(self, xpath):
elem = self.get_element(xpath)
# no se encontro elemento
if elem is None:
return False
# el placeholder no ha sido populado
if elem.get('facho_placeholder') == 'True':
return False
# el valor opcional no ha sido populado
if elem.get('facho_optional') == 'True':
return False
return True
def _remove_facho_attributes(self, elem):
self.builder.remove_attributes(elem, ['facho_optional', 'facho_placeholder'])
def tostring(self, **kw):
return self.builder.tostring(self.root, **kw)
@@ -406,17 +466,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

@@ -234,10 +234,9 @@ def _append_timestamp(security, expires_dt=None):
if expires_dt is None:
expires_dt = timedelta(seconds=6000)
timestamp = datetime.now()
etimestamp = utils.WSU.Timestamp({'{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id': utils.get_unique_id()})
etimestamp.append(utils.WSU.Created(get_timestamp(timestamp=timestamp)))
etimestamp.append(utils.WSU.Expires(get_timestamp(timestamp=timestamp, delta=expires_dt)))
etimestamp.append(utils.WSU.Created(get_timestamp()))
etimestamp.append(utils.WSU.Expires(get_timestamp(delta=expires_dt)))
security.insert(0, etimestamp)
if etree.LXML_VERSION[:2] >= (3, 5):
etree.cleanup_namespaces(security,

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,7 @@
# 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.builder import E
from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder
import uuid
import xmlsig
@@ -7,16 +9,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,51 +31,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 = {
'apr': 'urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2',
'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
#'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
#'no': 'dian:gov:co:facturaelectronica:NominaIndividual',
'noa': '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',
'ds': 'http://www.w3.org/2000/09/xmldsig#',
'xades': 'http://uri.etsi.org/01903/v1.3.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#',
}
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
@@ -94,39 +75,28 @@ 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
urn_oasis = {
'AttachedDocument': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
'Invoice': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
'CreditNote': 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2',
'ApplicationResponse': 'urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2'
}
root_namespace = self.root_namespace()
root_localname = self.root_localname()
xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace]
return super().tostring(**kw)\
.replace(xmlns_name + ':', '')\
.replace('xmlns:'+xmlns_name, 'xmlns')\
.replace(root_namespace, urn_oasis[root_localname])
.replace("noa:", "")\
.replace("xmlns:noa", "xmlns")\
.replace("change", "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
@@ -151,25 +121,12 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
fachoxml.set_element('./cbc:UUID', cufe,
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())
#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())
#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')
@@ -185,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'
@@ -216,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
@@ -253,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
@@ -291,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
@@ -335,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
@@ -366,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
@@ -376,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)
@@ -409,6 +327,7 @@ class DianXMLExtensionSigner:
)
xml.append(signature)
ref = xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="", name="xmldsig-%s-ref0" % (id_uuid)
)
@@ -416,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)
@@ -433,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(
@@ -446,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)
@@ -460,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
@@ -469,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):
@@ -520,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:
@@ -537,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):
@@ -559,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')
@@ -581,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,14 +53,10 @@ class AmountCollection(Collection):
total += v
return total
class Amount:
def __init__(
self, amount: int or float or str,
currency: Currency = Currency('COP'),
precision=DECIMAL_PRECISION):
self.precision = precision
# DIAN 1.7.-2020: 1.2.3.1
def __init__(self, amount: int or float or str or Amount, currency: Currency = Currency('COP')):
#DIAN 1.7.-2020: 1.2.3.1
if isinstance(amount, Amount):
if amount < Amount(0.0):
raise ValueError('amount must be positive >= 0')
@@ -75,17 +67,14 @@ class Amount:
if float(amount) < 0:
raise ValueError('amount must be positive >= 0')
self.amount = Decimal(
amount, decimal.Context(
prec=self.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)
@@ -98,25 +87,20 @@ class Amount:
def __lt__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return round(self.amount, self.precision) < round(other, 2)
return round(self.amount, DECIMAL_PRECISION) < round(other, 2)
def __eq__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return round(self.amount, self.precision) == round(
other.amount, self.precision)
return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION)
def _cast(self, val):
if type(val) in [int, float]:
return self.fromNumber(val)
if isinstance(val, Amount):
return val
if isinstance(val, Decimal):
return self.fromNumber(float(val))
raise TypeError("cant cast %s to amount" % (type(val)))
raise TypeError("cant cast to amount")
def __add__(self, rother):
other = self._cast(rother)
if not self.is_same_currency(other):
@@ -140,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')
@@ -169,7 +153,6 @@ class Quantity:
def __repr__(self):
return str(self)
@dataclass
class Item:
scheme_name: str
@@ -180,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='')
@@ -194,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
@@ -207,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
@@ -218,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
@@ -229,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:
@@ -265,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
@@ -290,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
@@ -303,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 = ''
@@ -334,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:
@@ -344,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
@@ -361,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
@@ -410,7 +348,6 @@ class Price:
self.amount *= self.quantity
@dataclass
class PaymentMean:
DEBIT = '01'
@@ -428,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
@@ -454,7 +375,6 @@ class BillingReference:
uuid: str
date: date
class CreditNoteDocumentReference(BillingReference):
"""
ident: Prefijo + Numero de la factura relacionada
@@ -470,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
@@ -478,7 +397,6 @@ class InvoiceDocumentReference(BillingReference):
date: fecha de emision de la nota credito relacionada
"""
@dataclass
class AllowanceChargeReason:
code: str
@@ -491,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
@@ -524,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
@@ -543,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):
@@ -556,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)\
@@ -588,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):
@@ -607,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 \
@@ -630,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:
@@ -672,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
@@ -698,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):
"""
@@ -730,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")
@@ -749,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)
@@ -763,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):
@@ -798,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())
@@ -828,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')
@@ -857,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

@@ -2,7 +2,4 @@ from .invoice import *
from .credit_note import *
from .debit_note import *
from .utils import *
from .support_document import *
from .support_document_credit_note import *
from .attached_document import *
from .application_response import *

View File

@@ -1,177 +0,0 @@
from .. import fe
__all__ = ['ApplicationResponse']
class ApplicationResponse:
def __init__(self, invoice, tag_document='ApplicationResponse'):
self.schema =\
'urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2'
self.tag_document = tag_document
self.invoice = invoice
self.fexml = fe.FeXML(
self.tag_document, self.schema)
self.application_response = self.application_response()
def application_response(self):
# DIAN 1.9.-2023: AE02
self.fexml.set_element(
'./cbc:UBLVersionID', 'UBL 2.1')
# DIAN 1.9.-2023: AE03
self.fexml.set_element(
'./cbc:CustomizationID', 'Documentos adjuntos')
# DIAN 1.9.-2023: AE04
self.fexml.set_element(
'./cbc:ProfileID', 'DIAN 2.1')
# DIAN 1.9.-2023: AE04a
self.fexml.set_element(
'./cbc:ProfileExecutionID', '1')
self.fexml.set_element(
'./cbc:ID', '1')
self.fexml.set_element(
'./cbc:UUID', '1', schemeName="CUDE-SHA384")
self.fexml.set_element(
'./cbc:IssueDate',
self.invoice.invoice_issue.strftime('%Y-%m-%d'))
# DIAN 1.9.-2023: AE06
self.fexml.set_element(
'./cbc:IssueTime', self.invoice.invoice_issue.strftime(
'%H:%M:%S-05:00'))
self.set_sender_party()
self.set_receiver_party()
self.set_document_response()
def set_sender_party(self):
# DIAN 1.9.-2023: AE09
self.fexml.placeholder_for(
'./cac:SenderParty')
# DIAN 1.9.-2023: AE10
self.fexml.placeholder_for(
'./cac:SenderParty/cac:PartyTaxScheme')
# DIAN 1.9.-2023: AE11
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cbc:RegistrationName',
self.invoice.invoice_supplier.name)
# DIAN 1.9.-2023: AE12
# DIAN 1.9.-2023: AE13
# DIAN 1.9.-2023: AE14
# DIAN 1.9.-2023: AE15
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cbc:CompanyID',
self.invoice.invoice_supplier.ident,
schemeAgencyID='195',
schemeID=self.invoice.invoice_supplier.ident.dv,
schemeName=self.invoice.invoice_supplier.ident.type_fiscal)
# DIAN 1.9.-2023: AE16
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cbc:TaxLevelCode',
self.invoice.invoice_supplier.responsability_code)
# DIAN 1.9.-2023: AE18
self.fexml.placeholder_for(
'./cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.9.-2023: AE19
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
self.invoice.invoice_supplier.tax_scheme.code)
# DIAN 1.9.-2023: AE20
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
self.invoice.invoice_supplier.tax_scheme.name)
def set_receiver_party(self):
# DIAN 1.9.-2023: AE21
self.fexml.placeholder_for(
'./cac:ReceiverParty')
# DIAN 1.9.-2023: AE22
self.fexml.placeholder_for(
'./cac:ReceiverParty/cac:PartyTaxScheme')
# DIAN 1.9.-2023: AE23
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cbc:RegistrationName',
self.invoice.invoice_customer.name)
# DIAN 1.9.-2023: AE24
# DIAN 1.9.-2023: AE25
# DIAN 1.9.-2023: AE26
# DIAN 1.9.-2023: AE27
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cbc:CompanyID',
self.invoice.invoice_customer.ident,
schemeAgencyID='195',
schemeID=self.invoice.invoice_customer.ident.dv,
schemeName=self.invoice.invoice_customer.ident.type_fiscal)
# DIAN 1.9.-2023: AE28
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cbc:TaxLevelCode',
self.invoice.invoice_customer.responsability_code)
# DIAN 1.9.-2023: AE30
self.fexml.placeholder_for(
'./cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.9.-2023: AE31
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
self.invoice.invoice_customer.tax_scheme.code)
# DIAN 1.9.-2023: AE32
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
self.invoice.invoice_customer.tax_scheme.name)
def set_document_response(self):
self.fexml.placeholder_for(
'./cac:DocumentResponse')
self.fexml.placeholder_for(
'./cac:DocumentResponse/cac:Response')
self.fexml.set_element(
'./cac:DocumentResponse/cac:Response/cbc:ResponseCode',
'02')
self.fexml.set_element(
'./cac:DocumentResponse/cac:Response/cbc:Description',
'Documento validado por la DIAN')
self.set_documnent_reference()
def set_documnent_reference(self):
self.fexml.placeholder_for(
'./cac:DocumentResponse/cac:DocumentReference')
self.fexml.set_element(
'./cac:DocumentResponse/cac:DocumentReference/cbc:ID',
'FESS19566058')
self.fexml.set_element(
'./cac:DocumentResponse/cac:DocumentReference/cbc:UUID',
'f51ee529aabd19d10e39444f2f593b94d56d5885fbf433faf718d53a7e968f64bf54a6ee43c6a2df842771b54a6aae1a',
schemeName="CUFE-SHA384")
self.set_response_lines()
def set_response_lines(self):
lines = [{
'LineID': '1',
'ResponseCode': '0000',
'Description': '0',
}]
for line in lines:
self.fexml.set_element(
'./cac:DocumentResponse/cac:LineResponse/cac:LineReference/cbc:LineID', line[
'LineID'])
self.fexml.set_element(
'./cac:DocumentResponse/cac:LineResponse/cac:Response/cbc:ResponseCode', line[
'ResponseCode'])
self.fexml.set_element(
'./cac:DocumentResponse/cac:LineResponse/cac:Response/cbc:Description', line[
'Description'])
def toFachoXML(self):
return self.fexml

View File

@@ -1,227 +1,14 @@
from .. import fe
from .application_response import ApplicationResponse
__all__ = ['AttachedDocument']
class AttachedDocument():
def __init__(self, invoice, DIANInvoiceXML, id):
self.schema =\
'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2'
self.id = id
self.invoice = invoice
self.DIANInvoiceXML = DIANInvoiceXML
self.attached_document_invoice = self.attached_document_invoice()
def attached_document_invoice(self):
self.fexml = fe.FeXML(
'AttachedDocument', self.schema)
# DIAN 1.9.-2023: AE02
self.fexml.set_element(
'./cbc:UBLVersionID', 'UBL 2.1')
# DIAN 1.9.-2023: AE03
self.fexml.set_element(
'./cbc:CustomizationID', 'Documentos adjuntos')
# DIAN 1.9.-2023: AE04
self.fexml.set_element(
'./cbc:ProfileID', 'Factura Electrónica de Venta')
# DIAN 1.9.-2023: AE04a
self.fexml.set_element(
'./cbc:ProfileExecutionID', '1')
# DIAN 1.9.-2023: AE04b
self.fexml.set_element(
'./cbc:ID', self.id)
# DIAN 1.9.-2023: AE05
self.fexml.set_element(
'./cbc:IssueDate',
self.invoice.invoice_issue.strftime('%Y-%m-%d'))
# DIAN 1.9.-2023: AE06
self.fexml.set_element(
'./cbc:IssueTime', self.invoice.invoice_issue.strftime(
'%H:%M:%S-05:00'))
# DIAN 1.9.-2023: AE08
self.fexml.set_element(
'./cbc:DocumentType', 'Contenedor de Factura Electrónica')
# DIAN 1.9.-2023: AE08a
self.fexml.set_element(
'./cbc:ParentDocumentID', self.invoice.invoice_ident)
# DIAN 1.9.-2023: AE09
self.set_sender_party()
# DIAN 1.9.-2023: AE20
self.set_receiver_party()
# DIAN 1.9.-2023: AE33
self.set_attachment()
self.set_parent_document_line_reference()
def set_sender_party(self):
# DIAN 1.9.-2023: AE09
self.fexml.placeholder_for(
'./cac:SenderParty')
# DIAN 1.9.-2023: AE10
self.fexml.placeholder_for(
'./cac:SenderParty/cac:PartyTaxScheme')
# DIAN 1.9.-2023: AE11
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cbc:RegistrationName',
self.invoice.invoice_supplier.name)
# DIAN 1.9.-2023: AE12
# DIAN 1.9.-2023: AE13
# DIAN 1.9.-2023: AE14
# DIAN 1.9.-2023: AE15
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cbc:CompanyID',
self.invoice.invoice_supplier.ident,
schemeAgencyID='195',
schemeID=self.invoice.invoice_supplier.ident.dv,
schemeName=self.invoice.invoice_supplier.ident.type_fiscal)
# DIAN 1.9.-2023: AE16
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cbc:TaxLevelCode',
self.invoice.invoice_supplier.responsability_code)
# DIAN 1.9.-2023: AE18
self.fexml.placeholder_for(
'./cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.9.-2023: AE19
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
self.invoice.invoice_supplier.tax_scheme.code)
# DIAN 1.9.-2023: AE20
self.fexml.set_element(
'./cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
self.invoice.invoice_supplier.tax_scheme.name)
def set_receiver_party(self):
# DIAN 1.9.-2023: AE21
self.fexml.placeholder_for(
'./cac:ReceiverParty')
# DIAN 1.9.-2023: AE22
self.fexml.placeholder_for(
'./cac:ReceiverParty/cac:PartyTaxScheme')
# DIAN 1.9.-2023: AE23
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cbc:RegistrationName',
self.invoice.invoice_customer.name)
# DIAN 1.9.-2023: AE24
# DIAN 1.9.-2023: AE25
# DIAN 1.9.-2023: AE26
# DIAN 1.9.-2023: AE27
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cbc:CompanyID',
self.invoice.invoice_customer.ident,
schemeAgencyID='195',
schemeID=self.invoice.invoice_customer.ident.dv,
schemeName=self.invoice.invoice_customer.ident.type_fiscal)
# DIAN 1.9.-2023: AE28
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cbc:TaxLevelCode',
self.invoice.invoice_customer.responsability_code)
# DIAN 1.9.-2023: AE30
self.fexml.placeholder_for(
'./cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.9.-2023: AE31
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
self.invoice.invoice_customer.tax_scheme.code)
# DIAN 1.9.-2023: AE32
self.fexml.set_element(
'./cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
self.invoice.invoice_customer.tax_scheme.name)
def set_attachment(self):
# DIAN 1.9.-2023: AE33
self.fexml.placeholder_for(
'./cac:Attachment')
# DIAN 1.9.-2023: AE34
self.fexml.placeholder_for(
'./cac:Attachment/cac:ExternalReference')
# DIAN 1.9.-2023: AE35
self.fexml.set_element(
'./cac:Attachment/cac:ExternalReference/cbc:MimeCode',
'text/xml')
# DIAN 1.9.-2023: AE36
self.fexml.set_element(
'./cac:Attachment/cac:ExternalReference/cbc:EncodingCode',
'UTF-8')
# DIAN 1.9.-2023: AE37
self.fexml.set_element(
'./cac:Attachment/cac:ExternalReference/cbc:Description',
self._build_attachment(self.DIANInvoiceXML)
)
def set_parent_document_line_reference(self):
self.fexml.placeholder_for(
'./cac:ParentDocumentLineReference')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cbc:LineID', 1)
self.fexml.placeholder_for(
'./cac:ParentDocumentLineReference/cac:DocumentReference')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cbc:ID',
'1234')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cbc:UUID',
'1234',
schemeName="CUFE-SHA384")
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cbc:IssueDate',
'2024-11-28')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cbc:DocumentType',
'ApplicationResponse')
self.fexml.placeholder_for(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:MimeCode',
'text/xml')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:EncodingCode',
'UTF-8')
application_response = ApplicationResponse(
self.invoice).toFachoXML()
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:Description',
self._build_attachment(application_response))
self.fexml.placeholder_for(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidatorID',
'Unidad Especial Dirección de Impuestos y Aduanas Nacionales')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidationResultCode',
'02')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidationDate',
'2024-11-28')
self.fexml.set_element(
'./cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidationTime',
'10:35:11-05:00')
def _build_attachment(self, DIANInvoiceXML):
document = (
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
) + DIANInvoiceXML.tostring()
attachment = "<![CDATA[{}]]>".format(
document)
return attachment
def __init__(self, id):
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

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
@@ -20,24 +19,19 @@ class DIANDebitNoteXML(DIANInvoiceXML):
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)

File diff suppressed because it is too large Load Diff

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,367 +0,0 @@
import facho.model as model
import facho.model.fields as fields
import facho.fe.form as form
from facho import fe
from .common import *
from . import dian
from datetime import date, datetime
from collections import defaultdict
from copy import copy
import hashlib
class PhysicalLocation(model.Model):
__name__ = 'PhysicalLocation'
address = fields.Many2One(Address, namespace='cac')
class PartyTaxScheme(model.Model):
__name__ = 'PartyTaxScheme'
registration_name = fields.Many2One(Name, name='RegistrationName', namespace='cbc')
company_id = fields.Many2One(ID, name='CompanyID', namespace='cbc')
tax_level_code = fields.Many2One(ID, name='TaxLevelCode', namespace='cbc', default='ZZ')
class Party(model.Model):
__name__ = 'Party'
id = fields.Virtual(setter='_on_set_id')
name = fields.Many2One(PartyName, namespace='cac')
tax_scheme = fields.Many2One(PartyTaxScheme, namespace='cac')
location = fields.Many2One(PhysicalLocation, namespace='cac')
contact = fields.Many2One(Contact, namespace='cac')
def _on_set_id(self, name, value):
self.tax_scheme.company_id = value
return value
class AccountingCustomerParty(model.Model):
__name__ = 'AccountingCustomerParty'
party = fields.Many2One(Party, namespace='cac')
class AccountingSupplierParty(model.Model):
__name__ = 'AccountingSupplierParty'
party = fields.Many2One(Party, namespace='cac')
class Quantity(model.Model):
__name__ = 'Quantity'
code = fields.Attribute('unitCode', default='NAR')
def __setup__(self):
self.value = 0
def __default_set__(self, value):
self.value = value
return value
def __default_get__(self, name, value):
return self.value
class Amount(model.Model):
__name__ = 'Amount'
currency = fields.Attribute('currencyID', default='COP')
value = fields.Amount(name='amount', default=0.00, precision=2)
def __default_set__(self, value):
self.value = value
return value
def __default_get__(self, name, value):
return self.value
def __str__(self):
return str(self.value)
class Price(model.Model):
__name__ = 'Price'
amount = fields.Many2One(Amount, name='PriceAmount', namespace='cbc')
def __default_set__(self, value):
self.amount = value
return value
def __default_get__(self, name, value):
return self.amount
class Percent(model.Model):
__name__ = 'Percent'
class TaxScheme(model.Model):
__name__ = 'TaxScheme'
id = fields.Many2One(ID, namespace='cbc')
name= fields.Many2One(Name, namespace='cbc')
class TaxCategory(model.Model):
__name__ = 'TaxCategory'
percent = fields.Many2One(Percent, namespace='cbc')
tax_scheme = fields.Many2One(TaxScheme, namespace='cac')
class TaxSubTotal(model.Model):
__name__ = 'TaxSubTotal'
taxable_amount = fields.Many2One(Amount, name='TaxableAmount', namespace='cbc', default=0.00)
tax_amount = fields.Many2One(Amount, name='TaxAmount', namespace='cbc', default=0.00)
tax_percent = fields.Many2One(Percent, namespace='cbc')
tax_category = fields.Many2One(TaxCategory, namespace='cac')
percent = fields.Virtual(setter='set_category', getter='get_category')
scheme = fields.Virtual(setter='set_category', getter='get_category')
def set_category(self, name, value):
if name == 'percent':
self.tax_category.percent = value
# TODO(bit4bit) debe variar en conjunto?
self.tax_percent = value
elif name == 'scheme':
self.tax_category.tax_scheme.id = value
return value
def get_category(self, name, value):
if name == 'percent':
return value
elif name == 'scheme':
return self.tax_category.tax_scheme
class TaxTotal(model.Model):
__name__ = 'TaxTotal'
tax_amount = fields.Many2One(Amount, name='TaxAmount', namespace='cbc', default=0.00)
subtotals = fields.One2Many(TaxSubTotal, namespace='cac')
class AllowanceCharge(model.Model):
__name__ = 'AllowanceCharge'
amount = fields.Many2One(Amount, namespace='cbc')
is_discount = fields.Virtual(default=False)
def isCharge(self):
return self.is_discount == False
def isDiscount(self):
return self.is_discount == True
class Taxes:
class Scheme:
def __init__(self, scheme):
self.scheme = scheme
class Iva(Scheme):
def __init__(self, percent):
super().__init__('01')
self.percent = percent
def calculate(self, amount):
return form.Amount(amount) * form.Amount(self.percent / 100)
class InvoiceLine(model.Model):
__name__ = 'InvoiceLine'
id = fields.Many2One(ID, namespace='cbc')
quantity = fields.Many2One(Quantity, name='InvoicedQuantity', namespace='cbc')
taxtotal = fields.Many2One(TaxTotal, namespace='cac')
price = fields.Many2One(Price, namespace='cac')
amount = fields.Many2One(Amount, name='LineExtensionAmount', namespace='cbc')
allowance_charge = fields.One2Many(AllowanceCharge, 'cac')
tax_amount = fields.Virtual(getter='get_tax_amount')
def __setup__(self):
self._taxs = defaultdict(list)
self._subtotals = {}
def add_tax(self, tax):
if not isinstance(tax, Taxes.Scheme):
raise ValueError('tax expected TaxIva')
# inicialiamos subtotal para impuesto
if not tax.scheme in self._subtotals:
subtotal = self.taxtotal.subtotals.create()
subtotal.scheme = tax.scheme
self._subtotals[tax.scheme] = subtotal
self._taxs[tax.scheme].append(tax)
def get_tax_amount(self, name, value):
total = form.Amount(0)
for (scheme, subtotal) in self._subtotals.items():
total += subtotal.tax_amount
return total
@fields.on_change(['price', 'quantity'])
def update_amount(self, name, value):
charge = form.AmountCollection(self.allowance_charge)\
.filter(lambda charge: charge.isCharge())\
.map(lambda charge: charge.amount)\
.sum()
discount = form.AmountCollection(self.allowance_charge)\
.filter(lambda charge: charge.isDiscount())\
.map(lambda charge: charge.amount)\
.sum()
total = form.Amount(self.quantity) * form.Amount(self.price)
self.amount = total + charge - discount
for (scheme, subtotal) in self._subtotals.items():
subtotal.tax_amount = 0
for (scheme, taxes) in self._taxs.items():
for tax in taxes:
self._subtotals[scheme].tax_amount += tax.calculate(self.amount)
class LegalMonetaryTotal(model.Model):
__name__ = 'LegalMonetaryTotal'
line_extension_amount = fields.Many2One(Amount, name='LineExtensionAmount', namespace='cbc', default=0)
tax_exclusive_amount = fields.Many2One(Amount, name='TaxExclusiveAmount', namespace='cbc', default=form.Amount(0))
tax_inclusive_amount = fields.Many2One(Amount, name='TaxInclusiveAmount', namespace='cbc', default=form.Amount(0))
charge_total_amount = fields.Many2One(Amount, name='ChargeTotalAmount', namespace='cbc', default=form.Amount(0))
payable_amount = fields.Many2One(Amount, name='PayableAmount', namespace='cbc', default=form.Amount(0))
@fields.on_change(['tax_inclusive_amount', 'charge_total'])
def update_payable_amount(self, name, value):
self.payable_amount = self.tax_inclusive_amount + self.charge_total_amount
class DIANExtensionContent(model.Model):
__name__ = 'ExtensionContent'
dian = fields.Many2One(dian.DianExtensions, name='DianExtensions', namespace='sts')
class DIANExtension(model.Model):
__name__ = 'UBLExtension'
content = fields.Many2One(DIANExtensionContent, namespace='ext')
def __default_get__(self, name, value):
return self.content.dian
class UBLExtension(model.Model):
__name__ = 'UBLExtension'
content = fields.Many2One(Element, name='ExtensionContent', namespace='ext', default='')
class UBLExtensions(model.Model):
__name__ = 'UBLExtensions'
dian = fields.Many2One(DIANExtension, namespace='ext', create=True)
extension = fields.Many2One(UBLExtension, namespace='ext', create=True)
class Invoice(model.Model):
__name__ = 'Invoice'
__namespace__ = fe.NAMESPACES
_ubl_extensions = fields.Many2One(UBLExtensions, namespace='ext')
# nos interesa el acceso solo los atributos de la DIAN
dian = fields.Virtual(getter='get_dian_extension')
profile_id = fields.Many2One(Element, name='ProfileID', namespace='cbc', default='DIAN 2.1')
profile_execute_id = fields.Many2One(Element, name='ProfileExecuteID', namespace='cbc', default='2')
id = fields.Many2One(ID, namespace='cbc')
issue = fields.Virtual(setter='set_issue')
issue_date = fields.Many2One(Date, name='IssueDate', namespace='cbc')
issue_time = fields.Many2One(Time, name='IssueTime', namespace='cbc')
period = fields.Many2One(Period, name='InvoicePeriod', namespace='cac')
supplier = fields.Many2One(AccountingSupplierParty, namespace='cac')
customer = fields.Many2One(AccountingCustomerParty, namespace='cac')
legal_monetary_total = fields.Many2One(LegalMonetaryTotal, namespace='cac')
lines = fields.One2Many(InvoiceLine, namespace='cac')
taxtotal_01 = fields.Many2One(TaxTotal)
taxtotal_04 = fields.Many2One(TaxTotal)
taxtotal_03 = fields.Many2One(TaxTotal)
def __setup__(self):
self._namespace_prefix = 'fe'
# Se requieren minimo estos impuestos para
# validar el cufe
self._subtotal_01 = self.taxtotal_01.subtotals.create()
self._subtotal_01.scheme = '01'
self._subtotal_01.percent = 19.0
self._subtotal_04 = self.taxtotal_04.subtotals.create()
self._subtotal_04.scheme = '04'
self._subtotal_03 = self.taxtotal_03.subtotals.create()
self._subtotal_03.scheme = '03'
def cufe(self, token, environment):
valor_bruto = self.legal_monetary_total.line_extension_amount
valor_total_pagar = self.legal_monetary_total.payable_amount
valor_impuesto_01 = form.Amount(0.0)
valor_impuesto_04 = form.Amount(0.0)
valor_impuesto_03 = form.Amount(0.0)
for line in self.lines:
for subtotal in line.taxtotal.subtotals:
if subtotal.scheme.id == '01':
valor_impuesto_01 += subtotal.tax_amount
elif subtotal.scheme.id == '04':
valor_impuesto_04 += subtotal.tax_amount
elif subtotal.scheme.id == '03':
valor_impuesto_03 += subtotal.tax_amount
pattern = [
'%s' % str(self.id),
'%s' % str(self.issue_date),
'%s' % str(self.issue_time),
valor_bruto.truncate_as_string(2),
'01', valor_impuesto_01.truncate_as_string(2),
'04', valor_impuesto_04.truncate_as_string(2),
'03', valor_impuesto_03.truncate_as_string(2),
valor_total_pagar.truncate_as_string(2),
str(self.supplier.party.id),
str(self.customer.party.id),
str(token),
str(environment)
]
cufe = "".join(pattern)
h = hashlib.sha384()
h.update(cufe.encode('utf-8'))
return h.hexdigest()
@fields.on_change(['lines'])
def update_legal_monetary_total(self, name, value):
self.legal_monetary_total.line_extension_amount = 0
self.legal_monetary_total.tax_inclusive_amount = 0
for line in self.lines:
self.legal_monetary_total.line_extension_amount += line.amount
self.legal_monetary_total.tax_inclusive_amount += line.amount + line.tax_amount
def set_issue(self, name, value):
if not isinstance(value, datetime):
raise ValueError('expected type datetime')
self.issue_date = value.date()
self.issue_time = value
def get_dian_extension(self, name, _value):
return self._ubl_extensions.dian
def to_xml(self, **kw):
# al generar documento el namespace
# se hace respecto a la raiz
return super().to_xml(**kw)\
.replace("fe:", "")\
.replace("xmlns:fe", "xmlns")

View File

@@ -1,90 +0,0 @@
import facho.model as model
import facho.model.fields as fields
from datetime import date, datetime
__all__ = ['Element', 'PartyName', 'Name', 'Date', 'Time', 'Period', 'ID', 'Address', 'Country', 'Contact']
class Element(model.Model):
"""
Lo usuamos para elementos que solo manejan contenido
"""
__name__ = 'Element'
class Name(model.Model):
__name__ = 'Name'
class Date(model.Model):
__name__ = 'Date'
def __default_set__(self, value):
if isinstance(value, str):
return value
if isinstance(value, date):
return value.isoformat()
def __str__(self):
return str(self._value)
class Time(model.Model):
__name__ = 'Time'
def __default_set__(self, value):
if isinstance(value, str):
return value
if isinstance(value, date):
return value.strftime('%H:%M:%S-05:00')
def __str__(self):
return str(self._value)
class Period(model.Model):
__name__ = 'Period'
start_date = fields.Many2One(Date, name='StartDate', namespace='cbc')
end_date = fields.Many2One(Date, name='EndDate', namespace='cbc')
class ID(model.Model):
__name__ = 'ID'
def __default_get__(self, name, value):
return self._value
def __str__(self):
return str(self._value)
class Country(model.Model):
__name__ = 'Country'
name = fields.Many2One(Element, name='Name', namespace='cbc')
class Address(model.Model):
__name__ = 'Address'
#DIAN 1.7.-2020: FAJ08
#DIAN 1.7.-2020: CAJ09
id = fields.Many2One(Element, name='ID', namespace='cbc')
#DIAN 1.7.-2020: FAJ09
#DIAN 1.7.-2020: CAJ10
city = fields.Many2One(Element, name='CityName', namespace='cbc')
class PartyName(model.Model):
__name__ = 'PartyName'
name = fields.Many2One(Name, namespace='cbc')
def __default_set__(self, value):
self.name = value
return value
def __default_get__(self, name, value):
return self.name
class Contact(model.Model):
__name__ = 'Contact'
email = fields.Many2One(Name, name='ElectronicEmail', namespace='cbc')

View File

@@ -1,58 +0,0 @@
import facho.model as model
import facho.model.fields as fields
from .common import *
class DIANElement(Element):
"""
Elemento que contiene atributos por defecto.
Puede extender esta clase y modificar los atributos nuevamente
"""
__name__ = 'DIANElement'
scheme_id = fields.Attribute('schemeID', default='4')
scheme_name = fields.Attribute('schemeName', default='31')
scheme_agency_name = fields.Attribute('schemeAgencyName', default='CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)')
scheme_agency_id = fields.Attribute('schemeAgencyID', default='195')
class SoftwareProvider(model.Model):
__name__ = 'SoftwareProvider'
provider_id = fields.Many2One(Element, name='ProviderID', namespace='sts')
software_id = fields.Many2One(Element, name='SoftwareID', namespace='sts')
class InvoiceSource(model.Model):
__name__ = 'InvoiceSource'
identification_code = fields.Many2One(Element, name='IdentificationCode', namespace='sts', default='CO')
class AuthorizedInvoices(model.Model):
__name__ = 'AuthorizedInvoices'
prefix = fields.Many2One(Element, name='Prefix', namespace='sts')
from_range = fields.Many2One(Element, name='From', namespace='sts')
to_range = fields.Many2One(Element, name='To', namespace='sts')
class InvoiceControl(model.Model):
__name__ = 'InvoiceControl'
authorization = fields.Many2One(Element, name='InvoiceAuthorization', namespace='sts')
period = fields.Many2One(Period, name='AuthorizationPeriod', namespace='sts')
invoices = fields.Many2One(AuthorizedInvoices, namespace='sts')
class AuthorizationProvider(model.Model):
__name__ = 'AuthorizationProvider'
id = fields.Many2One(DIANElement, name='AuthorizationProviderID', namespace='sts', default='800197268')
class DianExtensions(model.Model):
__name__ = 'DianExtensions'
authorization_provider = fields.Many2One(AuthorizationProvider, namespace='sts', create=True)
software_security_code = fields.Many2One(Element, name='SoftwareSecurityCode', namespace='sts')
software_provider = fields.Many2One(SoftwareProvider, namespace='sts')
source = fields.Many2One(InvoiceSource, namespace='sts')
control = fields.Many2One(InvoiceControl, namespace='sts')

View File

@@ -53,20 +53,14 @@ class FechaPago(Fecha):
@dataclass
class Novedad:
# cune de nomina a relacionar
# NIE204
cune: str
# NIE199
activa: bool = False
value: False
def apply(self, fragment):
if self.cune != "":
fragment.set_attributes('./Novedad',
CUNENov=self.cune,
)
fragment.set_attributes('./Novedad',
CUNENov=self.value,
)
def post_apply(self, fexml, scopexml, fragment):
scopexml.set_element('./Novedad', self.activa)
scopexml.set_element('./Novedad', "false")
@dataclass
@@ -144,12 +138,13 @@ class Proveedor:
ambiente = fexml.get_element_attribute(scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente')
codigo_qr = f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={cune}"
if InformacionGeneral.AMBIENTE_PRUEBAS == ambiente:
if InformacionGeneral.AMBIENTE_PRUEBAS.same(ambiente):
codigo_qr = f"https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey={cune}"
elif ambiente is None:
raise RuntimeError('fail to get InformacionGeneral/@Ambiente')
scopexml.set_element('./CodigoQR', codigo_qr)
scopexml.set_element('./Novedad', "false")
# NIE020
software_code = self._software_security_code(fexml, scopexml)
@@ -182,16 +177,14 @@ class Metadata:
proveedor: Proveedor
def apply(self, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
if novedad:
self.novedad.apply(novedad)
self.novedad.apply(novedad)
self.secuencia.apply(numero_secuencia_xml)
self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML')
self.proveedor.apply(proveedor_xml)
def post_apply(self, fexml, scopexml, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.proveedor.post_apply(fexml, scopexml, proveedor_xml)
if novedad:
self.novedad.post_apply(fexml, scopexml, proveedor_xml)
self.novedad.post_apply(fexml, scopexml, proveedor_xml)
@dataclass
class PeriodoNomina:
@@ -219,8 +212,9 @@ class InformacionGeneral:
class TIPO_AMBIENTE:
valor: str
def __eq__(self, other):
return self.valor == str(other)
@classmethod
def same(cls, value):
return cls.valor == str(value)
# TABLA 5.1.1
@dataclass
@@ -234,28 +228,6 @@ class InformacionGeneral:
class AMBIENTE_PRUEBAS(TIPO_AMBIENTE):
valor: str = '2'
def __str__(self):
self.valor
# TABLA 5.5.7
@dataclass
class TIPO_XML:
valor: str
def __eq__(self, other):
return self.valor == str(other)
@dataclass
class TIPO_XML_NORMAL(TIPO_XML):
valor: str = '102'
def __str__(self):
self.valor
@dataclass
class TIPO_XML_AJUSTES(TIPO_XML):
valor: str = '103'
def __str__(self):
self.valor
@@ -264,7 +236,6 @@ class InformacionGeneral:
periodo_nomina: PeriodoNomina
tipo_moneda: TipoMoneda
tipo_ambiente: TIPO_AMBIENTE
tipo_xml: TIPO_XML
software_pin: str
def __post_init__(self):
@@ -279,7 +250,7 @@ class InformacionGeneral:
# NIE202
# TABLA 5.5.2
# TODO(bit4bit) solo NominaIndividual
TipoXML = self.tipo_xml.valor,
TipoXML = '103',
# NIE024
CUNE = None,
# NIE025
@@ -338,18 +309,34 @@ class DianXMLExtensionSigner(fe.DianXMLExtensionSigner):
class DIANNominaXML:
def __init__(self, tag_document, xpath_ajuste=None, schemaLocation=None, namespace_ajuste=None):
def __init__(self, tag_document, xpath_ajuste=None,schemaLocation=None):
self.informacion_general_version = None
self.tag_document = tag_document
self.fexml = fe.FeXML(tag_document, "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste")
if namespace_ajuste:
self.fexml = fe.FeXML(tag_document, namespace_ajuste)
if schemaLocation is not None:
self.fexml.root.set(
"SchemaLocation",
"dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
)
self.fexml.root.set("change", schemaLocation)
else:
self.fexml = fe.FeXML(tag_document, 'dian:gov:co:facturaelectronica:NominaIndividual')
schemaLocation = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
self.fexml.root.set(
"SchemaLocation", "")
self.fexml.root.set(
"change", schemaLocation)
# self.fexml.root.set(
# "SchemaLocation",
# "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
#)
# self.fexml.root.set("change", schemaLocation)
self.fexml.root.set("SchemaLocation", "")
self.fexml.root.set("schemaLocation", schemaLocation)
# layout, la dian requiere que los elementos
# esten ordenados segun el anexo tecnico
@@ -361,8 +348,7 @@ class DIANNominaXML:
self.root_fragment = self.fexml.fragment(xpath_ajuste)
self.root_fragment.placeholder_for('./ReemplazandoPredecesor', optional=True)
self.root_fragment.placeholder_for('./EliminandoPredecesor', optional=True)
if not namespace_ajuste:
self.root_fragment.placeholder_for('./Novedad', optional=False)
self.root_fragment.placeholder_for('./Novedad', optional=False)
self.root_fragment.placeholder_for('./Periodo')
self.root_fragment.placeholder_for('./NumeroSecuenciaXML')
self.root_fragment.placeholder_for('./LugarGeneracionXML')
@@ -375,10 +361,8 @@ class DIANNominaXML:
self.root_fragment.placeholder_for('./FechasPagos')
self.root_fragment.placeholder_for('./Devengados/Basico')
self.root_fragment.placeholder_for('./Devengados/Transporte', optional=True)
if not namespace_ajuste:
self.novedad = self.root_fragment.fragment('./Novedad')
else:
self.novedad = None
self.novedad = self.root_fragment.fragment('./Novedad')
self.informacion_general_xml = self.root_fragment.fragment('./InformacionGeneral')
self.periodo_xml = self.root_fragment.fragment('./Periodo')
self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos')
@@ -398,7 +382,6 @@ class DIANNominaXML:
if not isinstance(metadata, Metadata):
raise ValueError('se espera tipo Metadata')
self.metadata = metadata
self.metadata.apply(self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
def asignar_informacion_general(self, general):
@@ -555,8 +538,6 @@ class DIANNominaXML:
devengados_total = Amount(0.0)
for devengado in devengados:
devengados_total += devengado
# TODO(bit4bit) nque valor va redondeado?
# NIE186
self.root_fragment.set_element('./Redondeo', str(round(0,2)))
self.root_fragment.set_element('./DevengadosTotal', str(round(devengados_total,2)))
@@ -576,14 +557,11 @@ class DIANNominaXML:
class DIANNominaIndividual(DIANNominaXML):
def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"
super().__init__('NominaIndividual', schemaLocation=schema)
self.informacion_general_version = 'V1.0: Documento Soporte de Pago de Nómina Electrónica'
# TODO(bit4bit) confirmar que no tienen en comun con NominaIndividual
class DIANNominaIndividualDeAjuste(DIANNominaXML):
class Reemplazar(DIANNominaXML):
@dataclass
class Predecesor:
@@ -592,8 +570,7 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
fecha_generacion: str
def apply(self, fragment):
# NIAE214
fragment.set_element('./TipoNota', '1')
fragment.set_element('./TipoNota', '1')
fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None,
# NIAE090
NumeroPred = self.numero,
@@ -604,11 +581,9 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
)
def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste', './Reemplazar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste')
self.informacion_general_version = 'V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica'
super().__init__('NominaIndividualDeAjuste', './Reemplazar')
# NIAE214
# self.root_fragment.set_element('./TipoNota', '1')
def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor):
@@ -636,16 +611,17 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
)
def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste', './Eliminar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste')
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste', './Eliminar')
# self.root_fragment.set_element('./TipoNota', '2')
self.informacion_general_version = "V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica"
def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor):
raise ValueError("se espera tipo Eliminar.Predecesor")
predecesor.apply(self.fexml)
def __init__(self):
super().__init__('NominaIndividualDeAjuste')
def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste')

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

@@ -1,175 +0,0 @@
from .fields import Field
from collections import defaultdict
class ModelMeta(type):
def __new__(cls, name, bases, ns):
new = type.__new__(cls, name, bases, ns)
# mapeamos asignacion en declaracion de clase
# a attributo de objeto
if '__name__' in ns:
new.__name__ = ns['__name__']
if '__namespace__' in ns:
new.__namespace__ = ns['__namespace__']
else:
new.__namespace__ = {}
return new
class ModelBase(object, metaclass=ModelMeta):
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
obj._xml_attributes = {}
obj._fields = {}
obj._value = None
obj._namespace_prefix = None
obj._on_change_fields = defaultdict(list)
obj._order_fields = []
def on_change_fields_for_function():
# se recorre arbol de herencia buscando attributo on_changes
for parent_cls in type(obj).__mro__:
for parent_attr in dir(parent_cls):
parent_meth = getattr(parent_cls, parent_attr, None)
if not callable(parent_meth):
continue
on_changes = getattr(parent_meth, 'on_changes', None)
if on_changes:
return (parent_meth, on_changes)
return (None, [])
# forzamos registros de campos al modelo
# al instanciar
for (key, v) in type(obj).__dict__.items():
if isinstance(v, fields.Field):
obj._order_fields.append(key)
if isinstance(v, fields.Attribute) or isinstance(v, fields.Many2One) or isinstance(v, fields.Function) or isinstance(v, fields.Amount):
if hasattr(v, 'default') and v.default is not None:
setattr(obj, key, v.default)
if hasattr(v, 'create') and v.create == True:
setattr(obj, key, '')
# register callbacks for changes
(fun, on_change_fields) = on_change_fields_for_function()
for field in on_change_fields:
obj._on_change_fields[field].append(fun)
# post inicializacion del objeto
obj.__setup__()
return obj
def _set_attribute(self, field, name, value):
self._xml_attributes[field] = (name, value)
def __setitem__(self, key, val):
self._xml_attributes[key] = val
def __getitem__(self, key):
return self._xml_attributes[key]
def _get_field(self, name):
return self._fields[name]
def _set_field(self, name, field):
field.name = name
self._fields[name] = field
def _set_content(self, value):
default = self.__default_set__(value)
if default is not None:
self._value = default
def to_xml(self):
"""
Genera xml del modelo y sus relaciones
"""
def _hook_before_xml():
self.__before_xml__()
for field in self._fields.values():
if hasattr(field, '__before_xml__'):
field.__before_xml__()
_hook_before_xml()
tag = self.__name__
ns = ''
if self._namespace_prefix is not None:
ns = "%s:" % (self._namespace_prefix)
pair_attributes = ["%s=\"%s\"" % (k, v) for (k, v) in self._xml_attributes.values()]
for (prefix, url) in self.__namespace__.items():
pair_attributes.append("xmlns:%s=\"%s\"" % (prefix, url))
attributes = ""
if pair_attributes:
attributes = " " + " ".join(pair_attributes)
content = ""
ordered_fields = {}
for name in self._order_fields:
if name in self._fields:
ordered_fields[name] = True
else:
for key in self._fields.keys():
if key.startswith(name):
ordered_fields[key] = True
for name in ordered_fields.keys():
value = self._fields[name]
# al ser virtual no adicinamos al arbol xml
if hasattr(value, 'virtual') and value.virtual:
continue
if hasattr(value, 'to_xml'):
content += value.to_xml()
elif isinstance(value, str):
content += value
if self._value is not None:
content += str(self._value)
if content == "":
return "<%s%s%s/>" % (ns, tag, attributes)
else:
return "<%s%s%s>%s</%s%s>" % (ns, tag, attributes, content, ns, tag)
def __str__(self):
return self.to_xml()
class Model(ModelBase):
"""
Model clase que representa el modelo
"""
def __before_xml__(self):
"""
Ejecuta antes de generar el xml, este
metodo sirve para realizar actualizaciones
en los campos en el ultimo momento
"""
pass
def __default_set__(self, value):
"""
Al asignar un valor al modelo atraves de una relacion (person.relation = '33')
se puede personalizar como hacer esta asignacion.
"""
return value
def __default_get__(self, name, value):
"""
Al obtener el valor atraves de una relacion (age = person.age)
Retorno de valor por defecto
"""
return value
def __setup__(self):
"""
Inicializar modelo
"""

View File

@@ -1,21 +0,0 @@
from .attribute import Attribute
from .many2one import Many2One
from .one2many import One2Many
from .function import Function
from .virtual import Virtual
from .field import Field
from .amount import Amount
__all__ = [Attribute, One2Many, Many2One, Virtual, Field, Amount]
def on_change(fields):
from functools import wraps
def decorator(func):
setattr(func, 'on_changes', fields)
@wraps(func)
def wrapper(self, *arg, **kwargs):
return func(self, *arg, **kwargs)
return wrapper
return decorator

View File

@@ -1,35 +0,0 @@
from .field import Field
from collections import defaultdict
import facho.fe.form as form
class Amount(Field):
"""
Amount representa un campo moneda usando form.Amount
"""
def __init__(self, name=None, default=None, precision=6):
self.field_name = name
self.values = {}
self.default = default
self.precision = precision
def __get__(self, model, cls):
if model is None:
return self
assert self.name is not None
self.__init_value(model)
model._set_field(self.name, self)
return self.values[model]
def __set__(self, model, value):
assert self.name is not None
self.__init_value(model)
model._set_field(self.name, self)
self.values[model] = form.Amount(value, precision=self.precision)
self._changed_field(model, self.name, value)
def __init_value(self, model):
if model not in self.values:
self.values[model] = form.Amount(self.default or 0)

View File

@@ -1,29 +0,0 @@
from .field import Field
class Attribute(Field):
"""
Attribute es un atributo del elemento actual.
"""
def __init__(self, name, default=None):
"""
:param name: nombre del atribute
:param default: valor por defecto del attributo
"""
self.attribute = name
self.value = default
self.default = default
def __get__(self, inst, cls):
if inst is None:
return self
assert self.name is not None
return self.value
def __set__(self, inst, value):
assert self.name is not None
self.value = value
self._changed_field(inst, self.name, value)
inst._set_attribute(self.name, self.attribute, value)

View File

@@ -1,60 +0,0 @@
import warnings
class Field:
def __set_name__(self, owner, name, virtual=False):
self.name = name
self.virtual = virtual
def __get__(self, inst, cls):
if inst is None:
return self
assert self.name is not None
return inst._fields[self.name]
def __set__(self, inst, value):
assert self.name is not None
inst._fields[self.name] = value
def _set_namespace(self, inst, name, namespaces):
if name is None:
return
#TODO(bit4bit) aunque las pruebas confirmar
#que si se escribe el namespace que es
#no ahi confirmacion de declaracion previa del namespace
inst._namespace_prefix = name
def _call(self, inst, method, *args):
call = getattr(inst, method or '', None)
if callable(call):
return call(*args)
def _create_model(self, inst, name=None, model=None, attribute=None, namespace=None):
try:
return inst._fields[self.name]
except KeyError:
if model is not None:
obj = model()
else:
obj = self.model()
if name is not None:
obj.__name__ = name
if namespace:
self._set_namespace(obj, namespace, inst.__namespace__)
else:
self._set_namespace(obj, self.namespace, inst.__namespace__)
if attribute:
inst._fields[attribute] = obj
else:
inst._fields[self.name] = obj
return obj
def _changed_field(self, inst, name, value):
for fun in inst._on_change_fields[name]:
fun(inst, name, value)

View File

@@ -1,36 +0,0 @@
from .field import Field
class Function(Field):
"""
Permite modificar el modelo cuando se intenta,
obtener el valor de este campo.
DEPRECATED usar Virtual
"""
def __init__(self, field, getter=None, default=None):
self.field = field
self.getter = getter
self.default = default
def __get__(self, inst, cls):
if inst is None:
return self
assert self.name is not None
# si se indica `field` se adiciona
# como campo del modelo, esto es
# que se serializa a xml
inst._set_field(self.name, self.field)
if self.getter is not None:
value = self._call(inst, self.getter, self.name, self.field)
if value is not None:
self.field.__set__(inst, value)
return self.field
def __set__(self, inst, value):
inst._set_field(self.name, self.field)
self._changed_field(inst, self.name, value)
self.field.__set__(inst, value)

View File

@@ -1,62 +0,0 @@
from .field import Field
from collections import defaultdict
class Many2One(Field):
"""
Many2One describe una relacion pertenece a.
"""
def __init__(self, model, name=None, setter=None, namespace=None, default=None, virtual=False, create=False):
"""
:param model: nombre del modelo destino
:param name: nombre del elemento xml
:param setter: nombre de methodo usado cuando se asigna usa como asignacion ejemplo model.relation = 3
:param namespace: sufijo del namespace al que pertenece el elemento
:param default: el valor o contenido por defecto
:param virtual: se crea la relacion por no se ve reflejada en el xml final
:param create: fuerza la creacion del elemento en el xml, ya que los elementos no son creados sino tienen contenido
"""
self.model = model
self.setter = setter
self.namespace = namespace
self.field_name = name
self.default = default
self.virtual = virtual
self.relations = defaultdict(dict)
self.create = create
def __get__(self, inst, cls):
if inst is None:
return self
assert self.name is not None
if self.name in self.relations:
value = self.relations[inst][self.name]
else:
value = self._create_model(inst, name=self.field_name)
self.relations[inst][self.name] = value
# se puede obtener directamente un valor indicado por el modelo
if hasattr(value, '__default_get__'):
return value.__default_get__(self.name, value)
elif hasattr(inst, '__default_get__'):
return inst.__default_get__(self.name, value)
else:
return value
def __set__(self, inst, value):
assert self.name is not None
inst_model = self._create_model(inst, name=self.field_name, model=self.model)
self.relations[inst][self.name] = inst_model
# si hay setter manual se ejecuta
# de lo contrario se asigna como texto del elemento
setter = getattr(inst, self.setter or '', None)
if callable(setter):
setter(inst_model, value)
else:
inst_model._set_content(value)
self._changed_field(inst, self.name, value)

View File

@@ -1,86 +0,0 @@
from .field import Field
from collections import defaultdict
# TODO(bit4bit) lograr que isinstance se aplique
# al objeto envuelto
class _RelationProxy():
def __init__(self, obj, inst, attribute):
self.__dict__['_obj'] = obj
self.__dict__['_inst'] = inst
self.__dict__['_attribute'] = attribute
def __getattr__(self, name):
if (name in self.__dict__):
return self.__dict__[name]
rel = getattr(self.__dict__['_obj'], name)
if hasattr(rel, '__default_get__'):
return rel.__default_get__(name, rel)
return rel
def __setattr__(self, attr, value):
# TODO(bit4bit) hacemos proxy al sistema de notificacion de cambios
# algo burdo, se usa __dict__ para saltarnos el __getattr__ y evitar un fallo por recursion
rel = getattr(self.__dict__['_obj'], attr)
if hasattr(rel, '__default_set__'):
response = setattr(self._obj, attr, rel.__default_set__(value))
else:
response = setattr(self._obj, attr, value)
for fun in self.__dict__['_inst']._on_change_fields[self.__dict__['_attribute']]:
fun(self.__dict__['_inst'], self.__dict__['_attribute'], value)
return response
class _Relation():
def __init__(self, creator, inst, attribute):
self.creator = creator
self.inst = inst
self.attribute = attribute
self.relations = []
def create(self):
n_relations = len(self.relations)
attribute = '%s_%d' % (self.attribute, n_relations)
relation = self.creator(attribute)
proxy = _RelationProxy(relation, self.inst, self.attribute)
self.relations.append(relation)
return proxy
def __len__(self):
return len(self.relations)
def __iter__(self):
for relation in self.relations:
yield relation
class One2Many(Field):
"""
One2Many describe una relacion tiene muchos.
"""
def __init__(self, model, name=None, namespace=None, default=None):
"""
:param model: nombre del modelo destino
:param name: nombre del elemento xml cuando se crea hijo
:param namespace: sufijo del namespace al que pertenece el elemento
:param default: el valor o contenido por defecto
"""
self.model = model
self.field_name = name
self.namespace = namespace
self.default = default
self.relation = {}
def __get__(self, inst, cls):
assert self.name is not None
def creator(attribute):
return self._create_model(inst, name=self.field_name, model=self.model, attribute=attribute, namespace=self.namespace)
if inst in self.relation:
return self.relation[inst]
else:
self.relation[inst] = _Relation(creator, inst, self.name)
return self.relation[inst]

View File

@@ -1,54 +0,0 @@
from .field import Field
# Un campo virtual
# no participa del renderizado
# pero puede interactura con este
class Virtual(Field):
"""
Virtual es un campo que no es renderizado en el xml final
"""
def __init__(self,
setter=None,
getter='',
default=None,
update_internal=False):
"""
:param setter: nombre de methodo usado cuando se asigna usa como asignacion ejemplo model.relation = 3
:param getter: nombre del metodo usando cuando se obtiene, ejemplo: valor = mode.relation
:param default: valor por defecto
:param update_internal: indica que cuando se asigne algun valor este se almacena localmente
"""
self.default = default
self.setter = setter
self.getter = getter
self.values = {}
self.update_internal = update_internal
self.virtual = True
def __get__(self, inst, cls):
if inst is None:
return self
assert self.name is not None
value = self.default
try:
value = self.values[inst]
except KeyError:
pass
try:
self.values[inst] = getattr(inst, self.getter)(self.name, value)
except AttributeError:
self.values[inst] = value
return self.values[inst]
def __set__(self, inst, value):
if self.update_internal:
inst._value = value
if self.setter is None:
self.values[inst] = value
else:
self.values[inst] = self._call(inst, self.setter, self.name, value)
self._changed_field(inst, self.name, value)

View File

@@ -1,17 +0,0 @@
attrs==22.1.0
distlib==0.3.6
filelock==3.8.0
iniconfig==1.1.1
packaging==21.3
platformdirs==2.5.2
pluggy==1.0.0
py==1.11.0
pyparsing==3.0.9
pytest==7.1.3
semantic-version==2.10.0
setuptools-rust==1.5.2
six==1.16.0
tomli==2.0.1
tox==3.26.0
typing_extensions==4.4.0
virtualenv==20.16.5

129
scrips/AJU00001.py Normal file
View File

@@ -0,0 +1,129 @@
from facho import fe
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero='A000001',
fecha_generacion='2025-03-23',
cune = '38e3a0863b2cc46e0746d0d97d0b305125292f24c0e478f9dd4f4a097c4403ae4e52d96d90f8b5702fc5eac8bc71004b',
))
# nomina.asignar_fecha_pago('2024-04-30')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(False),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='AJU',
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(
razon_social='BICI PIZZA S.A.S',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '2022-01-05',
fecha_liquidacion_inicio='2024-04-01',
fecha_liquidacion_fin='2024-04-30',
fecha_generacion='2024-04-30'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2024-04-30',
hora_generacion = '08:01:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='BICI PIZZA S.A.S',
nit = NIT,
dv = DV,
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 = 'GONZALEZ',
segundo_apellido = '',
primer_nombre = 'JUAN',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'CL 35C 102-17 BL 7 AP 101'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(2400000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(2400000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(96000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(96000)
))
return nomina

89
scrips/AJU00001.xml Normal file
View File

@@ -0,0 +1,89 @@
<?xml version='1.0' encoding='UTF-8'?>
<NominaIndividualDeAjuste xmlns="dian:gov:co:facturaelectronica:NominaIndividualDeAjuste" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" SchemaLocation="" xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"><ext:UBLExtensions><ext:UBLExtension><ext:ExtensionContent><ds:Signature Id="xmlsig-3fde635a-ad2d-4667-84f5-5f4207e64319">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Id="xmldsig-3fde635a-ad2d-4667-84f5-5f4207e64319-ref0" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>v3gLXFVP20p3X5RTkooSR2z/9jmFi4R6YbPQlKHLRwo=</ds:DigestValue>
</ds:Reference>
<ds:Reference Id="xmldsig-3fde635a-ad2d-4667-84f5-5f4207e64319-ref1" URI="#xmldsig-3fde635a-ad2d-4667-84f5-5f4207e64319-KeyInfo">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>4vxQuFc85SS7cssuTdGgdTC/9qxq5XFRvGMQDp+1xeM=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#xmldsig-3fde635a-ad2d-4667-84f5-5f4207e64319-signedprops" Type="http://uri.etsi.org/01903#SignedProperties">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>CRSEul95G+Fqlnj1I1vKAj4c2HRsHJgpqcsYs+JJd/4=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>O6ZPJ5b6iosFTKFV0CBoAAJqeQ3XmI73tKILKe5OG7idjWjVDKSlwVNOpTMw3uaM
qPt95yAzEYcy6f1NiHE5I6DhwmJhrpUJ1RlJZkSm0RAH4YUIWBStue+MbZIcPZqx
7TKS3O80DIGlZGAhn7MpP1AfevHD42ubDzLeVPM8dA205ZV6htmuknBO1rhNy7Te
J4ph/bk8eJc5N2d33ImlTJv32zc1Yyg4Vg7i6BbL2ngduRlPLoaac2VEDKs7JJxp
RZJi3N9e5RLkpzK5s+5+1tvHFf19yKR+zvGfb5i8GzKK/a5WPJ2aDYAtLuvUvLum
VmU3yzMVKAIAhsU8aa+E8w==</ds:SignatureValue>
<ds:KeyInfo Id="xmldsig-3fde635a-ad2d-4667-84f5-5f4207e64319-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>5J1O+4ZedrSBUsb+9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5
/wra04OU57cS1/+/yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/Pbs
wnrKukFU2acxISwTLu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SI
iqqPwHKh+6EDIooujo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891
KBA9Yebdw7m/+ufu+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWns
pvDK97D8YG0MeifPYveIPw==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object><xades:QualifyingProperties Target="#xmlsig-3fde635a-ad2d-4667-84f5-5f4207e64319" Id="XadesObjects"><xades:SignedProperties Id="xmldsig-3fde635a-ad2d-4667-84f5-5f4207e64319-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>2025-04-13T18:25:09.305178</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>YcIPspAVFcNg+B/galYrdCLYvLIEwFI4KWdSzcuupPY=</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>C=CO, L=Bogota D.C., O=Andes SCD, OU=Division de certificacion entidad final, CN=CA ANDES SCD S.A. Clase II v3, OID.1.2.840.113549.1.9.1=info@andesscd.com.co</ds:X509IssuerName><ds:X509SerialNumber>4834208642831502931</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate><xades:SignaturePolicyIdentifier><xades:SignaturePolicyId><xades:SigPolicyId><xades:Identifier>https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf</xades:Identifier><xades:Description>Política de firma para facturas electrónicas de la República de Colombia.</xades:Description></xades:SigPolicyId><xades:SigPolicyHash><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>dMoMvtcG5aIzgYo0tIsSQeVJBDnUnfSOfBpxXrmor0Y=</ds:DigestValue></xades:SigPolicyHash></xades:SignaturePolicyId></xades:SignaturePolicyIdentifier><xades:SignerRole><xades:ClaimedRoles><xades:ClaimedRole>supplier</xades:ClaimedRole></xades:ClaimedRoles></xades:SignerRole></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions><TipoNota>2</TipoNota><Eliminar><EliminandoPredecesor NumeroPred="A000001" CUNEPred="38e3a0863b2cc46e0746d0d97d0b305125292f24c0e478f9dd4f4a097c4403ae4e52d96d90f8b5702fc5eac8bc71004b" FechaGenPred="2025-03-23"/><Novedad CUNENov="False">false</Novedad><Periodo FechaIngreso="2022-01-05" FechaLiquidacionInicio="2024-04-01" FechaLiquidacionFin="2024-04-30" TiempoLaborado="1" FechaGen="2024-04-30"/><NumeroSecuenciaXML Prefijo="AJU" Consecutivo="00001" Numero="AJU00001"/><LugarGeneracionXML Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Idioma="es"/><ProveedorXML NIT="901575528" DV="2" SoftwareID="100b0d10-0ca0-4ad4-a894-b704a568cbf3" RazonSocial="BICI PIZZA S.A.S" SoftwareSC="181675298da86622f2c4213f75cf67f434a34169373602ddfe1cedbc86be3d5a2dd3a8b3df431d8f1f0d3477cf30a387"/><CodigoQR>https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=19a695aad7a8fa12c4c2c4c07dbed5a5daf39ba43b333503cc23a1c7a09295ddf222c0e278762ca67a48874e9c68efc6</CodigoQR><InformacionGeneral Version="V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica" Ambiente="2" TipoXML="103" EncripCUNE="CUNE-SHA384" FechaGen="2024-04-30" HoraGen="08:01:00-05:00" PeriodoNomina="1" TipoMoneda="COP" TRM="0" CUNE="19a695aad7a8fa12c4c2c4c07dbed5a5daf39ba43b333503cc23a1c7a09295ddf222c0e278762ca67a48874e9c68efc6"/><Empleador NIT="901575528" DV="2" Pais="CO" DepartamentoEstado="05" MunicipioCiudad="05001" Direccion="calle etrivial" RazonSocial="BICI PIZZA S.A.S"/><Trabajador TipoTrabajador="01" SubTipoTrabajador="00" AltoRiesgoPension="false" TipoDocumento="11" NumeroDocumento="901575528" PrimerApellido="GONZALEZ" SegundoApellido="" PrimerNombre="JUAN" LugarTrabajoPais="CO" LugarTrabajoDepartamentoEstado="05" LugarTrabajoMunicipioCiudad="05001" LugarTrabajoDireccion="CL 35C 102-17 BL 7 AP 101" SalarioIntegral="false" TipoContrato="1" Sueldo="2400000.0"/><Pago Forma="1" Metodo="10"/><FechasPagos/><Devengados><Basico DiasTrabajados="30" SueldoTrabajado="2400000.00"/></Devengados><Deducciones><Salud Porcentaje="4.00" Deduccion="96000.0"/><FondoPension Porcentaje="4.00" Deduccion="96000.0"/></Deducciones><Redondeo>0</Redondeo><DevengadosTotal>2400000.00</DevengadosTotal><DeduccionesTotal>192000.00</DeduccionesTotal><ComprobanteTotal>2208000.00</ComprobanteTotal></Eliminar></NominaIndividualDeAjuste>

BIN
scrips/AJU00001.zip Normal file

Binary file not shown.

BIN
scrips/Certificado.pfx Normal file

Binary file not shown.

1
scrips/nomina.csv Normal file
View File

@@ -0,0 +1 @@
N,0000011,JUAN,GONZALEZ,CARDONA,71380802,CL 35C 102-17 BL 7 AP 101,2400000,30,96000,96000,140606,2400000,2024-04-30,2022-01-05,2024-04-01,2024-04-30,2024-04-30,08:01:00``
1 N 0000011 JUAN GONZALEZ CARDONA 71380802 CL 35C 102-17 BL 7 AP 101 2400000 30 96000 96000 140606 2400000 2024-04-30 2022-01-05 2024-04-01 2024-04-30 2024-04-30 08:01:00``

178
scrips/nomina.sh Executable file
View File

@@ -0,0 +1,178 @@
#!/bin/bash
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
TIPO_AMBIENTE='AMBIENTE_PRUEBAS'
RAZON_SOCIAL='BICI PIZZA S.A.S'
SET_PRUEBAS='be4d232c-9c7b-4542-b24d-6044c4e316ce'
PASSPHARASE='7U3XbtZ76W'
#La ruta donde se encuentren estos archivos
LLAVE_PRIVADA='privada.pem'
LLAVE_PUBLICA='publica.pem'
CERTIFICADO='Certificado.pfx'
HABILITACION='True'
ARCHIVO_CSV='./nomina.csv'
IFS=,
while read line; do
field=( $line )
Prefijo=${field[0]}
Numero=${field[1]}
Nombre=${field[2]}
P_Apellido=${field[3]}
S_Apellifo=${field[4]}
Cedula=${field[5]}
Direccion=${field[6]}
Salario=${field[7]}
Sueldo=${field[12]}
Dias=${field[8]}
Salud=${field[9]}
Pension=${field[10]}
AuxTp=${field[11]}
Pago=${field[13]}
Ingreso=${field[14]}
Inicio=${field[15]}
Fin=${field[16]}
Generacion=${field[17]}
Hora=${field[18]}
echo ${Pago}
echo "
from facho import fe
SOFTWARE_PIN='${SOFTWARE_PIN}'
SOFTWARE_ID='${SOFTWARE_ID}'
NIT='${NIT}'
DV='${DV}'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_fecha_pago('${Pago}')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(value='false'),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='${Prefijo}',
consecutivo='${Numero}'
),
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(
razon_social='${RAZON_SOCIAL}',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '${Ingreso}',
fecha_liquidacion_inicio='${Inicio}',
fecha_liquidacion_fin='${Fin}',
fecha_generacion='${Generacion}'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '${Generacion}',
hora_generacion = '${Hora}-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.${TIPO_AMBIENTE},
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='${RAZON_SOCIAL}',
nit = NIT,
dv = DV,
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 = '${P_Apellido}',
segundo_apellido = '${S_Apellido}',
primer_nombre = '${Nombre}',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = '${Direccion}'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(${Sueldo})
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = ${field[8]},
sueldo_trabajado = fe.nomina.Amount(${Salario})
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(${Salud})
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(${Pension})
))
return nomina
" > "${Prefijo}${Numero}".py
facho generate-nomina --private-key ${CERTIFICADO} --passphrase ${PASSPHARASE} --use-cache-policy --sign "${field[0]}${field[1]}".py "${Prefijo}${Numero}".xml
CUNE=`grep -oP "<CodigoQR>(.*)</CodigoQR>" "${Prefijo}${Numero}".xml | sed -n 's/.*documentkey=\([^<]*\)<\/CodigoQR>.*/\1/p'`
zip "${field[0]}${field[1]}".zip "${field[0]}${field[1]}".xml
if [ "$HABILITACION" == "True" ];then
facho soap-send-test-set-async --private-key ${LLAVE_PRIVADA} --public-key ${LLAVE_PUBLICA} --habilitacion --password ${PASSPHARASE} --test-setid ${SET_PRUEBAS} "${field[0]}${field[1]}".xml "${field[0]}${field[1]}".zip
else
facho soap-send-nomina-sync --private-key ${LLAVE_PRIVADA} --public-key ${LLAVE_PUBLICA} --password ${PASSPHARASE} "${field[0]}${field[1]}".xml "${field[0]}${field[1]}".zip --produccion
fi
done < ${ARCHIVO_CSV}

1
scrips/nomina_ajuste.csv Normal file
View File

@@ -0,0 +1 @@
AJU,00001,JUAN,GONZALEZ,CARDONA,71380802,CL 35C 102-17 BL 7 AP 101,2400000,30,96000,96000,140606,2400000,2024-04-30,2022-01-05,2024-04-01,2024-04-30,2024-04-30,08:01:00
1 AJU 00001 JUAN GONZALEZ CARDONA 71380802 CL 35C 102-17 BL 7 AP 101 2400000 30 96000 96000 140606 2400000 2024-04-30 2022-01-05 2024-04-01 2024-04-30 2024-04-30 08:01:00

182
scrips/nomina_ajuste.sh Executable file
View File

@@ -0,0 +1,182 @@
#!/bin/bash
SOFTWARE_PIN='20234'
SOFTWARE_ID='100b0d10-0ca0-4ad4-a894-b704a568cbf3'
NIT='901575528'
DV='2'
TIPO_AMBIENTE='AMBIENTE_PRUEBAS'
RAZON_SOCIAL='BICI PIZZA S.A.S'
SET_PRUEBAS='be4d232c-9c7b-4542-b24d-6044c4e316ce'
PASSPHARASE='7U3XbtZ76W'
#La ruta donde se encuentren estos archivos
LLAVE_PRIVADA='privada.pem'
LLAVE_PUBLICA='publica.pem'
CERTIFICADO='Certificado.pfx'
HABILITACION='True'
ARCHIVO_CSV='./nomina_ajuste.csv'
IFS=,
while read line; do
field=($line)
Prefijo=${field[0]}
Numero=${field[1]}
Nombre=${field[2]}
P_Apellido=${field[3]}
S_Apellifo=${field[4]}
Cedula=${field[5]}
Direccion=${field[6]}
Salario=${field[7]}
Sueldo=${field[12]}
Dias=${field[8]}
Salud=${field[9]}
Pension=${field[10]}
AuxTp=${field[11]}
Pago=${field[13]}
Ingreso=${field[14]}
Inicio=${field[15]}
Fin=${field[16]}
Generacion=${field[17]}
Hora=${field[18]}
echo ${Pago}
echo "
from facho import fe
SOFTWARE_PIN='${SOFTWARE_PIN}'
SOFTWARE_ID='${SOFTWARE_ID}'
NIT='${NIT}'
DV='${DV}'
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero='A000001',
fecha_generacion='2025-03-23',
cune = '38e3a0863b2cc46e0746d0d97d0b305125292f24c0e478f9dd4f4a097c4403ae4e52d96d90f8b5702fc5eac8bc71004b',
))
# nomina.asignar_fecha_pago('${Pago}')
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(False),
secuencia=fe.nomina.NumeroSecuencia(
prefijo='${Prefijo}',
consecutivo='${Numero}'
),
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(
razon_social='${RAZON_SOCIAL}',
nit=NIT,
dv=DV,
software_id=SOFTWARE_ID,
software_pin=SOFTWARE_PIN
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso= '${Ingreso}',
fecha_liquidacion_inicio='${Inicio}',
fecha_liquidacion_fin='${Fin}',
fecha_generacion='${Generacion}'
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '${Generacion}',
hora_generacion = '${Hora}-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.${TIPO_AMBIENTE},
software_pin = SOFTWARE_PIN,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
forma=fe.nomina.FormaPago(
code='1',
),
metodo=fe.nomina.MetodoPago(
code='10'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='${RAZON_SOCIAL}',
nit = NIT,
dv = DV,
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 = '${P_Apellido}',
segundo_apellido = '${S_Apellido}',
primer_nombre = '${Nombre}',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = '${Direccion}'
),
numero_documento = NIT,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = False,
sueldo = fe.nomina.Amount(${Sueldo})
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = ${field[8]},
sueldo_trabajado = fe.nomina.Amount(${Salario})
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(${Salud})
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(4),
deduccion = fe.nomina.Amount(${Pension})
))
return nomina
" >"${Prefijo}${Numero}".py
facho generate-nomina --private-key ${CERTIFICADO} --passphrase ${PASSPHARASE} --use-cache-policy --sign "${field[0]}${field[1]}".py "${Prefijo}${Numero}".xml
CUNE=$(grep -oP "<CodigoQR>(.*)</CodigoQR>" "${Prefijo}${Numero}".xml | sed -n 's/.*documentkey=\([^<]*\)<\/CodigoQR>.*/\1/p')
zip "${field[0]}${field[1]}".zip "${field[0]}${field[1]}".xml
if [ "$HABILITACION" == "True" ]; then
facho soap-send-test-set-async --private-key ${LLAVE_PRIVADA} --public-key ${LLAVE_PUBLICA} --habilitacion --password ${PASSPHARASE} --test-setid ${SET_PRUEBAS} "${field[0]}${field[1]}".xml "${field[0]}${field[1]}".zip
else
facho soap-send-nomina-sync --private-key ${LLAVE_PRIVADA} --public-key ${LLAVE_PUBLICA} --password ${PASSPHARASE} "${field[0]}${field[1]}".xml "${field[0]}${field[1]}".zip --produccion
fi
done <${ARCHIVO_CSV}

169
scrips/privada.pem Normal file
View File

@@ -0,0 +1,169 @@
Bag Attributes
localKeyID: 47 1D 7A 81 07 9A 0F 92 5D D8 E8 FD 6D 22 D2 A6 D9 20 23 40
friendlyName: BICI PIZZA S.A.S.
subject=street = TV 46 C 42 ESTE 789 CORR SANTA ELENA, emailAddress = bicipizza@gmail.com, CN = BICI PIZZA S.A.S., serialNumber = 9015755282, title = Emisor Factura Electronica - Persona Juridica, OU = Emitido por Andes SCD Ac 26 69 C 03 Torre B Of 701, O = GERENTE, L = MEDELL\C3\8DN, ST = ANTIOQUIA, C = CO
issuer=emailAddress = info@andesscd.com.co, CN = CA ANDES SCD S.A. Clase II v3, OU = Division de certificacion entidad final, O = Andes SCD, L = Bogota D.C., C = CO
-----BEGIN CERTIFICATE-----
MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK
-----END CERTIFICATE-----
Bag Attributes: <No Attributes>
subject=emailAddress = info@andesscd.com.co, CN = ROOT CA ANDES SCD S.A., OU = Division de certificacion, O = Andes SCD, L = Bogota D.C., C = CO
issuer=emailAddress = info@andesscd.com.co, CN = ROOT CA ANDES SCD S.A., OU = Division de certificacion, O = Andes SCD, L = Bogota D.C., C = CO
-----BEGIN CERTIFICATE-----
MIIGKTCCBBGgAwIBAgIILDECIDXRkbIwDQYJKoZIhvcNAQELBQAwgaExIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMR8wHQYDVQQDExZST09UIENB
IEFOREVTIFNDRCBTLkEuMSIwIAYDVQQLExlEaXZpc2lvbiBkZSBjZXJ0aWZpY2Fj
aW9uMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDASBgNVBAcTC0JvZ290YSBELkMuMQsw
CQYDVQQGEwJDTzAeFw0xNjA5MjQxNjUwNTdaFw0zNTA3MDkxNjM2NTlaMIGhMSMw
IQYJKoZIhvcNAQkBFhRpbmZvQGFuZGVzc2NkLmNvbS5jbzEfMB0GA1UEAxMWUk9P
VCBDQSBBTkRFUyBTQ0QgUy5BLjEiMCAGA1UECxMZRGl2aXNpb24gZGUgY2VydGlm
aWNhY2lvbjESMBAGA1UEChMJQW5kZXMgU0NEMRQwEgYDVQQHEwtCb2dvdGEgRC5D
LjELMAkGA1UEBhMCQ08wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCc
svKA1BIq5MzsIQtZsf8QfaI6uzllLZCdnw1jczyTTE+xlygz9+Bv5ynFiWnZThLF
LMC3DSMVhAPBSYQ3qierOF/U7QE84NM0bHCKVK33MiA6ruu5804HsdVJuLqP6YCb
S+nH1Ygnw0q2fX/H094wBWb2Jr2oVe+ydDDjy1RYjZHXiZVekwZTb6oY+f2rE25w
nTNj1/3B4JfYPBbIDz6aRXPyeSBtI/RVKZBjD4NBXd4mCdXCE6/puOdvAbBWMhq7
9wLCQIgtU21nne6/YaEHISah2S5KTC4P1nS+1nHvxMxdC1cszv7mheP4/nszfAgU
LEeI6eL+lvBy+vjssT8dv+utofmj76QQdn2MkTb1paZaan4+3a+c1PsjTO7yZ86k
KDQDxfnYaGF9b7wOgIDvacqJlREpmlvT2DN+4YAp2RLnuK1+ws6dnS+e1t73qbnZ
Q7lntelAjFtKms3YIpXfs5sWUlPza1ozxEpmkSM4ZeuPKI6WF7YJuEo5s11Cu+Rw
CtBOIVjYZWMUipxwfZcT5L3vZYWPSkboDlWZGZFX4mu2srzfv2ac4Ij5M8/hTD/n
5WT2WzWcLId0xJuY9dZT/6XIrNZ6ZEFweEXxM7zHp3SrMfNSJZdG22d28gTmSyVg
FPCVop+bdW7fMo6rmCdfveih5LkBRbxE6SPUnztVcwIDAQABo2MwYTAdBgNVHQ4E
FgQU3RGSWqlsBAz+NKJYv+sd+98IsG8wDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
GDAWgBTdEZJaqWwEDP40oli/6x373wiwbzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI
hvcNAQELBQADggIBAFpZLihgW/5L2ZpIoh3Jo+W281od5UoIMmnJ7zjnBsEj37d5
iIaQjBNKMQdiEfOHkHxb5TaBnil6nnlt5KyS+H+fmMThIKYOrmkS1kCtGF8Yn3JC
qLWuA3Nhq+3xerbhrrZrjKhdX/b+GWWBzRgeqguvAMnRDk87oWRWYFjFKt0u2pdT
DYzqORiAKgy06h7NTpfLfN5O5/BHpfoz7nLVrVDLFzZJH16HPBdvbcX7p2MjW+fl
WAO47E8CYEbK4wOJfK8L2s6lN3Zruq3RL/a9HSPGoI/Ftuu8YFDsU61k+JCWtFWX
lwO+MjkIOIygabsWqUAY9R4DVnCx2soXXN2vPUakHiBWC1V/s3xv2M7EgoyQfST7
fFoZdFt7r8v8lUljhjTPPrP8sCJ1FIn4w23hr5NjqQo8T1wo89nlupPFZ8aHqBVU
HsyQVQbuDsD4eEO0I37wBqd3fn7UWfZDec73kS8P0PMHMH8pCorHKkpeJtLDw2QP
FgYicFZ8u6fYjOYNikmQQe9c8FRJgwSYMeXQb0hf1dlC/WpJGO00Efwor99lS2cl
DegHOF5dv3wl7b+xvdzhUs3yqYiq9Qa/+N7oIPkgzhlltDabPRhIFLnJsmxfa/Kt
lF/MZ1Va+UjR+JQ1unNE/CTn6L5A4cavpOoI72N2elJRw+QP8icwGLApBp/W
-----END CERTIFICATE-----
Bag Attributes: <No Attributes>
subject=emailAddress = info@andesscd.com.co, CN = CA ANDES SCD S.A. Clase II v2, OU = Division de certificacion entidad final, O = Andes SCD, L = Bogota D.C., C = CO
issuer=emailAddress = info@andesscd.com.co, CN = ROOT CA ANDES SCD S.A., OU = Division de certificacion, O = Andes SCD, L = Bogota D.C., C = CO
-----BEGIN CERTIFICATE-----
MIIGrzCCBJegAwIBAgIIRLa0wz3hC2gwDQYJKoZIhvcNAQELBQAwgaExIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMR8wHQYDVQQDExZST09UIENB
IEFOREVTIFNDRCBTLkEuMSIwIAYDVQQLExlEaXZpc2lvbiBkZSBjZXJ0aWZpY2Fj
aW9uMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDASBgNVBAcTC0JvZ290YSBELkMuMQsw
CQYDVQQGEwJDTzAeFw0xOTA4MDYxNjE0NTNaFw0yNTExMTUxNzM0MzBaMIG2MSMw
IQYJKoZIhvcNAQkBFhRpbmZvQGFuZGVzc2NkLmNvbS5jbzEmMCQGA1UEAxMdQ0Eg
QU5ERVMgU0NEIFMuQS4gQ2xhc2UgSUkgdjIxMDAuBgNVBAsTJ0RpdmlzaW9uIGRl
IGNlcnRpZmljYWNpb24gZW50aWRhZCBmaW5hbDESMBAGA1UEChMJQW5kZXMgU0NE
MRQwEgYDVQQHEwtCb2dvdGEgRC5DLjELMAkGA1UEBhMCQ08wggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDOWKOiAg0IgACNXNyDCoAyaXo514UwF9oCqMjg
xAOhOe+g3lUDWTXtV6l8O1VXHQa86KZwqgT0mfw0HqePQYKC93n9+timuuT1FuAJ
Z9TnF/f6DQDzFQWsRVLdqV/hzNQ/rbOvPX8WN4Z97TyJaUtlxbENOKgOlzsW8hGe
eVrjDz9fk1e0TPem/CswuY+7Z/Pi4/EVz28UoTlfEMOg84ZutTomHOskIp6S86lw
X+ts15yChEf/dXBVkAMkBUpFc39iZWhhtewZo56Q8u9an5ZBQtOorB33LKFKn2P1
V95Cf2XPZqWqLrP1wRQn0XWB1uDF+eAyQzShGA0jrZGgdlaFW8J7bx8+YntJHqFi
jI3UVwqCyb+TGO9RRcTRp3OfAwAKCbh56gHacvF5KEGj36d0zofw808WBpnhrnvQ
ofPp48mowpbByzuIqVtX0utVXgzg4QdNBSQ6uhDnjB8ETzwuJcUokC1lXwBMSDa0
f5psC1yflyqdTOpSjZC8pMxwGUXhhvkfxDBaSsueExLpcKlolVojQRWm6C2oiumS
2lgeL4ydh8DFhi2llMLVCLZQLffG9ziTZtMd97n55NvXrVa4XbDIf4i5/XFrYgFy
R/rBZduYWDqxcwFnUP6HhTfuael8qNxKA3UWmxFK3ixNN5kaC/ErX3g4fC+KGqh4
LeljKQIDAQABo4HTMIHQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU3RGS
WqlsBAz+NKJYv+sd+98IsG8wNwYIKwYBBQUHAQEEKzApMCcGCCsGAQUFBzABhhto
dHRwOi8vb2NzcC5hbmRlc3NjZC5jb20uY28wNAYDVR0fBC0wKzApoCegJYYjaHR0
cDovL2NybC5hbmRlc3NjZC5jb20uY28vUmFpei5jcmwwHQYDVR0OBBYEFDpXUNB3
Gz7Wi+r/l5nX/4QCSCkPMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC
AgEALA0nz8TvtoMZ70MBN7FFWZMqTXvkXY7mcnWImpTFLGzny+i119qODuItEf5G
977TzoTCxZC8DlPL1d88+YxAfYFRLXdb1HcDrNuWKMyoiFGH3vhNl+Ef//4zed98
DV5jtoaCofFt/JzPz2iA7Al2LZ1m0vAF0z12LAytHqSfob+5mdgWkBnLae0vaWU+
eGnoTkCYsasxca9oSKHDajqTvTa5WogibUaffcnb9LYaP3nkam9+favCyHHQW2lp
2xPPcP1gisvp99ga9Cwft7d/QHXNjseop2TwDoUHViUImaSpLz+2kWhkr2LOSTpL
43rWQhVQ/Y6SyYd3YWEBxJWwFvVlHvQ2wKwiE/z7kHnCP9ztPhS0umC1BMGq3pC4
fifzvx9218JjDIOVrKzwonCTj0vwQZ4FmTttAr0G+R5TrqqvgF1t/RTfhWIxhUTk
TR3xRpR/5mBtqrSmyojGtdVkwHq2dmmDIkjefSbxKDkkAkhDRt5CDeV913B3MvYm
UiMoOJmLyapIZjIko1HxzXBv3dPc5LYSKpG8rd9SjJtA7W8LQzAVUq8O32nO7edY
IvTrC11fLkdToGOxLmfTzZ+facUESteELfxchZoda6I9qH5Xa7Xka2M2mmkj0SGU
46KREw+DtpoUkpAQ7rLaZ+4stT0j1Z8soirB+EUk8oBLiTI=
-----END CERTIFICATE-----
Bag Attributes
localKeyID: 47 1D 7A 81 07 9A 0F 92 5D D8 E8 FD 6D 22 D2 A6 D9 20 23 40
friendlyName: BICI PIZZA S.A.S.
Key Attributes: <No Attributes>
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIZXaHIVVK8xgCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECDiCo/ndioOzBIIEyOaIf6RezHh0
6knOjiQyjWvc12TOqcPORR5yFaddeAZ5aoaHLfozNzhafH08QSsxAN2PW9aJI38u
rejfKXiOx9X98zFs5LVvbcNYaSlJBT7nCTvIJSRUO/p3lkDXUnW3+jbO2uT2UOgk
exV198RS1fZjat1VyIf/3DTCHYJKLvC2D46F6NgsdadEkDSyrLvqcCW3HAbE3zw/
SmYmxGyEQVJWfgjTuzja07AFjWspls1rGXO9gdxcLXXHcDDfArkUPe9OTmeKMoXM
gtMuAAMJaQ3xpw3M7FaLHB0F0zXec5UhYTgs7n0jw/D2jTORqHqvAcZGRGuGnGwZ
QtuzISqBMqDEtdkVoobz0QURJP1QVj4X14xcQnaVbXCJq5uUdPlTkBvjHS11/tqX
X31wk2DmkGSsEtdLfeVupsWjd7C83IWEhZfj86lh0oxZORdR8n+JHyOI3bIFJqK0
z5tv8HN7RfBZI09wr8YNYU45IfVgOBy3PDbeQ50eAVJg1T8pbQQHwUZ+0e1MXSiI
iiAU/j0VQYsSMFVZizNozfMAJ5kjV5Pb+BqhrPb10h1o/H4O+46kVJYUXakeVdMk
mLUDBvWG3rwqXvwan9k+Zeld9/frDoa9BjEi7QhgkuyaduzT8yXIai3oOoniX5FH
kDL6NAp0oBoVu4jLOvsGvYX69qc6AiMTaJgYxE9Nvv/hjhE3zuyEvGcXfDhH9yhp
BpQMXH9S18l/WBWb7ebzp+qFPfxQ/BzFEVGEJrWZMbwZZsGYs/t8Ug9Q2dZjPweS
42UWQU5qSabwT/A+NGtdNvPUbNnG/hpZr5Rt89JTUnqxQSTvqydvx3sijNniIOdu
nHrCT51yHXUIXgD3DsxBoXF2pPCotRALdxfVpA0f2Vcs4ihEKRqEfZ+EGrHarRgJ
u11LpS312Un2Ua8jifli2YkAGZiWxQ2VraoNdZiTRcUktFEjWaI73uXeoPhXNvTO
PnD5sjZgYwTFfd90zvhN+8oPs8csFUV1UjLE+WZOm0qtNUJWOncBca1ceonWNiCq
0HNZF9nnn+qCIoNQ+7MHDBWRAh7h68Tsp22M3FqbayLmsDQWIaruN3Kw7VzDFubk
d9seJxsOos58kbq5x9MXnbY630+1qjvDZaUP/SWyytJJrI0g24h8EC59RfiYXeGR
Mv2M4YIVZUqH0bivzDwumzb8MR7vFQGHFU0bbQZtouvulWc/naHF1eX5a3jrkqeI
WJ0Ms8wlqxtYONJWzo90E26Z10PH62QbYM+4Lu6lgFVC6Idzsy19GkksrQNfghE7
XdDQb8BeS1Uer8qbt8IXSaS9X8jSTx29ccOE/ZFU8F0ZnxOR3s3hL+QMePictzyO
KVPOx7mUPrIbFg7NTMqRssmeYS4RXRShdR45cGtfrqHyrVsaO3x02ukB5LHNHfvV
3Dkrem7xf+m/T74GONuZJ5jNXsFz2rSY7PCxFTpPgHTPb56Kdmb+jaBj5vxfUjgA
ObioPS4HMPt/OKTREHpHbGFhlPD/RXDzh8K6dLPUbyMRZMUKIoDb2Mp8JYhOv0yO
oKVE7E/nYU6OtUGbS/qkuirpUeVmpvadTSuIaPA2KZYZJhKj0y0u5AO61k8ZLGKb
HPL9j6aYYJSt6dWdlY0Y6A==
-----END ENCRYPTED PRIVATE KEY-----

135
scrips/publica.pem Normal file
View File

@@ -0,0 +1,135 @@
Bag Attributes
localKeyID: 47 1D 7A 81 07 9A 0F 92 5D D8 E8 FD 6D 22 D2 A6 D9 20 23 40
friendlyName: BICI PIZZA S.A.S.
subject=street = TV 46 C 42 ESTE 789 CORR SANTA ELENA, emailAddress = bicipizza@gmail.com, CN = BICI PIZZA S.A.S., serialNumber = 9015755282, title = Emisor Factura Electronica - Persona Juridica, OU = Emitido por Andes SCD Ac 26 69 C 03 Torre B Of 701, O = GERENTE, L = MEDELL\C3\8DN, ST = ANTIOQUIA, C = CO
issuer=emailAddress = info@andesscd.com.co, CN = CA ANDES SCD S.A. Clase II v3, OU = Division de certificacion entidad final, O = Andes SCD, L = Bogota D.C., C = CO
-----BEGIN CERTIFICATE-----
MIIH8TCCBdmgAwIBAgIIQxaPJd4YTlMwDQYJKoZIhvcNAQELBQAwgbYxIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMSYwJAYDVQQDEx1DQSBBTkRF
UyBTQ0QgUy5BLiBDbGFzZSBJSSB2MzEwMC4GA1UECxMnRGl2aXNpb24gZGUgY2Vy
dGlmaWNhY2lvbiBlbnRpZGFkIGZpbmFsMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDAS
BgNVBAcTC0JvZ290YSBELkMuMQswCQYDVQQGEwJDTzAeFw0yNDA1MTYwNTAwMDBa
Fw0yNTA1MTYwNDU5MDBaMIIBQDEtMCsGA1UECRMkVFYgNDYgQyA0MiBFU1RFIDc4
OSBDT1JSIFNBTlRBIEVMRU5BMSIwIAYJKoZIhvcNAQkBFhNiaWNpcGl6emFAZ21h
aWwuY29tMRowGAYDVQQDExFCSUNJIFBJWlpBIFMuQS5TLjETMBEGA1UEBRMKOTAx
NTc1NTI4MjE2MDQGA1UEDBMtRW1pc29yIEZhY3R1cmEgRWxlY3Ryb25pY2EgLSBQ
ZXJzb25hIEp1cmlkaWNhMTswOQYDVQQLEzJFbWl0aWRvIHBvciBBbmRlcyBTQ0Qg
QWMgMjYgNjkgQyAwMyBUb3JyZSBCIE9mIDcwMTEQMA4GA1UEChMHR0VSRU5URTES
MBAGA1UEBwwJTUVERUxMw41OMRIwEAYDVQQIEwlBTlRJT1FVSUExCzAJBgNVBAYT
AkNPMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5J1O+4ZedrSBUsb+
9tjNPHI9RGeIJcJl3Wc/208OqMYcCwGLUkrYBgH78E7IayD5/wra04OU57cS1/+/
yBUWYR60oqkaH2/8OXkJMqmjisVM/b58m7zyMw4TAF8N/PbswnrKukFU2acxISwT
Lu36HC4hshWw8bEGP54szvv1xnwqcOAWNBCxcBuc9k1JD+SIiqqPwHKh+6EDIoou
jo0H15w3rAxkHQRvYe6/IrpvH2sqJl1I3dLv0iqy9+d2l891KBA9Yebdw7m/+ufu
+eqs+0zKrwV6QLhRFmceHzEkPMTFepc2COGf80OUNbI6WWnspvDK97D8YG0MeifP
YveIPwIDAQABo4ICdDCCAnAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRA/iZp
RzInMtGsIcgu7M+N1TVo6DBvBggrBgEFBQcBAQRjMGEwNgYIKwYBBQUHMAKGKmh0
dHA6Ly9jZXJ0cy5hbmRlc3NjZC5jb20uY28vQ2xhc2VJSXYzLmNydDAnBggrBgEF
BQcwAYYbaHR0cDovL29jc3AuYW5kZXNzY2QuY29tLmNvMB4GA1UdEQQXMBWBE2Jp
Y2lwaXp6YUBnbWFpbC5jb20wggEjBgNVHSAEggEaMIIBFjCBwQYNKwYBBAGB9EgB
AgYIADCBrzCBrAYIKwYBBQUHAgIwgZ8MgZxMYSB1dGlsaXphY2nDs24gZGUgZXN0
ZSBjZXJ0aWZpY2FkbyBlc3TDoSBzdWpldGEgYSBsYSBQQyBkZSBGYWN0dXJhY2nD
s24gRWxlY3Ryw7NuaWNhIHkgRFBDIGVzdGFibGVjaWRhcyBwb3IgQW5kZXMgU0NE
LiBDw7NkaWdvIGRlIEFjcmVkaXRhY2nDs246IDE2LUVDRC0wMDQwUAYNKwYBBAGB
9EgBAQEKADA/MD0GCCsGAQUFBwIBFjFodHRwczovL3d3dy5hbmRlc3NjZC5jb20u
Y28vZG9jcy9EUENfQW5kZXNTQ0QucGRmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDBDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3JsLmFuZGVzc2NkLmNv
bS5jby9DbGFzZUlJdjMuY3JsMB0GA1UdDgQWBBRKPoh3BJM7SxEL0UXkSne2MyFO
jjAOBgNVHQ8BAf8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIBAA6Zzor3kpJ6vNKv
TAeHaMfmJ/PaghQ1+Lab7Pwk+lsPsMETFu/IpEK5qij2bV54UNnqyLYOZtIbnWTG
qgT7QxQvy+/of/I3zzF+kH4/Lp2TSlHaDEb/airCZ3I2G23M9iaZzSwYuOsOaGwp
4ovkXlYwQ7FVNfIIoAq95m9cBAigb06bRIlVBVTQq44hQFQQG6aSIT7SSPtCwPhB
5CJzG09pmgbxizqN/yxdjWdfW6Av79dh6K4uQT++Vtyp5DuAkmfn0ehayrUbDLkH
9jQFF128U5pnOPfKWf22acXqQBapesUSV/HZUZ3PXoWeHWXcMdz0azxOEunS4+px
fs5UzInRAmEcYwJHqJT3irFz+J2RsZ0WnJHrTGqFoXniQQH8QbCHehDTGN7/v/v1
LQBr5PQBnSEWhmrQ9uFrwPyMMg3yd+L75TaHLZ0MTSVezAG52oM9jBiU5tYXkSio
EfPdIsGlG74BybULGSG2OlTINlblj/lj7pL67V+gY9EGN1zzNKL5sW4YXlXewa6K
dTpLmmzWzI8Cm+tOuuJDSHSwMjn525O+Z/oyKLjdQdyfg1KnZYwNZFpMmCwfP5nd
cm4F7Anzb9HX9ciluAxc6as9TsNoDDTSAGPuUr0QvXqXd9ZCgXdNzdFeiaYknLP5
hT4f5CoO2M8qcG+CH7HnkaOHrSuK
-----END CERTIFICATE-----
Bag Attributes: <No Attributes>
subject=emailAddress = info@andesscd.com.co, CN = ROOT CA ANDES SCD S.A., OU = Division de certificacion, O = Andes SCD, L = Bogota D.C., C = CO
issuer=emailAddress = info@andesscd.com.co, CN = ROOT CA ANDES SCD S.A., OU = Division de certificacion, O = Andes SCD, L = Bogota D.C., C = CO
-----BEGIN CERTIFICATE-----
MIIGKTCCBBGgAwIBAgIILDECIDXRkbIwDQYJKoZIhvcNAQELBQAwgaExIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMR8wHQYDVQQDExZST09UIENB
IEFOREVTIFNDRCBTLkEuMSIwIAYDVQQLExlEaXZpc2lvbiBkZSBjZXJ0aWZpY2Fj
aW9uMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDASBgNVBAcTC0JvZ290YSBELkMuMQsw
CQYDVQQGEwJDTzAeFw0xNjA5MjQxNjUwNTdaFw0zNTA3MDkxNjM2NTlaMIGhMSMw
IQYJKoZIhvcNAQkBFhRpbmZvQGFuZGVzc2NkLmNvbS5jbzEfMB0GA1UEAxMWUk9P
VCBDQSBBTkRFUyBTQ0QgUy5BLjEiMCAGA1UECxMZRGl2aXNpb24gZGUgY2VydGlm
aWNhY2lvbjESMBAGA1UEChMJQW5kZXMgU0NEMRQwEgYDVQQHEwtCb2dvdGEgRC5D
LjELMAkGA1UEBhMCQ08wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCc
svKA1BIq5MzsIQtZsf8QfaI6uzllLZCdnw1jczyTTE+xlygz9+Bv5ynFiWnZThLF
LMC3DSMVhAPBSYQ3qierOF/U7QE84NM0bHCKVK33MiA6ruu5804HsdVJuLqP6YCb
S+nH1Ygnw0q2fX/H094wBWb2Jr2oVe+ydDDjy1RYjZHXiZVekwZTb6oY+f2rE25w
nTNj1/3B4JfYPBbIDz6aRXPyeSBtI/RVKZBjD4NBXd4mCdXCE6/puOdvAbBWMhq7
9wLCQIgtU21nne6/YaEHISah2S5KTC4P1nS+1nHvxMxdC1cszv7mheP4/nszfAgU
LEeI6eL+lvBy+vjssT8dv+utofmj76QQdn2MkTb1paZaan4+3a+c1PsjTO7yZ86k
KDQDxfnYaGF9b7wOgIDvacqJlREpmlvT2DN+4YAp2RLnuK1+ws6dnS+e1t73qbnZ
Q7lntelAjFtKms3YIpXfs5sWUlPza1ozxEpmkSM4ZeuPKI6WF7YJuEo5s11Cu+Rw
CtBOIVjYZWMUipxwfZcT5L3vZYWPSkboDlWZGZFX4mu2srzfv2ac4Ij5M8/hTD/n
5WT2WzWcLId0xJuY9dZT/6XIrNZ6ZEFweEXxM7zHp3SrMfNSJZdG22d28gTmSyVg
FPCVop+bdW7fMo6rmCdfveih5LkBRbxE6SPUnztVcwIDAQABo2MwYTAdBgNVHQ4E
FgQU3RGSWqlsBAz+NKJYv+sd+98IsG8wDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
GDAWgBTdEZJaqWwEDP40oli/6x373wiwbzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI
hvcNAQELBQADggIBAFpZLihgW/5L2ZpIoh3Jo+W281od5UoIMmnJ7zjnBsEj37d5
iIaQjBNKMQdiEfOHkHxb5TaBnil6nnlt5KyS+H+fmMThIKYOrmkS1kCtGF8Yn3JC
qLWuA3Nhq+3xerbhrrZrjKhdX/b+GWWBzRgeqguvAMnRDk87oWRWYFjFKt0u2pdT
DYzqORiAKgy06h7NTpfLfN5O5/BHpfoz7nLVrVDLFzZJH16HPBdvbcX7p2MjW+fl
WAO47E8CYEbK4wOJfK8L2s6lN3Zruq3RL/a9HSPGoI/Ftuu8YFDsU61k+JCWtFWX
lwO+MjkIOIygabsWqUAY9R4DVnCx2soXXN2vPUakHiBWC1V/s3xv2M7EgoyQfST7
fFoZdFt7r8v8lUljhjTPPrP8sCJ1FIn4w23hr5NjqQo8T1wo89nlupPFZ8aHqBVU
HsyQVQbuDsD4eEO0I37wBqd3fn7UWfZDec73kS8P0PMHMH8pCorHKkpeJtLDw2QP
FgYicFZ8u6fYjOYNikmQQe9c8FRJgwSYMeXQb0hf1dlC/WpJGO00Efwor99lS2cl
DegHOF5dv3wl7b+xvdzhUs3yqYiq9Qa/+N7oIPkgzhlltDabPRhIFLnJsmxfa/Kt
lF/MZ1Va+UjR+JQ1unNE/CTn6L5A4cavpOoI72N2elJRw+QP8icwGLApBp/W
-----END CERTIFICATE-----
Bag Attributes: <No Attributes>
subject=emailAddress = info@andesscd.com.co, CN = CA ANDES SCD S.A. Clase II v2, OU = Division de certificacion entidad final, O = Andes SCD, L = Bogota D.C., C = CO
issuer=emailAddress = info@andesscd.com.co, CN = ROOT CA ANDES SCD S.A., OU = Division de certificacion, O = Andes SCD, L = Bogota D.C., C = CO
-----BEGIN CERTIFICATE-----
MIIGrzCCBJegAwIBAgIIRLa0wz3hC2gwDQYJKoZIhvcNAQELBQAwgaExIzAhBgkq
hkiG9w0BCQEWFGluZm9AYW5kZXNzY2QuY29tLmNvMR8wHQYDVQQDExZST09UIENB
IEFOREVTIFNDRCBTLkEuMSIwIAYDVQQLExlEaXZpc2lvbiBkZSBjZXJ0aWZpY2Fj
aW9uMRIwEAYDVQQKEwlBbmRlcyBTQ0QxFDASBgNVBAcTC0JvZ290YSBELkMuMQsw
CQYDVQQGEwJDTzAeFw0xOTA4MDYxNjE0NTNaFw0yNTExMTUxNzM0MzBaMIG2MSMw
IQYJKoZIhvcNAQkBFhRpbmZvQGFuZGVzc2NkLmNvbS5jbzEmMCQGA1UEAxMdQ0Eg
QU5ERVMgU0NEIFMuQS4gQ2xhc2UgSUkgdjIxMDAuBgNVBAsTJ0RpdmlzaW9uIGRl
IGNlcnRpZmljYWNpb24gZW50aWRhZCBmaW5hbDESMBAGA1UEChMJQW5kZXMgU0NE
MRQwEgYDVQQHEwtCb2dvdGEgRC5DLjELMAkGA1UEBhMCQ08wggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDOWKOiAg0IgACNXNyDCoAyaXo514UwF9oCqMjg
xAOhOe+g3lUDWTXtV6l8O1VXHQa86KZwqgT0mfw0HqePQYKC93n9+timuuT1FuAJ
Z9TnF/f6DQDzFQWsRVLdqV/hzNQ/rbOvPX8WN4Z97TyJaUtlxbENOKgOlzsW8hGe
eVrjDz9fk1e0TPem/CswuY+7Z/Pi4/EVz28UoTlfEMOg84ZutTomHOskIp6S86lw
X+ts15yChEf/dXBVkAMkBUpFc39iZWhhtewZo56Q8u9an5ZBQtOorB33LKFKn2P1
V95Cf2XPZqWqLrP1wRQn0XWB1uDF+eAyQzShGA0jrZGgdlaFW8J7bx8+YntJHqFi
jI3UVwqCyb+TGO9RRcTRp3OfAwAKCbh56gHacvF5KEGj36d0zofw808WBpnhrnvQ
ofPp48mowpbByzuIqVtX0utVXgzg4QdNBSQ6uhDnjB8ETzwuJcUokC1lXwBMSDa0
f5psC1yflyqdTOpSjZC8pMxwGUXhhvkfxDBaSsueExLpcKlolVojQRWm6C2oiumS
2lgeL4ydh8DFhi2llMLVCLZQLffG9ziTZtMd97n55NvXrVa4XbDIf4i5/XFrYgFy
R/rBZduYWDqxcwFnUP6HhTfuael8qNxKA3UWmxFK3ixNN5kaC/ErX3g4fC+KGqh4
LeljKQIDAQABo4HTMIHQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU3RGS
WqlsBAz+NKJYv+sd+98IsG8wNwYIKwYBBQUHAQEEKzApMCcGCCsGAQUFBzABhhto
dHRwOi8vb2NzcC5hbmRlc3NjZC5jb20uY28wNAYDVR0fBC0wKzApoCegJYYjaHR0
cDovL2NybC5hbmRlc3NjZC5jb20uY28vUmFpei5jcmwwHQYDVR0OBBYEFDpXUNB3
Gz7Wi+r/l5nX/4QCSCkPMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC
AgEALA0nz8TvtoMZ70MBN7FFWZMqTXvkXY7mcnWImpTFLGzny+i119qODuItEf5G
977TzoTCxZC8DlPL1d88+YxAfYFRLXdb1HcDrNuWKMyoiFGH3vhNl+Ef//4zed98
DV5jtoaCofFt/JzPz2iA7Al2LZ1m0vAF0z12LAytHqSfob+5mdgWkBnLae0vaWU+
eGnoTkCYsasxca9oSKHDajqTvTa5WogibUaffcnb9LYaP3nkam9+favCyHHQW2lp
2xPPcP1gisvp99ga9Cwft7d/QHXNjseop2TwDoUHViUImaSpLz+2kWhkr2LOSTpL
43rWQhVQ/Y6SyYd3YWEBxJWwFvVlHvQ2wKwiE/z7kHnCP9ztPhS0umC1BMGq3pC4
fifzvx9218JjDIOVrKzwonCTj0vwQZ4FmTttAr0G+R5TrqqvgF1t/RTfhWIxhUTk
TR3xRpR/5mBtqrSmyojGtdVkwHq2dmmDIkjefSbxKDkkAkhDRt5CDeV913B3MvYm
UiMoOJmLyapIZjIko1HxzXBv3dPc5LYSKpG8rd9SjJtA7W8LQzAVUq8O32nO7edY
IvTrC11fLkdToGOxLmfTzZ+facUESteELfxchZoda6I9qH5Xa7Xka2M2mmkj0SGU
46KREw+DtpoUkpAQ7rLaZ+4stT0j1Z8soirB+EUk8oBLiTI=
-----END CERTIFICATE-----

View File

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

View File

@@ -13,36 +13,19 @@ 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',
'cryptography==3.3.2',
'pyOpenSSL==20.0.1',
'xmlsig==0.1.7',
'xades==0.2.2',
'xmlsec==1.3.12',
# usamos esta dependencia en runtime
# para forzar uso de policy_id de archivo local
'mock>=2.0.0',
'xmlschema>=1.8']
"""
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' ]
setup_requirements = ['pytest-runner', ]
@@ -57,10 +40,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={
@@ -84,10 +67,6 @@ setup(
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/bit4bit/facho',
<<<<<<< HEAD
version='0.2.0',
=======
version='0.2.1',
>>>>>>> morfo
zip_safe=False,
)

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

@@ -1,21 +0,0 @@
#!/usr/bin/env python
# -*- 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.
import pytest
from facho.fe import form_xml
from fixtures import simple_invoice
simple_invoice = simple_invoice
def test_application_response(simple_invoice):
doc = form_xml.ApplicationResponse(simple_invoice)
xml = doc.toFachoXML()
with open("application_response.xml", "w") as fh:
fh.write(xml.tostring())
# raise Exception(xml.tostring())
# assert xml.get_element_text(
# './apr:ApplicationResponse')

View File

@@ -2,124 +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
from datetime import datetime
import helpers
from fixtures import simple_invoice
simple_invoice = simple_invoice
def test_xml_with_required_elements(simple_invoice):
xml_header = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
DIANInvoiceXML = form_xml.DIANInvoiceXML(
simple_invoice)
doc = form_xml.AttachedDocument(
simple_invoice,
DIANInvoiceXML,
id='123')
def test_xml_with_required_elements():
doc = form_xml.AttachedDocument(id='123')
xml = doc.toFachoXML()
DIANInvoiceXML = form_xml.DIANInvoiceXML(
simple_invoice, 'Invoice').attach_invoice
ApplicationResponse = xml_header + form_xml.ApplicationResponse(simple_invoice).toFachoXML().tostring()
attached_document = xml_header + DIANInvoiceXML.tostring()
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:UBLVersionID') == 'UBL 2.1'
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:CustomizationID') == 'Documentos adjuntos'
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:ProfileID') == 'Factura Electrónica de Venta'
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:ProfileExecutionID') == '1'
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:ID') == '123'
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:IssueDate') == str(datetime.today().date())
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:IssueTime') == datetime.today(
).time().strftime(
'%H:%M:%S-05:00')
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:DocumentType'
) == 'Contenedor de Factura Electrónica'
assert xml.get_element_text(
'/atd:AttachedDocument/cbc:ParentDocumentID'
) == 'ABC123'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cbc:RegistrationName'
) == 'facho-supplier'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cbc:CompanyID'
) == '123'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cbc:TaxLevelCode'
) == 'ZZ'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID'
) == '01'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name'
) == 'IVA'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cbc:RegistrationName'
) == 'facho-customer'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cbc:CompanyID'
) == '321'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cbc:TaxLevelCode'
) == 'ZZ'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID'
) == '01'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name'
) == 'IVA'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:Attachment/cac:ExternalReference/cbc:MimeCode'
) == "text/xml"
assert xml.get_element_text(
'/atd:AttachedDocument/cac:Attachment/cac:ExternalReference/cbc:EncodingCode'
) == "UTF-8"
assert xml.get_element_text(
'/atd:AttachedDocument/cac:Attachment/cac:ExternalReference/cbc:Description'
) == "<![CDATA[{}]]>".format(attached_document)
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cbc:LineID'
) == '1'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:ID'
) == '1234'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:UUID'
) == '1234'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:IssueDate'
) == '2024-11-28'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:DocumentType'
) == 'ApplicationResponse'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:MimeCode'
) == 'text/xml'
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:EncodingCode'
) == "UTF-8"
assert xml.get_element_text(
'/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:Description'
) == "<![CDATA[{}]]>".format(ApplicationResponse)
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,17 +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)

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
@@ -65,11 +42,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 +66,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 +93,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 +133,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 +200,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 +235,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 +251,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

@@ -1,601 +0,0 @@
#!/usr/bin/env python
# -*- 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.
"""Tests for `facho` package."""
import pytest
import facho.model
import facho.model.fields as fields
def test_model_to_element():
class Person(facho.model.Model):
__name__ = 'Person'
person = Person()
assert "<Person/>" == person.to_xml()
def test_model_to_element_with_attribute():
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Attribute('id')
person = Person()
person.id = 33
personb = Person()
personb.id = 44
assert "<Person id=\"33\"/>" == person.to_xml()
assert "<Person id=\"44\"/>" == personb.to_xml()
def test_model_to_element_with_attribute_as_element():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID)
person = Person()
person.id = 33
assert "<Person><ID>33</ID></Person>" == person.to_xml()
def test_many2one_with_custom_attributes():
class TaxAmount(facho.model.Model):
__name__ = 'TaxAmount'
currencyID = fields.Attribute('currencyID')
class TaxTotal(facho.model.Model):
__name__ = 'TaxTotal'
amount = fields.Many2One(TaxAmount)
tax_total = TaxTotal()
tax_total.amount = 3333
tax_total.amount.currencyID = 'COP'
assert '<TaxTotal><TaxAmount currencyID="COP">3333</TaxAmount></TaxTotal>' == tax_total.to_xml()
def test_many2one_with_custom_setter():
class PhysicalLocation(facho.model.Model):
__name__ = 'PhysicalLocation'
id = fields.Attribute('ID')
class Party(facho.model.Model):
__name__ = 'Party'
location = fields.Many2One(PhysicalLocation, setter='location_setter')
def location_setter(self, field, value):
field.id = value
party = Party()
party.location = 99
assert '<Party><PhysicalLocation ID="99"/></Party>' == party.to_xml()
def test_many2one_always_create():
class Name(facho.model.Model):
__name__ = 'Name'
class Person(facho.model.Model):
__name__ = 'Person'
name = fields.Many2One(Name, default='facho')
person = Person()
assert '<Person><Name>facho</Name></Person>' == person.to_xml()
def test_many2one_nested_always_create():
class Name(facho.model.Model):
__name__ = 'Name'
class Contact(facho.model.Model):
__name__ = 'Contact'
name = fields.Many2One(Name, default='facho')
class Person(facho.model.Model):
__name__ = 'Person'
contact = fields.Many2One(Contact, create=True)
person = Person()
assert '<Person><Contact><Name>facho</Name></Contact></Person>' == person.to_xml()
def test_many2one_auto_create():
class TaxAmount(facho.model.Model):
__name__ = 'TaxAmount'
currencyID = fields.Attribute('currencyID')
class TaxTotal(facho.model.Model):
__name__ = 'TaxTotal'
amount = fields.Many2One(TaxAmount)
tax_total = TaxTotal()
tax_total.amount.currencyID = 'COP'
tax_total.amount = 3333
assert '<TaxTotal><TaxAmount currencyID="COP">3333</TaxAmount></TaxTotal>' == tax_total.to_xml()
def test_field_model():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID)
person = Person()
person.id = ID()
person.id = 33
assert "<Person><ID>33</ID></Person>" == person.to_xml()
def test_field_multiple_model():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID)
id2 = fields.Many2One(ID)
person = Person()
person.id = 33
person.id2 = 44
assert "<Person><ID>33</ID><ID>44</ID></Person>" == person.to_xml()
def test_field_model_failed_initialization():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID)
person = Person()
person.id = 33
assert "<Person><ID>33</ID></Person>" == person.to_xml()
def test_field_model_with_custom_name():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID, name='DID')
person = Person()
person.id = 33
assert "<Person><DID>33</DID></Person>" == person.to_xml()
def test_field_model_default_initialization_with_attributes():
class ID(facho.model.Model):
__name__ = 'ID'
reference = fields.Attribute('REFERENCE')
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID)
person = Person()
person.id = 33
person.id.reference = 'haber'
assert '<Person><ID REFERENCE="haber">33</ID></Person>' == person.to_xml()
def test_model_with_xml_namespace():
class Person(facho.model.Model):
__name__ = 'Person'
__namespace__ = {
'facho': 'http://lib.facho.cyou'
}
person = Person()
assert '<Person xmlns:facho="http://lib.facho.cyou"/>'
def test_model_with_xml_namespace_nested():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
__namespace__ = {
'facho': 'http://lib.facho.cyou'
}
id = fields.Many2One(ID, namespace='facho')
person = Person()
person.id = 33
assert '<Person xmlns:facho="http://lib.facho.cyou"><facho:ID>33</facho:ID></Person>' == person.to_xml()
def test_model_with_xml_namespace_nested_nested():
class ID(facho.model.Model):
__name__ = 'ID'
class Party(facho.model.Model):
__name__ = 'Party'
id = fields.Many2One(ID, namespace='party')
def __default_set__(self, value):
self.id = value
class Person(facho.model.Model):
__name__ = 'Person'
__namespace__ = {
'person': 'http://lib.facho.cyou',
'party': 'http://lib.facho.cyou'
}
id = fields.Many2One(Party, namespace='person')
person = Person()
person.id = 33
assert '<Person xmlns:person="http://lib.facho.cyou" xmlns:party="http://lib.facho.cyou"><person:Party><party:ID>33</party:ID></person:Party></Person>' == person.to_xml()
def test_model_with_xml_namespace_nested_one_many():
class Name(facho.model.Model):
__name__ = 'Name'
class Contact(facho.model.Model):
__name__ = 'Contact'
name = fields.Many2One(Name, namespace='contact')
class Person(facho.model.Model):
__name__ = 'Person'
__namespace__ = {
'facho': 'http://lib.facho.cyou',
'contact': 'http://lib.facho.cyou'
}
contacts = fields.One2Many(Contact, namespace='facho')
person = Person()
contact = person.contacts.create()
contact.name = 'contact1'
contact = person.contacts.create()
contact.name = 'contact2'
assert '<Person xmlns:facho="http://lib.facho.cyou" xmlns:contact="http://lib.facho.cyou"><facho:Contact><contact:Name>contact1</contact:Name></facho:Contact><facho:Contact><contact:Name>contact2</contact:Name></facho:Contact></Person>' == person.to_xml()
def test_field_model_with_namespace():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
__namespace__ = {
"facho": "http://lib.facho.cyou"
}
id = fields.Many2One(ID, namespace="facho")
person = Person()
person.id = 33
assert '<Person xmlns:facho="http://lib.facho.cyou"><facho:ID>33</facho:ID></Person>' == person.to_xml()
def test_field_hook_before_xml():
class Hash(facho.model.Model):
__name__ = 'Hash'
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Many2One(Hash)
def __before_xml__(self):
self.hash = "calculate"
person = Person()
assert "<Person><Hash>calculate</Hash></Person>" == person.to_xml()
def test_field_function_with_attribute():
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Function(fields.Attribute('hash'), getter='get_hash')
def get_hash(self, name, field):
return 'calculate'
person = Person()
assert '<Person hash="calculate"/>'
def test_field_function_with_model():
class Hash(facho.model.Model):
__name__ = 'Hash'
id = fields.Attribute('id')
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Function(fields.Many2One(Hash), getter='get_hash')
def get_hash(self, name, field):
field.id = 'calculate'
person = Person()
assert person.hash.id == 'calculate'
assert '<Person/>'
def test_field_function_setter():
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Attribute('hash')
password = fields.Virtual(setter='set_hash')
def set_hash(self, name, value):
self.hash = "%s+2" % (value)
person = Person()
person.password = 'calculate'
assert '<Person hash="calculate+2"/>' == person.to_xml()
def test_field_function_only_setter():
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Attribute('hash')
password = fields.Virtual(setter='set_hash')
def set_hash(self, name, value):
self.hash = "%s+2" % (value)
person = Person()
person.password = 'calculate'
assert '<Person hash="calculate+2"/>' == person.to_xml()
def test_model_set_default_setter():
class Hash(facho.model.Model):
__name__ = 'Hash'
def __default_set__(self, value):
return "%s+3" % (value)
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Many2One(Hash)
person = Person()
person.hash = 'hola'
assert '<Person><Hash>hola+3</Hash></Person>' == person.to_xml()
def test_field_virtual():
class Person(facho.model.Model):
__name__ = 'Person'
age = fields.Virtual()
person = Person()
person.age = 55
assert person.age == 55
assert "<Person/>" == person.to_xml()
def test_field_inserted_default_attribute():
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Attribute('hash', default='calculate')
person = Person()
assert '<Person hash="calculate"/>' == person.to_xml()
def test_field_function_inserted_default_attribute():
class Person(facho.model.Model):
__name__ = 'Person'
hash = fields.Function(fields.Attribute('hash'), default='calculate')
person = Person()
assert '<Person hash="calculate"/>' == person.to_xml()
def test_field_inserted_default_many2one():
class ID(facho.model.Model):
__name__ = 'ID'
key = fields.Attribute('key')
def __default_set__(self, value):
self.key = value
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID, default="oe")
person = Person()
assert '<Person><ID key="oe"/></Person>' == person.to_xml()
def test_field_inserted_default_nested_many2one():
class ID(facho.model.Model):
__name__ = 'ID'
class Person(facho.model.Model):
__name__ = 'Person'
id = fields.Many2One(ID, default="ole")
person = Person()
assert '<Person><ID>ole</ID></Person>' == person.to_xml()
def test_model_on_change_field():
class Hash(facho.model.Model):
__name__ = 'Hash'
class Person(facho.model.Model):
__name__ = 'Person'
react = fields.Attribute('react')
hash = fields.Many2One(Hash)
@fields.on_change(['hash'])
def on_change_react(self, name, value):
assert name == 'hash'
self.react = "%s+4" % (value)
person = Person()
person.hash = 'hola'
assert '<Person react="hola+4"><Hash>hola</Hash></Person>' == person.to_xml()
def test_model_on_change_field_attribute():
class Person(facho.model.Model):
__name__ = 'Person'
react = fields.Attribute('react')
hash = fields.Attribute('Hash')
@fields.on_change(['hash'])
def on_react(self, name, value):
assert name == 'hash'
self.react = "%s+4" % (value)
person = Person()
person.hash = 'hola'
assert '<Person react="hola+4" Hash="hola"/>' == person.to_xml()
def test_model_one2many():
class Line(facho.model.Model):
__name__ = 'Line'
quantity = fields.Attribute('quantity')
class Invoice(facho.model.Model):
__name__ = 'Invoice'
lines = fields.One2Many(Line)
invoice = Invoice()
line = invoice.lines.create()
line.quantity = 3
line = invoice.lines.create()
line.quantity = 5
assert '<Invoice><Line quantity="3"/><Line quantity="5"/></Invoice>' == invoice.to_xml()
def test_model_one2many_with_on_changes():
class Line(facho.model.Model):
__name__ = 'Line'
quantity = fields.Attribute('quantity')
class Invoice(facho.model.Model):
__name__ = 'Invoice'
lines = fields.One2Many(Line)
count = fields.Attribute('count', default=0)
@fields.on_change(['lines'])
def refresh_count(self, name, value):
self.count = len(self.lines)
invoice = Invoice()
line = invoice.lines.create()
line.quantity = 3
line = invoice.lines.create()
line.quantity = 5
assert len(invoice.lines) == 2
assert '<Invoice count="2"><Line quantity="3"/><Line quantity="5"/></Invoice>' == invoice.to_xml()
def test_model_one2many_as_list():
class Line(facho.model.Model):
__name__ = 'Line'
quantity = fields.Attribute('quantity')
class Invoice(facho.model.Model):
__name__ = 'Invoice'
lines = fields.One2Many(Line)
invoice = Invoice()
line = invoice.lines.create()
line.quantity = 3
line = invoice.lines.create()
line.quantity = 5
lines = list(invoice.lines)
assert len(list(invoice.lines)) == 2
for line in lines:
assert isinstance(line, Line)
assert '<Invoice><Line quantity="3"/><Line quantity="5"/></Invoice>' == invoice.to_xml()
def test_model_attributes_order():
class Line(facho.model.Model):
__name__ = 'Line'
quantity = fields.Attribute('quantity')
class Invoice(facho.model.Model):
__name__ = 'Invoice'
line1 = fields.Many2One(Line, name='Line1')
line2 = fields.Many2One(Line, name='Line2')
line3 = fields.Many2One(Line, name='Line3')
invoice = Invoice()
invoice.line2.quantity = 2
invoice.line3.quantity = 3
invoice.line1.quantity = 1
assert '<Invoice><Line1 quantity="1"/><Line2 quantity="2"/><Line3 quantity="3"/></Invoice>' == invoice.to_xml()
def test_field_amount():
class Line(facho.model.Model):
__name__ = 'Line'
amount = fields.Amount(name='Amount', precision=1)
amount_as_attribute = fields.Attribute('amount')
@fields.on_change(['amount'])
def on_amount(self, name, value):
self.amount_as_attribute = self.amount
line = Line()
line.amount = 33
assert '<Line amount="33.0"/>' == line.to_xml()
def test_model_setup():
class Line(facho.model.Model):
__name__ = 'Line'
amount = fields.Attribute(name='amount')
def __setup__(self):
self.amount = 23
line = Line()
assert '<Line amount="23"/>' == line.to_xml()

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env python
# -*- 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.
"""Nuevo esquema para modelar segun decreto"""
from datetime import datetime
import pytest
from lxml import etree
import facho.fe.model as model
import facho.fe.form as form
from facho import fe
import helpers
def simple_invoice():
invoice = model.Invoice()
invoice.dian.software_security_code = '12345'
invoice.dian.software_provider.provider_id = 'provider-id'
invoice.dian.software_provider.software_id = 'facho'
invoice.dian.control.prefix = 'SETP'
invoice.dian.control.from_range = '1000'
invoice.dian.control.to_range = '1000'
invoice.id = '323200000129'
invoice.issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
invoice.supplier.party.id = '700085371'
invoice.customer.party.id = '800199436'
line = invoice.lines.create()
line.add_tax(model.Taxes.Iva(19.0))
# TODO(bit4bit) acoplamiento temporal
# se debe crear primero el subotatl
# para poder calcularse al cambiar el precio
line.quantity = 1
line.price = 1_500_000
return invoice
def test_simple_invoice_cufe():
token = '693ff6f2a553c3646a063436fd4dd9ded0311471'
environment = fe.AMBIENTE_PRODUCCION
invoice = simple_invoice()
assert invoice.cufe(token, environment) == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'
def test_simple_invoice_sign_dian(monkeypatch):
invoice = simple_invoice()
xmlstring = invoice.to_xml()
p12_data = open('./tests/example.p12', 'rb').read()
signer = fe.DianXMLExtensionSigner.from_bytes(p12_data)
with monkeypatch.context() as m:
helpers.mock_urlopen(m)
xmlsigned = signer.sign_xml_string(xmlstring)
assert "Signature" in xmlsigned
def test_dian_extension_authorization_provider():
invoice = simple_invoice()
xml = fe.FeXML.from_string(invoice.to_xml())
provider_id = xml.get_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID')
assert provider_id.attrib['schemeID'] == '4'
assert provider_id.attrib['schemeName'] == '31'
assert provider_id.attrib['schemeAgencyName'] == 'CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)'
assert provider_id.attrib['schemeAgencyID'] == '195'
assert provider_id.text == '800197268'
def test_invoicesimple_xml_signed_using_fexml(monkeypatch):
invoice = simple_invoice()
xml = fe.FeXML.from_string(invoice.to_xml())
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
print(xml.tostring())
with monkeypatch.context() as m:
import helpers
helpers.mock_urlopen(m)
xml.add_extension(signer)
elem = xml.get_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent/ds:Signature')
assert elem.text is not None
def test_invoice_supplier_party():
invoice = simple_invoice()
invoice.supplier.party.name = 'superfacho'
invoice.supplier.party.tax_scheme.registration_name = 'legal-superfacho'
invoice.supplier.party.contact.email = 'superfacho@etrivial.net'
xml = fe.FeXML.from_string(invoice.to_xml())
name = xml.get_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name')
assert name.text == 'superfacho'
name = xml.get_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName')
assert name.text == 'legal-superfacho'
name = xml.get_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicEmail')
assert name.text == 'superfacho@etrivial.net'
def test_invoice_customer_party():
invoice = simple_invoice()
invoice.customer.party.name = 'superfacho-customer'
invoice.customer.party.tax_scheme.registration_name = 'legal-superfacho-customer'
invoice.customer.party.contact.email = 'superfacho@etrivial.net'
xml = fe.FeXML.from_string(invoice.to_xml())
name = xml.get_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name')
assert name.text == 'superfacho-customer'
name = xml.get_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName')
assert name.text == 'legal-superfacho-customer'
name = xml.get_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicEmail')
assert name.text == 'superfacho@etrivial.net'

File diff suppressed because it is too large Load Diff

View File

@@ -1,236 +0,0 @@
#!/usr/bin/env python
# -*- 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.
"""Tests for `facho` package."""
# import re
# import pytest
# from facho import fe
# import helpers
# def atest_nomina_ajuste_reemplazar():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# 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'
# ))
# 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()
# assert xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/InformacionGeneral', 'TipoXML') == '103'
# 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_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00'
# 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'
# ))
# 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()
# 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('/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()
# 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
# 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_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00'
# 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'
# ))
# 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()
# 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'

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 != ''

10
tox.ini
View File

@@ -1,12 +1,12 @@
[tox]
envlist = py39, py310, py311, py312, flake8
envlist = py27, py34, py35, py36, flake8
[travis]
python =
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.6: py36
3.5: py35
3.4: py34
2.7: py27
[testenv:flake8]
basepython = python