Compare commits

..

157 Commits

Author SHA1 Message Date
c49e67b8a6 Fix: Test incluir ApplicationResponse 2025-01-01 18:40:05 -05:00
7672ae4b7f Fix: Delete escribir archivo para ver salida 2025-01-01 18:39:22 -05:00
25c5fda3f0 Fix: Delete archivo para ver salida 2025-01-01 18:37:09 -05:00
a758b8678b Feat: Se agrega ApplicationResponse a AttachedDocument 2025-01-01 16:56:15 -05:00
3a385c63e3 Feat: Attachment, ParentDocumentLine 2024-12-30 19:00:51 -05:00
9b33b4486c Fix: Se elimina simple_invoice arg 2024-12-30 11:01:22 -05:00
612aae1f86 Fix: Se reemplazan datos de prueba por objeto Invoice issue #4 2024-12-27 21:04:35 -05:00
4fe82daac6 AttachedDocument(WIP): Se escribe envoltorio AttachedDocument issue #4 2024-12-27 18:54:56 -05:00
e237d1b45f Fix: Se descomenta test AttachedDocument 2024-12-27 00:10:03 -05:00
8cc6146be2 Merge remote-tracking branch 'origin/documento_soporte' 2024-12-26 23:20:16 -05:00
f98ab98c9c Fix: Se agrega dateutil 2024-08-28 16:48:41 -05:00
d04596ed3a Fix: SigningTime tz='America/Bogota' 2024-08-28 16:35:34 -05:00
9297be22e0 Fix: UPDATE Dependencies 2024-08-28 13:10:49 -05:00
cosmos
30773e042b Add test signartime - timezone America/Bogota 2024-08-23 11:03:38 -05:00
cosmos
c919d8e36c Add timestamp 2024-08-22 09:41:37 -05:00
fb44498e53 Fix: Formateo PEP8 2024-08-14 12:50:53 -05:00
9f7349ccee file(requirements_dev): Pruebas Tox 2024-08-07 00:29:48 -05:00
9cc41e1c5d files: cude.txt, cufe.txt 2024-08-07 00:17:58 -05:00
fc75126ca0 FIX: Formateo PEP8, Test OK 2024-08-07 00:17:16 -05:00
d061077b30 fix(WIP): Reemplazando OpenSsl.crypto,pkcs12 2024-08-06 16:41:14 -05:00
a3d2176068 update(test_data): Anexo 1.9 2024-08-06 15:29:47 -05:00
98677bc162 update(TipoDocumento): Anexo 1.9 2024 2024-08-06 15:25:46 -05:00
398d27d049 Fix: Se importan fixtures 2024-08-06 15:21:26 -05:00
8765a3d2c8 Fix(form_xml/invoice): Se agrega defaultdict 2024-08-06 14:38:36 -05:00
1935ed3048 style(test_form.py): Formateo PEP8 se agrega withholding 2024-08-06 14:33:21 -05:00
097cf97fc3 style(test_form_xml): Formateado PEP8 2024-08-06 14:09:52 -05:00
de99633211 Fix: Formateado PEP8 se elimina responsabilidad O-07 segun anexo 1.9 2024-08-06 14:02:40 -05:00
028cf8b687 Merge pull request 'FachoStyle' (#3) from FachoStyle into MigrationPython312Test
Reviewed-on: #3
2024-08-06 13:35:10 -05:00
e7a3976b14 style(test_query): Formateado PEP8 2024-08-06 13:30:34 -05:00
f08954ee43 style(__init__): Formateado PEP8 2024-08-06 13:28:59 -05:00
a0321020c7 Fix(TipoResponsabilidad-2.1.gc): UPDATE Anexo 1.9 2024 2024-08-06 13:27:48 -05:00
dde24b9739 Fix: Dependencies 2024-06-12 10:06:59 -05:00
6e5d358c73 Fix: Se actualizan dependencias para python3.12 y primeros pasos con pruebas 2024-06-11 15:59:44 -05:00
c12d985f76 Fix: Se añade condición para Period in invoice.py 2024-05-17 14:01:28 -05:00
9c126d961b Fix: Se implementa estrategía para CreditNote 2024-05-15 16:02:56 -05:00
95f16b2842 Fix: Se actualiza DockerFile Con base en la rama Master 2024-05-15 16:01:25 -05:00
8f327f7abc feat: UPDATE Habilitacion RUSTIK 2024-03-12 17:05:55 -05:00
b7c9f2b201 revert 7f974b7077
revert feat: Se actualizan dependencias para python3.11 issue #1
2023-12-05 09:09:40 -05:00
7f974b7077 feat: Se actualizan dependencias para python3.11 issue #1 2023-12-05 09:15:48 -05:00
78477de2c2 Se Cambia tipo de impuesto en linea de factura 2023-10-24 16:25:58 -05:00
sinergia
75b41379c4 add withholding tax to support document
FossilOrigin-Name: 203ecd7e08ef0c050f27059c09af894600a45cbed73b11710f4e598362a414b4
2023-03-30 19:47:50 +00:00
sinergia
d26cc2bef7 add RTE to invoice
FossilOrigin-Name: 8d9aa1694a61e0e358e4cc8c5ecb1b3544d18e353535491f5b8a576c5b5fe446
2023-02-19 22:32:36 +00:00
sinergia
1abf34d4f0 actualización de responsabilidades anexo 1.8
FossilOrigin-Name: f872310df40172344c5de9957e0db5800a34e2736e087878f785253e9c11540a
2022-10-25 19:15:15 +00:00
root
3862b3e934 Update files gc taxes
FossilOrigin-Name: 9bceb0c3494ecda7acffe86ebd9b4705078db1028fe62686c8f9010c4850959f
2022-09-07 17:11:39 +00:00
sinergia
5cf929cca9 supportDocument add files
FossilOrigin-Name: 13f3285cfe89ea2859463326536c23f60862112d70816d1c792e1e074edf6785
2022-08-31 19:51:38 +00:00
sinergia
7e51726a0d SupportDocumentCreditNote
FossilOrigin-Name: 7ac7f5bbd22e5ac381c21adf358b021182eac84ebfe38624a59355c79075d70b
2022-08-31 05:51:56 +00:00
cosmos
f05eb61d6e Add DIANXMLExtencionCUDS in __ini__.py
FossilOrigin-Name: a8b2f04c0805336c13516f63f1bbcac6490e7704019eb55caf90ddca548a0cd3
2022-08-28 22:10:17 +00:00
cosmos
5a045ccdef support document updates DSAZ13 DSAZ14
FossilOrigin-Name: 18a1849b96425974b7e53bc236a9484e7c064e960b90844675eb4fe02cf6ad8a
2022-08-28 16:42:54 +00:00
sinergia
bd0fe70f33 xml support document
FossilOrigin-Name: 4d1f4e81f2527a0c4ba735f4f5edc8bc8dfebff2365e8e2ee91d4cd435acecf8
2022-08-27 22:43:16 +00:00
sinergia
7c4c9648fe add new module support_document
FossilOrigin-Name: df52f8e96f61cf3fc6698ee840e64cd412cf4e2c899890def50f3bf4f48a2bb3
2022-08-27 17:00:40 +00:00
alnus
5eecae0740 Create new branch named "documento_soporte"
FossilOrigin-Name: 9a8d5c9f6240da5b4aaa46cea4305e0b673644bca7f6a42cbbfd9842e3092b60
2022-08-27 16:46:55 +00:00
bit4bit
a59df60fc2 se adiciona prueba para tipo xml ajusts en nomina de ajuste
FossilOrigin-Name: 8cb6aa07cdafad91fc13f0d3a4454ddcd26c4ce40feb7cde43a413c3bddf409f
2022-05-30 19:50:47 +00:00
bit4bit
19c5a5bca6 se usa __eq__ en vez de same
FossilOrigin-Name: 05cda833815fbceb23ea6356c71daa37e3cc0d53300c726b0ffacae63bf4ab41
2022-05-30 19:28:00 +00:00
bit4bit
c50f1df1e7 se usa __eq__ en vez de same
FossilOrigin-Name: 96875a241ba61f2072f531e8845ae7dd981c9739c5d322555035243214204e4f
2022-05-30 19:26:44 +00:00
bit4bit
2a1f3b6b43 se actualiza CONTRIBUTING.md
FossilOrigin-Name: a814f92dd0ebacac700618b48c0cc3d677918c1e461519461489dd14f4b98cca
2022-05-30 19:18:47 +00:00
bit4bit
a208d924dd se mueve pruebas nomina de ajuste a test_nomina_ajuste.py
FossilOrigin-Name: 5cf7c9d1ea339d30416076f0fa1f8d3dd89452ac2f8f955b8daa67dd351c8843
2022-05-30 19:17:15 +00:00
pingara
c3b0f7cfe8 Pruebas de nómina en Nómina Ajuste actualizadas
FossilOrigin-Name: d799f3660231a69b7483298e7c7d62d51933529190fff51fa3d5de1a3ac50304
2022-05-19 21:33:46 +00:00
pingara
005f90166e Actualizar para envío de Nómina Ajuste
FossilOrigin-Name: 98e5f9522b428c2e401b9fb5ba2f8a96157415ada89aef70d7b7900674a461b8
2022-05-19 21:27:55 +00:00
pingara
73bb90b74b Validación contiene errores en campos mandatorio en schemalocation
FossilOrigin-Name: 834a1cd8bca356b5794fcaf6cafe5b84adc2a70bc719a25085bae4379a58f038
2022-05-19 02:18:02 +00:00
bit4bit
6bed600dd4 mezcla machete
FossilOrigin-Name: 0ea0f8f6c5fe4b809abdb59e33163316a1794bc4571649ce5a1c89aa58a6f32e
2022-05-11 01:19:59 +00:00
bit4bit
aa3c83933d se le saca filo al machete
FossilOrigin-Name: 3c8cccda8e119b5d1d0edbce2ec326aa2f99053024efd9dd21c2092dacd08889
2022-05-11 01:17:48 +00:00
pingara
ac1678b1cc Machete Anexo ID NIE901
FossilOrigin-Name: ce5cd5e95d640dd97a277bfc072e376566bac007c33f9ff521a70470f39dc5bb
2022-04-22 01:55:01 +00:00
bit4bit
29db6b0342 se corrige nombre de metodo abstracto
FossilOrigin-Name: a428ca63cc4dd4991d3a006685f6538cb2317228af12e3e8fdbc892442530540
2022-02-12 00:43:46 +00:00
bit4bit
417de535f3 se usa POLICY_ID desde archivo local, actualizacion de commit [47fd230ff9]
FossilOrigin-Name: 190ab3228ef9e3fbfb4210ae74a8e332cf0aa9d6a0d476e561164bd76ae772f0
2022-02-12 00:37:30 +00:00
bit4bit
a9564f9a70 comentarios, se remueve requirements_dev.txt
FossilOrigin-Name: 22450fbe7fb9f02c4723c2820d9b7bc3e0d7018a4cb900bdc4f6867e4550951f
2022-02-12 00:28:49 +00:00
bit4bit
48c56631ec inicio AttachedDocument
FossilOrigin-Name: 45488436368ceeb9cbe6ed5b1b63b004821b868b1eba6a80aec6db5504a21f07
2022-02-05 18:37:31 +00:00
bit4bit
d5f484ac30 se actualizan las pruebas de nomina
FossilOrigin-Name: 61d3f68769528af14de6a10bba0401f5ebdf98ae5b9e365e33a87961e0476d49
2022-02-05 18:22:30 +00:00
bit4bit
fc904f8f92 se especifican las dependencias para no evitar uso de setuptools_rust
FossilOrigin-Name: b31db1100a014ee5e60712e36650c4e0d9802a9467dc81340d66ab8405ab20ca
2022-02-05 18:19:27 +00:00
bit4bit
6e2f450ee9 facho-signer: se encapsulan cadenas de texto
FossilOrigin-Name: bfb5dfbaa6d2be262a8f613945ce64f696ed83503159f6b03788cf09744a84f5
2022-01-09 20:30:19 +00:00
bit4bit
099b23df74 facho-signer: comentarios
FossilOrigin-Name: b8b342a3dc3feb82744f79332200629663944e25f5fda92a2419fed6d787d1aa
2021-12-26 20:39:29 +00:00
bit4bit
82e043e01d facho-signer: se adiciona cabezera copyleft
FossilOrigin-Name: 689429ff0f376deb1cc4f5ac6b6d260bea421695eacb66fed71ec6567363e9b3
2021-12-26 20:21:04 +00:00
bit4bit
7054e2da22 facho-signer: correciones para primera compilacion de wasm
FossilOrigin-Name: 7e2792a899cccff417c583b419ff7081d9630e405f39dc67aa96ed479b4b5164
2021-12-21 02:01:15 +00:00
bit4bit
dc9e0d9ad7 facho-signer: se genera libfachosigner
FossilOrigin-Name: 4c169cc94f834a83ace421f4afffe2b61cea7faa570ac0c078505026b1e80eb5
2021-12-20 01:49:37 +00:00
bit4bit
c08629337c facho-signer: se separa main de libreria facho-signer
FossilOrigin-Name: ccb333de3a3210a4e82fb102e3eb41cb2d26c8fea5dd153898728978f85539b0
2021-12-20 01:38:13 +00:00
bit4bit
95bc4e73f5 facho-signer: adiciona ChangeLog
FossilOrigin-Name: 9b47c3611afa463de180efd0353c16e1c936cf6197e36b9c878e517323aa551f
2021-12-20 00:38:19 +00:00
bit4bit
a411562af1 facho-signer: se adicion dependencias de compilacion
FossilOrigin-Name: cf4db27f53dea6286132944e318c1a0e778aae905bca386c67a27ead1a750035
2021-12-19 20:50:19 +00:00
bit4bit
27ebbecc01 facho-signer: se usa autoconf
FossilOrigin-Name: 5a7e25376d54d22f8d955c9b6827785d2efd673fee9813b27f4ce39d4a3e8f7e
2021-12-15 23:05:31 +00:00
bit4bit
7181c004cb facho-signer: se adiciona policy Hash usando como contenido archivo pdf
FossilOrigin-Name: fcbbfe001fcbd8131064b5feee76f838e4c345a99eea26c5d0592b0277fbff17
2021-12-15 01:16:03 +00:00
bit4bit
012615f984 facho-signer: se adiciona SigPolicyId
FossilOrigin-Name: c27a531da0a2dbca77e3c0b944735a4fc4266142e505b310d4ca8cf1f3d11596
2021-12-14 02:00:44 +00:00
bit4bit
a320b5a7b0 facho-signer: se adicionan digest de certificados
FossilOrigin-Name: 86d510a9c5ea4bf2ec5e297d9fb9fbc04b99761bd62f364b4d32dc749a1cf3bc
2021-12-12 02:53:41 +00:00
bit4bit
ebc67d1327 facho-signer: primer firmador sin xades
FossilOrigin-Name: c10547642e8d5e6f5b6e6e3a71b8294ebe566a68a2bd14a9233e21ae016aef20
2021-12-11 22:16:43 +00:00
bit4bit
9c2c3d9ce3 correcion NIE077
FossilOrigin-Name: e562715e1ea1a809d692abf065a77e785ee36c487f49118982cad63ecdd2ed51
2021-12-10 19:39:24 +00:00
bit4bit
d36259d121 se adicionan archivos faltantes
FossilOrigin-Name: 85fd66cb6f56b56c71826cb1cc02afa625c277c1f12dc92cfbc741b3808cf646
2021-12-10 14:31:00 +00:00
bit4bit
23d6f668bf facho-xades: se adicionan mas etiquetas xml
FossilOrigin-Name: 9891b60b972442eb5244d54d397498678cdd041fb7f7de44905f6b33a89e0849
2021-12-06 22:46:30 +00:00
bit4bit
dd445b59f0 experimental: facho-signer: xml layout
FossilOrigin-Name: 6fda6ceb8a3fd6d45cd68601b4608b9ce3a8f8af0eb79b99f9ec73a20a8023b3
2021-12-06 17:39:37 +00:00
bit4bit
1a302f605f se adiciona experimental/facho-wasm para crear wasm de firmado para facturacion electronica
FossilOrigin-Name: f5823c4b16ec6f81eaf92676dfe4a2bee3edc150ce3485ffcd8732c2c4284e5b
2021-12-04 15:44:45 +00:00
bit4bit
cbe0c512eb se adiciona DevengadoHorasRecargoNocturnoDominicalesYFestivos
FossilOrigin-Name: 52b9984b034583135a80817f41a4a5e2613e0092cbf062fdc49f75fbc7bdd645
2021-12-01 00:37:48 +00:00
bit4bit
1e03d4f289 se adiciona DevengadoHorasExtrasNocturnasDominicalesYFestivos
FossilOrigin-Name: 8110dc80fd5b94da8f2f0a4ed1612173467dbf468a5d1dbdbf8965808453f586
2021-12-01 00:36:10 +00:00
bit4bit
ec391f93f6 se adiciona DevengadoHorasRecargoDiariasDominicalesYFestivos
FossilOrigin-Name: 75e778f2509c15cbd050799d101085b617564adf0e5ece50704112242d275b70
2021-12-01 00:34:29 +00:00
bit4bit
27208c46ec se adiciona DevengadoHorasExtrasDiariasDominicalesYFestivos
FossilOrigin-Name: 28ffd20199539124d49e82097cf88cf39e1dfc4886593e32d0047f5018c68c9c
2021-12-01 00:32:43 +00:00
bit4bit
c861cf5a79 se adiciona DevengadoHorasRecargoNocturno
FossilOrigin-Name: 04ebb683c1e63e29d874bb57d94df8b4778864b10746bc22f4e19138a8b9052e
2021-12-01 00:30:55 +00:00
bit4bit
e88c2dd83b se adiciona DevengadoHorasExtrasNocturnas
FossilOrigin-Name: b91f84e2a1e2973db222d88f3d29286aa43806404562bb9df81d512219439ca3
2021-12-01 00:28:56 +00:00
bit4bit
67f29fcca6 se adiciona DevengadoHorasExtrasDiarias
FossilOrigin-Name: c544c4bb273c7f548f7cb5d4466af9975bb7f5a35e4bd38a22fbdeae39acea4c
2021-12-01 00:24:14 +00:00
bit4bit
ee3910136d ajuste usando validacion de la dian
FossilOrigin-Name: 0e42c46b8071bc8d8ed750160d739de073fd7dfb8037bc15b241748a8ac529fd
2021-11-27 01:24:55 +00:00
bit4bit
ab1646f156 se corrige NIE021
FossilOrigin-Name: ae3eae5bc793a328a0fd65a7172f65a243ed87f1a616f390fba4d721a0864875
2021-11-27 00:15:57 +00:00
pingara
e13896db28 Pobladas codelist TipoTrabajador, PeriodoNomina y TipoContrato
FossilOrigin-Name: 00f3a7fe94624599e6ca541e5c4b58126d707b89d547e3000a312e89b9e649ac
2021-11-25 16:27:53 +00:00
bit4bit
44f551ff77 se adiciona mas pruebas respecto al CUNE
FossilOrigin-Name: de7477d39e9df7ca26db72fa61c32d73b85f1ccdb05fa06624e65adb38fa8419
2021-11-25 13:56:03 +00:00
bit4bit
c1cdd4815c se prueba valor de CodigoQR
FossilOrigin-Name: 32e23a06342250eea0e37f1439ddbfcef47b917a7c2477276ac55a5fa2b0bec7
2021-11-24 02:28:14 +00:00
bit4bit
b52a90dd76 se actualizan pruebas sobre cune
FossilOrigin-Name: b99aa36a75c8bd17256d26c412ac08fc8b3ef651ec8380b9724af6360f8e9388
2021-11-24 02:13:52 +00:00
bit4bit
a4d0d214dc se actualizacion fechas de nomina a tipo Fecha
FossilOrigin-Name: 70fd8c71f0b1b0af68f10bdce72122e1edad0930c9ca59249e341633423674ef
2021-11-24 01:39:23 +00:00
bit4bit
248ec4304d se crea tipo Fecha para gestion de fechas
FossilOrigin-Name: ca4b0b511cb9ac37e174fbda1f53e8590e5615b0779916781ca4c1fdc002325a
2021-11-24 01:33:00 +00:00
bit4bit
4ceec2ca53 se adiciona zip a entorno de desarrollo
FossilOrigin-Name: c7fb9f265f90a6d025ee408216ee96b1323b18acbfc2fd5083ac3bae2fd099f3
2021-11-24 01:16:27 +00:00
bit4bit
49a6cb75ff correciones segun dian
FossilOrigin-Name: 4a7f379192aa3a17183e5c4f375dfceeb4044c436347c28d472a492621ea116f
2021-11-23 01:55:29 +00:00
bit4bit
6d02ad0bf5 se adiciona comando validate-nominaindividual para validacion usando XSD de caja de herramientas
FossilOrigin-Name: e2f075b294e19cd447ad6e1e83ef7f00c8b863718abf35fcfccbb182097e300d
2021-11-23 01:18:28 +00:00
bit4bit
0c28eea6e2 se adicionan pruebas para Ajuste Reemplazar y Eliminar
FossilOrigin-Name: 750ed4cb15a88abfe296f1d9b2e4013df95b76bf1dfd4ecca22e07b3c12aacf2
2021-11-17 01:06:22 +00:00
bit4bit
56c7e2c453 se adiciona Ajuste Reemplazar y Eliminar
FossilOrigin-Name: b488d606e28c44a581d7a387d33f08442d53d613149fd19b264e28a6b94a8951
2021-11-17 00:50:19 +00:00
bit4bit
cd1b14ff1d se adiciona SendNominaSync
FossilOrigin-Name: e09a3b15439d3f052dd7dbf9504035507abad0cc885c69432d0b989bb4b8b55f
2021-11-13 18:33:36 +00:00
bit4bit
903b1bad64 cli permite generar NominaIndividual
FossilOrigin-Name: c30edea460eb16340183c373ed448e3719e2277550882db2d2daafe7bd4bca7e
2021-11-13 18:26:21 +00:00
bit4bit
aa3e14de95 se adicionan archivos faltantes
FossilOrigin-Name: 32b80e8ede4097e4596f01ad450f5b45afa379d5ae6cb9f293eb96aa0e1d2fbb
2021-11-12 03:13:45 +00:00
bit4bit
fb88d56652 se adiciona firmador de NominaIndividual
FossilOrigin-Name: 61f093ca18fd0d4edf76d84693fc5cb3b6c4b008ba238f7e7defef23e400f328
2021-11-12 03:13:08 +00:00
bit4bit
a34963ca49 se adicionan mas campos requeridos para NominaIndividual
FossilOrigin-Name: 12c10fc4b3e4b69c665c8cde599a788266402977af62eed9b9a25459e1545f02
2021-11-12 02:56:17 +00:00
bit4bit
8deebbdfa4 se adiciona faltantes obligatorios en InformacionGeneral
FossilOrigin-Name: 2dad4c3725349ce55968669763d7321d3e5dbecec951f9d01f2193b3bd4ab528
2021-11-10 02:19:17 +00:00
bit4bit
b23be4b42f nueva estructura nomina
FossilOrigin-Name: 54638bc6d28c9d12b1a079bfaf7321e9da788c76827acbf88d41187ce9636502
2021-11-10 02:12:47 +00:00
bit4bit
f7a29b6daa se adicionan restantes obligatorios para NominaIndividual
FossilOrigin-Name: 428f40698c252453c5b6ae39f35553c6484d90568b1d31f097115a88d94fadd9
2021-11-10 01:58:59 +00:00
bit4bit
080014cbb6 se adiciona requeridos por Trabajador
FossilOrigin-Name: 89012ae06fd60e69b9432618c9338094c0c2020651cb315ad151013ba00567af
2021-11-10 01:49:07 +00:00
bit4bit
d152da31ed se adiciona Departamente,Municipio,direccion de empleador
FossilOrigin-Name: cc89d9499c75b55b43f8a9a2538b2327dd97addb13f27c8d7f67481ddbe3ab66
2021-11-10 01:21:28 +00:00
bit4bit
311bfef3e3 se adiciona nomina.Pais
FossilOrigin-Name: cebbf3e7a96da1a9ab742f87f5aa0c747a067ead94b51f2cd94fe7c6390ad6a8
2021-11-10 01:16:19 +00:00
bit4bit
bb16b5e968 se extendie NominaIndividualDeAjuste de NominaIndividual
FossilOrigin-Name: 10b3af439bee9b932e4f5067bec0768af60e514f595e00ecb1bdf7c88c39afc1
2021-11-10 01:08:39 +00:00
bit4bit
b60fe675c1 doc
FossilOrigin-Name: a4647dcd3d601d0181c5dbb245cf4c83c280b4aa4832261ce10ef1172d3c4648
2021-11-06 21:44:16 +00:00
bit4bit
0e5235b095 doc
FossilOrigin-Name: a372538829628a435acaf2347735c2bd8f9ab78874c5e90e430abf2f3bcd9067
2021-11-06 21:41:06 +00:00
bit4bit
d5fd7db23c se simplifica la obtencion de contenido y atributos de multiples elementos xml
FossilOrigin-Name: cf1d924be9a62e231090f6448b10ba23c04bbbacc8d94fc16d5ca4e0735c4bbc
2021-11-06 21:40:36 +00:00
bit4bit
3ec31c36b4 se confirma calculo de devengadostotal,deduccionestotal y comprobantetotal
FossilOrigin-Name: 6c297492215eac22da511abe720a3922432711c194f1bd3b5543f23ff789f58c
2021-11-06 21:26:51 +00:00
bit4bit
8d17c282e3 se adiciona FachoXml.get_element_text_or_attribute
FossilOrigin-Name: 2179f4376613dda1e554a4f1304990f32185c1ec9bec6181d8a00e31a5ee5390
2021-11-06 20:43:07 +00:00
bit4bit
35e1c5b609 primer bosquejo para cune
FossilOrigin-Name: 415cd610f28f743b8ae79e98f49d34c8d1fa4cf5f5a542c03226a642457b28c0
2021-11-06 02:38:41 +00:00
bit4bit
3b2e3ff8a0 se adiciona archivo faltante
FossilOrigin-Name: 9b5286528fbefb452e42cf375031ad4037220fc5643bfb0dd9b1106d37544789
2021-11-05 02:34:57 +00:00
bit4bit
4b11f6b06d nueva estructura de directorios para nomina
FossilOrigin-Name: f3a95167abace679098bac9daffda6f17a8c819b92a5d096558f20dfce3acbbe
2021-11-05 02:34:45 +00:00
bit4bit
791d534653 se adicionan validaciones de requerimientos
FossilOrigin-Name: b070d78fc4c3ba5d247a54a8cc7e7305ea054ae2ce34eb9a5d9db38802e8c27c
2021-11-05 02:18:30 +00:00
bit4bit
74d98e249d se adiciona archivo faltante de nomina, y se agregan mas deducciones
FossilOrigin-Name: afb6c19bd3d7f71dd3ceff6e95cc39aaf65d15707eef8535d092e68b34c05c65
2021-11-05 02:09:21 +00:00
bit4bit
3ef002e137 se adiciona deduccion salud
FossilOrigin-Name: 79f746a18098e602e887c387a7549a450c9f7fa595cbb33296a3905e0ff5a81e
2021-11-05 01:38:55 +00:00
bit4bit
c758912cf2 se adicionan mas pruebas a facho xml
FossilOrigin-Name: 1425e28d9ca9a0025dacd40fc57fff5cf3bbf67d0fc8482899502b996a0d41f9
2021-11-05 01:28:35 +00:00
bit4bit
18342b7e23 se adiciona prueba para probar adicion en fragmentos
FossilOrigin-Name: 5094189dae307766ff8bb0869831a0245d4c092fdc6de8b5e7697d835a62c4d4
2021-11-04 23:56:49 +00:00
bit4bit
1602dd35e3 adiciona NIE071,NIE072,NIE073
FossilOrigin-Name: a04d79d7ce56391301a0babd48c413be10dfd628764b91471746041ca9175458
2021-11-04 03:01:01 +00:00
bit4bit
7f72bab06a facho placeholder mantiene orden de elementos
FossilOrigin-Name: c535b486c8f956a25f8e88bf9053aab312ed441b1d217699a36924e24b46ab21
2021-11-04 02:31:45 +00:00
bit4bit
adbd6d0d0e al insertar elemento se ubica continuo a los primos
FossilOrigin-Name: 3c26c6d8803c30d15f9baa083fc8cfc2ed8993061196f3299162d6a899680735
2021-11-04 01:46:43 +00:00
bit4bit
2b76cf6a10 se adiciona NIE069 y NIE070
FossilOrigin-Name: e907e9c9bd118f8a86ca5a27ce283b2d5a4fedcf55f8a850b588480cbf793a79
2021-11-04 00:30:18 +00:00
bit4bit
f975e666ee se adiciona FachoXml.get_element_attribute para obtener atributo de xpath
FossilOrigin-Name: b3be980dca00b8bd117c4a7e176b8d3a9bac804a1200ad4b7cc39e0f4ab0be15
2021-11-03 23:55:20 +00:00
bit4bit
fcd5060485 se adiciona FachoXML.set_element_validator para validar contenido y attributos de elementos
FossilOrigin-Name: 0212899abf1632256a040252e45b1fc6d294cea558c930d96af22e68c9393d47
2021-11-02 13:57:18 +00:00
bit4bit
f627e3e22a se adiciona documentacion acerca de nomina electronica
FossilOrigin-Name: b07cad5f67ad1b67ccb396e7e2b46d960bbc83d1403e61dc3747e072c57a75a0
2021-11-01 19:32:10 +00:00
bit4bit
58307bf9cf se adiciona Makefile.dev para gestion de entorno de desarrollo
FossilOrigin-Name: 80412ae07450b3e5f59a6a5c04233376fa451ea3369c75e6ee824772964a17ac
2021-10-21 02:10:44 +00:00
bit4bit
9199af9b68 se adiciona anexo tecnico version 1.8 de 2020
FossilOrigin-Name: 8944af29eeb6c1f8afd921cf713bd11a05b110a980af6fc83b4765935c7f721f
2021-10-06 01:45:21 +00:00
lubricantes
4c871afb73 fixed FAS01b y FAS01a
FossilOrigin-Name: e593722ec6e2c353bf615dcd080c365123aeb809899936a9f5219fbfffdfe5b5
2021-09-07 14:59:26 +00:00
one
50b1a13c0a DIAN 1.8.-2021: FAD03
FossilOrigin-Name: 0dd43cdbcd489f5433785b3b6281854e528719761b183d1eb498c43c8ae57924
2021-08-05 01:17:16 +00:00
bit4bit
4e68025e48 se adiciona fe.DianXmlExtensionSigner.from_bytes para firmado usando bytes
FossilOrigin-Name: 39ed96abb30b2559e72d4948f2fd65de8f0e5e0f9004f0f1d4e88135b950bd61
2021-06-16 02:35:07 +00:00
bit4bit
ead21bd4f2 se adiciona prueba para confirmar que el zip sea comprimido
FossilOrigin-Name: 5a220c699199ea94baed777c3a9a51be954c44d18630122907dbab817c98d4d1
2021-06-16 02:20:09 +00:00
bit4bit
5e79850686 se corrige prueba de redondeo de Amount segun indicado decreto
FossilOrigin-Name: 5a1555e4c6af4db50a11027974da0a694daef68ede90ce445f0d175e6a0cd6b8
2021-06-16 02:13:36 +00:00
pingara
988d01daf7 Se agrega condicional para el caso de no recibir errores
FossilOrigin-Name: 09314cc648235ca61b8c7b161ef0b7ddcb33fca30467e57bc75a7010b1c6435b
2021-05-04 05:31:21 +00:00
pingara
28a963b76a Política de firma es leída localmente, para mejorar rendimiento
FossilOrigin-Name: 47fd230ff94210ce928b7dd1e7446b98cbc8a13ca3baf8ce126eeedea528e832
2021-05-03 02:23:03 +00:00
pingara
af99a7593a Se agrega método de comprensión
FossilOrigin-Name: e8ef9c8d9ea2ca55fc37a56bd9934628757ad6b9e6de73253d941134491ae2c9
2021-05-01 21:05:00 +00:00
bit4bit
d02c0b13b8 se elimina comentario de inestabilidad, actualmente tenemos usuarios usando esta libreria en produccion
FossilOrigin-Name: 44d1ea9e124620506c65eee06e6be8b5a49ffc1f2437d4ad728abce19ead78e4
2021-04-28 02:20:46 +00:00
pingara
79209964e0 Se actualiza plantilla de factura
FossilOrigin-Name: 2a3cb9db35a149014195962f31d561d41997d1df637adee1cc9317680f2b86cd
2021-03-10 02:12:39 +00:00
bit4bit
38f4c5ae45 se adiciona xml para linea de AllowanceCharge
FossilOrigin-Name: 20a354ef9c6c5aa3fb0436716b43d68cac7bfa1b56be550abc18af79444f1662
2020-12-02 20:30:28 +00:00
bit4bit
1143b26988 Se adiciona xml de AllowanceCharge para documento
FossilOrigin-Name: 93cd9530bb2f63a2803b8a32a5aceeeda015eff2d26c96e95a13dbb9193cad82
2020-12-02 18:46:18 +00:00
bit4bit
e571009945 form.AllowanceChargeAsDiscount nueva clase para descuentos
FossilOrigin-Name: 5b11ff93dff3a301628694c2a6e71940915aea8754a9eab3f35644bc669ddf87
2020-12-02 18:10:29 +00:00
bit4bit
48619106c5 se adiciona documentacion
FossilOrigin-Name: 20a903a2426d4454a9909c78411b3ad7bd7f7d34d576ed5618e73784f77c8d92
2020-11-15 23:21:52 +00:00
bit4bit
bcf5120d82 Amount implementa truncate_as_string para forzar truncado de Decimal
FossilOrigin-Name: e65c80a4d6956aff8cd80f3ee35cad827ffbd5c965a1fb651ab07424e6b22f31
2020-11-11 01:58:01 +00:00
bit4bit
f648188834 nuevo atribute Price.quantity para cantidad base y se separa de InvoiceLine.quantity
FossilOrigin-Name: 4ed8ab2f9ce5505b59f75c9a0ac9f01d7ba5b9512856f4da8ea0b40fe4acfef0
2020-11-11 01:08:48 +00:00
bit4bit
67156ec9a6 usa Amount en Quantity
FossilOrigin-Name: 0f51683a044925969f6f113710ca9456dfa541a80ec3d7a3b9e5235718cbf0ea
2020-11-10 23:17:25 +00:00
130 changed files with 30956 additions and 2140 deletions

1
.gitignore vendored
View File

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

View File

@ -94,6 +94,14 @@ Ready to contribute? Here's how to set up `facho` for local development.
7. Submit a pull request through the GitHub website. 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 Pull Request Guidelines
----------------------- -----------------------

40
Dockerfile Normal file
View File

@ -0,0 +1,40 @@
# DERIVADO DE https://alextereshenkov.github.io/run-python-tests-with-tox-in-docker.html
FROM ubuntu:24.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 \
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 \
&& rm get-pip.py
RUN apt-get install -y --no-install-recommends \
libxml2-dev \
libxmlsec1-dev \
build-essential \
zip
RUN python3.9 --version
RUN python3.10 --version
RUN python3.11 --version
RUN python3.12 --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 install tox pytest --break-system-packages

21
Makefile.dev Normal file
View File

@ -0,0 +1,21 @@
# Entorno de desarrollo
#
# solo es necesario una vez:
# * make -f Makefile.dev dev-setup
#
# luego se pueden ejecutar las pruebas:
# * make -f Makefile.dev test
.PHONY: dev-setup dev-shell py-develop test tox
dev-setup:
docker build -t facho .
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'
tox:
docker run -it -v $(PWD)/:/app -w /app facho tox

View File

@ -2,8 +2,6 @@
facho facho
===== =====
!!INESTABLE NO RECOMENDAMOS USO PARA PRODUCION!!
Libreria para facturacion electronica colombia. Libreria para facturacion electronica colombia.
- facho/facho.py: abstracion para manipulacion del XML - facho/facho.py: abstracion para manipulacion del XML
@ -24,7 +22,7 @@ usando pip::
CLI CLI
=== ===
tambien se provee linea de comandos **facho** para firmado y envio de documentos:: tambien se provee linea de comandos **facho** para generacion, firmado y envio de documentos::
facho --help facho --help
CONTRIBUIR CONTRIBUIR
@ -32,17 +30,13 @@ CONTRIBUIR
ver **CONTRIBUTING.rst** ver **CONTRIBUTING.rst**
USO
===
ver **USAGE.rst**
DIAN HABILITACION DIAN HABILITACION
================= =================
guia oficial actualizada al 2020-04-20: https://www.dian.gov.co/fizcalizacioncontrol/herramienconsulta/FacturaElectronica/Facturaci%C3%B3n_Gratuita_DIAN/Documents/Guia_usuario_08052019.pdf#search=numeracion guia oficial actualizada al 2020-04-20: https://www.dian.gov.co/fizcalizacioncontrol/herramienconsulta/FacturaElectronica/Facturaci%C3%B3n_Gratuita_DIAN/Documents/Guia_usuario_08052019.pdf#search=numeracion
ERROR X509SerialNumber
======================
lxml.etree.DocumentInvalid: Element '{http://www.w3.org/2000/09/xmldsig#}X509SerialNumber': '632837201711293159666920255411738137494572618415' is not a valid value of the atomic type 'xs:integer'
Actualmente el xmlschema usado por xmlsig para el campo X509SerialNumber es tipo
integer ahi que parchar manualmente a tipo string, en el archivo site-packages/xmlsig/data/xmldsig-core-schema.xsd.

24
USAGE.rst Normal file
View File

@ -0,0 +1,24 @@
uso de la libreria
==================
**facho** es tanto una libreria para modelar y generar los documentos xml requeridos para la facturacion,
asi como una herramienta de **consola** para facilitar algunas actividades como: generaciones de xml
apartir de una especificacion en python, comprimir y enviar archivos según el SOAP vigente.
**facho** es diseñado para ser usado en conjunto con el documento **docs/DIAN/Anexo_Tecnico_Factura_Electronica_Vr1_7_2020.pdf**, ya que en gran medida sigue la terminologia presente en este.
Para ejemplos ver **examples/** .
En terminos generales seria modelar la factura usando **facho/fe/form.py**, instanciar las extensiones requeridas ver **facho/fe/fe.py** y
una vez generado el objeto invoice y las extensiones requeridas se procede a crear el XML, ejemplo:
~~~python
....
xml = form_xml.DIANInvoiceXML(invoice)
extensions = module.extensions(invoice)
for extension in extensions:
xml.add_extension(extension)
form_xml.DIANWriteSigned(xml, "factura.xml", "llave privada", "frase")
~~~

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
# notas
* se extrae documento tecnico de resolucion usando **pdfarranger**
## CUNE
ver 8.1.1.1 del archivo **Resolucion 000013 de 11-02-2021.pdf**
# enlaces
* https://www.dian.gov.co/impuestos/Paginas/Sistema-de-Factura-Electronica/documento-soporte-de-pago-de-nomina-electronica.aspx
* https://bitbucket.org/presik/electronic_payroll.git
* https://bitbucket.org/presik/trytonpsk-staff_payroll_co.git

Binary file not shown.

View File

@ -0,0 +1,120 @@
# este archivo es un ejemplo para le generacion
# una factura de venta nacional usando el comando **facho**.
#
# ejemplo: facho generate-invoice generate-invoice-from-cli.py
#
# importar libreria de modelos
from facho.fe import form
from facho.fe import form_xml
# importar libreria extensiones xml para cumplir decreto
from facho.fe import fe
# importar otras necesarias
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
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
# callback que retonar las extensiones XML necesarias
# para que el documento final XML cumpla el decreto.
#
# muchos de los valores usados son obtenidos
# del servicio web de la DIAN.
def extensions(inv):
security_code = fe.DianXMLExtensionSoftwareSecurityCode(ID_SOFTWARE, PIN, inv.invoice_ident)
authorization_provider = fe.DianXMLExtensionAuthorizationProvider()
cufe = fe.DianXMLExtensionCUFE(inv, CLAVE_TECNICA, fe.AMBIENTE_PRUEBAS)
software_provider = fe.DianXMLExtensionSoftwareProvider('nit_empresa', 'dígito_verificación', ID_SOFTWARE)
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization(INVOICE_AUTHORIZATION,
datetime(2019, 1, 19),#Datos toamdos de
datetime(2030, 1, 19),#la configuración
'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')
# 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('SETP990000008')
# asignar tipo de operacion ver DIAN:6.1.5
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'),
# obligaciones del contribuyente ver DIAN:FAK26
responsability_code=form.Responsability(['ZZ', 'O-14', 'O-48']),
# ver DIAN:FAJ28
responsability_regime_code='48',
# tipo de organizacion juridica ver DIAN:6.2.3
organization_code='1',
email="correoempresa@correoempresa.correo",
address=form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia')),
))
#Tercero a quien se le factura
inv.set_customer(form.Party(
legal_name = 'consumidor final',
name = 'consumidor final',
ident = form.PartyIdentification('222222222222', '', '13'),
responsability_code = form.Responsability(['R-99-PN']),
responsability_regime_code = '49',
organization_code = '2',
email = "consumidor_final0final.final",
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia')),
# 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'
))
# 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')
)]
)
))
return inv
def document_xml():
return form_xml.DIANInvoiceXML

View File

@ -1,74 +0,0 @@
import facho.fe.form as form
from facho.fe import fe
from datetime import datetime
def extensions(inv):
nit = form.PartyIdentification('nit', '5', '31')
security_code = fe.DianXMLExtensionSoftwareSecurityCode('id software', 'pin', inv.invoice_ident)
authorization_provider = fe.DianXMLExtensionAuthorizationProvider()
cufe = fe.DianXMLExtensionCUFE(inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS,
'clave tecnica')
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]
def invoice():
inv = form.Invoice()
inv.set_period(datetime.now(), datetime.now())
inv.set_issue(datetime.now())
inv.set_ident('SETP990003033')
inv.set_operation_type('10')
inv.set_supplier(form.Party(
legal_name = 'FACHO SOS',
name = 'FACHO SOS',
ident = form.PartyIdentification('900579212', '5', '31'),
responsability_code = form.Responsability(['O-07', 'O-09', 'O-14', 'O-48']),
responsability_regime_code = '48',
organization_code = '1',
email = "sdds@sd.com",
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
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(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.set_payment_mean(form.PaymentMean(
id = '1',
code = '10',
due_at = datetime.now(),
payment_id = '1'
))
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem('test', 9999),
price = form.Price(
amount = form.Amount(100.00),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.00,
)
]
)
))
return inv

View File

@ -0,0 +1,93 @@
from facho import fe
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_metadata(fe.nomina.Metadata(
secuencia=fe.nomina.NumeroSecuencia(
numero = 'N00001',
consecutivo=232
),
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_sc='yy'
)
))
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',
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_empleador(fe.nomina.Empleador(
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion = 'calle etrivial'
))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(3_500_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
return nomina

132
examples/use-as-lib.py Normal file
View File

@ -0,0 +1,132 @@
# 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'
# consultar las extensiones necesarias
def extensions(inv):
security_code = fe.DianXMLExtensionSoftwareSecurityCode(
'id software', 'pin', inv.invoice_ident)
authorization_provider = fe.DianXMLExtensionAuthorizationProvider()
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
]
# 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)
# 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'),
# obligaciones del contribuyente ver DIAN:FAK26
responsability_code=form.Responsability(['ZZ', 'O-09', 'O-14', 'O-48']),
# ver DIAN:FAJ28
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'))
))
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'))
))
# asignar metodo de pago
inv.set_payment_mean(form.PaymentMean(
# metodo de pago ver DIAN:3.4.1
id='1',
# codigo correspondiente al medio de pago ver DIAN:3.4.2
code='10',
# fecha de vencimiento de la factura
due_at=datetime.now(),
# identificador numerico
payment_id='1'
))
# adicionar una linea al documento
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto facho',
# item general de codigo 999
item=form.StandardItem('test', 9999),
price=form.Price(
# precio base del tiem
amount=form.Amount(100.00),
# ver DIAN:6.3.5.1
type_code='01',
type='x'
),
tax=form.TaxTotal(
subtotals=[
form.TaxSubTotal(
percent=19.00,
)]
)
))
# refrescar valores de la factura
inv.calculate()
# generar el documento xml en la ruta indicada
generate_document(inv, 'ruta a donde guardar el .xml')

View File

View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
facho-signer Copyright (C) 2021 Jovany Leandro G.C <bit4bit@riseup.net>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

View File

@ -0,0 +1 @@
SUBDIRS = src

View File

View File

View File

@ -0,0 +1,3 @@
#!/bin/sh
libtoolize -c --force
autoreconf --install --force

View File

@ -0,0 +1,56 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.71])
AC_INIT([facho-signer], [0.0.1], [bit4bit@riseup.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/facho_signer.c])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([m4])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
PKG_CHECK_MODULES([OPENSSL], [openssl])
PKG_CHECK_MODULES([XMLSEC1], [xmlsec1-openssl], [
AC_DEFINE([XMLSEC_CRYPTO_OPENSSL], [1], [enable crypto openssl])
])
PKG_CHECK_MODULES([LZMA], [liblzma])
AC_ARG_ENABLE([xmlsec-crypto-dynamic-loading],
AS_HELP_STRING([--enable-xmlsec-crypto-dynamic-loading],
[enable crypto xmlsec dynamic loading])
)
if test "x$enable_xmlsec_crypto_dynamic_loading" == xyes; then
AC_DEFINE([XMLSEC_CRYPTO_DYNAMIC_LOADING], [1], [enable xmlsec crypto dynamic loading])
AC_CHECK_HEADER([ltdl.h], [
AC_CHECK_LIB(
[ltdl],
[lt_dlopenext],
[LIBLTDL=-lltdl],
[LIBLTDL=]
)
], [ LIBLTDL= ])
else
AC_DEFINE([XMLSEC_NO_CRYPTO_DYNAMIC_LOADING], [1], [disable xmlsec crypto dynamic loading])
fi
# Checks for header files.
AC_CHECK_HEADERS([sys/time.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
# Checks for library functions.
AC_FUNC_MALLOC
AC_CHECK_FUNCS([clock_gettime gethrtime gettimeofday memset strdup])
# libtool
LT_INIT
AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT

View File

@ -0,0 +1,12 @@
SOURCES = xades/xmlsec1/xmltree.c xades/xmlsec1/errors.c xades/templates.c xades/xades.c facho_signer.c
lib_LTLIBRARIES = libfachosigner.la
libfachosigner_la_SOURCES = $(LTDL_SOURCE_FILES) $(SOURCES)
libfachosigner_la_CFLAGS = $(OPENSSL_CFLAGS) $(XMLSEC1_CFLAGS) -DXMLSEC_NOT_CRYPTO_DYNAMIC_LOADING
libfachosigner_la_LDFLAGS = $(OPENSSL_LIBS) $(LIBLTDL) $(XMLSEC1_LIBS)
bin_PROGRAMS = facho_signer
facho_signer_SOURCES = main.c
facho_signer_CFLAGS = $(OPENSSL_CFLAGS) $(XMLSEC1_CFLAGS) -DXMLSEC_NOT_CRYPTO_DYNAMIC_LOADING
facho_signer_LDADD = libfachosigner.la

View File

@ -0,0 +1,11 @@
# facho-signer
## guia
- http://xmlsoft.org/html/libxml-tree.html
- http://xmlsoft.org/html/libxml-xpath.html
- http://xmlsoft.org/examples/xpath1.c
- http://xmlsoft.org/tutorial/ar01s05.html
- https://www.aleksey.com/xmlsec/api/xmlsec-reference.html
- https://zakird.com/2013/10/13/certificate-parsing-with-openssl

View File

@ -0,0 +1,371 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#include "config.h"
#include "xades/xades.h"
#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/templates.h>
#include <xmlsec/crypto.h>
#include <stdio.h>
#include <stdlib.h>
#define xmlFachoPrintError(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
#define xmlFachoPrintInfo(fmt, ...) fprintf(stdout, fmt, ##__VA_ARGS__)
static const xmlChar ublExtensionDSigNs[] = "urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2";
static const xmlChar policyIdDescription[] = "Política de firma para facturas electrónicas de la República de Colombia.";
static const xmlChar policyIdIdentifier[] = "https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf";
// crea elemento /Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent
static xmlNodePtr
xmlFachoTmplUBLExtensionAddExtensionContent(xmlDocPtr doc);
// FeC requiere que el digest value del policy identifier sea
// apartir del contenido de la url.
static int
xmlFachoPolicyIdentifierCtxFromFilename(const xmlChar *, xmlSecBufferPtr);
static int
xmlFachoTmplXadesCreate(xmlDocPtr doc, xmlNodePtr signNode) {
xmlNodePtr qualifyingPropertiesNode = NULL;
xmlNodePtr signedPropertiesNode = NULL;
xmlNodePtr signedSignaturePropertiesNode = NULL;
xmlNodePtr signingCertificateNode = NULL;
xmlNodePtr signaturePolicyIdentifierNode = NULL;
xmlNodePtr signaturePolicyIdNode = NULL;
xmlNodePtr sigPolicyIdNode = NULL;
xmlNodePtr sigPolicyHashNode = NULL;
xmlNodePtr signerRoleNode = NULL;
xmlNodePtr refNode = NULL;
const xmlChar signedPropertiesId[] = "xmldsig-facho-signed-props";
const xmlChar signedPropertiesRef[] = "#xmldsig-facho-signed-props";
qualifyingPropertiesNode = xmlXadesTmplQualifyingPropertiesCreate(doc, signNode, BAD_CAST "xades-ref1");
if ( qualifyingPropertiesNode == NULL ) {
xmlFachoPrintError("error: failed to add QualifyingProperties node.\n");
goto fail;
}
signedPropertiesNode = xmlXadesTmplAddSignedProperties(qualifyingPropertiesNode, signedPropertiesId);
if ( signedPropertiesNode == NULL ) {
xmlFachoPrintError("error: xades failed to add signed properties node.\n");
goto fail;
}
refNode = xmlSecTmplSignatureAddReference(signNode,
xmlSecTransformSha256Id,
BAD_CAST "xmldsig-facho-ref1",
signedPropertiesRef,
BAD_CAST "http://uri.etsi.org/01903#SignedProperties");
if ( refNode == NULL ) {
xmlFachoPrintError("error: failed to add reference to signature template xades.\n");
goto fail;
}
if ( xmlSecTmplReferenceAddTransform(refNode, xmlSecTransformInclC14NId) == NULL ) {
xmlFachoPrintError("error: failed to add enveloped transform to reference for xades\n");
goto fail;
}
const time_t now = time(NULL);
signedSignaturePropertiesNode = xmlXadesTmplAddSignedSignatureProperties(signedPropertiesNode, localtime(&now));
if ( signedSignaturePropertiesNode == NULL ) {
xmlFachoPrintError("error: xades failed to add signed signature properties node.\n");
goto fail;
}
signingCertificateNode = xmlXadesTmplAddSigningCertificate(signedSignaturePropertiesNode, xmlSecTransformSha256Id);
if ( signingCertificateNode == NULL ) {
xmlFachoPrintError("error: failed to add SigningCertificate node \n");
goto fail;
}
signaturePolicyIdentifierNode = xmlXadesTmplAddSignaturePolicyIdentifier(signedSignaturePropertiesNode);
if ( signaturePolicyIdentifierNode == NULL ) {
xmlFachoPrintError("error: failed to add PolicyIdentifier node\n");
goto fail;
}
signaturePolicyIdNode = xmlXadesTmplAddSignaturePolicyId(signaturePolicyIdentifierNode);
if ( signaturePolicyIdNode == NULL ) {
xmlFachoPrintError("error: failed to add SignaturePolicyId node.\n");
goto fail;
}
sigPolicyIdNode = xmlXadesTmplAddSigPolicyId(signaturePolicyIdNode, policyIdIdentifier, policyIdDescription);
if ( sigPolicyIdNode == NULL ) {
xmlFachoPrintError("error: failed to add SigPolicyId node.\n");
goto fail;
}
sigPolicyHashNode = xmlXadesTmplAddSigPolicyHash(signaturePolicyIdNode, xmlSecTransformSha256Id);
if ( sigPolicyHashNode == NULL ) {
xmlFachoPrintError("error: failed to add SigPolicyHash node.\n");
goto fail;
}
signerRoleNode = xmlXadesTmplAddSignerRole(signedSignaturePropertiesNode, BAD_CAST "supplier");
if ( signerRoleNode == NULL ) {
xmlFachoPrintError("error: failed to add SignerRole node.\n");
goto fail;
}
return(0);
fail:
xmlUnlinkNode(qualifyingPropertiesNode);
xmlFreeNode(qualifyingPropertiesNode);
return(-1);
}
int
xmlFachoInit() {
xmlInitParser();
LIBXML_TEST_VERSION;
if ( xmlSecInit() < 0 ) {
xmlFachoPrintError("xmlsec initialization failed.\n");
return(-1);
}
if ( xmlSecCheckVersion() != 1 ) {
xmlFachoPrintError("loaded xmlsec library version is not compatible.\n");
return(-1);
}
#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
if(xmlSecCryptoDLLoadLibrary( NULL ) < 0) {
fprintf(stderr, "Error: unable to load default xmlsec-crypto library. Make sure\n"
"that you have it installed and check shared libraries path\n"
"(LD_LIBRARY_PATH and/or LTDL_LIBRARY_PATH) environment variables.\n");
return(-1);
}
#endif /* XMLSEC_CRYPTO_DYNAMIC_LOADING */
if ( xmlSecCryptoAppInit(NULL) < 0 ) {
xmlFachoPrintError("crypto initialization failed.\n");
return(-1);
}
if ( xmlSecCryptoInit() < 0 ) {
xmlFachoPrintError("xmlsec-crypto initialization failed.\n");
return(-1);
}
return(0);
}
int
xmlFachoShutdown() {
if ( xmlSecCryptoShutdown() < 0 ) {
xmlFachoPrintError("xmlSecCryptoShutdown failed.\n");
}
if ( xmlSecCryptoAppShutdown() < 0 ) {
xmlFachoPrintError("xmlSecCryptoAppShutdown failed.\n");
}
if ( xmlSecShutdown() < 0 ) {
xmlFachoPrintError("xmlsec shutdown failed.\n");
}
xmlCleanupParser();
return(0);
}
int
xmlFachoSignFile(FILE *out, const char *filename, const char *pkcs12name, const char *password) {
xmlDocPtr doc = NULL;
xmlNodePtr signNode = NULL;
xmlNodePtr refNode = NULL;
xmlNodePtr keyInfoNode = NULL;
xmlNodePtr x509DataNode = NULL;
xmlNodePtr node = NULL;
xmlSecDSigCtxPtr dsigCtx = NULL;
xmlXadesDSigCtxPtr xadesDsigCtx = NULL;
int res = -1;
if (filename == NULL) {
return(-1);
}
doc = xmlParseFile(filename);
if ( (doc == NULL) || (xmlDocGetRootElement(doc) == NULL) ) {
xmlFachoPrintError("error: unable to parse file %s\n", filename);
goto done;
}
signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformInclC14NId,
xmlSecTransformRsaSha256Id, NULL);
if ( signNode == NULL ) {
xmlFachoPrintError("error: failed to create signature template.\n");
goto done;
}
xmlAddChild(xmlDocGetRootElement(doc), signNode);
refNode = xmlSecTmplSignatureAddReference(signNode,
xmlSecTransformSha256Id,
BAD_CAST "xmldsig-facho-ref0", // id
BAD_CAST "", //uri
NULL); //type
if ( refNode == NULL ) {
xmlFachoPrintError("error: failed to add reference to signature template.\n");
goto done;
}
if ( xmlSecTmplReferenceAddTransform(refNode, xmlSecTransformEnvelopedId) == NULL ) {
xmlFachoPrintError("error: failed to add enveloped transform to reference\n");
goto done;
}
refNode = xmlSecTmplSignatureAddReference(signNode,
xmlSecTransformSha256Id,
BAD_CAST "xmldsig-facho-ref2",
BAD_CAST "#xmldsig-facho-KeyInfo",
NULL);
if ( refNode == NULL ) {
xmlFachoPrintError("error: failed to add reference to signature template key-info.\n");
goto done;
}
keyInfoNode = xmlSecTmplSignatureEnsureKeyInfo(signNode, BAD_CAST "xmldsig-facho-KeyInfo");
if ( keyInfoNode == NULL ) {
xmlFachoPrintError("error: failed to add key info.\n");
goto done;
}
x509DataNode = xmlSecTmplKeyInfoAddX509Data(keyInfoNode);
if ( x509DataNode == NULL ) {
xmlFachoPrintError("error: failde to add x509 DATA \n");
goto done;
}
if ( xmlSecTmplX509DataAddCertificate(x509DataNode) == NULL ) {
xmlFachoPrintError("error: failde to add x509Certificate node\n");
goto done;
}
if ( xmlFachoTmplXadesCreate(doc, signNode) < 0 ){
xmlFachoPrintError("error: xmlFachoTmplXadesCreate failed.\n");
goto done;
}
dsigCtx = xmlSecDSigCtxCreate(NULL);
if ( dsigCtx == NULL ) {
xmlFachoPrintError("error: dsig context creating failed\n");
return(-1);
}
// cargamos el archivo pkcs12 con llave privado y certificados x509
dsigCtx->signKey = xmlSecCryptoAppKeyLoad(pkcs12name,
xmlSecKeyDataFormatPkcs12,
password,
NULL, NULL);
if ( dsigCtx->signKey == NULL ) {
xmlFachoPrintError("error: failed to load pkcs12\n");
goto done;
}
xmlXadesPolicyIdentifierCtx policyIdCtx;
// por ahora el hash del identificador lo tomamos del pdf de la dian
policyIdCtx.contentCallback = &xmlFachoPolicyIdentifierCtxFromFilename;
xadesDsigCtx = xmlXadesDSigCtxCreate(dsigCtx, XADES_DIGEST_SHA256, &policyIdCtx);
if ( xadesDsigCtx == NULL ) {
xmlFachoPrintError("error: xades context creating failed.\n");
return(-1);
}
// debe existir el elemento antes del firmado
node = xmlFachoTmplUBLExtensionAddExtensionContent(doc);
if ( node == NULL ) {
xmlFachoPrintError("error: failed to add UBLExtensions/UBLExtension/ExtensionContent\n");
goto done;
}
// realizar firma de documento
if ( xmlXadesDSigCtxSign(xadesDsigCtx, signNode) < 0 ) {
xmlFachoPrintError("error: signature failed\n");
goto done;
}
xmlUnlinkNode(signNode);
xmlSecAddChildNode(node, signNode);
xmlDocDump(out, doc);
res = 0;
done:
if ( xadesDsigCtx != NULL ) {
xmlXadesDSigCtxDestroy(xadesDsigCtx);
}
if ( dsigCtx != NULL ) {
xmlSecDSigCtxDestroy(dsigCtx);
}
if ( doc != NULL ) {
xmlFreeDoc(doc);
}
return(res);
}
static xmlNodePtr
xmlFachoTmplUBLExtensionAddExtensionContent(xmlDocPtr doc) {
xmlNodePtr node = NULL;
xmlNodePtr parent = NULL;
const xmlChar ublExtensionsName[] = "UBLExtensions";
const xmlChar ublExtensionName[] = "UBLExtension";
const xmlChar extensionContentName[] = "ExtensionContent";
parent = xmlSecFindNode(xmlDocGetRootElement(doc), ublExtensionsName, ublExtensionDSigNs);
if ( parent == NULL ) {
parent = xmlSecAddChild(xmlDocGetRootElement(doc), ublExtensionsName, ublExtensionDSigNs);
if ( parent == NULL ) {
xmlFachoPrintError("error: failed to cleate UBLExtensions.\n");
return(NULL);
}
}
// adicionamos nuevo elemento UBLExtension
node = xmlSecAddChild(parent, ublExtensionName, ublExtensionDSigNs);
if ( node == NULL ) {
xmlFachoPrintError("error: failed to add UBLExtension\n");
xmlFreeNode(parent);
return(NULL);
}
// adicionamos nuevo elemento ExtensionContent
node = xmlSecAddChild(node, extensionContentName, ublExtensionDSigNs);
if ( node == NULL ) {
xmlFachoPrintError("error: failed to add ExtensionContent");
return(NULL);
}
return(node);
}
static int
xmlFachoPolicyIdentifierCtxFromFilename(const xmlChar *policyId, xmlSecBufferPtr buffer) {
static unsigned char politicafirmav2[] = {
/**
* generado con https://github.com/Jamesits/bin2array
*/
#include "politicafirmav2.c"
};
return xmlSecBufferAppend(buffer, politicafirmav2, sizeof(politicafirmav2));
}

View File

@ -0,0 +1,20 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#ifndef FACHO_SIGNER_H
#define FACHO_SIGNER_H
#include <stdio.h>
int
xmlFachoInit();
int
xmlFachoShutdown();
int
xmlFachoSignFile(FILE *out, const char *filename, const char *pkcs12name, const char *password);
#endif /* FACHO_SIGNER_H */

View File

@ -0,0 +1,47 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#include "xades/xades.h"
#include "facho_signer.h"
#include <stdio.h>
#include <stdlib.h>
static char *basename = NULL;
void usage(FILE *out);
int main(int argc, char *argv[]) {
int exitStatus = EXIT_SUCCESS;
basename = argv[0];
if (argc != 4) {
usage(stderr);
return(EXIT_FAILURE);
}
if ( xmlFachoInit() < 0 ) {
fprintf(stderr, "initialization failed.\n");
return(EXIT_FAILURE);
}
if ( xmlFachoSignFile( stdout, argv[1], argv[2], argv[3] ) != 0 ) {
fprintf(stderr, "fail to sign file\n");
exitStatus = EXIT_FAILURE;
}
xmlFachoShutdown();
return(exitStatus);
}
void
usage(FILE *out) {
fprintf(out, "%s: <factura.xml> <pc12> <password>\n", basename);
fprintf(out, "%s", "Firmado electronico para facturacion en Colombia.\n"
"Segun el documento (Anexo Técnico de Factura Electrónica de Venta Versión 1.7.-2020).\n"
"A considerar:\n" \
" * adiciona un nuevo elemento //ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent\n");
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,391 @@
/*
* Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MINUNIT_MINUNIT_H
#define MINUNIT_MINUNIT_H
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_WIN32)
#include <Windows.h>
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#define __func__ __FUNCTION__
#endif
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* Change POSIX C SOURCE version for pure c99 compilers */
#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif
#include <unistd.h> /* POSIX flags */
#include <time.h> /* clock_gettime(), time() */
#include <sys/time.h> /* gethrtime(), gettimeofday() */
#include <sys/resource.h>
#include <sys/times.h>
#include <string.h>
#if defined(__MACH__) && defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_time.h>
#endif
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
#define __func__ __extension__ __FUNCTION__
#endif
#else
#error "Unable to define timers for an unknown OS."
#endif
#include <stdio.h>
#include <math.h>
/* Maximum length of last message */
#define MINUNIT_MESSAGE_LEN 1024
/* Accuracy with which floats are compared */
#define MINUNIT_EPSILON 1E-12
/* Misc. counters */
static int minunit_run = 0;
static int minunit_assert = 0;
static int minunit_fail = 0;
static int minunit_status = 0;
/* Timers */
static double minunit_real_timer = 0;
static double minunit_proc_timer = 0;
/* Last message */
static char minunit_last_message[MINUNIT_MESSAGE_LEN];
/* Test setup and teardown function pointers */
static void (*minunit_setup)(void) = NULL;
static void (*minunit_teardown)(void) = NULL;
/* Definitions */
#define MU_TEST(method_name) static void method_name(void)
#define MU_TEST_SUITE(suite_name) static void suite_name(void)
#define MU__SAFE_BLOCK(block) do {\
block\
} while(0)
/* Run test suite and unset setup and teardown functions */
#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
suite_name();\
minunit_setup = NULL;\
minunit_teardown = NULL;\
)
/* Configure setup and teardown functions */
#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
minunit_setup = setup_fun;\
minunit_teardown = teardown_fun;\
)
/* Test runner */
#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
if (minunit_real_timer==0 && minunit_proc_timer==0) {\
minunit_real_timer = mu_timer_real();\
minunit_proc_timer = mu_timer_cpu();\
}\
if (minunit_setup) (*minunit_setup)();\
minunit_status = 0;\
test();\
minunit_run++;\
if (minunit_status) {\
minunit_fail++;\
printf("F");\
printf("\n%s\n", minunit_last_message);\
}\
fflush(stdout);\
if (minunit_teardown) (*minunit_teardown)();\
)
/* Report */
#define MU_REPORT() MU__SAFE_BLOCK(\
double minunit_end_real_timer;\
double minunit_end_proc_timer;\
printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
minunit_end_real_timer = mu_timer_real();\
minunit_end_proc_timer = mu_timer_cpu();\
printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
minunit_end_real_timer - minunit_real_timer,\
minunit_end_proc_timer - minunit_proc_timer);\
)
#define MU_EXIT_CODE minunit_fail
/* Assertions */
#define mu_check(test) MU__SAFE_BLOCK(\
minunit_assert++;\
if (!(test)) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_fail(message) MU__SAFE_BLOCK(\
minunit_assert++;\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
minunit_status = 1;\
return;\
)
#define mu_assert(test, message) MU__SAFE_BLOCK(\
minunit_assert++;\
if (!(test)) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
int minunit_tmp_e;\
int minunit_tmp_r;\
minunit_assert++;\
minunit_tmp_e = (expected);\
minunit_tmp_r = (result);\
if (minunit_tmp_e != minunit_tmp_r) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
double minunit_tmp_e;\
double minunit_tmp_r;\
minunit_assert++;\
minunit_tmp_e = (expected);\
minunit_tmp_r = (result);\
if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
const char* minunit_tmp_e = expected;\
const char* minunit_tmp_r = result;\
minunit_assert++;\
if (!minunit_tmp_e) {\
minunit_tmp_e = "<null pointer>";\
}\
if (!minunit_tmp_r) {\
minunit_tmp_r = "<null pointer>";\
}\
if(strcmp(minunit_tmp_e, minunit_tmp_r)) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
/*
* The following two functions were written by David Robert Nadeau
* from http://NadeauSoftware.com/ and distributed under the
* Creative Commons Attribution 3.0 Unported License
*/
/**
* Returns the real time, in seconds, or -1.0 if an error occurred.
*
* Time is measured since an arbitrary and OS-dependent start time.
* The returned real time is only useful for computing an elapsed time
* between two calls to this function.
*/
static double mu_timer_real(void)
{
#if defined(_WIN32)
/* Windows 2000 and later. ---------------------------------- */
LARGE_INTEGER Time;
LARGE_INTEGER Frequency;
QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&Time);
Time.QuadPart *= 1000000;
Time.QuadPart /= Frequency.QuadPart;
return (double)Time.QuadPart / 1000000.0;
#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
/* HP-UX, Solaris. ------------------------------------------ */
return (double)gethrtime( ) / 1000000000.0;
#elif defined(__MACH__) && defined(__APPLE__)
/* OSX. ----------------------------------------------------- */
static double timeConvert = 0.0;
if ( timeConvert == 0.0 )
{
mach_timebase_info_data_t timeBase;
(void)mach_timebase_info( &timeBase );
timeConvert = (double)timeBase.numer /
(double)timeBase.denom /
1000000000.0;
}
return (double)mach_absolute_time( ) * timeConvert;
#elif defined(_POSIX_VERSION)
/* POSIX. --------------------------------------------------- */
struct timeval tm;
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
{
struct timespec ts;
#if defined(CLOCK_MONOTONIC_PRECISE)
/* BSD. --------------------------------------------- */
const clockid_t id = CLOCK_MONOTONIC_PRECISE;
#elif defined(CLOCK_MONOTONIC_RAW)
/* Linux. ------------------------------------------- */
const clockid_t id = CLOCK_MONOTONIC_RAW;
#elif defined(CLOCK_HIGHRES)
/* Solaris. ----------------------------------------- */
const clockid_t id = CLOCK_HIGHRES;
#elif defined(CLOCK_MONOTONIC)
/* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
const clockid_t id = CLOCK_MONOTONIC;
#elif defined(CLOCK_REALTIME)
/* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
const clockid_t id = CLOCK_REALTIME;
#else
const clockid_t id = (clockid_t)-1; /* Unknown. */
#endif /* CLOCK_* */
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
/* Fall thru. */
}
#endif /* _POSIX_TIMERS */
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
gettimeofday( &tm, NULL );
return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
#else
return -1.0; /* Failed. */
#endif
}
/**
* Returns the amount of CPU time used by the current process,
* in seconds, or -1.0 if an error occurred.
*/
static double mu_timer_cpu(void)
{
#if defined(_WIN32)
/* Windows -------------------------------------------------- */
FILETIME createTime;
FILETIME exitTime;
FILETIME kernelTime;
FILETIME userTime;
/* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
if ( GetProcessTimes( GetCurrentProcess( ),
&createTime, &exitTime, &kernelTime, &userTime ) != 0 )
{
ULARGE_INTEGER userSystemTime;
memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
return (double)userSystemTime.QuadPart / 10000000.0;
}
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
/* Prefer high-res POSIX timers, when available. */
{
clockid_t id;
struct timespec ts;
#if _POSIX_CPUTIME > 0
/* Clock ids vary by OS. Query the id, if possible. */
if ( clock_getcpuclockid( 0, &id ) == -1 )
#endif
#if defined(CLOCK_PROCESS_CPUTIME_ID)
/* Use known clock id for AIX, Linux, or Solaris. */
id = CLOCK_PROCESS_CPUTIME_ID;
#elif defined(CLOCK_VIRTUAL)
/* Use known clock id for BSD or HP-UX. */
id = CLOCK_VIRTUAL;
#else
id = (clockid_t)-1;
#endif
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
}
#endif
#if defined(RUSAGE_SELF)
{
struct rusage rusage;
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
return (double)rusage.ru_utime.tv_sec +
(double)rusage.ru_utime.tv_usec / 1000000.0;
}
#endif
#if defined(_SC_CLK_TCK)
{
const double ticks = (double)sysconf( _SC_CLK_TCK );
struct tms tms;
if ( times( &tms ) != (clock_t)-1 )
return (double)tms.tms_utime / ticks;
}
#endif
#if defined(CLOCKS_PER_SEC)
{
clock_t cl = clock( );
if ( cl != (clock_t)-1 )
return (double)cl / (double)CLOCKS_PER_SEC;
}
#endif
#endif
return -1; /* Failed. */
}
#ifdef __cplusplus
}
#endif
#endif /* MINUNIT_MINUNIT_H */

View File

@ -0,0 +1,13 @@
#ifndef MINUNIT_EXT_H
#include <string.h>
#include "minunit.h"
// cuando escribe esto el compilar me arrojo que si no era
// mejor usar mu_assert_string_eq increble a
//int mu_assert_string_equals(const char *a, const char *b) {
// return mu_assert(strcmp(a, b) == 0, "string not equals");
//}
#endif //MINUNIT_EXT_H

View File

@ -0,0 +1,456 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#include "xades.h"
#include <xmlsec/templates.h>
#include <time.h>
#include <string.h>
static const xmlChar xmlXadesNodeQualifyingProperties[] = "QualifyingProperties";
static const xmlChar xmlXadesNodeSignedProperties[] = "SignedProperties";
static const xmlChar xmlXadesNodeSignedSignatureProperties[] = "SignedSignatureProperties";
static const xmlChar xmlXadesNodeSigningTime[] = "SigningTime";
static const xmlChar xmlXadesNodeSigningCertificate[] = "SigningCertificate";
static const xmlChar xmlXadesNodeCert[] = "Cert";
static const xmlChar xmlXadesNodeCertDigest[] = "CertDigest";
static const xmlChar xmlXadesNodeSignaturePolicyIdentifier[] = "SignaturePolicyIdentifier";
static const xmlChar xmlXadesNodeSignaturePolicyId[] = "SignaturePolicyId";
static const xmlChar xmlXadesNodeSigPolicyId[] = "SigPolicyId";
static const xmlChar xmlXadesNodeIdentifier[] = "Identifier";
static const xmlChar xmlXadesNodeDescription[] = "Description";
static const xmlChar xmlXadesNodeSigPolicyHash[] = "SigPolicyHash";
static const xmlChar xmlXadesNodeSignerRole[] = "SignerRole";
static const xmlChar xmlXadesNodeClaimedRoles[] = "ClaimedRoles";
static const xmlChar xmlXadesNodeClaimedRole[] = "ClaimedRole";
static const xmlChar xmlXadesNodeIssuerSerial[] = "IssuerSerial";
static const xmlChar xmlXadesNodeX509IssuerName[] = "X509IssuerName";
static const xmlChar xmlXadesNodeX509IssuerNumber[] = "X509IssuerNumber";
static const xmlChar xmlXadesDSigNs[] = "http://uri.etsi.org/01903/v1.3.2#";
xmlNodePtr
xmlXadesAddChildRecursiveNs(xmlNodePtr startNode, const xmlChar* path, const xmlChar* nsPrefix) {
char *curToken;
char* cpath = strdup((char *)path);
char* savePtr;
xmlNodePtr curNode = NULL;
xmlNodePtr parentNode = startNode;
curToken = strtok_r(cpath, "/", &savePtr);
while(curToken != NULL) {
curNode = xmlSecFindChild(parentNode, BAD_CAST curToken, nsPrefix);
if (curNode == NULL) {
curNode = xmlSecAddChild(parentNode, BAD_CAST curToken, nsPrefix);
if (curNode == NULL) {
xmlXadesInternalError("xmlSecAddChild(%s)", curToken);
return(NULL);
}
}
parentNode = curNode;
curToken = strtok_r(NULL, "/", &savePtr);
}
free(cpath);
return(curNode);
}
xmlNodePtr
xmlXadesTmplQualifyingPropertiesCreate(xmlDocPtr doc, xmlNodePtr signatureNode, const xmlChar *id) {
xmlNodePtr objectNode;
xmlNodePtr qualifyingPropertiesNode;
xmlNewGlobalNs(doc, xmlXadesDSigNs, BAD_CAST "xades");
objectNode = xmlSecTmplSignatureAddObject(signatureNode, NULL, NULL, NULL);
if (objectNode == NULL) {
xmlXadesInternalError("xmlSecTmplSignatureAddObject(signatureNode)", NULL);
return(NULL);
}
qualifyingPropertiesNode = xmlSecAddChild(objectNode, xmlXadesNodeQualifyingProperties, xmlXadesDSigNs);
if (qualifyingPropertiesNode == NULL) {
xmlXadesXmlError2("xmlNewDocNode", NULL, "node=%s", xmlXadesErrorsSafeString(xmlXadesNodeQualifyingProperties));
return(NULL);
}
if (id != NULL) {
xmlSetProp(qualifyingPropertiesNode, BAD_CAST "Id", id);
}
return(qualifyingPropertiesNode);
}
xmlNodePtr
xmlXadesTmplQualifyingPropertiesCreateNsPref(xmlDocPtr doc, const xmlChar* id, const xmlChar* nsPrefix) {
xmlNodePtr qualifyingPropertiesNode;
xmlNsPtr ns;
// crear nodo
qualifyingPropertiesNode = xmlNewDocNode(doc, NULL, xmlXadesNodeQualifyingProperties, NULL);
if (qualifyingPropertiesNode == NULL) {
xmlXadesXmlError2("xmlNewDocNode", NULL, "node=%s", xmlXadesErrorsSafeString(xmlXadesNodeQualifyingProperties));
return(NULL);
}
// crear namespace y asignar
ns = xmlNewNs(qualifyingPropertiesNode, xmlXadesDSigNs, nsPrefix);
if (ns == NULL) {
xmlXadesXmlError2("xmlNewNs", NULL,
"ns=%s", xmlXadesErrorsSafeString(xmlXadesDSigNs));
xmlFreeNode(qualifyingPropertiesNode);
return(NULL);
}
xmlSetNs(qualifyingPropertiesNode, ns);
if (id != NULL) {
xmlSetProp(qualifyingPropertiesNode, BAD_CAST "Id", id);
}
return (qualifyingPropertiesNode);
}
xmlNodePtr
xmlXadesTmplAddSignedProperties(xmlNodePtr qualifyingPropertiesNode, const xmlChar* id) {
xmlNodePtr cur;
xmlXadesAssert2(qualifyingPropertiesNode != NULL, NULL);
cur = xmlSecAddChild(qualifyingPropertiesNode, xmlXadesNodeSignedProperties, xmlXadesDSigNs);
if (cur == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeSignedProperties)", NULL);
return(NULL);
}
if (id != NULL) {
xmlSetProp(cur, BAD_CAST "Id", id);
}
return(cur);
}
xmlNodePtr
xmlXadesTmplAddSignedSignatureProperties(xmlNodePtr signedPropertiesNode, struct tm* signingTime) {
xmlNodePtr cur;
xmlNodePtr node;
xmlXadesAssert2(signedPropertiesNode != NULL, NULL);
// add SignedSignatureProperties
node = xmlSecAddChild(signedPropertiesNode, xmlXadesNodeSignedSignatureProperties, xmlXadesDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeSignedSignatureProperties)", NULL);
return(NULL);
}
// add SignigTime
cur = xmlSecAddChild(node, xmlXadesNodeSigningTime, xmlXadesDSigNs);
if (cur == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeSigningTime)", NULL);
xmlFreeNode(node);
return(NULL);
}
{
int ret;
char strtime[200];
if (strftime(strtime, sizeof(strtime), "%Y-%m-%dT%T", signingTime) == 0) {
xmlXadesInternalError("strftime", NULL);
xmlFreeNode(cur);
xmlFreeNode(node);
return(NULL);
}
ret = xmlSecNodeEncodeAndSetContent(cur, BAD_CAST strtime);
if (ret < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
xmlFreeNode(cur);
xmlFreeNode(node);
return(NULL);
}
}
return(node);
}
xmlNodePtr
xmlXadesTmplAddSigningCertificate(xmlNodePtr signedSignaturePropertiesNode, xmlSecTransformId digestMethodId) {
xmlNodePtr node;
xmlXadesAssert2(signedSignaturePropertiesNode != NULL, NULL);
if (xmlSecFindChild(signedSignaturePropertiesNode, xmlXadesNodeSigningCertificate, xmlXadesDSigNs) != NULL) {
xmlXadesNodeAlreadyPresentError(signedSignaturePropertiesNode, xmlXadesNodeSigningCertificate, NULL);
return(NULL);
}
node = xmlSecAddChild(signedSignaturePropertiesNode, xmlXadesNodeSigningCertificate, xmlXadesDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlsecAddChild(xmlXadesNodeSigningCertificate)", NULL);
return(NULL);
}
return(node);
}
xmlNodePtr
xmlXadesTmplAddCert(xmlNodePtr signingCertificateNode) {
xmlNodePtr certNode;
xmlXadesAssert2(signingCertificateNode != NULL, NULL);
certNode = xmlSecAddChild(signingCertificateNode, xmlXadesNodeCert, xmlXadesDSigNs);
if (certNode == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeCert)", NULL);
return(NULL);
}
return(certNode);
}
xmlNodePtr
xmlXadesTmplAddCertDigest(xmlNodePtr certNode, const xmlChar *digestMethod, const xmlChar *digestValue) {
xmlNodePtr node;
xmlXadesAssert2(certNode != NULL, NULL);
node = xmlSecAddChild(certNode, xmlXadesNodeCertDigest, xmlXadesDSigNs);
if ( node == NULL ) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeCertDigest)", NULL);
return(NULL);
}
if ( xmlXadesTmplAddDigest(node, digestMethod, digestValue) == NULL) {
xmlXadesInternalError("xmlXadesTmplAddDigest(node, digestMethodId)", NULL);
return(NULL);
}
return(certNode);
}
xmlNodePtr
xmlXadesTmplAddSignaturePolicyIdentifier(xmlNodePtr signedSignaturePropertiesNode) {
xmlNodePtr cur;
xmlXadesAssert2(signedSignaturePropertiesNode != NULL, NULL);
cur = xmlSecAddChild(signedSignaturePropertiesNode, xmlXadesNodeSignaturePolicyIdentifier, xmlXadesDSigNs);
if (cur == NULL) {
xmlXadesInternalError("xmlsecAddChild(xmlXadesNodeSignaturePolicyIdentifier)", NULL);
return(NULL);
}
return(cur);
}
xmlNodePtr
xmlXadesTmplAddSignaturePolicyId(xmlNodePtr signaturePolicyIdentifierNode) {
xmlNodePtr cur;
xmlXadesAssert2(signaturePolicyIdentifierNode != NULL, NULL);
cur = xmlSecAddChild(signaturePolicyIdentifierNode, xmlXadesNodeSignaturePolicyId, xmlXadesDSigNs);
if (cur == NULL) {
xmlXadesInternalError("xmlsecAddChild(cur)", NULL);
return(NULL);
}
return(cur);
}
xmlNodePtr
xmlXadesTmplAddSigPolicyId(xmlNodePtr signaturePolicyId, const xmlChar* identifier, const xmlChar *description) {
xmlNodePtr sigPolicyIdNode;
xmlNodePtr node;
int ret;
sigPolicyIdNode = xmlSecAddChild(signaturePolicyId, xmlXadesNodeSigPolicyId, xmlXadesDSigNs);
if (sigPolicyIdNode == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeSigPolicyId)", NULL);
return(NULL);
}
node = xmlSecAddChild(sigPolicyIdNode, xmlXadesNodeIdentifier, xmlXadesDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeIdentifier)", NULL);
xmlFreeNode(sigPolicyIdNode);
return(NULL);
}
ret = xmlSecNodeEncodeAndSetContent(node, identifier);
if (ret < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
xmlFreeNode(sigPolicyIdNode);
xmlFreeNode(node);
return(NULL);
}
node = xmlSecAddChild(sigPolicyIdNode, xmlXadesNodeDescription, xmlXadesDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeDescription)", NULL);
xmlFreeNode(sigPolicyIdNode);
return(NULL);
}
ret = xmlSecNodeEncodeAndSetContent(node, description);
if (ret < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
xmlFreeNode(sigPolicyIdNode);
xmlFreeNode(node);
return(NULL);
}
return(sigPolicyIdNode);
}
xmlNodePtr
xmlXadesTmplAddSigPolicyHash(xmlNodePtr parentNode, xmlSecTransformId digestMethodId) {
xmlNodePtr node;
xmlXadesAssert2(parentNode != NULL, NULL);
//add policyHash
node = xmlSecAddChild(parentNode, xmlXadesNodeSigPolicyHash, xmlXadesDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeSigPolicyHash)", NULL);
return(NULL);
}
if ( xmlXadesTmplAddDigest(node, digestMethodId->href, NULL) == NULL) {
xmlXadesInternalError("xmlXadesTmplAddDigest(node, digestMethodId)", NULL);
return(NULL);
}
return node;
}
// MACHETE(bit4bit) como usar SecTransform para almacenar el digest
xmlNodePtr
xmlXadesTmplAddDigest(xmlNodePtr parentNode, const xmlChar *digestMethod, const xmlChar *digestValue) {
xmlNodePtr node;
xmlXadesAssert2(parentNode != NULL, NULL);
if ( digestMethod != NULL ) {
node = xmlSecAddChild(parentNode, xmlSecNodeDigestMethod, xmlSecDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlSecNodeDigestMethod)", NULL);
return(NULL);
}
if (xmlSetProp(node, xmlSecAttrAlgorithm, digestMethod) == NULL) {
xmlXadesXmlError2("xmlSetProp", NULL,
"name=%s", xmlXadesErrorsSafeString(xmlSecAttrAlgorithm));
xmlUnlinkNode(node);
xmlFreeNode(node);
return(NULL);
}
}
if ( digestValue != NULL ) {
node = xmlSecAddChild(parentNode, xmlSecNodeDigestValue, xmlSecDSigNs);
if (node == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlSecNodeDigestValue)", NULL);
return(NULL);
}
if (xmlSecNodeEncodeAndSetContent(node, digestValue) < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
return(NULL);
}
}
return parentNode;
}
xmlNodePtr
xmlXadesTmplAddSignerRole(xmlNodePtr signedSignaturePropertiesNode, const xmlChar* role) {
xmlNodePtr signerRoleNode;
xmlNodePtr claimedRolesNode;
xmlNodePtr claimedRoleNode;
int ret;
signerRoleNode = xmlSecAddChild(signedSignaturePropertiesNode, xmlXadesNodeSignerRole, xmlXadesDSigNs);
if (signerRoleNode == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeSignerRole)", NULL);
return(NULL);
}
claimedRolesNode = xmlSecAddChild(signerRoleNode, xmlXadesNodeClaimedRoles, xmlXadesDSigNs);
if (claimedRolesNode == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeClaimedRoles)", NULL);
xmlUnlinkNode(signerRoleNode);
xmlFreeNode(signerRoleNode);
return(NULL);
}
claimedRoleNode = xmlSecAddChild(claimedRolesNode, xmlXadesNodeClaimedRole, xmlXadesDSigNs);
if (claimedRoleNode == NULL) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeClaimedRole)", NULL);
xmlUnlinkNode(signerRoleNode);
xmlFreeNode(signerRoleNode);
return(NULL);
}
ret = xmlSecNodeEncodeAndSetContent(claimedRoleNode, role);
if (ret < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
xmlUnlinkNode(signerRoleNode);
xmlFreeNode(signerRoleNode);
return(NULL);
}
return(signerRoleNode);
}
xmlNodePtr
xmlXadesTmplAddIssuerSerial(xmlNodePtr certNode, const xmlChar *issuerName, const xmlChar *issuerNumber) {
xmlNodePtr issuerSerialNode;
xmlNodePtr node;
xmlXadesAssert2(certNode != NULL, NULL);
issuerSerialNode = xmlSecAddChild(certNode, xmlXadesNodeIssuerSerial, xmlXadesDSigNs);
if ( issuerSerialNode == NULL ) {
xmlXadesInternalError("xmlSecAddChild(certNode, xmlXadesIssuerSerial)", NULL);
return(NULL);
}
node = xmlSecAddChild(issuerSerialNode, xmlXadesNodeX509IssuerName, xmlSecDSigNs);
if ( node == NULL ) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeX509IssuerName)", NULL);
xmlFreeNode(issuerSerialNode);
return(NULL);
}
if (xmlSecNodeEncodeAndSetContent(node, issuerName) < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
xmlUnlinkNode(issuerSerialNode);
xmlFreeNode(issuerSerialNode);
return(NULL);
}
node = xmlSecAddChild(issuerSerialNode, xmlXadesNodeX509IssuerNumber, xmlSecDSigNs);
if ( node == NULL ) {
xmlXadesInternalError("xmlSecAddChild(xmlXadesNodeX509IssuerNumber)", NULL);
xmlFreeNode(issuerSerialNode);
return(NULL);
}
if (xmlSecNodeEncodeAndSetContent(node, issuerNumber) < 0) {
xmlXadesInternalError("xmlSecNodeEncodeAndSetContent", NULL);
xmlUnlinkNode(issuerSerialNode);
xmlFreeNode(issuerSerialNode);
return(NULL);
}
return(issuerSerialNode);
}

View File

@ -0,0 +1,301 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#include "config.h"
#include "xades.h"
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/bio.h>
#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <xmlsec/buffer.h>
#include <xmlsec/app.h>
#include <xmlsec/transforms.h>
#include <xmlsec/keysdata.h>
#ifdef XMLSEC_NO_CRYPTO_DYNAMIC_LOADING
#include <xmlsec/openssl/x509.h>
#include <xmlsec/openssl/crypto.h>
#include <xmlsec/openssl/symbols.h>
#endif
#include <string.h>
#include <ctype.h>
static xmlChar *
xmlXadesSha256DigestValueInBase64(const unsigned char *message, size_t message_len);
static xmlNodePtr
xmlXadesXPathFirstElement(xmlDocPtr doc, const xmlChar *xpath);
xmlXadesDSigCtxPtr
xmlXadesDSigCtxCreate(xmlSecDSigCtxPtr dsigCtx, XADES_DIGEST_METHOD digestMethod, xmlXadesPolicyIdentifierCtxPtr policyCtx) {
xmlXadesDSigCtxPtr ctx = NULL;
ctx = malloc(sizeof(xmlXadesDSigCtx));
if ( ctx == NULL ) {
return(NULL);
}
ctx->dsigCtx = dsigCtx;
ctx->digestMethod = digestMethod;
ctx->policyCtx = policyCtx;
return ctx;
}
int
xmlXadesDSigCtxSign(xmlXadesDSigCtxPtr ctx, xmlNodePtr signNode) {
xmlNodePtr signingCertificateNode = NULL;
xmlSecKeyDataPtr keyDataX509;
xmlSecSize certsSize;
signingCertificateNode = xmlXadesXPathFirstElement(signNode->doc, BAD_CAST "//ds:Object/xades:QualifyingProperties//xades:SigningCertificate[1]");
if ( signingCertificateNode == NULL ) {
return(-1);
}
keyDataX509 = xmlSecKeyEnsureData(ctx->dsigCtx->signKey, xmlSecKeyDataX509Id);
if ( keyDataX509 == NULL ) {
xmlXadesInternalError("failed to get X509.\n", NULL);
return(-1);
}
certsSize = xmlSecOpenSSLKeyDataX509GetCertsSize(keyDataX509);
for (xmlSecSize i = 0; i < certsSize; i++) {
// calculamos el digest del certificado
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_n;
// TODO(bit4bit) podemos obtener el digest de openssl por medio de la transformacion? o se puede usar la transformacion para generar el digest?
xmlChar *digestMethod = NULL;
EVP_MD *digest = NULL;
switch(ctx->digestMethod) {
case XADES_DIGEST_SHA256:
digestMethod = (xmlChar *)xmlSecTransformSha256Id->href;
digest = (EVP_MD *) EVP_sha256();
break;
default:
xmlXadesInternalError("xmlXadesDSigCtxSign not known how to handle digest method.\n", NULL);
return(-1);
}
X509 *cert = xmlSecOpenSSLKeyDataX509GetCert(keyDataX509, i);
if ( cert == NULL ) {
xmlXadesInternalError("openssl: failed to get X509 cert.\n", NULL);
return(-1);
}
X509_digest(cert, digest, md, &md_n);
xmlChar *digestValue = xmlSecBase64Encode(md, md_n, 0);
xmlNodePtr certNode = xmlXadesTmplAddCert(signingCertificateNode);
if ( certNode == NULL ) {
xmlXadesInternalError("xmlXadesTmplAddCert(signingCertificateNode)\n", NULL);
return(-1);
}
// adicionamos digest
xmlXadesTmplAddCertDigest(certNode,
digestMethod,
digestValue);
char *issuerName = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
/* TODO(bit4bit) formatear?
char *issuerNamePtr = issuerName;
for(issuerNamePtr = strchr(issuerNamePtr, '/'); issuerNamePtr != NULL; issuerNamePtr = strchr(issuerNamePtr, '/')) {
if (issuerNamePtr == issuerName) {
issuerName += 1;
} else {
*issuerNamePtr = ',';
}
}*/
ASN1_INTEGER *serial = X509_get_serialNumber(cert);
BIGNUM *bn = ASN1_INTEGER_to_BN(serial, NULL);
if ( bn == NULL ) {
xmlXadesInternalError("unable to convert ASN1_INTEGER_to_BN to BN\n", NULL);
return(-1);
}
char *issuerNumber = BN_bn2dec(bn);
if ( issuerNumber == NULL ) {
xmlXadesInternalError("unable to convert BN to decimal string\n", NULL);
return(-1);
}
if (xmlXadesTmplAddIssuerSerial(certNode, BAD_CAST issuerName, BAD_CAST issuerNumber) == NULL) {
xmlXadesInternalError("xmlXadesTmplAddIssuerSerial", NULL);
return(-1);
}
BN_free(bn);
OPENSSL_free(issuerNumber);
}
// digest de policy identifier
xmlNodePtr sigPolicyId = xmlXadesXPathFirstElement(signNode->doc, BAD_CAST "//xades:SigPolicyId/xades:Identifier[1]");
if ( sigPolicyId == NULL ) {
xmlXadesInternalError("xmlXadesXPathFirstElement(xades:SigPolicyId/xades:Identifier\n", NULL);
return(-1);
}
if ( ctx->policyCtx == NULL ) {
xmlXadesInternalError("not found policy context.\n", NULL);
return(-1);
}
if ( ctx->policyCtx != NULL ) {
if ( ctx->policyCtx->contentCallback == NULL ) {
xmlXadesInternalError("not found policy content callback.\n", NULL);
return(-1);
}
xmlSecTransformCtxPtr transformCtx = xmlSecTransformCtxCreate();
if (transformCtx == NULL ) {
xmlXadesInternalError("xmlSecTransformCtxCreate().\n", NULL);
return(-1);
}
// elemento del digest
xmlNodePtr sigPolicyHashDigestMethod = xmlXadesXPathFirstElement(signNode->doc, BAD_CAST "//xades:SigPolicyHash/ds:DigestMethod[1]");
if ( sigPolicyHashDigestMethod == NULL ) {
xmlXadesInternalError("xmlXadesXPathFirstElement(xades:SigPolicyHash/xades:DigestMethod\n", NULL);
return(-1);
}
xmlSecTransformPtr transformPolicyDigestMethod = xmlSecTransformNodeRead(sigPolicyHashDigestMethod,
xmlSecTransformUsageDigestMethod,
transformCtx);
if ( transformPolicyDigestMethod == NULL ) {
xmlXadesInternalError("xmlSecTransformNodeRead\n", NULL);
xmlFreeNode(sigPolicyHashDigestMethod);
return(-1);
}
if ( xmlSecTransformCheckId(transformPolicyDigestMethod, xmlSecTransformSha256Id) == 0 ) {
xmlXadesInternalError("sigPolicyHash only support sha256 digest method .\n", NULL);
xmlFreeNode(sigPolicyHashDigestMethod);
return(-1);
}
// TODO(bit4bit) podemos usar xmlSecTransform para calcular el digest?
xmlNodePtr sigPolicyHashNode = xmlXadesXPathFirstElement(signNode->doc, BAD_CAST "//xades:SigPolicyHash[1]");
if ( sigPolicyHashNode == NULL ) {
xmlXadesInternalError("failed to find sigPolicyHash node.\n", NULL);
xmlFreeNode(sigPolicyHashDigestMethod);
return(-1);
}
// obtenemos contenido de la policy
xmlChar *identifier = xmlNodeListGetString(signNode->doc, sigPolicyId->xmlChildrenNode, 1);
xmlSecBufferPtr policyContent = xmlSecBufferCreate(1024);
;
if ( (ctx->policyCtx->contentCallback)(identifier, policyContent) < 0 ) {
xmlXadesInternalError("policyContext callback fails.\n", NULL);
xmlFree(identifier);
return(-1);
}
xmlFree(identifier);
xmlChar *policyHashValue = xmlXadesSha256DigestValueInBase64(xmlSecBufferGetData(policyContent),
xmlSecBufferGetSize(policyContent));
xmlSecBufferDestroy(policyContent);
xmlXadesTmplAddDigest(sigPolicyHashNode, NULL, policyHashValue);
}
return xmlSecDSigCtxSign(ctx->dsigCtx, signNode);
}
int
xmlXadesDSigCtxDestroy(xmlXadesDSigCtxPtr ctx) {
if ( ctx == NULL ) {
return(-1);
}
free(ctx);
return(0);
}
xmlNodePtr
xmlXadesXPathFirstElement(xmlDocPtr doc, const xmlChar *xpath) {
xmlXPathContextPtr xpathCtx;
xmlXPathObjectPtr xpathResult;
xmlNodePtr node;
// obtener QualifyingProteries
xpathCtx = xmlXPathNewContext(doc);
/* register namespaces */
// TOMADO DE: xmlsec1/src/xpath.c
for(xmlNsPtr ns = xmlDocGetRootElement(doc)->nsDef; ns != NULL; ns = ns->next) {
/* check that we have no other namespace with same prefix already */
if((ns->prefix != NULL) && (xmlXPathNsLookup(xpathCtx, ns->prefix) == NULL)){
int ret = xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href);
if(ret != 0) {
xmlXadesXmlError2("xmlXPathRegisterNs", NULL,
"prefix=%s", xmlSecErrorsSafeString(ns->prefix));
return(NULL);
}
}
}
xpathResult = xmlXPathEvalExpression(xpath, xpathCtx);
if ( xmlXPathNodeSetIsEmpty( xpathResult->nodesetval ) ) {
xmlXadesInternalError("can't find %s \n", xpath);
xmlXPathFreeObject(xpathResult);
return(NULL);
}
// obtener puntero a nodo
node = xpathResult->nodesetval->nodeTab[0];
if ( node->type != XML_ELEMENT_NODE ) {
xmlXadesInternalError("expected element\n", NULL);
return(NULL);
}
return(node);
}
static xmlChar *
xmlXadesSha256DigestValueInBase64(const unsigned char *message, size_t message_len)
{
unsigned char digest[2048];
unsigned int digest_len;
EVP_MD_CTX *mdctx;
if((mdctx = EVP_MD_CTX_new()) == NULL) {
xmlXadesInternalError("EVP_MD_CTX_new().\n", NULL);
return(NULL);
}
if(1 != EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) {
xmlXadesInternalError("EVP_DigestInit_ex().\n", NULL);
return(NULL);
}
if(1 != EVP_DigestUpdate(mdctx, message, message_len)) {
xmlXadesInternalError("EVP_DigestUpdate().\n", NULL);
return(NULL);
}
if(1 != EVP_DigestFinal_ex(mdctx, digest, &digest_len)) {
xmlXadesInternalError("EVP_DigestFinal_ex().\n", NULL);
return(NULL);
}
EVP_MD_CTX_free(mdctx);
return(xmlSecBase64Encode(digest, digest_len, 0));
}

View File

@ -0,0 +1,108 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#ifndef XADES_H
#define XADES_H
#include <libxml/tree.h>
#define XMLSEC_NO_XSLT 1
#include <xmlsec/xmltree.h>
#include <xmlsec/transforms.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/base64.h>
#include "xmlsec1/errors_helpers.h"
#define xmlXadesAssert2(p, ret) \
xmlSecAssert2(p, ret)
#define xmlXadesNodeNotFoundError(errorFunction, startNode, targetNodeName, errorObject) \
xmlSecNodeNotFoundError(errorFunction, startNode, targetNodeName, errorObject)
#define xmlXadesXmlError2(errorFunction, errorObject, msg, param) \
xmlSecXmlError2(errorFunction, errorObject, msg, param)
#define xmlXadesErrorsSafeString(msg) \
xmlSecErrorsSafeString(msg)
#define xmlXadesInternalError(errorFunction, errorObject) \
xmlSecInternalError(errorFunction, errorObject)
#define xmlXadesNodeAlreadyPresentError(parent, nodeName, errObject) \
xmlSecNodeAlreadyPresentError(parent, nodeName, errObject)
typedef int xmlXadesSize;
typedef enum _XADES_DIGEST_METHOD{
XADES_DIGEST_SHA256
} XADES_DIGEST_METHOD;
typedef int(*xmlXadesPolicyIdentifierContentCallback)(const xmlChar *policyId, xmlSecBuffer *);
typedef struct _xmlXadesPolicyIdentifierCtx xmlXadesPolicyIdentifierCtx, *xmlXadesPolicyIdentifierCtxPtr;
struct _xmlXadesPolicyIdentifierCtx {
xmlXadesPolicyIdentifierContentCallback contentCallback;
};
typedef struct _xmlXadesDSigCtx xmlXadesDSigCtx, *xmlXadesDSigCtxPtr;
struct _xmlXadesDSigCtx {
xmlSecDSigCtxPtr dsigCtx;
XADES_DIGEST_METHOD digestMethod;
xmlXadesPolicyIdentifierCtxPtr policyCtx;
};
xmlXadesDSigCtxPtr
xmlXadesDSigCtxCreate(xmlSecDSigCtxPtr dsigCtx, XADES_DIGEST_METHOD digestMethod, xmlXadesPolicyIdentifierCtxPtr policyCtx);
int
xmlXadesDSigCtxSign(xmlXadesDSigCtxPtr ctx, xmlNodePtr signNode);
int
xmlXadesDSigCtxDestroy(xmlXadesDSigCtxPtr ctx);
xmlNodePtr
xmlXadesTmplQualifyingPropertiesCreate(xmlDocPtr doc, xmlNodePtr signatureNode, const xmlChar *id);
xmlNodePtr
xmlXadesTmplAddSignedProperties(xmlNodePtr qualifyingPropertiesNode, const xmlChar* id);
xmlNodePtr
xmlXadesTmplAddSigningCertificate(xmlNodePtr parentNode, xmlSecTransformId digestMethodId);
xmlNodePtr
xmlXadesTmplAddCert(xmlNodePtr signingCertificateNode);
xmlNodePtr
xmlXadesTmplAddCertDigest(xmlNodePtr signingCertificateNode, const xmlChar *digestMethod, const xmlChar *digestValue);
xmlNodePtr
xmlXadesTmplAddSignedSignatureProperties(xmlNodePtr parentNode, struct tm* signingTime);
xmlNodePtr
xmlXadesTmplAddSignaturePolicyIdentifier(xmlNodePtr signedSignaturePropertiesNode);
xmlNodePtr
xmlXadesTmplAddSignaturePolicyId(xmlNodePtr signaturePolicyIdentifierNode);
xmlNodePtr
xmlXadesTmplAddSigPolicyId(xmlNodePtr signaturePolicyId, const xmlChar* identifier, const xmlChar *description);
xmlNodePtr
xmlXadesTmplAddSigPolicyHash(xmlNodePtr parentNode, xmlSecTransformId digestMethodId);
xmlNodePtr
xmlXadesTmplAddSignerRole(xmlNodePtr signedSignaturePropertiesNode, const xmlChar* role);
xmlNodePtr
xmlXadesTmplAddDigest(xmlNodePtr parentNode, const xmlChar *digestMethod, const xmlChar *digestValue);
xmlNodePtr
xmlXadesTmplAddIssuerSerial(xmlNodePtr certNode, const xmlChar *issuerName, const xmlChar *issuerNumber);
#endif //XADES_H

View File

@ -0,0 +1,94 @@
/**
* This file is part of facho. The COPYRIGHT file at the top level of
* this repository contains the full copyright notices and license terms.
*/
#include <time.h>
#include <libxml/tree.h>
#include "minunit.h"
#include "xades.h"
MU_TEST(test_xml_add_node_recursive) {
xmlDocPtr doc;
xmlNodePtr root;
xmlNodePtr child;
xmlChar* xmlbuff;
int xmlbuffsize;
doc = xmlNewDoc(BAD_CAST "1.0");
root = xmlNewNode(NULL, BAD_CAST "root");
xmlDocSetRootElement(doc, root);
child = xmlXadesAddChildRecursiveNs(root, BAD_CAST "A/B/C", NULL);
mu_check(child != NULL);
xmlDocDumpMemory(doc, &xmlbuff, &xmlbuffsize);
mu_assert_string_eq("<?xml version=\"1.0\"?>\n"
"<root>\n"
"<A>\n"
"<B>\n"
"<C/>\n"
"</B>\n"
"</A>\n"
"</root>\n"
, (char *)xmlbuff);
}
MU_TEST(test_qualifying_properties_layout) {
xmlDocPtr doc;
xmlNodePtr root;
xmlNodePtr node;
xmlChar* xmlbuff;
int buffersize;
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_year = 2021 - 1900;
tm.tm_mon = 11;
tm.tm_mday = 6;
tm.tm_hour = 12;
tm.tm_min = 0;
tm.tm_sec = 50;
doc = xmlNewDoc(BAD_CAST "1.0");
root = xmlNewNode(NULL, BAD_CAST "root");
xmlDocSetRootElement(doc, root);
node = xmlXadesTmplQualifyingPropertiesCreateNsPref(doc, BAD_CAST "123", NULL);
xmlXadesTmplAddSignedSignatureProperties(node, &tm);
mu_check(node != NULL);
xmlSecAddChildNode(root, node);
xmlDocDumpMemory(doc, &xmlbuff, &buffersize);
// bit4bit: no se como pasar el namespace al root
mu_assert_string_eq("<?xml version=\"1.0\"?>\n"
"<root>\n"
"<QualifyingProperties xmlns=\"http://uri.etsi.org/01903/v1.3.2#\" id=\"123\">\n"
"<SignedProperties>\n"
"<SignedSignatureProperties>\n"
"<SigningTime>2021-12-06T12:00:50</SigningTime>\n"
"</SignedSignatureProperties>\n"
"</SignedProperties>\n"
"</QualifyingProperties>\n"
"</root>\n"
, (char *)xmlbuff);
xmlFree(xmlbuff);
xmlFreeDoc(doc);
}
MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(test_xml_add_node_recursive);
MU_RUN_TEST(test_qualifying_properties_layout);
}
int main() {
MU_RUN_SUITE(test_suite);
MU_REPORT();
return MU_EXIT_CODE;
}

View File

@ -0,0 +1,260 @@
/*
* XML Security Library (http://www.aleksey.com/xmlsec).
*
*
* This is free software; see Copyright file in the source
* distribution for preciese wording.
*
* Copyright (C) 2002-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
*/
/**
* SECTION:errors
* @Short_description: Error reporting and logging functions.
* @Stability: Stable
*
*/
#define XMLSEC_PRIVATE 1
#define XMLSEC_NO_XSLT 1
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <libxml/tree.h>
#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/private.h>
#include <xmlsec/errors.h>
/* Must be bigger than fatal_error */
#define XMLSEC_ERRORS_BUFFER_SIZE 1024
/* Must fit into xmlChar[XMLSEC_ERRORS_BUFFER_SIZE] */
static const xmlChar fatal_error[] = "Can not format error message";
typedef struct _xmlSecErrorDescription xmlSecErrorDescription, *xmlSecErrorDescriptionPtr;
struct _xmlSecErrorDescription {
int errorCode;
const char* errorMsg;
};
static xmlSecErrorDescription xmlSecErrorsTable[XMLSEC_ERRORS_MAX_NUMBER + 1] = {
{ XMLSEC_ERRORS_R_XMLSEC_FAILED, "xmlsec library function failed" },
{ XMLSEC_ERRORS_R_MALLOC_FAILED, "malloc function failed" },
{ XMLSEC_ERRORS_R_STRDUP_FAILED, "strdup function failed" },
{ XMLSEC_ERRORS_R_CRYPTO_FAILED, "crypto library function failed" },
{ XMLSEC_ERRORS_R_XML_FAILED, "libxml2 library function failed" },
{ XMLSEC_ERRORS_R_XSLT_FAILED, "libxslt library function failed" },
{ XMLSEC_ERRORS_R_IO_FAILED, "io function failed" },
{ XMLSEC_ERRORS_R_DISABLED, "feature is disabled" },
{ XMLSEC_ERRORS_R_NOT_IMPLEMENTED, "feature is not implemented" },
{ XMLSEC_ERRORS_R_INVALID_CONFIG, "invalid configuration" },
{ XMLSEC_ERRORS_R_INVALID_SIZE, "invalid size" },
{ XMLSEC_ERRORS_R_INVALID_DATA, "invalid data" },
{ XMLSEC_ERRORS_R_INVALID_RESULT, "invalid result" },
{ XMLSEC_ERRORS_R_INVALID_TYPE, "invalid type" },
{ XMLSEC_ERRORS_R_INVALID_OPERATION, "invalid operation" },
{ XMLSEC_ERRORS_R_INVALID_STATUS, "invalid status" },
{ XMLSEC_ERRORS_R_INVALID_FORMAT, "invalid format" },
{ XMLSEC_ERRORS_R_DATA_NOT_MATCH, "data do not match" },
{ XMLSEC_ERRORS_R_INVALID_VERSION, "invalid version" },
{ XMLSEC_ERRORS_R_INVALID_NODE, "invalid node" },
{ XMLSEC_ERRORS_R_INVALID_NODE_CONTENT, "invalid node content" },
{ XMLSEC_ERRORS_R_INVALID_NODE_ATTRIBUTE, "invalid node attribute" },
{ XMLSEC_ERRORS_R_MISSING_NODE_ATTRIBUTE, "missing node attribute" },
{ XMLSEC_ERRORS_R_NODE_ALREADY_PRESENT, "node already present" },
{ XMLSEC_ERRORS_R_UNEXPECTED_NODE, "unexpected node" },
{ XMLSEC_ERRORS_R_NODE_NOT_FOUND, "node node found" },
{ XMLSEC_ERRORS_R_INVALID_TRANSFORM, "invalid transform" },
{ XMLSEC_ERRORS_R_INVALID_TRANSFORM_KEY, "invalid transform key" },
{ XMLSEC_ERRORS_R_INVALID_URI_TYPE, "invalid URI type" },
{ XMLSEC_ERRORS_R_TRANSFORM_SAME_DOCUMENT_REQUIRED, "same document is required for transform" },
{ XMLSEC_ERRORS_R_TRANSFORM_DISABLED, "transform is disabled" },
{ XMLSEC_ERRORS_R_INVALID_KEY_DATA, "invalid key data" },
{ XMLSEC_ERRORS_R_KEY_DATA_NOT_FOUND, "key data is not found" },
{ XMLSEC_ERRORS_R_KEY_DATA_ALREADY_EXIST, "key data already exist" },
{ XMLSEC_ERRORS_R_INVALID_KEY_DATA_SIZE, "invalid key data size" },
{ XMLSEC_ERRORS_R_KEY_NOT_FOUND, "key is not found" },
{ XMLSEC_ERRORS_R_KEYDATA_DISABLED, "key data is disabled" },
{ XMLSEC_ERRORS_R_MAX_RETRIEVALS_LEVEL, "maximum key retrieval level" },
{ XMLSEC_ERRORS_R_MAX_RETRIEVAL_TYPE_MISMATCH,"key retrieval type mismatch" },
{ XMLSEC_ERRORS_R_MAX_ENCKEY_LEVEL, "maximum encrypted key level" },
{ XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, "certificate verification failed" },
{ XMLSEC_ERRORS_R_CERT_NOT_FOUND, "certificate is not found" },
{ XMLSEC_ERRORS_R_CERT_REVOKED, "certificate is revoked" },
{ XMLSEC_ERRORS_R_CERT_ISSUER_FAILED, "certificate issuer check failed" },
{ XMLSEC_ERRORS_R_CERT_NOT_YET_VALID, "certificate is not yet valid" },
{ XMLSEC_ERRORS_R_CERT_HAS_EXPIRED, "certificate has expired" },
{ XMLSEC_ERRORS_R_DSIG_NO_REFERENCES, "Reference nodes are not found" },
{ XMLSEC_ERRORS_R_DSIG_INVALID_REFERENCE, "Reference verification failed" },
{ XMLSEC_ERRORS_R_ASSERTION, "assertion" },
{ 0, NULL}
};
static xmlSecErrorsCallback xmlSecErrorsClbk = xmlSecErrorsDefaultCallback;
static int xmlSecPrintErrorMessages = 1; /* whether the error messages will be printed immediately */
/**
* xmlSecErrorsInit:
*
* Initializes the errors reporting. It is called from #xmlSecInit function.
* and applications must not call this function directly.
*/
void
xmlSecErrorsInit(void) {
}
/**
* xmlSecErrorsShutdown:
*
* Cleanups the errors reporting. It is called from #xmlSecShutdown function.
* and applications must not call this function directly.
*/
void
xmlSecErrorsShutdown(void) {
}
/**
* xmlSecErrorsSetCallback:
* @callback: the new errors callback function.
*
* Sets the errors callback function to @callback that will be called
* every time an error occurs.
*/
void
xmlSecErrorsSetCallback(xmlSecErrorsCallback callback) {
xmlSecErrorsClbk = callback;
}
/**
* xmlSecErrorsDefaultCallback:
* @file: the error location file name (__FILE__ macro).
* @line: the error location line number (__LINE__ macro).
* @func: the error location function name (__FUNCTION__ macro).
* @errorObject: the error specific error object
* @errorSubject: the error specific error subject.
* @reason: the error code.
* @msg: the additional error message.
*
* The default error reporting callback that utilizes LibXML
* error reporting #xmlGenericError function.
*/
void
xmlSecErrorsDefaultCallback(const char* file, int line, const char* func,
const char* errorObject, const char* errorSubject,
int reason, const char* msg) {
if(xmlSecPrintErrorMessages) {
const char* error_msg = NULL;
xmlSecSize i;
for(i = 0; (i < XMLSEC_ERRORS_MAX_NUMBER) && (xmlSecErrorsGetMsg(i) != NULL); ++i) {
if(xmlSecErrorsGetCode(i) == reason) {
error_msg = xmlSecErrorsGetMsg(i);
break;
}
}
xmlGenericError(xmlGenericErrorContext,
"func=%s:file=%s:line=%d:obj=%s:subj=%s:error=%d:%s:%s\n",
(func != NULL) ? func : "unknown",
(file != NULL) ? file : "unknown",
line,
(errorObject != NULL) ? errorObject : "unknown",
(errorSubject != NULL) ? errorSubject : "unknown",
reason,
(error_msg != NULL) ? error_msg : "",
(msg != NULL) ? msg : "");
}
}
/**
* xmlSecErrorsDefaultCallbackEnableOutput:
* @enabled: the flag.
*
* Enables or disables calling LibXML2 callback from the default
* errors callback.
*/
void
xmlSecErrorsDefaultCallbackEnableOutput(int enabled) {
xmlSecPrintErrorMessages = enabled;
}
/**
* xmlSecErrorsGetCode:
* @pos: the error position.
*
* Gets the known error code at position @pos.
*
* Returns: the known error code or 0 if @pos is greater than
* total number of known error codes.
*/
int
xmlSecErrorsGetCode(xmlSecSize pos) {
/* could not use asserts here! */
if(pos < sizeof(xmlSecErrorsTable) / sizeof(xmlSecErrorsTable[0])) {
return(xmlSecErrorsTable[pos].errorCode);
}
return(0);
}
/**
* xmlSecErrorsGetMsg:
* @pos: the error position.
*
* Gets the known error message at position @pos.
*
* Returns: the known error message or NULL if @pos is greater than
* total number of known error codes.
*/
const char*
xmlSecErrorsGetMsg(xmlSecSize pos) {
/* could not use asserts here! */
if(pos < sizeof(xmlSecErrorsTable) / sizeof(xmlSecErrorsTable[0])) {
return(xmlSecErrorsTable[pos].errorMsg);
}
return(NULL);
}
/**
* xmlSecError:
* @file: the error location filename (__FILE__).
* @line: the error location line number (__LINE__).
* @func: the error location function (__FUNCTION__).
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @errorSubject: the error specific error subject (e.g. failed function name).
* @reason: the error code.
* @msg: the error message in printf format.
* @...: the parameters for the @msg.
*
* Reports an error to the default (#xmlSecErrorsDefaultCallback) or
* application specific callback installed using #xmlSecErrorsSetCallback
* function.
*/
void
xmlSecError(const char* file, int line, const char* func,
const char* errorObject, const char* errorSubject,
int reason, const char* msg, ...) {
if(xmlSecErrorsClbk != NULL) {
xmlChar error_msg[XMLSEC_ERRORS_BUFFER_SIZE];
int ret;
if(msg != NULL) {
va_list va;
va_start(va, msg);
ret = xmlStrVPrintf(error_msg, sizeof(error_msg), msg, va);
if(ret < 0) {
/* Can't really report an error from an error callback */
memcpy(error_msg, fatal_error, sizeof(fatal_error));
}
error_msg[sizeof(error_msg) - 1] = '\0'; /* just in case */
va_end(va);
} else {
error_msg[0] = '\0';
}
xmlSecErrorsClbk(file, line, func, errorObject, errorSubject, reason, (char*)error_msg);
}
}

View File

@ -0,0 +1,869 @@
/*
* XML Security Library (http://www.aleksey.com/xmlsec).
*
* Internal header only used during the compilation,
*
* This is free software; see Copyright file in the source
* distribution for preciese wording.
*
* Copyright (C) 2002-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
*/
#ifndef __XMLSEC_ERROR_HELPERS_H__
#define __XMLSEC_ERROR_HELPERS_H__
#include <errno.h>
#include <xmlsec/errors.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**********************************************************************
*
* Error handling macros.
*
**********************************************************************/
/**
* xmlSecInternalError:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError(errorFunction, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
XMLSEC_ERRORS_NO_MESSAGE \
)
/**
* xmlSecInternalError2:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError2(errorFunction, errorObject, msg, param) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
(msg), (param) \
)
/**
* xmlSecInternalError3:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param1.
* @param2: the extra message param2.
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError3(errorFunction, errorObject, msg, param1, param2) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
(msg), (param1), (param2) \
)
/**
* xmlSecInternalError4:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param1.
* @param2: the extra message param2.
* @param3: the extra message param3.
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError4(errorFunction, errorObject, msg, param1, param2, param3) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
(msg), (param1), (param2), (param3) \
)
/**
* xmlSecMallocError:
* @allocSize: the failed allocation size.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting xmlMalloc() errors.
*/
#define xmlSecMallocError(allocSize, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
"xmlMalloc", \
XMLSEC_ERRORS_R_MALLOC_FAILED, \
"size=%lu", (unsigned long)(allocSize) \
)
/**
* xmlSecStrdupError:
* @str: the failed string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting xmlStrdup() errors.
*/
#define xmlSecStrdupError(str, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
"xmlStrdup", \
XMLSEC_ERRORS_R_STRDUP_FAILED, \
"size=%lu", (unsigned long)xmlStrlen(str) \
)
/**
* xmlSecXmlError:
* @errorFunction: the failed function.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting generic XML errors.
*/
#define xmlSecXmlError(errorFunction, errorObject) \
{ \
xmlErrorPtr error = xmlGetLastError(); \
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
"xml error: %lu: %s", \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXmlError2:
* @errorFunction: the failed function.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting generic XML errors.
*/
#define xmlSecXmlError2(errorFunction, errorObject, msg, param) \
{ \
xmlErrorPtr error = xmlGetLastError(); \
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
msg "; xml error: %lu: %s", \
(param), \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXmlParserError:
* @errorFunction: the failed function.
* @ctxt: the parser context.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting XML parser errors.
*/
#define xmlSecXmlParserError(errorFunction, ctxt, errorObject) \
{ \
xmlErrorPtr error = xmlCtxtGetLastError(ctxt);\
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
"xml error: %lu: %s", \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXmlParserError2:
* @errorFunction: the failed function.
* @ctxt: the parser context.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting XML parser errors.
*/
#define xmlSecXmlParserError2(errorFunction, ctxt, errorObject, msg, param) \
{ \
xmlErrorPtr error = xmlCtxtGetLastError(ctxt);\
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
msg "; xml error: %lu: %s", \
(param), \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXsltError:
* @errorFunction: the failed function.
* @ctxt: the parser context.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting XSLT errors.
*/
#define xmlSecXsltError(errorFunction, ctxt, errorObject) \
{ \
xmlErrorPtr error = xmlGetLastError(); \
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XSLT_FAILED, \
"xslt error: %lu: %s", \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecIOError:
* @errorFunction: the failed function.
* @name: the filename, function name, uri, etc.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting IO errors.
*/
#define xmlSecIOError(errorFunction, name, errorObject) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_IO_FAILED, \
"name=\"%s\"; errno=%d", \
xmlSecErrorsSafeString(name), \
errno \
); \
}
/**
* xmlSecNotImplementedError:
* @details: the additional details.
*
* Macro. The XMLSec library macro for reporting "not implemented" errors.
*/
#define xmlSecNotImplementedError(details) \
xmlSecError(XMLSEC_ERRORS_HERE, \
NULL, \
NULL, \
XMLSEC_ERRORS_R_NOT_IMPLEMENTED, \
"details=%s", \
xmlSecErrorsSafeString(details) \
)
/**
* xmlSecInvalidSizeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect exact match.
*/
#define xmlSecInvalidSizeError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_SIZE, \
"invalid size for '%s': actual=%lu is not equal to expected=%lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidSizeLessThanError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect at least the expected size.
*/
#define xmlSecInvalidSizeLessThanError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_SIZE, \
"invalid size for '%s': actual=%lu is less than expected=%lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidSizeMoreThanError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect at most the expected size.
*/
#define xmlSecInvalidSizeMoreThanError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_NOT_IMPLEMENTED, \
"invalid size for '%s': actual=%lu is more than expected=%lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidSizeNotMultipleOfError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @divider: the expected divider.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect the size to be a multiple of the divider.
*/
#define xmlSecInvalidSizeNotMultipleOfError(name, actual, divider, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_NOT_IMPLEMENTED, \
"invalid size for '%s': actual=%lu is not a multiple of %lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(divider) \
)
/**
* xmlSecInvalidSizeOtherError:
* @msg: the message about the error.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect exact match.
*/
#define xmlSecInvalidSizeOtherError(msg, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_SIZE, \
"invalid size: %s", \
xmlSecErrorsSafeString(msg) \
)
/**
* xmlSecInvalidDataError:
* @msg: the msg with explanation.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors.
*/
#define xmlSecInvalidDataError(msg, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"%s", \
xmlSecErrorsSafeString(msg) \
)
/**
* xmlSecInvalidStringDataError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as a string.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors for string.
*/
#define xmlSecInvalidStringDataError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"invalid data for '%s': actual='%s' and expected %s", \
xmlSecErrorsSafeString(name), \
xmlSecErrorsSafeString(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerDataError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors for integers.
*/
#define xmlSecInvalidIntegerDataError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"invalid data for '%s': actual=%ld and expected %s", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerDataError2:
* @name1: the name of the first variable, parameter, etc.
* @actual1: the actual first value as an integer.
* @name2: the name of the second variable, parameter, etc.
* @actual2: the actual second value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors for integers.
*/
#define xmlSecInvalidIntegerDataError2(name1, actual1, name2, actual2, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"invalid data: actual value '%s'=%ld, actual value '%s'=%ld and expected %s", \
xmlSecErrorsSafeString(name1), \
(unsigned long)(actual1), \
xmlSecErrorsSafeString(name2), \
(unsigned long)(actual2), \
(expected) \
)
/**
* xmlSecInvalidTypeError:
* @msg: the msg with explanation.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors.
*/
#define xmlSecInvalidTypeError(msg, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"%s", \
xmlSecErrorsSafeString(msg) \
)
/**
* xmlSecInvalidStringTypeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as a string.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors for string.
*/
#define xmlSecInvalidStringTypeError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"invalid type for '%s': actual='%s' and expected %s", \
xmlSecErrorsSafeString(name), \
xmlSecErrorsSafeString(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerTypeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors for integers.
*/
#define xmlSecInvalidIntegerTypeError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"invalid type for '%s': actual=%ld and expected %s", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerTypeError2:
* @name1: the name of the first variable, parameter, etc.
* @actual1: the actual first value as an integer.
* @name2: the name of the second variable, parameter, etc.
* @actual2: the actual second value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors for integers.
*/
#define xmlSecInvalidIntegerTypeError2(name1, actual1, name2, actual2, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"invalid type: actual value '%s'=%ld, actual value '%s'=%ld and expected %s", \
xmlSecErrorsSafeString(name1), \
(unsigned long)(actual1), \
xmlSecErrorsSafeString(name2), \
(unsigned long)(actual2), \
(expected) \
)
/**
* xmlSecInvalidNodeError:
* @actualNode: the actual node.
* @expectedNodeName: the expected node name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting an invalid node errors.
*/
#define xmlSecInvalidNodeError(actualNode, expectedNodeName, errorObject) \
{ \
const char* actualNodeName = xmlSecNodeGetName(actualNode); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_NODE, \
"actual=%s; expected=%s", \
xmlSecErrorsSafeString(actualNodeName), \
xmlSecErrorsSafeString(expectedNodeName) \
); \
}
/**
* xmlSecInvalidNodeContentError:
* @node: the node.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @reason: the reason why node content is invalid.
*
* Macro. The XMLSec library macro for reporting an invalid node content errors.
*/
#define xmlSecInvalidNodeContentError(node, errorObject, reason) \
{ \
const char* nName = xmlSecNodeGetName(node); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_NODE_CONTENT, \
"node=%s; reason=%s", \
xmlSecErrorsSafeString(nName), \
xmlSecErrorsSafeString(reason) \
); \
}
/**
* xmlSecInvalidNodeAttributeError:
* @node: the node.
* @attrName: the attribute name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @reason: the reason why node content is invalid.
*
* Macro. The XMLSec library macro for reporting an invalid node attribute errors.
*/
#define xmlSecInvalidNodeAttributeError(node, attrName, errorObject, reason) \
{ \
const char* nName = xmlSecNodeGetName(node); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_NODE_ATTRIBUTE, \
"node=%s; attribute=%s; reason=%s",\
xmlSecErrorsSafeString(nName), \
xmlSecErrorsSafeString(attrName), \
xmlSecErrorsSafeString(reason) \
); \
}
/**
* xmlSecNodeAlreadyPresentError:
* @parent: the parent node.
* @nodeName: the node name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting node already present errors.
*/
#define xmlSecNodeAlreadyPresentError(parent, nodeName, errorObject) \
{ \
const char* pName = xmlSecNodeGetName(parent);\
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_NODE_ALREADY_PRESENT, \
"parent=%s; node=%s", \
xmlSecErrorsSafeString(pName), \
xmlSecErrorsSafeString(nodeName) \
); \
}
/**
* xmlSecUnexpectedNodeError:
* @node: the node.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting an invalid node errors.
*/
#define xmlSecUnexpectedNodeError(node, errorObject) \
{ \
const char* nName = xmlSecNodeGetName(node); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_UNEXPECTED_NODE, \
"node=%s", \
xmlSecErrorsSafeString(nName) \
); \
}
/**
* xmlSecNodeNotFoundError:
* @errorFunction: the failed function.
* @startNode: the search start node.
* @targetNodeName: the expected child node name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting node not found errors.
*/
#define xmlSecNodeNotFoundError(errorFunction, startNode, targetNodeName, errorObject) \
{ \
const char* startNodeName = xmlSecNodeGetName(startNode); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_NODE_NOT_FOUND, \
"startNode=%s; target=%s", \
xmlSecErrorsSafeString(startNodeName), \
xmlSecErrorsSafeString(targetNodeName) \
); \
}
/**
* xmlSecInvalidTransfromError:
* @transform: the transform.
*
* Macro. The XMLSec library macro for reporting an invalid transform errors.
*/
#define xmlSecInvalidTransfromError(transform) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TRANSFORM, \
XMLSEC_ERRORS_NO_MESSAGE \
); \
}
/**
* xmlSecInvalidTransfromError2:
* @transform: the transform.
* @msg: the extra message.
* @param: the extra message param.
*
*
* Macro. The XMLSec library macro for reporting an invalid transform errors.
*/
#define xmlSecInvalidTransfromError2(transform, msg, param) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TRANSFORM, \
(msg), (param) \
); \
}
/**
* xmlSecInvalidTransfromStatusError:
* @transform: the transform.
*
* Macro. The XMLSec library macro for reporting an invalid transform status errors.
*/
#define xmlSecInvalidTransfromStatusError(transform) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_STATUS, \
"transformStatus=%d", \
(int)((transform)->status) \
); \
}
/**
* xmlSecInvalidTransfromStatusError2:
* @transform: the transform.
* @msg: the extra message.
*
* Macro. The XMLSec library macro for reporting an invalid transform status errors.
*/
#define xmlSecInvalidTransfromStatusError2(transform, msg) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_STATUS, \
"transformStatus=%ld, msg=%s", \
(long int)((transform)->status), \
msg \
); \
}
/**
* xmlSecInvalidKeyDataSizeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value(s).
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid keydata size" errors.
*/
#define xmlSecInvalidKeyDataSizeError(actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_KEY_DATA_SIZE, \
"invalid key data size: actual=%ld and expected=%ld", \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidZeroKeyDataSizeError:
* @name: the name of the variable, parameter, etc.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid keydata size" errors.
*/
#define xmlSecInvalidZeroKeyDataSizeError(errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_KEY_DATA_SIZE, \
"invalid zero key data size" \
)
/**
* xmlSecOtherError:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @details: the error message.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError(code, errorObject, details) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
"details=%s", \
xmlSecErrorsSafeString(details) \
)
/**
* xmlSecOtherError2:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError2(code, errorObject, msg, param) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param) \
)
/**
* xmlSecOtherError3:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param.
* @param2: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError3(code, errorObject, msg, param1, param2) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param1), (param2) \
)
/**
* xmlSecOtherError4:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param.
* @param2: the extra message param.
* @param3: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError4(code, errorObject, msg, param1, param2, param3) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param1), (param2), (param3) \
)
/**
* xmlSecOtherError5:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param.
* @param2: the extra message param.
* @param3: the extra message param.
* @param4: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError5(code, errorObject, msg, param1, param2, param3, param4) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param1), (param2), (param3), (param4) \
)
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __XMLSEC_ERROR_HELPERS_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,259 @@
/*
* XML Security Library (http://www.aleksey.com/xmlsec).
*
*
* This is free software; see Copyright file in the source
* distribution for preciese wording.
*
* Copyright (C) 2002-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
*/
/**
* SECTION:errors
* @Short_description: Error reporting and logging functions.
* @Stability: Stable
*
*/
#define XMLSEC_PRIVATE 1
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <libxml/tree.h>
#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/private.h>
#include <xmlsec/errors.h>
/* Must be bigger than fatal_error */
#define XMLSEC_ERRORS_BUFFER_SIZE 1024
/* Must fit into xmlChar[XMLSEC_ERRORS_BUFFER_SIZE] */
static const xmlChar fatal_error[] = "Can not format error message";
typedef struct _xmlSecErrorDescription xmlSecErrorDescription, *xmlSecErrorDescriptionPtr;
struct _xmlSecErrorDescription {
int errorCode;
const char* errorMsg;
};
static xmlSecErrorDescription xmlSecErrorsTable[XMLSEC_ERRORS_MAX_NUMBER + 1] = {
{ XMLSEC_ERRORS_R_XMLSEC_FAILED, "xmlsec library function failed" },
{ XMLSEC_ERRORS_R_MALLOC_FAILED, "malloc function failed" },
{ XMLSEC_ERRORS_R_STRDUP_FAILED, "strdup function failed" },
{ XMLSEC_ERRORS_R_CRYPTO_FAILED, "crypto library function failed" },
{ XMLSEC_ERRORS_R_XML_FAILED, "libxml2 library function failed" },
{ XMLSEC_ERRORS_R_XSLT_FAILED, "libxslt library function failed" },
{ XMLSEC_ERRORS_R_IO_FAILED, "io function failed" },
{ XMLSEC_ERRORS_R_DISABLED, "feature is disabled" },
{ XMLSEC_ERRORS_R_NOT_IMPLEMENTED, "feature is not implemented" },
{ XMLSEC_ERRORS_R_INVALID_CONFIG, "invalid configuration" },
{ XMLSEC_ERRORS_R_INVALID_SIZE, "invalid size" },
{ XMLSEC_ERRORS_R_INVALID_DATA, "invalid data" },
{ XMLSEC_ERRORS_R_INVALID_RESULT, "invalid result" },
{ XMLSEC_ERRORS_R_INVALID_TYPE, "invalid type" },
{ XMLSEC_ERRORS_R_INVALID_OPERATION, "invalid operation" },
{ XMLSEC_ERRORS_R_INVALID_STATUS, "invalid status" },
{ XMLSEC_ERRORS_R_INVALID_FORMAT, "invalid format" },
{ XMLSEC_ERRORS_R_DATA_NOT_MATCH, "data do not match" },
{ XMLSEC_ERRORS_R_INVALID_VERSION, "invalid version" },
{ XMLSEC_ERRORS_R_INVALID_NODE, "invalid node" },
{ XMLSEC_ERRORS_R_INVALID_NODE_CONTENT, "invalid node content" },
{ XMLSEC_ERRORS_R_INVALID_NODE_ATTRIBUTE, "invalid node attribute" },
{ XMLSEC_ERRORS_R_MISSING_NODE_ATTRIBUTE, "missing node attribute" },
{ XMLSEC_ERRORS_R_NODE_ALREADY_PRESENT, "node already present" },
{ XMLSEC_ERRORS_R_UNEXPECTED_NODE, "unexpected node" },
{ XMLSEC_ERRORS_R_NODE_NOT_FOUND, "node node found" },
{ XMLSEC_ERRORS_R_INVALID_TRANSFORM, "invalid transform" },
{ XMLSEC_ERRORS_R_INVALID_TRANSFORM_KEY, "invalid transform key" },
{ XMLSEC_ERRORS_R_INVALID_URI_TYPE, "invalid URI type" },
{ XMLSEC_ERRORS_R_TRANSFORM_SAME_DOCUMENT_REQUIRED, "same document is required for transform" },
{ XMLSEC_ERRORS_R_TRANSFORM_DISABLED, "transform is disabled" },
{ XMLSEC_ERRORS_R_INVALID_KEY_DATA, "invalid key data" },
{ XMLSEC_ERRORS_R_KEY_DATA_NOT_FOUND, "key data is not found" },
{ XMLSEC_ERRORS_R_KEY_DATA_ALREADY_EXIST, "key data already exist" },
{ XMLSEC_ERRORS_R_INVALID_KEY_DATA_SIZE, "invalid key data size" },
{ XMLSEC_ERRORS_R_KEY_NOT_FOUND, "key is not found" },
{ XMLSEC_ERRORS_R_KEYDATA_DISABLED, "key data is disabled" },
{ XMLSEC_ERRORS_R_MAX_RETRIEVALS_LEVEL, "maximum key retrieval level" },
{ XMLSEC_ERRORS_R_MAX_RETRIEVAL_TYPE_MISMATCH,"key retrieval type mismatch" },
{ XMLSEC_ERRORS_R_MAX_ENCKEY_LEVEL, "maximum encrypted key level" },
{ XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, "certificate verification failed" },
{ XMLSEC_ERRORS_R_CERT_NOT_FOUND, "certificate is not found" },
{ XMLSEC_ERRORS_R_CERT_REVOKED, "certificate is revoked" },
{ XMLSEC_ERRORS_R_CERT_ISSUER_FAILED, "certificate issuer check failed" },
{ XMLSEC_ERRORS_R_CERT_NOT_YET_VALID, "certificate is not yet valid" },
{ XMLSEC_ERRORS_R_CERT_HAS_EXPIRED, "certificate has expired" },
{ XMLSEC_ERRORS_R_DSIG_NO_REFERENCES, "Reference nodes are not found" },
{ XMLSEC_ERRORS_R_DSIG_INVALID_REFERENCE, "Reference verification failed" },
{ XMLSEC_ERRORS_R_ASSERTION, "assertion" },
{ 0, NULL}
};
static xmlSecErrorsCallback xmlSecErrorsClbk = xmlSecErrorsDefaultCallback;
static int xmlSecPrintErrorMessages = 1; /* whether the error messages will be printed immediately */
/**
* xmlSecErrorsInit:
*
* Initializes the errors reporting. It is called from #xmlSecInit function.
* and applications must not call this function directly.
*/
void
xmlSecErrorsInit(void) {
}
/**
* xmlSecErrorsShutdown:
*
* Cleanups the errors reporting. It is called from #xmlSecShutdown function.
* and applications must not call this function directly.
*/
void
xmlSecErrorsShutdown(void) {
}
/**
* xmlSecErrorsSetCallback:
* @callback: the new errors callback function.
*
* Sets the errors callback function to @callback that will be called
* every time an error occurs.
*/
void
xmlSecErrorsSetCallback(xmlSecErrorsCallback callback) {
xmlSecErrorsClbk = callback;
}
/**
* xmlSecErrorsDefaultCallback:
* @file: the error location file name (__FILE__ macro).
* @line: the error location line number (__LINE__ macro).
* @func: the error location function name (__FUNCTION__ macro).
* @errorObject: the error specific error object
* @errorSubject: the error specific error subject.
* @reason: the error code.
* @msg: the additional error message.
*
* The default error reporting callback that utilizes LibXML
* error reporting #xmlGenericError function.
*/
void
xmlSecErrorsDefaultCallback(const char* file, int line, const char* func,
const char* errorObject, const char* errorSubject,
int reason, const char* msg) {
if(xmlSecPrintErrorMessages) {
const char* error_msg = NULL;
xmlSecSize i;
for(i = 0; (i < XMLSEC_ERRORS_MAX_NUMBER) && (xmlSecErrorsGetMsg(i) != NULL); ++i) {
if(xmlSecErrorsGetCode(i) == reason) {
error_msg = xmlSecErrorsGetMsg(i);
break;
}
}
xmlGenericError(xmlGenericErrorContext,
"func=%s:file=%s:line=%d:obj=%s:subj=%s:error=%d:%s:%s\n",
(func != NULL) ? func : "unknown",
(file != NULL) ? file : "unknown",
line,
(errorObject != NULL) ? errorObject : "unknown",
(errorSubject != NULL) ? errorSubject : "unknown",
reason,
(error_msg != NULL) ? error_msg : "",
(msg != NULL) ? msg : "");
}
}
/**
* xmlSecErrorsDefaultCallbackEnableOutput:
* @enabled: the flag.
*
* Enables or disables calling LibXML2 callback from the default
* errors callback.
*/
void
xmlSecErrorsDefaultCallbackEnableOutput(int enabled) {
xmlSecPrintErrorMessages = enabled;
}
/**
* xmlSecErrorsGetCode:
* @pos: the error position.
*
* Gets the known error code at position @pos.
*
* Returns: the known error code or 0 if @pos is greater than
* total number of known error codes.
*/
int
xmlSecErrorsGetCode(xmlSecSize pos) {
/* could not use asserts here! */
if(pos < sizeof(xmlSecErrorsTable) / sizeof(xmlSecErrorsTable[0])) {
return(xmlSecErrorsTable[pos].errorCode);
}
return(0);
}
/**
* xmlSecErrorsGetMsg:
* @pos: the error position.
*
* Gets the known error message at position @pos.
*
* Returns: the known error message or NULL if @pos is greater than
* total number of known error codes.
*/
const char*
xmlSecErrorsGetMsg(xmlSecSize pos) {
/* could not use asserts here! */
if(pos < sizeof(xmlSecErrorsTable) / sizeof(xmlSecErrorsTable[0])) {
return(xmlSecErrorsTable[pos].errorMsg);
}
return(NULL);
}
/**
* xmlSecError:
* @file: the error location filename (__FILE__).
* @line: the error location line number (__LINE__).
* @func: the error location function (__FUNCTION__).
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @errorSubject: the error specific error subject (e.g. failed function name).
* @reason: the error code.
* @msg: the error message in printf format.
* @...: the parameters for the @msg.
*
* Reports an error to the default (#xmlSecErrorsDefaultCallback) or
* application specific callback installed using #xmlSecErrorsSetCallback
* function.
*/
void
xmlSecError(const char* file, int line, const char* func,
const char* errorObject, const char* errorSubject,
int reason, const char* msg, ...) {
if(xmlSecErrorsClbk != NULL) {
xmlChar error_msg[XMLSEC_ERRORS_BUFFER_SIZE];
int ret;
if(msg != NULL) {
va_list va;
va_start(va, msg);
ret = xmlStrVPrintf(error_msg, sizeof(error_msg), msg, va);
if(ret < 0) {
/* Can't really report an error from an error callback */
memcpy(error_msg, fatal_error, sizeof(fatal_error));
}
error_msg[sizeof(error_msg) - 1] = '\0'; /* just in case */
va_end(va);
} else {
error_msg[0] = '\0';
}
xmlSecErrorsClbk(file, line, func, errorObject, errorSubject, reason, (char*)error_msg);
}
}

View File

@ -0,0 +1,869 @@
/*
* XML Security Library (http://www.aleksey.com/xmlsec).
*
* Internal header only used during the compilation,
*
* This is free software; see Copyright file in the source
* distribution for preciese wording.
*
* Copyright (C) 2002-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
*/
#ifndef __XMLSEC_ERROR_HELPERS_H__
#define __XMLSEC_ERROR_HELPERS_H__
#include <errno.h>
#include <xmlsec/errors.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**********************************************************************
*
* Error handling macros.
*
**********************************************************************/
/**
* xmlSecInternalError:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError(errorFunction, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
XMLSEC_ERRORS_NO_MESSAGE \
)
/**
* xmlSecInternalError2:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError2(errorFunction, errorObject, msg, param) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
(msg), (param) \
)
/**
* xmlSecInternalError3:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param1.
* @param2: the extra message param2.
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError3(errorFunction, errorObject, msg, param1, param2) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
(msg), (param1), (param2) \
)
/**
* xmlSecInternalError4:
* @errorFunction: the failed function name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param1.
* @param2: the extra message param2.
* @param3: the extra message param3.
*
* Macro. The XMLSec library macro for reporting internal XMLSec errors.
*/
#define xmlSecInternalError4(errorFunction, errorObject, msg, param1, param2, param3) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XMLSEC_FAILED, \
(msg), (param1), (param2), (param3) \
)
/**
* xmlSecMallocError:
* @allocSize: the failed allocation size.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting xmlMalloc() errors.
*/
#define xmlSecMallocError(allocSize, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
"xmlMalloc", \
XMLSEC_ERRORS_R_MALLOC_FAILED, \
"size=%lu", (unsigned long)(allocSize) \
)
/**
* xmlSecStrdupError:
* @str: the failed string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting xmlStrdup() errors.
*/
#define xmlSecStrdupError(str, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
"xmlStrdup", \
XMLSEC_ERRORS_R_STRDUP_FAILED, \
"size=%lu", (unsigned long)xmlStrlen(str) \
)
/**
* xmlSecXmlError:
* @errorFunction: the failed function.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting generic XML errors.
*/
#define xmlSecXmlError(errorFunction, errorObject) \
{ \
xmlErrorPtr error = xmlGetLastError(); \
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
"xml error: %lu: %s", \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXmlError2:
* @errorFunction: the failed function.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting generic XML errors.
*/
#define xmlSecXmlError2(errorFunction, errorObject, msg, param) \
{ \
xmlErrorPtr error = xmlGetLastError(); \
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
msg "; xml error: %lu: %s", \
(param), \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXmlParserError:
* @errorFunction: the failed function.
* @ctxt: the parser context.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting XML parser errors.
*/
#define xmlSecXmlParserError(errorFunction, ctxt, errorObject) \
{ \
xmlErrorPtr error = xmlCtxtGetLastError(ctxt);\
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
"xml error: %lu: %s", \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXmlParserError2:
* @errorFunction: the failed function.
* @ctxt: the parser context.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting XML parser errors.
*/
#define xmlSecXmlParserError2(errorFunction, ctxt, errorObject, msg, param) \
{ \
xmlErrorPtr error = xmlCtxtGetLastError(ctxt);\
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XML_FAILED, \
msg "; xml error: %lu: %s", \
(param), \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecXsltError:
* @errorFunction: the failed function.
* @ctxt: the parser context.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting XSLT errors.
*/
#define xmlSecXsltError(errorFunction, ctxt, errorObject) \
{ \
xmlErrorPtr error = xmlGetLastError(); \
int code = (error != NULL) ? error->code : 0; \
const char* message = (error != NULL) ? error->message : NULL; \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_XSLT_FAILED, \
"xslt error: %lu: %s", \
(unsigned long)code, \
xmlSecErrorsSafeString(message) \
); \
}
/**
* xmlSecIOError:
* @errorFunction: the failed function.
* @name: the filename, function name, uri, etc.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting IO errors.
*/
#define xmlSecIOError(errorFunction, name, errorObject) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_IO_FAILED, \
"name=\"%s\"; errno=%d", \
xmlSecErrorsSafeString(name), \
errno \
); \
}
/**
* xmlSecNotImplementedError:
* @details: the additional details.
*
* Macro. The XMLSec library macro for reporting "not implemented" errors.
*/
#define xmlSecNotImplementedError(details) \
xmlSecError(XMLSEC_ERRORS_HERE, \
NULL, \
NULL, \
XMLSEC_ERRORS_R_NOT_IMPLEMENTED, \
"details=%s", \
xmlSecErrorsSafeString(details) \
)
/**
* xmlSecInvalidSizeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect exact match.
*/
#define xmlSecInvalidSizeError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_SIZE, \
"invalid size for '%s': actual=%lu is not equal to expected=%lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidSizeLessThanError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect at least the expected size.
*/
#define xmlSecInvalidSizeLessThanError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_SIZE, \
"invalid size for '%s': actual=%lu is less than expected=%lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidSizeMoreThanError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect at most the expected size.
*/
#define xmlSecInvalidSizeMoreThanError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_NOT_IMPLEMENTED, \
"invalid size for '%s': actual=%lu is more than expected=%lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidSizeNotMultipleOfError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @divider: the expected divider.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect the size to be a multiple of the divider.
*/
#define xmlSecInvalidSizeNotMultipleOfError(name, actual, divider, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_NOT_IMPLEMENTED, \
"invalid size for '%s': actual=%lu is not a multiple of %lu", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(unsigned long)(divider) \
)
/**
* xmlSecInvalidSizeOtherError:
* @msg: the message about the error.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid size" errors when
* we expect exact match.
*/
#define xmlSecInvalidSizeOtherError(msg, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_SIZE, \
"invalid size: %s", \
xmlSecErrorsSafeString(msg) \
)
/**
* xmlSecInvalidDataError:
* @msg: the msg with explanation.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors.
*/
#define xmlSecInvalidDataError(msg, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"%s", \
xmlSecErrorsSafeString(msg) \
)
/**
* xmlSecInvalidStringDataError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as a string.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors for string.
*/
#define xmlSecInvalidStringDataError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"invalid data for '%s': actual='%s' and expected %s", \
xmlSecErrorsSafeString(name), \
xmlSecErrorsSafeString(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerDataError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors for integers.
*/
#define xmlSecInvalidIntegerDataError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"invalid data for '%s': actual=%ld and expected %s", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerDataError2:
* @name1: the name of the first variable, parameter, etc.
* @actual1: the actual first value as an integer.
* @name2: the name of the second variable, parameter, etc.
* @actual2: the actual second value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid data" errors for integers.
*/
#define xmlSecInvalidIntegerDataError2(name1, actual1, name2, actual2, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_DATA, \
"invalid data: actual value '%s'=%ld, actual value '%s'=%ld and expected %s", \
xmlSecErrorsSafeString(name1), \
(unsigned long)(actual1), \
xmlSecErrorsSafeString(name2), \
(unsigned long)(actual2), \
(expected) \
)
/**
* xmlSecInvalidTypeError:
* @msg: the msg with explanation.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors.
*/
#define xmlSecInvalidTypeError(msg, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"%s", \
xmlSecErrorsSafeString(msg) \
)
/**
* xmlSecInvalidStringTypeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as a string.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors for string.
*/
#define xmlSecInvalidStringTypeError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"invalid type for '%s': actual='%s' and expected %s", \
xmlSecErrorsSafeString(name), \
xmlSecErrorsSafeString(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerTypeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors for integers.
*/
#define xmlSecInvalidIntegerTypeError(name, actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"invalid type for '%s': actual=%ld and expected %s", \
xmlSecErrorsSafeString(name), \
(unsigned long)(actual), \
(expected) \
)
/**
* xmlSecInvalidIntegerTypeError2:
* @name1: the name of the first variable, parameter, etc.
* @actual1: the actual first value as an integer.
* @name2: the name of the second variable, parameter, etc.
* @actual2: the actual second value as an integer.
* @expected: the expected value(s) as a string.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid type" errors for integers.
*/
#define xmlSecInvalidIntegerTypeError2(name1, actual1, name2, actual2, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TYPE, \
"invalid type: actual value '%s'=%ld, actual value '%s'=%ld and expected %s", \
xmlSecErrorsSafeString(name1), \
(unsigned long)(actual1), \
xmlSecErrorsSafeString(name2), \
(unsigned long)(actual2), \
(expected) \
)
/**
* xmlSecInvalidNodeError:
* @actualNode: the actual node.
* @expectedNodeName: the expected node name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting an invalid node errors.
*/
#define xmlSecInvalidNodeError(actualNode, expectedNodeName, errorObject) \
{ \
const char* actualNodeName = xmlSecNodeGetName(actualNode); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_NODE, \
"actual=%s; expected=%s", \
xmlSecErrorsSafeString(actualNodeName), \
xmlSecErrorsSafeString(expectedNodeName) \
); \
}
/**
* xmlSecInvalidNodeContentError:
* @node: the node.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @reason: the reason why node content is invalid.
*
* Macro. The XMLSec library macro for reporting an invalid node content errors.
*/
#define xmlSecInvalidNodeContentError(node, errorObject, reason) \
{ \
const char* nName = xmlSecNodeGetName(node); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_NODE_CONTENT, \
"node=%s; reason=%s", \
xmlSecErrorsSafeString(nName), \
xmlSecErrorsSafeString(reason) \
); \
}
/**
* xmlSecInvalidNodeAttributeError:
* @node: the node.
* @attrName: the attribute name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @reason: the reason why node content is invalid.
*
* Macro. The XMLSec library macro for reporting an invalid node attribute errors.
*/
#define xmlSecInvalidNodeAttributeError(node, attrName, errorObject, reason) \
{ \
const char* nName = xmlSecNodeGetName(node); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_NODE_ATTRIBUTE, \
"node=%s; attribute=%s; reason=%s",\
xmlSecErrorsSafeString(nName), \
xmlSecErrorsSafeString(attrName), \
xmlSecErrorsSafeString(reason) \
); \
}
/**
* xmlSecNodeAlreadyPresentError:
* @parent: the parent node.
* @nodeName: the node name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting node already present errors.
*/
#define xmlSecNodeAlreadyPresentError(parent, nodeName, errorObject) \
{ \
const char* pName = xmlSecNodeGetName(parent);\
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_NODE_ALREADY_PRESENT, \
"parent=%s; node=%s", \
xmlSecErrorsSafeString(pName), \
xmlSecErrorsSafeString(nodeName) \
); \
}
/**
* xmlSecUnexpectedNodeError:
* @node: the node.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting an invalid node errors.
*/
#define xmlSecUnexpectedNodeError(node, errorObject) \
{ \
const char* nName = xmlSecNodeGetName(node); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_UNEXPECTED_NODE, \
"node=%s", \
xmlSecErrorsSafeString(nName) \
); \
}
/**
* xmlSecNodeNotFoundError:
* @errorFunction: the failed function.
* @startNode: the search start node.
* @targetNodeName: the expected child node name.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting node not found errors.
*/
#define xmlSecNodeNotFoundError(errorFunction, startNode, targetNodeName, errorObject) \
{ \
const char* startNodeName = xmlSecNodeGetName(startNode); \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
(errorFunction), \
XMLSEC_ERRORS_R_NODE_NOT_FOUND, \
"startNode=%s; target=%s", \
xmlSecErrorsSafeString(startNodeName), \
xmlSecErrorsSafeString(targetNodeName) \
); \
}
/**
* xmlSecInvalidTransfromError:
* @transform: the transform.
*
* Macro. The XMLSec library macro for reporting an invalid transform errors.
*/
#define xmlSecInvalidTransfromError(transform) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TRANSFORM, \
XMLSEC_ERRORS_NO_MESSAGE \
); \
}
/**
* xmlSecInvalidTransfromError2:
* @transform: the transform.
* @msg: the extra message.
* @param: the extra message param.
*
*
* Macro. The XMLSec library macro for reporting an invalid transform errors.
*/
#define xmlSecInvalidTransfromError2(transform, msg, param) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_TRANSFORM, \
(msg), (param) \
); \
}
/**
* xmlSecInvalidTransfromStatusError:
* @transform: the transform.
*
* Macro. The XMLSec library macro for reporting an invalid transform status errors.
*/
#define xmlSecInvalidTransfromStatusError(transform) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_STATUS, \
"transformStatus=%d", \
(int)((transform)->status) \
); \
}
/**
* xmlSecInvalidTransfromStatusError2:
* @transform: the transform.
* @msg: the extra message.
*
* Macro. The XMLSec library macro for reporting an invalid transform status errors.
*/
#define xmlSecInvalidTransfromStatusError2(transform, msg) \
{ \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)xmlSecTransformGetName(transform), \
NULL, \
XMLSEC_ERRORS_R_INVALID_STATUS, \
"transformStatus=%ld, msg=%s", \
(long int)((transform)->status), \
msg \
); \
}
/**
* xmlSecInvalidKeyDataSizeError:
* @name: the name of the variable, parameter, etc.
* @actual: the actual value.
* @expected: the expected value(s).
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid keydata size" errors.
*/
#define xmlSecInvalidKeyDataSizeError(actual, expected, errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_KEY_DATA_SIZE, \
"invalid key data size: actual=%ld and expected=%ld", \
(unsigned long)(actual), \
(unsigned long)(expected) \
)
/**
* xmlSecInvalidZeroKeyDataSizeError:
* @name: the name of the variable, parameter, etc.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
*
* Macro. The XMLSec library macro for reporting "invalid keydata size" errors.
*/
#define xmlSecInvalidZeroKeyDataSizeError(errorObject) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
XMLSEC_ERRORS_R_INVALID_KEY_DATA_SIZE, \
"invalid zero key data size" \
)
/**
* xmlSecOtherError:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @details: the error message.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError(code, errorObject, details) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
"details=%s", \
xmlSecErrorsSafeString(details) \
)
/**
* xmlSecOtherError2:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError2(code, errorObject, msg, param) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param) \
)
/**
* xmlSecOtherError3:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param.
* @param2: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError3(code, errorObject, msg, param1, param2) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param1), (param2) \
)
/**
* xmlSecOtherError4:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param.
* @param2: the extra message param.
* @param3: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError4(code, errorObject, msg, param1, param2, param3) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param1), (param2), (param3) \
)
/**
* xmlSecOtherError5:
* @code: the error code.
* @errorObject: the error specific error object (e.g. transform, key data, etc).
* @msg: the extra message.
* @param1: the extra message param.
* @param2: the extra message param.
* @param3: the extra message param.
* @param4: the extra message param.
*
* Macro. The XMLSec library macro for reporting other XMLSec errors.
*/
#define xmlSecOtherError5(code, errorObject, msg, param1, param2, param3, param4) \
xmlSecError(XMLSEC_ERRORS_HERE, \
(const char*)(errorObject), \
NULL, \
(code), \
(msg), (param1), (param2), (param3), (param4) \
)
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __XMLSEC_ERROR_HELPERS_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1" 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="http://www.dian.gov.co/contratos/facturaelectronica/v1/Structures" 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:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#">
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent>
<sts:DianExtensions>
<sts:SoftwareSecurityCode schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195">2bd9a18f23cf1188d36888d218e9b0c31df77aa00bc036eaeaa1619184054237eee56d2d8db4ecab0173f65f8dab7753</sts:SoftwareSecurityCode>
<sts:AuthorizationProvider>
<sts:AuthorizationProviderID schemeID="4" schemeName="31" schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195">800197268</sts:AuthorizationProviderID>
</sts:AuthorizationProvider>
<sts:SoftwareProvider>
<sts:ProviderID schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195" schemeID="5" schemaName="31">900579212</sts:ProviderID>
<sts:SoftwareID schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195">01ab9caa-6838-461e-82fb-da44da36a084</sts:SoftwareID>
</sts:SoftwareProvider>
<sts:InvoiceSource>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
</sts:InvoiceSource>
<sts:InvoiceControl>
<sts:InvoiceAuthorization>fc8eac422eba16e22ffd8c6f94b3f40a6e38162c</sts:InvoiceAuthorization>
<sts:AuthorizationPeriod>
<cbc:StartDate>2019-01-19</cbc:StartDate>
<cbc:EndDate>2030-01-19</cbc:EndDate>
</sts:AuthorizationPeriod>
<sts:AuthorizedInvoices>
<sts:Prefix>SETP</sts:Prefix>
<sts:From>990000000</sts:From>
<sts:To>995000000</sts:To>
</sts:AuthorizedInvoices>
</sts:InvoiceControl>
</sts:DianExtensions>
</ext:ExtensionContent>
</ext:UBLExtension>
</ext:UBLExtensions>
<cbc:UBLVersionID>UBL 2.1</cbc:UBLVersionID>
<cbc:CustomizationID>10</cbc:CustomizationID>
<cbc:ProfileID>DIAN 2.1</cbc:ProfileID>
<cbc:ProfileExecutionID>2</cbc:ProfileExecutionID>
<cbc:ID>SETP990000001</cbc:ID>
<cbc:UUID schemeID="2" schemeName="CUFE-SHA384">50578625ce1fb2f6bb2bc57b22180d2763c8a868c4e62da72e0e43001f9c7f3459c81e2bf9324830175b1225af37e435</cbc:UUID>
<cbc:DocumentCurrencyCode>COP</cbc:DocumentCurrencyCode>
<cbc:IssueDate>2020-09-05</cbc:IssueDate>
<cbc:IssueTime>22:38:53-05:00</cbc:IssueTime>
<cbc:InvoiceTypeCode listAgencyID="195" listAgencyName="No matching global declaration available for the validation root" listURI="http://www.dian.gov.co">01</cbc:InvoiceTypeCode>
<cbc:LineCountNumeric>1</cbc:LineCountNumeric>
<cac:InvoicePeriod>
<cbc:StartDate>2020-09-05</cbc:StartDate>
<cbc:EndDate>2020-09-05</cbc:EndDate>
</cac:InvoicePeriod>
<cac:AccountingSupplierParty>
<cbc:AdditionalAccountID>1</cbc:AdditionalAccountID>
<cac:Party>
<cac:PartyName>
<cbc:Name>NEUROTEC TECNOLOGIA S.A.S</cbc:Name>
</cac:PartyName>
<cac:PhysicalLocation>
<cac:Address>
<cac:AddressLine>
<cbc:Line/>
</cac:AddressLine>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyLegalEntity>
<cbc:CompanyID schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195" schemeID="5" schemeName="31">900579212</cbc:CompanyID>
<cbc:RegistrationName>NEUROTEC TECNOLOGIA S.A.S</cbc:RegistrationName>
<cac:RegistrationAddress>
<cbc:CityName/>
<cac:AddressLine>
<cbc:Line/>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<cbc:Name>COLOMBIA</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
</cac:PartyLegalEntity>
<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>
<cac:TaxScheme/>
</cac:PartyTaxScheme>
<cac:Contact>
<cbc:ElectronicMail>sdds@sd.com</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cbc:AdditionalAccountID>2</cbc:AdditionalAccountID>
<cac:Party>
<cac:PartyName>
<cbc:Name>facho-customer</cbc:Name>
</cac:PartyName>
<cac:PhysicalLocation>
<cac:Address>
<cac:AddressLine>
<cbc:Line/>
</cac:AddressLine>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:CompanyID schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195" schemeID="" schemeName="13">43851425</cbc:CompanyID>
<cbc:RegistrationName>facho-customer</cbc:RegistrationName>
<cbc:TaxLevelCode>ZZ</cbc:TaxLevelCode>
<cac:TaxScheme/>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>facho-customer</cbc:RegistrationName>
<cac:RegistrationAddress>
<cbc:CityName/>
<cac:AddressLine>
<cbc:Line/>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<cbc:Name>COLOMBIA</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="COP">100.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="COP">100.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="COP">119.00</cbc:TaxInclusiveAmount>
<cbc:ChargeTotalAmount currencyID="COP">19.00</cbc:ChargeTotalAmount>
<cbc:PayableAmount currencyID="COP">119.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:TaxTotal/>
<cac:TaxTotal>
<cac:TaxSubtotal>
<cac:TaxCategory>
<cac:TaxScheme>
<cbc:ID>01</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
<cbc:TaxAmount>19.00</cbc:TaxAmount>
</cac:TaxTotal>
<cac:TaxTotal>
<cac:TaxSubtotal>
<cac:TaxCategory>
<cac:TaxScheme>
<cbc:ID>04</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
<cbc:TaxAmount>0.00</cbc:TaxAmount>
</cac:TaxTotal>
<cac:TaxTotal>
<cac:TaxSubtotal>
<cac:TaxCategory>
<cac:TaxScheme>
<cbc:ID>03</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
<cbc:TaxAmount>0.00</cbc:TaxAmount>
</cac:TaxTotal>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="NAR">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="COP">100.0</cbc:LineExtensionAmount>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="COP">19.0</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="COP">100.0</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="COP">19.0</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>19.0</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>01</cbc:ID>
<cbc:Name>IVA</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:PricingReference>
<cac:AlternativeConditionPrice>
<cbc:PriceAmount currencyID="COP">100.0</cbc:PriceAmount>
<cbc:PriceTypeCode>01</cbc:PriceTypeCode>
<cbc:PriceType>x</cbc:PriceType>
</cac:AlternativeConditionPrice>
</cac:PricingReference>
<cac:Item>
<cbc:Description>test</cbc:Description>
<cac:StandardItemIdentification>
<cbc:ID>9999</cbc:ID>
</cac:StandardItemIdentification>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="COP">100.0</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
<cac:PaymentMeans>
<cbc:ID>1</cbc:ID>
<cbc:PaymentMeansCode>10</cbc:PaymentMeansCode>
<cbc:PaymentDueDate>2020-09-05</cbc:PaymentDueDate>
<cbc:PaymentID>1</cbc:PaymentID>
</cac:PaymentMeans>
</Invoice>

View File

@ -0,0 +1,7 @@
#!/bin/sh
set -ex
sh build_openssl.sh
sh build_libxml2.sh
sh build_xmlsec.sh

View File

@ -0,0 +1,21 @@
#!/bin/sh
LIBXML2_VERSION=2.9.12
tar xf libxml2-${LIBXML2_VERSION}.tar.gz
mv libxml2-${LIBXML2_VERSION} libxml2
pushd libxml2
wasiconfigure ./configure --enable-static --without-http --without-ftp --without-modules --without-python --without-zlib --without-lzma --without-threads --host=x86_64
wasimake make clean
wasimake make -j4
popd
mkdir -p vendor/libxml2/lib
mkdir -p vendor/libxml2/include
cp -r libxml2/include/libxml2 vendor/libxml2/include
cp -r libxml2/.libs/libxml2.a vendor/libxml2/lib

View File

@ -0,0 +1,41 @@
#!/bin/sh
# TOMADO DE: https://github.com/voltbuilder/openssl-wasm
OPENSSL_VERSION=1.1.1l
# get the source
tar xf openssl-${OPENSSL_VERSION}.tar.gz
patch -p0 < openssl-${OPENSSL_VERSION}.patch
cd openssl-${OPENSSL_VERSION}
make clean
set -e
# why ./Configure instead of ./config? We want to force using the generic gcc profile which is more conservative than linux-x32
# -no-sock - we don't have sockets in WASI
# new -no-ui-console - sdk 12 has no termios???
# check in 12 -DHAVE_FORK=0 - no fork() in WASI
# new -D_WASI_EMULATED_MMAN - works with the library below to enable WASI mman emulation
# new -D_WASI_EMULATED_SIGNAL - with sdk 12
# new -DOPENSSL_NO_SECURE_MEMORY - wasi doesn't have secure mem (madvise, mlock, etc...)
# new -DNO_SYSLOG - get rid of need for patch above
# --with-rand-seed=getrandom (needed to force using getentropy because WASI has no /dev/random or getrandom)
wasiconfigure ./Configure gcc -no-sock -no-ui-console -DHAVE_FORK=0 -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL -DOPENSSL_NO_SECURE_MEMORY -DNO_SYSLOG --with-rand-seed=getrandom
# enables stuff from mman.h (see define above) also add -lwasi-emulated-signal
#sed -i -e "s/CNF_EX_LIBS=/CNF_EX_LIBS=-lwasi-emulated-mman -lwasi-emulated-signal /g" Makefile
# build!
wasimake make -j4 build_generated libssl.a libcrypto.a
rm -rf ../vendor/openssl/include
mkdir -p ../vendor/openssl/include
cp -R include/openssl ../vendor/openssl/include
mkdir -p ../vendor/openssl/lib/
cp libssl.a ../vendor/openssl/lib/
cp libcrypto.a ../vendor/openssl/lib/
exit 0

View File

@ -0,0 +1,14 @@
#!/bin/sh
XMLSEC1_VERSION=1.2.33
tar xf xmlsec1-${XMLSEC1_VERSION}.tar.gz
cd xmlsec1-${XMLSEC1_VERSION}
cp -r ../libxml2 .
cp -r ../vendor .
wasiconfigure ./configure --with-libxml-src=`pwd`/libxml2 --with-openssl=`pwd`/vendor/openssl --enable-static-linking --disable-folders-search --disable-apps --disable-apps-crypto-dl --disable-crypto-dl
wasimake make -j4

View File

@ -0,0 +1,6 @@
# ejemplo
~~~
$ bash build.sh
$ wasirun ./a.out sign1-tmpl.xml example.key
~~~

View File

@ -0,0 +1,12 @@
cp ../vendor/openssl/lib/*.a .
cp ../xmlsec1-1.2.33/src/.libs/*.a .
cp ../xmlsec1-1.2.33/src/openssl/.libs/*.a .
cp ../libxml2/.libs/libxml2.a .
mkdir -p include
cp -r ../libxml2/include/libxml include/
cp -r ../xmlsec1-1.2.33/include/xmlsec include/
cp -r ../vendor/openssl/include/* include/
wasicc -Iinclude libxml2.a libcrypto.a libssl.a libxmlsec1.a libxmlsec1-openssl.a sign.c

View File

@ -0,0 +1,216 @@
/**
* XML Security Library example: Signing a template file.
*
* Signs a template file using a key from PEM file
*
* Usage:
* ./sign1 <xml-tmpl> <pem-key>
*
* Example:
* ./sign1 sign1-tmpl.xml rsakey.pem > sign1-res.xml
*
* The result signature could be validated using verify1 example:
* ./verify1 sign1-res.xml rsapub.pem
*
* This is free software; see Copyright file in the source
* distribution for preciese wording.
*
* Copyright (C) 2002-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <libxml/tree.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#define XMLSEC_NO_XSLT
#define XMLSEC_CRYPTO_OPENSSL
#ifndef XMLSEC_NO_XSLT
#include <libxslt/xslt.h>
#include <libxslt/security.h>
#endif /* XMLSEC_NO_XSLT */
#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/crypto.h>
int sign_file(const char* tmpl_file, const char* key_file);
int
main(int argc, char **argv) {
#ifndef XMLSEC_NO_XSLT
xsltSecurityPrefsPtr xsltSecPrefs = NULL;
#endif /* XMLSEC_NO_XSLT */
setenv("RANDFILE", "/dev/random", 1);
assert(argv);
if(argc != 3) {
fprintf(stderr, "Error: wrong number of arguments.\n");
fprintf(stderr, "Usage: %s <tmpl-file> <key-file>\n", argv[0]);
return(1);
}
printf("template %s key %s\n", argv[1], argv[2]);
/* Init libxml and libxslt libraries */
xmlInitParser();
LIBXML_TEST_VERSION
xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
xmlSubstituteEntitiesDefault(1);
#ifndef XMLSEC_NO_XSLT
xmlIndentTreeOutput = 1;
#endif /* XMLSEC_NO_XSLT */
/* Init libxslt */
#ifndef XMLSEC_NO_XSLT
/* disable everything */
xsltSecPrefs = xsltNewSecurityPrefs();
xsltSetSecurityPrefs(xsltSecPrefs, XSLT_SECPREF_READ_FILE, xsltSecurityForbid);
xsltSetSecurityPrefs(xsltSecPrefs, XSLT_SECPREF_WRITE_FILE, xsltSecurityForbid);
xsltSetSecurityPrefs(xsltSecPrefs, XSLT_SECPREF_CREATE_DIRECTORY, xsltSecurityForbid);
xsltSetSecurityPrefs(xsltSecPrefs, XSLT_SECPREF_READ_NETWORK, xsltSecurityForbid);
xsltSetSecurityPrefs(xsltSecPrefs, XSLT_SECPREF_WRITE_NETWORK, xsltSecurityForbid);
xsltSetDefaultSecurityPrefs(xsltSecPrefs);
#endif /* XMLSEC_NO_XSLT */
/* Init xmlsec library */
if(xmlSecInit() < 0) {
fprintf(stderr, "Error: xmlsec initialization failed.\n");
return(-1);
}
puts("done xmlSecInit");
/* Check loaded library version */
if(xmlSecCheckVersion() != 1) {
fprintf(stderr, "Error: loaded xmlsec library version is not compatible.\n");
return(-1);
}
puts("done xmlSecCheckVersion");
/* Load default crypto engine if we are supporting dynamic
* loading for xmlsec-crypto libraries. Use the crypto library
* name ("openssl", "nss", etc.) to load corresponding
* xmlsec-crypto library.
*/
#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
if(xmlSecCryptoDLLoadLibrary(NULL) < 0) {
fprintf(stderr, "Error: unable to load default xmlsec-crypto library. Make sure\n"
"that you have it installed and check shared libraries path\n"
"(LD_LIBRARY_PATH and/or LTDL_LIBRARY_PATH) environment variables.\n");
return(-1);
}
#endif /* XMLSEC_CRYPTO_DYNAMIC_LOADING */
/* Init crypto library */
if(xmlSecCryptoAppInit(NULL) < 0) {
fprintf(stderr, "Error: crypto initialization failed.\n");
return(-1);
}
puts("done xmlSecCryptoAppInit");
/* Init xmlsec-crypto library */
if(xmlSecCryptoInit() < 0) {
fprintf(stderr, "Error: xmlsec-crypto initialization failed.\n");
return(-1);
}
puts("done xmlSecCryptoInit");
if(sign_file(argv[1], argv[2]) < 0) {
return(-1);
}
/* Shutdown xmlsec-crypto library */
xmlSecCryptoShutdown();
/* Shutdown crypto library */
xmlSecCryptoAppShutdown();
/* Shutdown xmlsec library */
xmlSecShutdown();
/* Shutdown libxslt/libxml */
#ifndef XMLSEC_NO_XSLT
xsltFreeSecurityPrefs(xsltSecPrefs);
xsltCleanupGlobals();
#endif /* XMLSEC_NO_XSLT */
xmlCleanupParser();
return(0);
}
/**
* sign_file:
* @tmpl_file: the signature template file name.
* @key_file: the PEM private key file name.
*
* Signs the #tmpl_file using private key from #key_file.
*
* Returns 0 on success or a negative value if an error occurs.
*/
int
sign_file(const char* tmpl_file, const char* key_file) {
xmlDocPtr doc = NULL;
xmlNodePtr node = NULL;
xmlSecDSigCtxPtr dsigCtx = NULL;
int res = -1;
assert(tmpl_file);
assert(key_file);
/* load template */
doc = xmlParseFile(tmpl_file);
if ((doc == NULL) || (xmlDocGetRootElement(doc) == NULL)){
fprintf(stderr, "Error: unable to parse file \"%s\"\n", tmpl_file);
goto done;
}
/* find start node */
node = xmlSecFindNode(xmlDocGetRootElement(doc), xmlSecNodeSignature, xmlSecDSigNs);
if(node == NULL) {
fprintf(stderr, "Error: start node not found in \"%s\"\n", tmpl_file);
goto done;
}
/* create signature context, we don't need keys manager in this example */
dsigCtx = xmlSecDSigCtxCreate(NULL);
if(dsigCtx == NULL) {
fprintf(stderr,"Error: failed to create signature context\n");
goto done;
}
/* load private key, assuming that there is not password */
dsigCtx->signKey = xmlSecCryptoAppKeyLoad(key_file, xmlSecKeyDataFormatPem, NULL, NULL, NULL);
if(dsigCtx->signKey == NULL) {
fprintf(stderr,"Error: failed to load private pem key from \"%s\"\n", key_file);
goto done;
}
/* set key name to the file name, this is just an example! */
if(xmlSecKeySetName(dsigCtx->signKey, key_file) < 0) {
fprintf(stderr,"Error: failed to set key name for key from \"%s\"\n", key_file);
goto done;
}
/* sign the template */
if(xmlSecDSigCtxSign(dsigCtx, node) < 0) {
fprintf(stderr,"Error: signature failed\n");
goto done;
}
/* print signed document to stdout */
xmlDocDump(stdout, doc);
/* success */
res = 0;
done:
/* cleanup */
if(dsigCtx != NULL) {
xmlSecDSigCtxDestroy(dsigCtx);
}
if(doc != NULL) {
xmlFreeDoc(doc);
}
return(res);
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
XML Security Library example: Simple signature template file for sign1 example.
-->
<Envelope xmlns="urn:envelope">
<Data>
Hello, World!
</Data>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue></DigestValue>
</Reference>
</SignedInfo>
<SignatureValue/>
<KeyInfo>
<KeyName/>
</KeyInfo>
</Signature>
</Envelope>

View File

@ -0,0 +1,199 @@
diff '--color=auto' -ruN openssl-1.1.1l/crypto/rand/rand_unix.c patch-1.1.1l/crypto/rand/rand_unix.c
--- openssl-1.1.1l/crypto/rand/rand_unix.c 2021-08-24 09:38:47.000000000 -0400
+++ patch-1.1.1l/crypto/rand/rand_unix.c 2021-09-12 02:26:35.765347423 -0400
@@ -372,36 +372,36 @@
* Note: Sometimes getentropy() can be provided but not implemented
* internally. So we need to check errno for ENOSYS
*/
-# if defined(__GNUC__) && __GNUC__>=2 && defined(__ELF__) && !defined(__hpux)
- extern int getentropy(void *buffer, size_t length) __attribute__((weak));
+/* # if defined(__GNUC__) && __GNUC__>=2 && defined(__ELF__) && !defined(__hpux) */
+/* extern int getentropy(void *buffer, size_t length) __attribute__((weak)); */
- if (getentropy != NULL) {
+/* if (getentropy != NULL) { */
if (getentropy(buf, buflen) == 0)
return (ssize_t)buflen;
if (errno != ENOSYS)
return -1;
- }
-# elif defined(__APPLE__)
- if (CCRandomGenerateBytes(buf, buflen) == kCCSuccess)
- return (ssize_t)buflen;
-
- return -1;
-# else
- union {
- void *p;
- int (*f)(void *buffer, size_t length);
- } p_getentropy;
-
- /*
- * We could cache the result of the lookup, but we normally don't
- * call this function often.
- */
- ERR_set_mark();
- p_getentropy.p = DSO_global_lookup("getentropy");
- ERR_pop_to_mark();
- if (p_getentropy.p != NULL)
- return p_getentropy.f(buf, buflen) == 0 ? (ssize_t)buflen : -1;
-# endif
+/* } */
+/* # elif defined(__APPLE__) */
+/* if (CCRandomGenerateBytes(buf, buflen) == kCCSuccess) */
+/* return (ssize_t)buflen; */
+
+/* return -1; */
+/* # else */
+/* union { */
+/* void *p; */
+/* int (*f)(void *buffer, size_t length); */
+/* } p_getentropy; */
+
+/* /\* */
+/* * We could cache the result of the lookup, but we normally don't */
+/* * call this function often. */
+/* *\/ */
+/* ERR_set_mark(); */
+/* p_getentropy.p = DSO_global_lookup("getentropy"); */
+/* ERR_pop_to_mark(); */
+/* if (p_getentropy.p != NULL) */
+/* return p_getentropy.f(buf, buflen) == 0 ? (ssize_t)buflen : -1; */
+/* # endif */
/* Linux supports this since version 3.17 */
# if defined(__linux) && defined(__NR_getrandom)
@@ -635,12 +635,12 @@
*/
size_t rand_pool_acquire_entropy(RAND_POOL *pool)
{
-# if defined(OPENSSL_RAND_SEED_NONE)
- return rand_pool_entropy_available(pool);
-# else
+/* # if defined(OPENSSL_RAND_SEED_NONE) */
+/* return rand_pool_entropy_available(pool); */
+/* # else */
size_t entropy_available;
-# if defined(OPENSSL_RAND_SEED_GETRANDOM)
+/* # if defined(OPENSSL_RAND_SEED_GETRANDOM) */
{
size_t bytes_needed;
unsigned char *buffer;
@@ -664,7 +664,7 @@
entropy_available = rand_pool_entropy_available(pool);
if (entropy_available > 0)
return entropy_available;
-# endif
+/* # endif */
# if defined(OPENSSL_RAND_SEED_LIBRANDOM)
{
@@ -752,7 +752,7 @@
# endif
return rand_pool_entropy_available(pool);
-# endif
+/* # endif */
}
# endif
#endif
diff '--color=auto' -ruN openssl-1.1.1l/test/drbgtest.c patch-1.1.1l/test/drbgtest.c
--- openssl-1.1.1l/test/drbgtest.c 2021-08-24 09:38:47.000000000 -0400
+++ patch-1.1.1l/test/drbgtest.c 2021-09-12 02:07:38.062332301 -0400
@@ -22,8 +22,21 @@
# include <windows.h>
#endif
+#ifndef HAVE_FORK
+# if defined(OPENSSL_SYS_VMS) || defined(OPENSSL_SYS_WINDOWS) || defined(OPENSSL_SYS_VXWORKS)
+# define HAVE_FORK 0
+# else
+# define HAVE_FORK 1
+# endif
+#endif
+
+#if HAVE_FORK
+# undef NO_FORK
+#else
+# define NO_FORK
+#endif
-#if defined(OPENSSL_SYS_UNIX)
+#if defined(OPENSSL_SYS_UNIX) && !defined(NO_FORK)
# include <sys/types.h>
# include <sys/wait.h>
# include <unistd.h>
@@ -676,7 +689,7 @@
}
-#if defined(OPENSSL_SYS_UNIX)
+#if defined(OPENSSL_SYS_UNIX) && !defined(NO_FORK)
/*
* Test whether master, public and private DRBG are reseeded after
* forking the process.
@@ -795,7 +808,7 @@
goto error;
reset_drbg_hook_ctx();
-#if defined(OPENSSL_SYS_UNIX)
+#if defined(OPENSSL_SYS_UNIX) && !defined(NO_FORK)
if (!TEST_true(test_drbg_reseed_after_fork(master, public, private)))
goto error;
#endif
diff '--color=auto' -ruN openssl-1.1.1l/test/run_tests.pl patch-1.1.1l/test/run_tests.pl
--- openssl-1.1.1l/test/run_tests.pl 2021-08-24 09:38:47.000000000 -0400
+++ patch-1.1.1l/test/run_tests.pl 2021-09-12 02:03:57.603451471 -0400
@@ -28,7 +28,7 @@
my $recipesdir = catdir($srctop, "test", "recipes");
my $libdir = rel2abs(catdir($srctop, "util", "perl"));
-$ENV{OPENSSL_CONF} = catdir($srctop, "apps", "openssl.cnf");
+$ENV{OPENSSL_CONF} = rel2abs(catdir($srctop, "apps", "openssl.cnf"));
my %tapargs =
( verbosity => $ENV{VERBOSE} || $ENV{V} || $ENV{HARNESS_VERBOSE} ? 1 : 0,
diff '--color=auto' -ruN openssl-1.1.1l/util/perl/OpenSSL/Test.pm patch-1.1.1l/util/perl/OpenSSL/Test.pm
--- openssl-1.1.1l/util/perl/OpenSSL/Test.pm 2021-08-24 09:38:47.000000000 -0400
+++ patch-1.1.1l/util/perl/OpenSSL/Test.pm 2021-09-12 02:01:14.155136681 -0400
@@ -65,7 +65,7 @@
rel2abs/;
use File::Path 2.00 qw/rmtree mkpath/;
use File::Basename;
-use Cwd qw/getcwd abs_path/;
+use Cwd qw/abs_path/;
my $level = 0;
@@ -904,26 +904,26 @@
BAIL_OUT("Must run setup() first") if (! $test_name);
my $f = pop;
- return abs2rel(catfile($directories{SRCTOP},@_,$f),getcwd);
+ return catfile($directories{SRCTOP},@_,$f);
}
sub __srctop_dir {
BAIL_OUT("Must run setup() first") if (! $test_name);
- return abs2rel(catdir($directories{SRCTOP},@_), getcwd);
+ return catdir($directories{SRCTOP},@_);
}
sub __bldtop_file {
BAIL_OUT("Must run setup() first") if (! $test_name);
my $f = pop;
- return abs2rel(catfile($directories{BLDTOP},@_,$f), getcwd);
+ return catfile($directories{BLDTOP},@_,$f);
}
sub __bldtop_dir {
BAIL_OUT("Must run setup() first") if (! $test_name);
- return abs2rel(catdir($directories{BLDTOP},@_), getcwd);
+ return catdir($directories{BLDTOP},@_);
}
# __exeext is a function that returns the platform dependent file extension

View File

@ -200,6 +200,18 @@ def validate_invoice(invoice_path):
XSD.validate(content, XSD.UBLInvoice) XSD.validate(content, XSD.UBLInvoice)
@click.command()
@click.argument('nomina_path')
def validate_nominaindividual(nomina_path):
from facho.fe.data.dian import XSD
content = open(nomina_path, 'r').read()
content = content.replace(
'xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1"',
'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"',
)
XSD.validate(content, XSD.NominaIndividual)
@click.command() @click.command()
@click.option('--private-key', type=click.Path(exists=True)) @click.option('--private-key', type=click.Path(exists=True))
@click.option('--passphrase') @click.option('--passphrase')
@ -215,7 +227,7 @@ def sign_xml(private_key, passphrase, xmlfile, ssl=True, use_cache_policy=False,
if use_cache_policy: if use_cache_policy:
warnings.warn("xades using cache policy") warnings.warn("xades using cache policy")
signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase, mockpolicy=use_cache_policy) signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase, localpolicy=use_cache_policy)
document = open(xmlfile, 'r').read().encode('utf-8') document = open(xmlfile, 'r').read().encode('utf-8')
with open(output, 'w') as f: with open(output, 'w') as f:
f.write(signer.sign_xml_string(document)) f.write(signer.sign_xml_string(document))
@ -247,14 +259,14 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
spec.loader.exec_module(module) spec.loader.exec_module(module)
import facho.fe.form as form import facho.fe.form as form
from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned,DIANWrite from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned, DIANWrite, DIANSupportDocumentXML
from facho import fe from facho import fe
try: try:
invoice_xml = module.document_xml() invoice_xml = module.document_xml()
except AttributeError: except AttributeError:
invoice_xml = DIANInvoiceXML #invoice_xml = DIANInvoiceXML
invoice_xml = DIANSupportDocumentXML
print("Using document xml:", invoice_xml) print("Using document xml:", invoice_xml)
invoice = module.invoice() invoice = module.invoice()
invoice.calculate() invoice.calculate()
@ -271,6 +283,65 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
else: else:
DIANWrite(xml, output) DIANWrite(xml, output)
@click.command()
@click.option('--private-key', type=click.Path(exists=True))
@click.option('--passphrase')
@click.option('--ssl/--no-ssl', default=False)
@click.option('--sign/--no-sign', default=False)
@click.option('--use-cache-policy/--no-use-cache-policy', default=False)
@click.argument('scriptname', type=click.Path(exists=True), required=True)
@click.argument('output', required=True)
def generate_nomina(private_key, passphrase, scriptname, ssl=True, sign=False, use_cache_policy=False, output=None):
"""
imprime xml en pantalla.
SCRIPTNAME espera
def nomina() -> fe.nomina.NominaIndividual
def extensions(fe.nomina.NominaIndividual): -> List[facho.FachoXMLExtension]
"""
if not ssl:
disable_ssl()
import importlib.util
spec = importlib.util.spec_from_file_location('nomina', scriptname)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
from facho.fe.form_xml import DIANWriteSigned, DIANWrite
import facho.fe
nomina = module.nomina()
xml = nomina.toFachoXML()
extensions = module.extensions(nomina)
for extension in extensions:
xml.add_extension(extension)
if sign:
DIANWriteSigned(xml, output, private_key, passphrase, use_cache_policy, dian_signer=facho.fe.nomina.DianXMLExtensionSigner)
else:
DIANWrite(xml, output)
@click.command()
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.argument('filename', required=True)
@click.argument('zipfile', type=click.Path(exists=True))
def soap_send_nomina_sync(private_key, public_key, habilitacion, password, filename, zipfile):
from facho.fe.client import dian
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.SendNominaSync
if habilitacion:
req = dian.Habilitacion.SendNominaSync
resp = client.request(req(
open(zipfile, 'rb').read()
))
print(resp)
@click.command() @click.command()
@click.option('--private-key', type=click.Path(exists=True)) @click.option('--private-key', type=click.Path(exists=True))
@ -287,7 +358,7 @@ def sign_verify_xml(private_key, passphrase, xmlfile, ssl=True, use_cache_policy
warnings.warn("xades using cache policy") warnings.warn("xades using cache policy")
print("THIS ONLY WORKS FOR DOCUMENTS GENERATE WITH FACHO") print("THIS ONLY WORKS FOR DOCUMENTS GENERATE WITH FACHO")
signer = fe.DianXMLExtensionSignerVerifier(private_key, passphrase=passphrase, mockpolicy=use_cache_policy) signer = fe.DianXMLExtensionSignerVerifier(private_key, passphrase=passphrase, localpolicy=use_cache_policy)
document = open(xmlfile, 'r').read().encode('utf-8') document = open(xmlfile, 'r').read().encode('utf-8')
if signer.verify_string(document): if signer.verify_string(document):
@ -295,6 +366,25 @@ def sign_verify_xml(private_key, passphrase, xmlfile, ssl=True, use_cache_policy
else: else:
print("-INVALID") print("-INVALID")
@click.command()
@click.option('--software-id')
@click.option('--software-pin')
@click.option('--nit')
@click.option('--dv')
@click.option('--output-zippath')
def generate_nomina_habilitacion(software_id, software_pin, nit, dv, output_zippath):
from facho import fe
generador = fe.nomina.habilitacion.Habilitacion(
fe.nomina.habilitacion.Habilitacion.Metadata(
software_id=software_id,
software_pin=software_pin,
nit=nit,
dv=dv
)
)
generador.generar(output_zippath)
@click.group() @click.group()
def main(): def main():
pass pass
@ -310,3 +400,7 @@ main.add_command(generate_invoice)
main.add_command(validate_invoice) main.add_command(validate_invoice)
main.add_command(sign_xml) main.add_command(sign_xml)
main.add_command(sign_verify_xml) main.add_command(sign_verify_xml)
main.add_command(generate_nomina)
main.add_command(soap_send_nomina_sync)
main.add_command(validate_nominaindividual)
main.add_command(generate_nomina_habilitacion)

View File

@ -1,9 +1,15 @@
# This file is part of facho. The COPYRIGHT file at the top level of # This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms. # this repository contains the full copyright notices and license terms.
from lxml import etree from lxml import etree
from lxml.etree import Element, SubElement, tostring from lxml.etree import Element, tostring
import re import re
from collections import defaultdict
from copy import deepcopy
class FachoValueInvalid(Exception):
def __init__(self, xpath):
super().__init__('FachoValueInvalid invalid xpath %s' % (xpath))
class FachoXMLExtension: class FachoXMLExtension:
@ -25,7 +31,10 @@ class LXMLBuilder:
def __init__(self, nsmap): def __init__(self, nsmap):
self.nsmap = nsmap self.nsmap = nsmap
self._re_node_expr = re.compile(r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))(?P<attrs>\[.+\])?') self._re_node_expr = \
re.compile(
r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))'
r'(?P<attrs>\[.+\])?')
self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?') self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?')
def match_expression(self, node_expr): def match_expression(self, node_expr):
@ -82,15 +91,21 @@ class LXMLBuilder:
def append(self, elem, child): def append(self, elem, child):
elem.append(child) elem.append(child)
def append_next(self, elem, slibing):
elem.addnext(slibing)
def remove(self, elem): def remove(self, elem):
elem.getparent().remove(elem) elem.getparent().remove(elem)
def set_text(self, elem, text): def set_text(self, elem, text):
elem.text = text elem.text = text
def xpath(self, elem, xpath): def xpath(self, elem, xpath, multiple=False):
elems = elem.xpath(xpath, namespaces=self.nsmap) elems = elem.xpath(xpath, namespaces=self.nsmap)
if elems: if elems:
if multiple:
return elems
else:
return elems[0] return elems[0]
return None return None
@ -98,13 +113,42 @@ class LXMLBuilder:
def get_text(self, elem): def get_text(self, elem):
return elem.text return elem.text
def get_attribute(self, elem, key):
return elem.attrib[key]
def is_attribute(self, elem, key, value):
return elem.get(key, False) == value
def set_attribute(self, elem, key, value): def set_attribute(self, elem, key, value):
elem.attrib[key] = value elem.attrib[key] = value
@classmethod @classmethod
def tostring(self, elem, **attrs): def remove_attributes(cls, elem, keys, exclude=[]):
for key in keys:
if key in exclude:
continue
try:
del elem.attrib[key]
except KeyError:
pass
@classmethod
def tostring(self, oelem, **attrs):
elem = deepcopy(oelem)
attrs['pretty_print'] = attrs.pop('pretty_print', False) attrs['pretty_print'] = attrs.pop('pretty_print', False)
attrs['encoding'] = attrs.pop('encoding', 'UTF-8') attrs['encoding'] = attrs.pop('encoding', 'UTF-8')
for el in elem.getiterator():
keys = filter(lambda key: key.startswith('facho_'), el.keys())
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']:
el.getparent().remove(el)
return tostring(elem, **attrs).decode('utf-8') return tostring(elem, **attrs).decode('utf-8')
@ -112,7 +156,8 @@ class FachoXML:
""" """
Decora XML con funciones de consulta XPATH de un solo elemento Decora XML con funciones de consulta XPATH de un solo elemento
""" """
def __init__(self, root, builder=None, nsmap=None, fragment_prefix=''): def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',
fragment_root_element=None):
if builder is None: if builder is None:
self.builder = LXMLBuilder(nsmap) self.builder = LXMLBuilder(nsmap)
else: else:
@ -125,9 +170,22 @@ class FachoXML:
else: else:
self.root = root self.root = root
self.fragment_root_element = fragment_root_element
self.fragment_prefix = fragment_prefix self.fragment_prefix = fragment_prefix
self.xpath_for = {} self.xpath_for = {}
self.extensions = [] self.extensions = []
self._validators = defaultdict(lambda: lambda v, attrs: True)
@classmethod
def from_string(cls, document: str, namespaces: dict() = []) -> '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): def append_element(self, elem, new_elem):
# elem = self.find_or_create_element(xpath, append=append) # elem = self.find_or_create_element(xpath, append=append)
@ -137,8 +195,8 @@ class FachoXML:
def add_extension(self, extension): def add_extension(self, extension):
extension.build(self) extension.build(self)
def fragment(
def fragment(self, xpath, append=False, append_not_exists=False): self, xpath, append=False, append_not_exists=False):
nodes = xpath.split('/') nodes = xpath.split('/')
nodes.pop() nodes.pop()
root_prefix = '/'.join(nodes) root_prefix = '/'.join(nodes)
@ -148,7 +206,9 @@ class FachoXML:
if parent is None: if parent is None:
parent = self.find_or_create_element(xpath, append=append) parent = self.find_or_create_element(xpath, append=append)
return FachoXML(parent, nsmap=self.nsmap, fragment_prefix=root_prefix) return FachoXML(
parent, nsmap=self.nsmap, fragment_prefix=root_prefix,
fragment_root_element=self.root)
def register_alias_xpath(self, alias, xpath): def register_alias_xpath(self, alias, xpath):
self.xpath_for[alias] = xpath self.xpath_for[alias] = xpath
@ -164,8 +224,12 @@ class FachoXML:
def _path_xpath_for(self, xpath): def _path_xpath_for(self, xpath):
return self._normalize_xpath(self._translate_xpath_for(xpath)) return self._normalize_xpath(self._translate_xpath_for(xpath))
def placeholder_for(self, xpath, append=False): def placeholder_for(self, xpath, append=False, optional=False):
return self.find_or_create_element(xpath, append) elem = self.find_or_create_element(xpath, append)
if optional:
elem.set('facho_optional', 'True')
elem.set('facho_placeholder', 'True')
return elem
def replacement_for(self, xpath, new_xpath, content, **attrs): def replacement_for(self, xpath, new_xpath, content, **attrs):
elem = self.get_element(xpath) elem = self.get_element(xpath)
@ -180,7 +244,8 @@ class FachoXML:
""" """
xpath = self._path_xpath_for(xpath) xpath = self._path_xpath_for(xpath)
node_paths = xpath.split('/') node_paths = xpath.split('/')
node_paths.pop(0) #remove empty / # remove empty /
node_paths.pop(0)
root_tag = node_paths.pop(0) root_tag = node_paths.pop(0)
root_node = self.builder.build_from_expression(root_tag) root_node = self.builder.build_from_expression(root_tag)
@ -190,16 +255,19 @@ class FachoXML:
root_node = self.root root_node = self.root
if not self.builder.same_tag(root_node.tag, self.root.tag): if not self.builder.same_tag(root_node.tag, self.root.tag):
raise ValueError('xpath %s must be absolute to /%s' % (
raise ValueError('xpath %s must be absolute to /%s' % (xpath, self.root.tag)) xpath, self.root.tag))
# crea jerarquia segun xpath indicado # crea jerarquia segun xpath indicado
parent = None parent = None
current_elem = self.root current_elem = self.root
node_tag = node_paths.pop(-1)
for node_path in node_paths: for node_path in node_paths:
node_expr = self.builder.match_expression(node_path) node_expr = self.builder.match_expression(node_path)
node = self.builder.build_from_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 parent = current_elem
if child is not None: if child is not None:
@ -208,15 +276,61 @@ class FachoXML:
self.builder.append(current_elem, node) self.builder.append(current_elem, node)
current_elem = node current_elem = node
# se fuerza la adicion como un nuevo elemento node_expr = self.builder.match_expression(node_tag)
if append: node = self.builder.build_from_expression(node_tag)
node = self.builder.build_from_expression(node_paths[-1]) 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) self.builder.append(parent, node)
return node return node
# se fuerza la adicion como un nuevo elemento
if append:
last_slibing = None
for child in parent.getchildren():
if child.tag == node_tag:
last_slibing = child
# si no ahi primos se adiciona como hijo
if last_slibing is None:
self.builder.append(parent, node)
return node
if self.builder.is_attribute(
last_slibing, 'facho_placeholder', 'True'):
self._remove_facho_attributes(last_slibing)
return last_slibing
self.builder.append_next(last_slibing, node)
return node
if child is None:
self.builder.append(current_elem, node)
return node
self._remove_facho_attributes(current_elem)
return current_elem return current_elem
def set_element(self, xpath, content, **attrs): def set_element_validator(
self, xpath, validator=False):
"""
validador al asignar contenido a xpath indicado
@param xpath ruta tipo XPath
@param validator callback(content, attributes)
"""
key = self._path_xpath_for(xpath)
if not validator:
self._validators[key] = lambda v, attrs: True
else:
self._validators[key] = validator
def set_element(
self, xpath, content, **attrs):
""" """
asigna contenido ubicado por ruta tipo XPATH. asigna contenido ubicado por ruta tipo XPATH.
@param xpath ruta tipo XPATH @param xpath ruta tipo XPATH
@ -227,10 +341,18 @@ class FachoXML:
format_ = attrs.pop('format_', '%s') format_ = attrs.pop('format_', '%s')
append_ = attrs.pop('append_', False) append_ = attrs.pop('append_', False)
elem = self.find_or_create_element(xpath, append=append_) elem = self.find_or_create_element(xpath, append=append_)
validator = self._validators[xpath]
if not validator(content, attrs):
raise FachoValueInvalid(xpath)
if content: if content:
self.builder.set_text(elem, format_ % content) self.builder.set_text(elem, format_ % content)
for k, v in attrs.items(): for k, v in attrs.items():
self.builder.set_attribute(elem, k, v) if v is not None or str(v) != 'None':
self.builder.set_attribute(elem, k, str(v))
return elem return elem
def set_attributes(self, xpath, **attrs): def set_attributes(self, xpath, **attrs):
@ -241,22 +363,143 @@ class FachoXML:
""" """
xpath = self._path_xpath_for(xpath) xpath = self._path_xpath_for(xpath)
elem = self.get_element(xpath) elem = self.get_element(xpath)
if elem is None:
raise ValueError("xpath %s not found" % (xpath))
for k, v in attrs.items(): for k, v in attrs.items():
self.builder.set_attribute(elem, k, v) if v is not None or str(v) != 'None':
self.builder.set_attribute(elem, k, str(v))
return self return self
def get_element(self, xpath): def get_element_attribute(
xpath = self.fragment_prefix + self._path_xpath_for(xpath) self, xpath, attribute, multiple=False):
return self.builder.xpath(self.root, xpath) elem = self.get_element(xpath, multiple=multiple)
def get_element_text(self, xpath, format_=str): if elem is None:
raise ValueError("xpath %s not found" % (xpath))
if multiple:
vals = []
for e in elem:
vals.append(self.builder.get_attribute(e, attribute))
return vals
else:
return self.builder.get_attribute(elem, attribute)
def get_element(self, xpath, multiple=False):
xpath = self.fragment_prefix + self._path_xpath_for(xpath) xpath = self.fragment_prefix + self._path_xpath_for(xpath)
elem = self.builder.xpath(self.root, xpath) return self.builder.xpath(self.root, xpath, multiple=multiple)
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) text = self.builder.get_text(elem)
if text is None:
return None
return format_(text) 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): def tostring(self, **kw):
return self.builder.tostring(self.root, **kw) return self.builder.tostring(self.root, **kw)
def xpath_from_root(self, xpath):
nsmap = {}
ns = ''
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('/')
else:
new_xpath = '/' + ns + etree.QName(root).localname + '/' + \
xpath.lstrip('/')
return new_xpath
def __str__(self): def __str__(self):
return self.tostring() return self.tostring()

View File

@ -5,6 +5,7 @@ from .fe import DianXMLExtensionSigner
from .fe import DianXMLExtensionSoftwareSecurityCode from .fe import DianXMLExtensionSoftwareSecurityCode
from .fe import DianXMLExtensionCUFE from .fe import DianXMLExtensionCUFE
from .fe import DianXMLExtensionCUDE from .fe import DianXMLExtensionCUDE
from .fe import DianXMLExtensionCUDS
from .fe import DianXMLExtensionInvoiceAuthorization from .fe import DianXMLExtensionInvoiceAuthorization
from .fe import DianXMLExtensionSoftwareProvider from .fe import DianXMLExtensionSoftwareProvider
from .fe import DianXMLExtensionAuthorizationProvider from .fe import DianXMLExtensionAuthorizationProvider
@ -12,3 +13,4 @@ from .fe import DianZIP
from .fe import AMBIENTE_PRUEBAS from .fe import AMBIENTE_PRUEBAS
from .fe import AMBIENTE_PRODUCCION from .fe import AMBIENTE_PRODUCCION
from . import form_xml from . import form_xml
from . import nomina

View File

@ -27,7 +27,7 @@ class SOAPService:
def get_service(self): def get_service(self):
raise NotImplementedError() raise NotImplementedError()
def builder_response(self, as_dict): def build_response(self, as_dict):
raise NotImplementedError() raise NotImplementedError()
def todict(self): def todict(self):
@ -138,10 +138,16 @@ class GetStatusResponse:
@classmethod @classmethod
def fromdict(cls, data): def fromdict(cls, data):
if data['ErrorMessage']:
error_message = data['ErrorMessage']['string']
else:
error_message = None
return cls(data['IsValid'], return cls(data['IsValid'],
data['StatusDescription'], data['StatusDescription'],
data['StatusCode'], data['StatusCode'],
data['ErrorMessage']['string']) error_message)
@dataclass @dataclass
class GetStatus(SOAPService): class GetStatus(SOAPService):
@ -169,6 +175,19 @@ class GetStatusZip(SOAPService):
def build_response(self, as_dict): def build_response(self, as_dict):
return GetStatusResponse.fromdict(as_dict[0]) return GetStatusResponse.fromdict(as_dict[0])
@dataclass
class SendNominaSync(SOAPService):
contentFile: bytes
def get_wsdl(self):
return 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
def get_service(self):
return 'SendNominaSync'
def build_response(self, as_dict):
return as_dict
class Habilitacion: class Habilitacion:
WSDL = 'https://vpfe-hab.dian.gov.co/WcfDianCustomerServices.svc?wsdl' WSDL = 'https://vpfe-hab.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
@ -197,6 +216,9 @@ class Habilitacion:
def get_wsdl(self): def get_wsdl(self):
return Habilitacion.WSDL return Habilitacion.WSDL
class SendNominaSync(SendNominaSync):
def get_wsdl(self):
return Habilitacion.WSDL
class DianGateway: class DianGateway:

View File

@ -234,9 +234,10 @@ def _append_timestamp(security, expires_dt=None):
if expires_dt is None: if expires_dt is None:
expires_dt = timedelta(seconds=6000) 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 = 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())) etimestamp.append(utils.WSU.Created(get_timestamp(timestamp=timestamp)))
etimestamp.append(utils.WSU.Expires(get_timestamp(delta=expires_dt))) etimestamp.append(utils.WSU.Expires(get_timestamp(timestamp=timestamp, delta=expires_dt)))
security.insert(0, etimestamp) security.insert(0, etimestamp)
if etree.LXML_VERSION[:2] >= (3, 5): if etree.LXML_VERSION[:2] >= (3, 5):
etree.cleanup_namespaces(security, etree.cleanup_namespaces(security,

View File

@ -9,5 +9,8 @@ def path_for_xsd(dirname, xsdname):
UBLInvoice= xmlschema.XMLSchema(path_for_xsd('maindoc', 'UBL-Invoice-2.1.xsd')) UBLInvoice= xmlschema.XMLSchema(path_for_xsd('maindoc', 'UBL-Invoice-2.1.xsd'))
NominaIndividual = xmlschema.XMLSchema(path_for_xsd('nomina', 'NominaIndividualElectronicaXSDV1.0.6.xsd'))
NominaIndividualDeAjuste = xmlschema.XMLSchema(path_for_xsd('nomina', 'NominaIndividualDeAjusteElectronicaXSDV1.0.6.xsd'))
def validate(xml, schema): def validate(xml, schema):
schema.validate(xml) schema.validate(xml)

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
creado por facho, la dian tiene oficial?
poblar usando anexo tecnico 5.3.1
-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>Idioma</ShortName>
<LongName xml:lang="es">Idioma</LongName>
<Version>1</Version>
</Identification>
<ColumnSet>
<Column Id="iso-639-1" Use="required">
<ShortName>ISO 639 1</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="iso-639-1">
<SimpleValue>es</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Español, castellano</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
creado por facho, la dian tiene oficial?
poblar usando anexo tecnico 5.5.1
-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>PeriodoNomina</ShortName>
<LongName xml:lang="es">Periodos Nomina</LongName>
<Version>1</Version>
</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>1</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Semanal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>2</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Decenal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>3</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Catorcenal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>4</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Quincenal</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>5</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Mensual</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>6</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Otro</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
creado por facho, la dian tiene oficial?
poblar usando anexo tecnico 5.5.4
-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>SubTipoTrabajador</ShortName>
<LongName xml:lang="es">Sub tipos de trabajador</LongName>
<Version>1</Version>
</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>00</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No Aplica</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Dependiente pensionado por vejez activo</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@ -46,5 +46,16 @@
<SimpleValue>ReteIVA</SimpleValue> <SimpleValue>ReteIVA</SimpleValue>
</Value> </Value>
</Row> </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> </SimpleCodeList>
</gc:CodeList> </gc:CodeList>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
creado por facho, la dian tiene oficial?
poblar usando anexo tecnico 5.5.2
-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoContrato</ShortName>
<LongName xml:lang="es">Tipos de contratos</LongName>
<Version>1</Version>
</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>1</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Termino Fijo</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>2</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Término Indefinido</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>3</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Obra o Labor</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>4</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Aprendizaje</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>5</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Pŕacticas o Pasantías</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- DIAN Genericode listas de valores:: Ultima modificación 18-02-2019 - evb--> <!-- 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/"> <gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification> <Identification>
<ShortName>TipoDocumento</ShortName> <ShortName>TipoDocumento</ShortName>
@ -35,7 +35,10 @@
<SimpleValue>01</SimpleValue> <SimpleValue>01</SimpleValue>
</Value> </Value>
<Value ColumnRef="name"> <Value ColumnRef="name">
<SimpleValue>Factura de Venta Nacional</SimpleValue> <SimpleValue>Factura electrónica de Venta</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipos de factura</SimpleValue>
</Value> </Value>
</Row> </Row>
<Row> <Row>
@ -43,7 +46,10 @@
<SimpleValue>02</SimpleValue> <SimpleValue>02</SimpleValue>
</Value> </Value>
<Value ColumnRef="name"> <Value ColumnRef="name">
<SimpleValue>Factura de Exportación </SimpleValue> <SimpleValue>Factura electrónica de venta con propósito de exportación</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipos de factura</SimpleValue>
</Value> </Value>
</Row> </Row>
<Row> <Row>
@ -51,7 +57,21 @@
<SimpleValue>03</SimpleValue> <SimpleValue>03</SimpleValue>
</Value> </Value>
<Value ColumnRef="name"> <Value ColumnRef="name">
<SimpleValue>Factura de Contingencia</SimpleValue> <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> </Value>
</Row> </Row>
<Row> <Row>
@ -61,6 +81,9 @@
<Value ColumnRef="name"> <Value ColumnRef="name">
<SimpleValue>Nota Crédito</SimpleValue> <SimpleValue>Nota Crédito</SimpleValue>
</Value> </Value>
<Value ColumnRef="description">
<SimpleValue>Exclusivo en referencias a documentos (elementos DocumentReference)</SimpleValue>
</Value>
</Row> </Row>
<Row> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
@ -69,6 +92,9 @@
<Value ColumnRef="name"> <Value ColumnRef="name">
<SimpleValue>Nota Débito</SimpleValue> <SimpleValue>Nota Débito</SimpleValue>
</Value> </Value>
<Value ColumnRef="description">
<SimpleValue>Exclusivo en referencias a documentos (elementos DocumentReference)</SimpleValue>
</Value>
</Row> </Row>
</SimpleCodeList> </SimpleCodeList>
</gc:CodeList> </gc:CodeList>

View File

@ -75,7 +75,7 @@
<SimpleValue>06</SimpleValue> <SimpleValue>06</SimpleValue>
</Value> </Value>
<Value ColumnRef="name"> <Value ColumnRef="name">
<SimpleValue>ReteFuente</SimpleValue> <SimpleValue>ReteRenta</SimpleValue>
</Value> </Value>
</Row> </Row>
<Row> <Row>
@ -150,6 +150,15 @@
<SimpleValue>Sordicom</SimpleValue> <SimpleValue>Sordicom</SimpleValue>
</Value> </Value>
</Row> </Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>30</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Impuesto al Consumo de Datos</SimpleValue>
</Value>
</Row>
<Row> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
<SimpleValue>ZZ</SimpleValue> <SimpleValue>ZZ</SimpleValue>

View File

@ -0,0 +1,47 @@
<?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,46 +30,6 @@
</Key> </Key>
</ColumnSet> </ColumnSet>
<SimpleCodeList> <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> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
<SimpleValue>O-13</SimpleValue> <SimpleValue>O-13</SimpleValue>
@ -78,14 +38,6 @@
<SimpleValue>Gran contribuyente</SimpleValue> <SimpleValue>Gran contribuyente</SimpleValue>
</Value> </Value>
</Row> </Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-14</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Informante de exógena</SimpleValue>
</Value>
</Row>
<Row> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
<SimpleValue>O-15</SimpleValue> <SimpleValue>O-15</SimpleValue>
@ -94,38 +46,6 @@
<SimpleValue>Autorretenedor</SimpleValue> <SimpleValue>Autorretenedor</SimpleValue>
</Value> </Value>
</Row> </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> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
<SimpleValue>O-23</SimpleValue> <SimpleValue>O-23</SimpleValue>
@ -134,62 +54,6 @@
<SimpleValue>Agente de retención en el impuesto sobre las ventas</SimpleValue> <SimpleValue>Agente de retención en el impuesto sobre las ventas</SimpleValue>
</Value> </Value>
</Row> </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> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
<SimpleValue>O-47</SimpleValue> <SimpleValue>O-47</SimpleValue>
@ -214,782 +78,6 @@
<SimpleValue>No responsable de IVA</SimpleValue> <SimpleValue>No responsable de IVA</SimpleValue>
</Value> </Value>
</Row> </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> <Row>
<Value ColumnRef="code"> <Value ColumnRef="code">
<SimpleValue>R-99-PN</SimpleValue> <SimpleValue>R-99-PN</SimpleValue>

View File

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

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
creado por facho, la dian tiene oficial?
poblar usando anexo tecnico 5.5.3
-->
<gc:CodeList xmlns:gc="http://docs.oasis-open.org/codelist/ns/genericode/1.0/">
<Identification>
<ShortName>TipoTrabajador</ShortName>
<LongName xml:lang="es">Tipos de trabajadores</LongName>
<Version>1</Version>
</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>01</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Dependiente</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>02</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Servicio domestico</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>04</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Madre comunitaria</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>12</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Aprendices del Sena en etapa lectiva</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>18</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Funcionarios públicos sin tope máximo de ibc</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>19</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Aprendices del SENA en etapa productiva</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>21</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Estudiantes de postgrado en salud</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>22</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Profesor de establecimiento particular</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>23</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Estudiantes aportes solo riesgos laborales</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>30</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Dependiente entidades o universidades públicas con régimen especial en salud</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>31</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Cooperados o pre cooperativas de trabajo asociado</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>47</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Trabajador dependiente de entidad beneficiaria del sistema general de participaciones aportes patronales</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>51</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Trabajador de tiempo parcial</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>54</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Pre pensionado de entidad en liquidación.</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>56</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Pre pensionado con aporte voluntario a salud</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>58</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Estudiantes de prácticas laborales en el sector público</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@ -12,9 +12,6 @@ class CodeList:
self.short_name = '' self.short_name = ''
self.long_name = '' self.long_name = ''
self.version = 1 self.version = 1
self.canonical_uri = ''
self.canonical_version_uri = ''
self.location_uri = ''
self.name_column = name_column self.name_column = name_column
self.rows = {} self.rows = {}
@ -27,9 +24,6 @@ class CodeList:
self.short_name = tree.find('./Identification/ShortName').text self.short_name = tree.find('./Identification/ShortName').text
self.long_name = tree.find('./Identification/LongName').text self.long_name = tree.find('./Identification/LongName').text
self.version = tree.find('./Identification/Version').text self.version = tree.find('./Identification/Version').text
self.canonical_uri = tree.find('./Identification/CanonicalUri').text
self.canonical_version_uri = tree.find('./Identification/CanonicalVersionUri').text
self.location_uri = tree.find('./Identification/LocationUri').text
#obtener registros... #obtener registros...
for row in tree.findall('./SimpleCodeList/Row'): for row in tree.findall('./SimpleCodeList/Row'):
@ -88,10 +82,16 @@ TipoAmbiente = CodeList(path_for_codelist('TipoAmbiente-2.1.gc'), 'code', 'name'
TipoDocumento = CodeList(path_for_codelist('TipoDocumento-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')\ TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TipoImpuesto-2.1.custom.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') CodigoPrecioReferencia = CodeList(path_for_codelist('CodigoPrecioReferencia-2.1.gc'), 'code', 'name')
MediosPago = CodeList(path_for_codelist('MediosPago-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') RegimenFiscal = CodeList(path_for_codelist('RegimenFiscal-2.1.custom.gc'), 'code', 'name')
TipoOperacionNC = CodeList(path_for_codelist('TipoOperacionNC-2.1.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') TipoOperacionND = CodeList(path_for_codelist('TipoOperacionND-2.1 - copia.gc'), 'code', 'name')
TipoOperacionF = CodeList(path_for_codelist('TipoOperacionF-2.1.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')) .update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name'))
@ -101,3 +101,9 @@ Paises = CodeList(path_for_codelist('Paises-2.1.gc'), 'code', 'name')
TipoIdFiscal = CodeList(path_for_codelist('TipoIdFiscal-2.1.gc'), 'code', 'name') TipoIdFiscal = CodeList(path_for_codelist('TipoIdFiscal-2.1.gc'), 'code', 'name')
CodigoDescuento = CodeList(path_for_codelist('CodigoDescuento-2.1.gc'), 'code', 'name') CodigoDescuento = CodeList(path_for_codelist('CodigoDescuento-2.1.gc'), 'code', 'name')
UnidadesMedida = CodeList(path_for_codelist('UnidadesMedida-2.1.gc'), 'code', 'name') UnidadesMedida = CodeList(path_for_codelist('UnidadesMedida-2.1.gc'), 'code', 'name')
TipoTrabajador = CodeList(path_for_codelist('TipoTrabajador-2.1.gc'), 'code', 'name')
SubTipoTrabajador = CodeList(path_for_codelist('SubTipoTrabajador-2.1.gc'), 'code', 'name')
TipoContrato = CodeList(path_for_codelist('TipoContrato-2.1.gc'), 'code', 'name')
PeriodoNomina = CodeList(path_for_codelist('PeriodoNomina-2.1.gc'), 'code', 'name')
TipoMoneda = CodeList(path_for_codelist('TipoMoneda-2.1.gc'), 'code', 'name')
IdiomaISO6391 = CodeList(path_for_codelist('Idioma-2.1.gc'), 'iso-639-1', 'name')

View File

@ -1,6 +1,5 @@
# This file is part of facho. The COPYRIGHT file at the top level of # This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms. # this repository contains the full copyright notices and license terms.
from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder
import uuid import uuid
import xmlsig import xmlsig
@ -8,12 +7,16 @@ import xades
from datetime import datetime from datetime import datetime
import OpenSSL import OpenSSL
import zipfile import zipfile
import warnings # import warnings
import hashlib import hashlib
from contextlib import contextmanager from contextlib import contextmanager
from .data.dian import codelist from .data.dian import codelist
from . import form from . import form
from collections import defaultdict from collections import defaultdict
# from pathlib import Path
from dateutil import tz
from cryptography.hazmat.primitives.serialization import pkcs12
AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code'] AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code']
AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code'] AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code']
@ -29,30 +32,51 @@ SCHEME_AGENCY_ATTRS = {
POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf' 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.' 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 = { NAMESPACES = {
'facho': 'http://git.disroot.org/Etrivial/facho', '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', 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-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', 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2',
'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2', 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2',
'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', 'sts': 'dian:gov:co:facturaelectronica:Structures-2-1',
'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2', 'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xades': 'http://uri.etsi.org/01903/v1.3.2#',
'ds': 'http://www.w3.org/2000/09/xmldsig#', 'ds': 'http://www.w3.org/2000/09/xmldsig#',
'sig': 'http://www.w3.org/2000/09/xmldsig#', 'xades': 'http://uri.etsi.org/01903/v1.3.2#',
} }
def fe_from_string(document: str) -> FachoXML:
xml = LXMLBuilder.from_string(document)
return FachoXML(xml, nsmap=NAMESPACES)
from contextlib import contextmanager def fe_from_string(document: str) -> FachoXML:
return FeXML.from_string(document)
# from contextlib import contextmanager
@contextmanager @contextmanager
def mock_xades_policy(): def mock_xades_policy():
from mock import patch from mock import patch
@ -60,6 +84,7 @@ def mock_xades_policy():
with patch('xades.policy.urllib.urlopen') as mock: with patch('xades.policy.urllib.urlopen') as mock:
class UrllibPolicyMock: class UrllibPolicyMock:
def read(self): def read(self):
# Usamos contenido de archivo local
cur_dir = os.path.dirname(os.path.abspath(__file__)) cur_dir = os.path.dirname(os.path.abspath(__file__))
data_dir = os.path.join(cur_dir, 'data', 'dian') data_dir = os.path.join(cur_dir, 'data', 'dian')
policy_file = os.path.join(data_dir, 'politicadefirmav2.pdf') policy_file = os.path.join(data_dir, 'politicadefirmav2.pdf')
@ -69,25 +94,38 @@ def mock_xades_policy():
mock.return_value = UrllibPolicyMock() mock.return_value = UrllibPolicyMock()
yield yield
class FeXML(FachoXML): class FeXML(FachoXML):
def __init__(self, root, namespace): def __init__(self, root, namespace):
# raise Exception(namespace)
super().__init__("{%s}%s" % (namespace, root), super().__init__("{%s}%s" % (namespace, root),
nsmap=NAMESPACES) nsmap=NAMESPACES)
self._cn = root.rstrip('/') @classmethod
#self.find_or_create_element(self._cn) def from_string(cls, document: str) -> 'FeXML':
return super().from_string(document, namespaces=NAMESPACES)
def tostring(self, **kw): def tostring(self, **kw):
return super().tostring(**kw)\ # MACHETE(bit4bit) la DIAN espera que la etiqueta raiz no este en un namespace
.replace("fe:", "")\ urn_oasis = {
.replace("xmlns:fe", "xmlns") '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])
class DianXMLExtensionCUDFE(FachoXMLExtension): 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.tipo_ambiente = tipo_ambiente
self.invoice = invoice self.invoice = invoice
@ -113,10 +151,24 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
fachoxml.set_element('./cbc:UUID', cufe, fachoxml.set_element('./cbc:UUID', cufe,
schemeID=self.tipo_ambiente, schemeID=self.tipo_ambiente,
schemeName=self.schemeName()) schemeName=self.schemeName())
fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1')
fachoxml.set_element('./cbc:ProfileExecutionID', self._tipo_ambiente_int()) 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.7.-2020: FAB36 #DIAN 1.7.-2020: FAB36
fachoxml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', fachoxml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode',
self._get_qrcode(cufe)) self._get_qrcode(cufe))
def issue_time(self, datetime_): def issue_time(self, datetime_):
@ -133,7 +185,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue) build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue)
# PAG 601 # PAG 601
build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount 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)) ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0))
build_vars['CodImpuesto1'] = '01' build_vars['CodImpuesto1'] = '01'
build_vars['CodImpuesto2'] = '04' build_vars['CodImpuesto2'] = '04'
@ -163,7 +216,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
class DianXMLExtensionCUFE(DianXMLExtensionCUDFE): 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.tipo_ambiente = tipo_ambiente
self.clave_tecnica = clave_tecnica self.clave_tecnica = clave_tecnica
self.invoice = invoice self.invoice = invoice
@ -185,20 +239,21 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE):
'%s' % build_vars['NumFac'], '%s' % build_vars['NumFac'],
'%s' % build_vars['FecFac'], '%s' % build_vars['FecFac'],
'%s' % build_vars['HoraFac'], '%s' % build_vars['HoraFac'],
form.Amount(build_vars['ValorBruto']).format('%.02f'), form.Amount(build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1, CodImpuesto1,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).format('%.02f'), build_vars['ValorImpuestoPara'].get(CodImpuesto1, form.Amount(0.0)).truncate_as_string(2),
CodImpuesto2, CodImpuesto2,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto2, 0.0)).format('%.02f'), build_vars['ValorImpuestoPara'].get(CodImpuesto2, form.Amount(0.0)).truncate_as_string(2),
CodImpuesto3, CodImpuesto3,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).format('%.02f'), build_vars['ValorImpuestoPara'].get(CodImpuesto3, form.Amount(0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorTotalPagar']).format('%.02f'), build_vars['ValorTotalPagar'].truncate_as_string(2),
'%s' % build_vars['NitOFE'], '%s' % build_vars['NitOFE'],
'%s' % build_vars['NumAdq'], '%s' % build_vars['NumAdq'],
'%s' % build_vars['ClTec'], '%s' % build_vars['ClTec'],
'%d' % build_vars['TipoAmb'], '%d' % build_vars['TipoAmb'],
] ]
class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS): def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente self.tipo_ambiente = tipo_ambiente
@ -222,20 +277,55 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
'%s' % build_vars['NumFac'], '%s' % build_vars['NumFac'],
'%s' % build_vars['FecFac'], '%s' % build_vars['FecFac'],
'%s' % build_vars['HoraFac'], '%s' % build_vars['HoraFac'],
form.Amount(build_vars['ValorBruto']).format('%.02f'), form.Amount(build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1, CodImpuesto1,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).format('%.02f'), form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2),
CodImpuesto2, CodImpuesto2,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto2, 0.0)).format('%.02f'), form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto2, 0.0)).truncate_as_string(2),
CodImpuesto3, CodImpuesto3,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).format('%.02f'), form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorTotalPagar']).format('%.02f'), form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' % build_vars['NitOFE'], '%s' % build_vars['NitOFE'],
'%s' % build_vars['NumAdq'], '%s' % build_vars['NumAdq'],
'%s' % build_vars['Software-PIN'], '%s' % build_vars['Software-PIN'],
'%d' % build_vars['TipoAmb'], '%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): class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
# RESOLUCION 0004: pagina 108 # RESOLUCION 0004: pagina 108
@ -245,7 +335,8 @@ class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
self.id_software = id_software self.id_software = id_software
def build(self, fexml): 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 = SCHEME_AGENCY_ATTRS.copy()
provider_id_attrs.update({'schemeID': self.dv}) provider_id_attrs.update({'schemeID': self.dv})
#DIAN 1.7.-2020: FAB23 #DIAN 1.7.-2020: FAB23
@ -275,17 +366,28 @@ class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension):
class DianXMLExtensionSigner: class DianXMLExtensionSigner:
def __init__(self, pkcs12_path, passphrase=None, localpolicy=True):
def __init__(self, pkcs12_path, passphrase=None, mockpolicy=False): self._pkcs12_data = open(pkcs12_path, 'rb').read()
self._pkcs12_path = pkcs12_path
self._passphrase = None self._passphrase = None
self._mockpolicy = mockpolicy self._localpolicy = localpolicy
if passphrase: if passphrase:
self._passphrase = passphrase.encode('utf-8') self._passphrase = passphrase.encode('utf-8')
@classmethod @classmethod
def from_pkcs12(self, filepath, password=None): def from_bytes(cls, data, passphrase=None, localpolicy=True):
p12 = OpenSSL.crypto.load_pkcs12(open(filepath, 'rb').read(), password) 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')
def sign_xml_string(self, document): def sign_xml_string(self, document):
xml = LXMLBuilder.from_string(document) xml = LXMLBuilder.from_string(document)
@ -293,7 +395,7 @@ class DianXMLExtensionSigner:
fachoxml = FachoXML(xml,nsmap=NAMESPACES) fachoxml = FachoXML(xml,nsmap=NAMESPACES)
#DIAN 1.7.-2020: FAB01 #DIAN 1.7.-2020: FAB01
extcontent = fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') extcontent = self._element_extension_content(fachoxml)
fachoxml.append_element(extcontent, signature) fachoxml.append_element(extcontent, signature)
return fachoxml.tostring(xml_declaration=True, encoding='UTF-8') return fachoxml.tostring(xml_declaration=True, encoding='UTF-8')
@ -307,7 +409,6 @@ class DianXMLExtensionSigner:
) )
xml.append(signature) xml.append(signature)
ref = xmlsig.template.add_reference( ref = xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="", name="xmldsig-%s-ref0" % (id_uuid) signature, xmlsig.constants.TransformSha256, uri="", name="xmldsig-%s-ref0" % (id_uuid)
) )
@ -315,14 +416,16 @@ class DianXMLExtensionSigner:
id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid) id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid)
xmlsig.template.add_reference( 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) ki = xmlsig.template.ensure_key_info(signature, name=id_keyinfo)
data = xmlsig.template.add_x509_data(ki) data = xmlsig.template.add_x509_data(ki)
xmlsig.template.x509_data_add_certificate(data) xmlsig.template.x509_data_add_certificate(data)
xmlsig.template.add_key_value(ki) 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) xades.utils.ensure_id(qualifying)
id_props = "xmldsig-%s-signedprops" % (id_uuid) id_props = "xmldsig-%s-signedprops" % (id_uuid)
@ -330,10 +433,12 @@ class DianXMLExtensionSigner:
signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props), signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props),
uri_type="http://uri.etsi.org/01903#SignedProperties" 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 # TODO assert with http://www.sic.gov.co/hora-legal-colombiana
props = xades.template.create_signed_properties(qualifying, name=id_props, datetime=datetime.now()) props = xades.template.create_signed_properties(
qualifying, name=id_props, datetime=datetime.now(tz=Bogota))
xades.template.add_claimed_role(props, "supplier") xades.template.add_claimed_role(props, "supplier")
policy = xades.policy.GenericPolicyId( policy = xades.policy.GenericPolicyId(
@ -341,10 +446,14 @@ class DianXMLExtensionSigner:
POLICY_NAME, POLICY_NAME,
xmlsig.constants.TransformSha256) xmlsig.constants.TransformSha256)
ctx = xades.XAdESContext(policy) ctx = xades.XAdESContext(policy)
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(open(self._pkcs12_path, 'rb').read(), ctx.load_pkcs12(pkcs12.load_key_and_certificates(
self._pkcs12_data,
self._passphrase)) self._passphrase))
if self._mockpolicy: # ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(
# self._pkcs12_data,
# self._passphrase))
if self._localpolicy:
with mock_xades_policy(): with mock_xades_policy():
ctx.sign(signature) ctx.sign(signature)
ctx.verify(signature) ctx.verify(signature)
@ -357,28 +466,28 @@ class DianXMLExtensionSigner:
def build(self, fachoxml): def build(self, fachoxml):
signature = self.sign_xml_element(fachoxml.root) signature = self.sign_xml_element(fachoxml.root)
extcontent = fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') extcontent = self._element_extension_content(fachoxml)
fachoxml.append_element(extcontent, signature) fachoxml.append_element(extcontent, signature)
class DianXMLExtensionAuthorizationProvider(FachoXMLExtension): class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
# RESOLUCION 0004: pagina 176 # RESOLUCION 0004: pagina 176
def build(self, fexml): def build(self, fexml):
attrs = {'schemeID': '4', 'schemeName': '31'} attrs = {'schemeID': '4', 'schemeName': '31'}
attrs.update(SCHEME_AGENCY_ATTRS) attrs.update(SCHEME_AGENCY_ATTRS)
authorization_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider') authorization_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider')
authorization_provider.set_element('./sts:AuthorizationProviderID', authorization_provider.set_element('./sts:AuthorizationProviderID',
'800197268', '800197268',
**attrs) **attrs)
class DianXMLExtensionInvoiceSource(FachoXMLExtension): class DianXMLExtensionInvoiceSource(FachoXMLExtension):
# CAB13 # CAB13
def build(self, fexml): def build(self, fexml):
dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode' dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode'
fexml.set_element(dian_path, 'CO', fexml.set_element(
dian_path, 'CO',
listAgencyID="6", listAgencyID="6",
listAgencyName="United Nations Economic Commission for Europe", listAgencyName="United Nations Economic Commission for Europe",
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1")
@ -411,16 +520,15 @@ class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To', invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To',
self.to) self.to)
fexml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode', fexml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode',
'CO', 'CO',
# DIAN 1.7.-2020: FAB15 # DIAN 1.7.-2020: FAB15
listAgencyID="6", listAgencyID="6",
# DIAN 1.7.-2020: FAB16 # DIAN 1.7.-2020: FAB16
listAgencyName="United Nations Economic Commission for Europe", listAgencyName="United Nations Economic Commission for Europe",
# DIAN 1.7.-2020: FAB17 # DIAN 1.7.-2020: FAB17
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1" listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1")
)
class DianZIP: class DianZIP:
@ -429,10 +537,11 @@ class DianZIP:
MAX_FILES = 50 MAX_FILES = 50
def __init__(self, file_like): def __init__(self, file_like):
self.zipfile = zipfile.ZipFile(file_like, mode='w') self.zipfile = zipfile.ZipFile(
file_like, mode='w', compression=zipfile.ZIP_DEFLATED)
self.num_files = 0 self.num_files = 0
def add_invoice_xml(self, name, xml_data): def add_xml(self, name, xml_data):
self.num_files += 1 self.num_files += 1
# TODO cual es la norma para los nombres de archivos? # TODO cual es la norma para los nombres de archivos?
m = hashlib.sha256() m = hashlib.sha256()
@ -443,7 +552,17 @@ class DianZIP:
return filename return filename
# DEPRECATED usar add_xml
def add_invoice_xml(self, name, xml_data):
return self.add_xml(name, xml_data)
def __enter__(self): 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')
"""
return self return self
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
@ -452,29 +571,35 @@ class DianZIP:
class DianXMLExtensionSignerVerifier: class DianXMLExtensionSignerVerifier:
def __init__(self, pkcs12_path, passphrase=None, mockpolicy=False): def __init__(self, pkcs12_path_or_bytes, passphrase=None, localpolicy=True):
self._pkcs12_path = pkcs12_path self._pkcs12_path_or_bytes = pkcs12_path_or_bytes
self._passphrase = None self._passphrase = None
self._mockpolicy = mockpolicy self._localpolicy = localpolicy
if passphrase: if passphrase:
self._passphrase = passphrase.encode('utf-8') self._passphrase = passphrase.encode('utf-8')
def verify_string(self, document): def verify_string(self, document):
# Obtener FachoXML
xml = LXMLBuilder.from_string(document) 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') signature = fachoxml.builder.xpath(fachoxml.root, '//ds:Signature')
assert signature is not None assert signature is not None
# Se mueve Signature a elemento raiz para realizar verificaion
signature.getparent().remove(signature) signature.getparent().remove(signature)
fachoxml.root.append(signature) fachoxml.root.append(signature)
# Verificar archivo usando Signature
pkcs12_data = self._pkcs12_path_or_bytes
if isinstance(self._pkcs12_path_or_bytes, str):
pkcs12_data = open(self._pkcs12_path_or_bytes, 'rb').read()
ctx = xades.XAdESContext() ctx = xades.XAdESContext()
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(open(self._pkcs12_path, 'rb').read(), ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(pkcs12_data,
self._passphrase)) self._passphrase))
try: try:
if self._mockpolicy: if self._localpolicy:
with mock_xades_policy(): with mock_xades_policy():
ctx.verify(signature) ctx.verify(signature)
else: else:

View File

@ -1,23 +1,26 @@
# This file is part of facho. The COPYRIGHT file at the top level of # This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms. # this repository contains the full copyright notices and license terms.
import hashlib # import hashlib
from functools import reduce # from functools import reduce
import copy # import copy
from dataclasses import dataclass
import dataclasses
from dataclasses import dataclass, field
from datetime import datetime, date from datetime import datetime, date
from collections import defaultdict # from collections import defaultdict
import decimal import decimal
from decimal import Decimal from decimal import Decimal
import typing import typing
from ..data.dian import codelist from ..data.dian import codelist
DECIMAL_PRECISION = 6 DECIMAL_PRECISION = 6
class AmountCurrencyError(TypeError): class AmountCurrencyError(TypeError):
pass pass
@dataclass @dataclass
class Currency: class Currency:
code: str code: str
@ -28,6 +31,7 @@ class Currency:
def __str__(self): def __str__(self):
return self.code return self.code
class Collection: class Collection:
def __init__(self, array): def __init__(self, array):
@ -44,6 +48,7 @@ class Collection:
def sum(self): def sum(self):
return sum(self.array) return sum(self.array)
class AmountCollection(Collection): class AmountCollection(Collection):
def sum(self): def sum(self):
@ -52,8 +57,11 @@ class AmountCollection(Collection):
total += v total += v
return total return total
class Amount: class Amount:
def __init__(self, amount: int or float or Amount, currency: Currency = Currency('COP')): def __init__(
self, amount: typing.Union[int, float, str, "Amount"],
currency: Currency = Currency('COP')):
# DIAN 1.7.-2020: 1.2.3.1 # DIAN 1.7.-2020: 1.2.3.1
if isinstance(amount, Amount): if isinstance(amount, Amount):
@ -63,20 +71,27 @@ class Amount:
self.amount = amount.amount self.amount = amount.amount
self.currency = amount.currency self.currency = amount.currency
else: else:
if amount < 0: if float(amount) < 0:
raise ValueError('amount must be positive >= 0') raise ValueError('amount must be positive >= 0')
self.amount = Decimal(amount, decimal.Context(prec=DECIMAL_PRECISION, self.amount = Decimal(
amount, decimal.Context(
prec=DECIMAL_PRECISION,
# DIAN 1.7.-2020: 1.2.1.1 # DIAN 1.7.-2020: 1.2.1.1
rounding=decimal.ROUND_HALF_EVEN)) rounding=decimal.ROUND_HALF_EVEN))
self.currency = currency 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)
def __round__(self, prec): def __round__(self, prec):
return round(self.amount, prec) return round(self.amount, prec)
def __str__(self): def __str__(self):
return '%.06f' % self.amount return str(self.float())
def __lt__(self, other): def __lt__(self, other):
if not self.is_same_currency(other): if not self.is_same_currency(other):
@ -86,19 +101,30 @@ class Amount:
def __eq__(self, other): def __eq__(self, other):
if not self.is_same_currency(other): if not self.is_same_currency(other):
raise AmountCurrencyError() raise AmountCurrencyError()
return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION) return round(self.amount, DECIMAL_PRECISION) == round(
other.amount, DECIMAL_PRECISION)
def __add__(self, other): def _cast(self, val):
if type(val) in [int, float]:
return self.fromNumber(val)
if isinstance(val, Amount):
return val
raise TypeError("cant cast to amount")
def __add__(self, rother):
other = self._cast(rother)
if not self.is_same_currency(other): if not self.is_same_currency(other):
raise AmountCurrencyError() raise AmountCurrencyError()
return Amount(self.amount + other.amount, self.currency) return Amount(self.amount + other.amount, self.currency)
def __sub__(self, other): def __sub__(self, rother):
other = self._cast(rother)
if not self.is_same_currency(other): if not self.is_same_currency(other):
raise AmountCurrencyError() raise AmountCurrencyError()
return Amount(self.amount - other.amount, self.currency) return Amount(self.amount - other.amount, self.currency)
def __mul__(self, other): def __mul__(self, rother):
other = self._cast(rother)
if not self.is_same_currency(other): if not self.is_same_currency(other):
raise AmountCurrencyError() raise AmountCurrencyError()
return Amount(self.amount * other.amount, self.currency) return Amount(self.amount * other.amount, self.currency)
@ -106,8 +132,9 @@ class Amount:
def is_same_currency(self, other): def is_same_currency(self, other):
return self.currency == other.currency return self.currency == other.currency
def format(self, formatter): def truncate_as_string(self, prec):
return formatter % self.float() parts = str(self.float()).split('.', 1)
return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec, '0'))
def float(self): def float(self):
return float(round(self.amount, DECIMAL_PRECISION)) return float(round(self.amount, DECIMAL_PRECISION))
@ -116,27 +143,26 @@ class Amount:
class Quantity: class Quantity:
def __init__(self, val, code): def __init__(self, val, code):
if not isinstance(val, int): if type(val) not in [float, int]:
raise ValueError('val expected int') raise ValueError('val expected int or float')
if code not in codelist.UnidadesMedida: if code not in codelist.UnidadesMedida:
raise ValueError("code [%s] not found" % (code)) raise ValueError("code [%s] not found" % (code))
self.value = val self.value = Amount(val)
self.code = code self.code = code
def __mul__(self, other): def __mul__(self, other):
if isinstance(other, Amount):
return Amount(self.value) * other
return self.value * other return self.value * other
def __lt__(self, other): def __lt__(self, other):
if isinstance(other, Amount):
return Amount(self.value) < other
return self.value < other return self.value < other
def __str__(self): def __str__(self):
return str(self.value) return str(self.value)
def __repr__(self):
return str(self)
@dataclass @dataclass
class Item: class Item:
@ -148,10 +174,10 @@ class Item:
class StandardItem(Item): class StandardItem(Item):
def __init__(self, id_: str, description: str = ''): def __init__(self, id_: str, description: str = '', name: str = ''):
super().__init__(id=id_, super().__init__(id=id_,
description=description, description=description,
scheme_name='', scheme_name=name,
scheme_id='999', scheme_id='999',
scheme_agency_id='') scheme_agency_id='')
@ -175,6 +201,7 @@ class Country:
raise ValueError("code [%s] not found" % (self.code)) raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Paises[self.code]['name'] self.name = codelist.Paises[self.code]['name']
@dataclass @dataclass
class CountrySubentity: class CountrySubentity:
code: str code: str
@ -185,6 +212,7 @@ class CountrySubentity:
raise ValueError("code [%s] not found" % (self.code)) raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Departamento[self.code]['name'] self.name = codelist.Departamento[self.code]['name']
@dataclass @dataclass
class City: class City:
code: str code: str
@ -195,13 +223,22 @@ class City:
raise ValueError("code [%s] not found" % (self.code)) raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Municipio[self.code]['name'] self.name = codelist.Municipio[self.code]['name']
@dataclass
class PostalZone:
code: str = ''
@dataclass @dataclass
class Address: class Address:
name: str name: str
street: str = '' street: str = ''
city: City = City('05001') city: City = field(default_factory=lambda: City('05001'))
country: Country = Country('CO') country: Country = field(default_factory=lambda: Country('CO'))
countrysubentity: CountrySubentity = CountrySubentity('05') countrysubentity: CountrySubentity = field(
default_factory=lambda: CountrySubentity('05'))
postalzone: PostalZone = field(default_factory=lambda: PostalZone(''))
@dataclass @dataclass
class PartyIdentification: class PartyIdentification:
@ -222,6 +259,7 @@ class PartyIdentification:
if self.type_fiscal not in codelist.TipoIdFiscal: if self.type_fiscal not in codelist.TipoIdFiscal:
raise ValueError("type_fiscal [%s] not found" % (self.type_fiscal)) raise ValueError("type_fiscal [%s] not found" % (self.type_fiscal))
@dataclass @dataclass
class Responsability: class Responsability:
codes: list codes: list
@ -246,12 +284,12 @@ class TaxScheme:
code: str code: str
name: str = '' name: str = ''
def __post_init__(self): def __post_init__(self):
if self.code not in codelist.TipoImpuesto: if self.code not in codelist.TipoImpuesto:
raise ValueError("code not found") raise ValueError("code not found")
self.name = codelist.TipoImpuesto[self.code]['name'] self.name = codelist.TipoImpuesto[self.code]['name']
@dataclass @dataclass
class Party: class Party:
name: str name: str
@ -259,10 +297,10 @@ class Party:
responsability_code: typing.List[Responsability] responsability_code: typing.List[Responsability]
responsability_regime_code: str responsability_regime_code: str
organization_code: str organization_code: str
tax_scheme: TaxScheme = TaxScheme('01') tax_scheme: TaxScheme = field(default_factory=lambda: TaxScheme('01'))
phone: str = '' phone: str = ''
address: Address = Address('') address: Address = field(default_factory=lambda: Address(''))
email: str = '' email: str = ''
legal_name: str = '' legal_name: str = ''
legal_company_ident: str = '' legal_company_ident: str = ''
@ -290,7 +328,7 @@ class TaxScheme:
class TaxSubTotal: class TaxSubTotal:
percent: float percent: float
scheme: typing.Optional[TaxScheme] = None scheme: typing.Optional[TaxScheme] = None
tax_amount: Amount = Amount(0.0) tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
def calculate(self, invline): def calculate(self, invline):
if self.percent is not None: if self.percent is not None:
@ -300,12 +338,11 @@ class TaxSubTotal:
@dataclass @dataclass
class TaxTotal: class TaxTotal:
subtotals: list subtotals: list
tax_amount: Amount = Amount(0.0) tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
taxable_amount: Amount = Amount(0.0) taxable_amount: Amount = field(default_factory=lambda: Amount(0.0))
def calculate(self, invline): def calculate(self, invline):
self.taxable_amount = invline.total_amount self.taxable_amount = invline.total_amount
for subtax in self.subtotals: for subtax in self.subtotals:
subtax.calculate(invline) subtax.calculate(invline)
self.tax_amount += subtax.tax_amount self.tax_amount += subtax.tax_amount
@ -318,15 +355,54 @@ class TaxTotalOmit(TaxTotal):
def calculate(self, invline): def calculate(self, invline):
pass 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 @dataclass
class Price: class Price:
amount: Amount amount: Amount
type_code: str type_code: str
type: str type: str
quantity: int = 1
def __post_init__(self): def __post_init__(self):
if self.type_code not in codelist.CodigoPrecioReferencia: if self.type_code not in codelist.CodigoPrecioReferencia:
raise ValueError("type_code [%s] not found" % (self.type_code)) raise ValueError("type_code [%s] not found" % (self.type_code))
if not isinstance(self.quantity, int):
raise ValueError("quantity must be int")
self.amount *= self.quantity
@dataclass @dataclass
@ -347,7 +423,23 @@ class PaymentMean:
@dataclass @dataclass
class PrePaidPayment: class PrePaidPayment:
# DIAN 1.7.-2020: FBD03 # DIAN 1.7.-2020: FBD03
paid_amount: Amount = Amount(0.0) 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.
"""
@dataclass @dataclass
@ -356,6 +448,7 @@ class BillingReference:
uuid: str uuid: str
date: date date: date
class CreditNoteDocumentReference(BillingReference): class CreditNoteDocumentReference(BillingReference):
""" """
ident: Prefijo + Numero de la factura relacionada ident: Prefijo + Numero de la factura relacionada
@ -371,6 +464,7 @@ class DebitNoteDocumentReference(BillingReference):
date: fecha de emision de la factura relacionada date: fecha de emision de la factura relacionada
""" """
class InvoiceDocumentReference(BillingReference): class InvoiceDocumentReference(BillingReference):
""" """
ident: Prefijo + Numero de la nota credito relacionada ident: Prefijo + Numero de la nota credito relacionada
@ -378,6 +472,59 @@ class InvoiceDocumentReference(BillingReference):
date: fecha de emision de la nota credito relacionada date: fecha de emision de la nota credito relacionada
""" """
@dataclass
class AllowanceChargeReason:
code: str
reason: str
def __post_init__(self):
if self.code not in codelist.CodigoDescuento:
raise ValueError("code [%s] not found" % (self.code))
@dataclass
class AllowanceCharge:
# DIAN 1.7.-2020: FAQ03
charge_indicator: bool = True
amount: Amount = field(default_factory=lambda: 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))
# Porcentaje: Porcentaje que aplicar.
multiplier_factor_numeric: Amount = field(
default_factory=lambda: Amount(1.0))
def isCharge(self):
charge_indicator = self.charge_indicator is True
return charge_indicator
def isDiscount(self):
charge_indicator = self.charge_indicator is False
return charge_indicator
def asCharge(self):
self.charge_indicator = True
def asDiscount(self):
self.charge_indicator = False
def hasReason(self):
return self.reason is not None
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 @dataclass
class InvoiceLine: class InvoiceLine:
# RESOLUCION 0004: pagina 155 # RESOLUCION 0004: pagina 155
@ -390,10 +537,33 @@ class InvoiceLine:
# la factura y el percent es unico por type_code # la factura y el percent es unico por type_code
# de subtotal # de subtotal
tax: typing.Optional[TaxTotal] tax: typing.Optional[TaxTotal]
withholding: typing.Optional[WithholdingTaxTotal]
allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(
default_factory=list)
def add_allowance_charge(self, charge):
if not isinstance(charge, AllowanceCharge):
raise TypeError('charge invalid type expected AllowanceCharge')
charge.set_base_amount(self.total_amount_without_charge)
self.allowance_charge.add(charge)
@property
def total_amount_without_charge(self):
return (self.quantity * self.price.amount)
@property @property
def total_amount(self): def total_amount(self):
return self.quantity * self.price.amount charge = AmountCollection(self.allowance_charge)\
.filter(lambda charge: charge.isCharge())\
.map(lambda charge: charge.amount)\
.sum()
discount = AmountCollection(self.allowance_charge)\
.filter(lambda charge: charge.isDiscount())\
.map(lambda charge: charge.amount)\
.sum()
return self.total_amount_without_charge + charge - discount
@property @property
def total_tax_inclusive_amount(self): def total_tax_inclusive_amount(self):
@ -412,8 +582,17 @@ class InvoiceLine:
def taxable_amount(self): def taxable_amount(self):
return self.tax.taxable_amount 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): def calculate(self):
self.tax.calculate(self) self.tax.calculate(self)
self.withholding.calculate(self)
def __post_init__(self): def __post_init__(self):
if not isinstance(self.quantity, Quantity): if not isinstance(self.quantity, Quantity):
@ -422,15 +601,19 @@ class InvoiceLine:
if self.tax is None: if self.tax is None:
self.tax = TaxTotalOmit() self.tax = TaxTotalOmit()
if self.withholding is None:
self.withholding = WithholdingTaxTotalOmit()
@dataclass @dataclass
class LegalMonetaryTotal: class LegalMonetaryTotal:
line_extension_amount: Amount = Amount(0.0) line_extension_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_exclusive_amount: Amount = Amount(0.0) tax_exclusive_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_inclusive_amount: Amount = Amount(0.0) tax_inclusive_amount: Amount = field(default_factory=lambda: Amount(0.0))
charge_total_amount: Amount = Amount(0.0) charge_total_amount: Amount = field(default_factory=lambda: Amount(0.0))
allowance_total_amount: Amount = Amount(0.0) allowance_total_amount: Amount = field(default_factory=lambda: Amount(0.0))
payable_amount: Amount = Amount(0.0) payable_amount: Amount = field(default_factory=lambda: Amount(0.0))
prepaid_amount: Amount = Amount(0.0) prepaid_amount: Amount = field(default_factory=lambda: Amount(0.0))
def calculate(self): def calculate(self):
# DIAN 1.7.-2020: FAU14 # DIAN 1.7.-2020: FAU14
@ -441,53 +624,29 @@ class LegalMonetaryTotal:
- self.prepaid_amount - self.prepaid_amount
@dataclass
class AllowanceChargeReason:
code: str
reason: str
def __post_init__(self):
if self.code not in codelist.CodigoDescuento:
raise ValueError("code [%s] not found" % (self.code))
@dataclass
class AllowanceCharge:
#DIAN 1.7.-2020: FAQ03
charge_indicator: bool = True
amount: Amount = Amount(0.0)
reason: AllowanceChargeReason = None
def isCharge(self):
return self.charge_indicator == True
def isDiscount(self):
return self.charge_indicator == False
def asCharge(self):
self.charge_indicator = True
def asDiscount(self):
self.charge_indicator = False
def hasReason(self):
return self.reason is not None
class NationalSalesInvoiceDocumentType(str): class NationalSalesInvoiceDocumentType(str):
def __str__(self): def __str__(self):
# 6.1.3 # 6.1.3
return '01' return '01'
class CreditNoteDocumentType(str): class CreditNoteDocumentType(str):
def __str__(self): def __str__(self):
# 6.1.3 # 6.1.3
return '91' return '91'
class DebitNoteDocumentType(str): class DebitNoteDocumentType(str):
def __str__(self): def __str__(self):
# 6.1.3 # 6.1.3
return '92' return '92'
class CreditNoteSupportDocumentType(str):
def __str__(self):
return '95'
class Invoice: class Invoice:
def __init__(self, type_code: str): def __init__(self, type_code: str):
if str(type_code) not in codelist.TipoDocumento: if str(type_code) not in codelist.TipoDocumento:
@ -507,6 +666,7 @@ class Invoice:
self.invoice_allowance_charge = [] self.invoice_allowance_charge = []
self.invoice_prepaid_payment = [] self.invoice_prepaid_payment = []
self.invoice_billing_reference = None self.invoice_billing_reference = None
self.invoice_discrepancy_response = None
self.invoice_type_code = str(type_code) self.invoice_type_code = str(type_code)
self.invoice_ident_prefix = None self.invoice_ident_prefix = None
@ -532,7 +692,8 @@ class Invoice:
if len(prefix) <= 4: if len(prefix) <= 4:
self.invoice_ident_prefix = prefix self.invoice_ident_prefix = prefix
else: 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): def set_ident(self, ident: str):
""" """
@ -570,7 +731,7 @@ class Invoice:
self.invoice_operation_type = operation self.invoice_operation_type = operation
def add_allownace_charge(self, charge: AllowanceCharge): def add_allowance_charge(self, charge: AllowanceCharge):
self.invoice_allowance_charge.append(charge) self.invoice_allowance_charge.append(charge)
def add_invoice_line(self, line: InvoiceLine): def add_invoice_line(self, line: InvoiceLine):
@ -582,6 +743,9 @@ class Invoice:
def set_billing_reference(self, billing_reference: BillingReference): def set_billing_reference(self, billing_reference: BillingReference):
self.invoice_billing_reference = billing_reference self.invoice_billing_reference = billing_reference
def set_discrepancy_response(self, billing_response: BillingResponse):
self.invoice_discrepancy_response = billing_response
def accept(self, visitor): def accept(self, visitor):
visitor.visit_payment_mean(self.invoice_payment_mean) visitor.visit_payment_mean(self.invoice_payment_mean)
visitor.visit_customer(self.invoice_customer) visitor.visit_customer(self.invoice_customer)
@ -593,35 +757,54 @@ class Invoice:
def _calculate_legal_monetary_total(self): def _calculate_legal_monetary_total(self):
for invline in self.invoice_lines: for invline in self.invoice_lines:
self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount self.invoice_legal_monetary_total.line_extension_amount +=\
self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_tax_exclusive_amount invline.total_amount
self.invoice_legal_monetary_total.tax_exclusive_amount +=\
invline.total_tax_exclusive_amount
# DIAN 1.7.-2020: FAU6 # DIAN 1.7.-2020: FAU6
self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount self.invoice_legal_monetary_total.tax_inclusive_amount +=\
invline.total_tax_inclusive_amount
# DIAN 1.7.-2020: FAU08 # DIAN 1.7.-2020: FAU08
self.invoice_legal_monetary_total.allowance_total_amount = AmountCollection(self.invoice_allowance_charge)\ self.invoice_legal_monetary_total.allowance_total_amount =\
AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isDiscount())\ .filter(lambda charge: charge.isDiscount())\
.map(lambda charge: charge.amount)\ .map(lambda charge: charge.amount)\
.sum() .sum()
# DIAN 1.7.-2020: FAU10 # DIAN 1.7.-2020: FAU10
self.invoice_legal_monetary_total.charge_total_amount = AmountCollection(self.invoice_allowance_charge)\ self.invoice_legal_monetary_total.charge_total_amount =\
AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isCharge())\ .filter(lambda charge: charge.isCharge())\
.map(lambda charge: charge.amount)\ .map(lambda charge: charge.amount)\
.sum() .sum()
# DIAN 1.7.-2020: FAU12 # DIAN 1.7.-2020: FAU12
self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(self.invoice_prepaid_payment)\ self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(
.map(lambda paid: paid.paid_amount)\ self.invoice_prepaid_payment).map(
.sum() lambda paid: paid.paid_amount).sum()
# DIAN 1.7.-2020: FAU14 # DIAN 1.7.-2020: FAU14
self.invoice_legal_monetary_total.calculate() self.invoice_legal_monetary_total.calculate()
def _refresh_charges_base_amount(self):
if self.invoice_allowance_charge:
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')
# cargos a nivel de factura
for charge in self.invoice_allowance_charge:
charge.set_base_amount(
self.invoice_legal_monetary_total.line_extension_amount)
def calculate(self): def calculate(self):
for invline in self.invoice_lines: for invline in self.invoice_lines:
invline.calculate() invline.calculate()
self._calculate_legal_monetary_total() self._calculate_legal_monetary_total()
self._refresh_charges_base_amount()
class NationalSalesInvoice(Invoice): class NationalSalesInvoice(Invoice):
@ -668,3 +851,30 @@ class DebitNote(Invoice):
if not self.invoice_ident_prefix: if not self.invoice_ident_prefix:
self.invoice_ident_prefix = self.invoice_ident[0:6] 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,3 +2,7 @@ from .invoice import *
from .credit_note import * from .credit_note import *
from .debit_note import * from .debit_note import *
from .utils import * from .utils import *
from .support_document import *
from .support_document_credit_note import *
from .attached_document import *
from .application_response import *

View File

@ -0,0 +1,177 @@
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

@ -0,0 +1,227 @@
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 toFachoXML(self):
return self.fexml

View File

@ -1,9 +1,10 @@
from .. import fe # from .. import fe
from ..form import * # from ..form import *
from .invoice import DIANInvoiceXML from .invoice import DIANInvoiceXML
__all__ = ['DIANCreditNoteXML'] __all__ = ['DIANCreditNoteXML']
class DIANCreditNoteXML(DIANInvoiceXML): class DIANCreditNoteXML(DIANInvoiceXML):
""" """
DianInvoiceXML mapea objeto form.Invoice a XML segun DianInvoiceXML mapea objeto form.Invoice a XML segun

View File

@ -1,9 +1,10 @@
from .. import fe # from .. import fe
from ..form import * # from ..form import *
from .invoice import DIANInvoiceXML from .invoice import DIANInvoiceXML
__all__ = ['DIANDebitNoteXML'] __all__ = ['DIANDebitNoteXML']
class DIANDebitNoteXML(DIANInvoiceXML): class DIANDebitNoteXML(DIANInvoiceXML):
""" """
DianInvoiceXML mapea objeto form.Invoice a XML segun DianInvoiceXML mapea objeto form.Invoice a XML segun
@ -21,17 +22,22 @@ class DIANDebitNoteXML(DIANInvoiceXML):
# DIAN 1.7.-2020: DAU03 # DIAN 1.7.-2020: DAU03
def set_legal_monetary(fexml, invoice): def set_legal_monetary(fexml, invoice):
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount', fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount) invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount', fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount) invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount', fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount) invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount', fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount) invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:PayableAmount', fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount) invoice.invoice_legal_monetary_total.payable_amount)

View File

@ -1,5 +1,7 @@
from .. import fe from .. import fe
from ..form import * from ..form import *
from collections import defaultdict
from .attached_document import AttachedDocument
__all__ = ['DIANInvoiceXML'] __all__ = ['DIANInvoiceXML']
@ -20,79 +22,147 @@ class DIANInvoiceXML(fe.FeXML):
# ZE02 se requiere existencia para firmar # ZE02 se requiere existencia para firmar
ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True) ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True)
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent') extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice) self.attach_invoice = self.attach_invoice(invoice)
# self.attach_document = self.attached_document_invoice(attach_invoice)
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: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:DocumentCurrencyCode', 'COP')
fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines))
if fexml.tag_document() == 'Invoice':
fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (
fexml.tag_document()),
invoice.invoice_period_start.strftime(
'%Y-%m-%d'))
fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (
fexml.tag_document()),
invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.set_billing_reference(invoice)
fexml.customize(invoice)
fexml.set_supplier(invoice)
fexml.set_customer(invoice)
fexml.set_payment_mean(invoice)
fexml.set_invoice_totals(invoice)
fexml.set_legal_monetary(invoice)
fexml.set_invoice_lines(invoice)
fexml.set_allowance_charge(invoice)
return fexml
def attached_document_invoice(fexml, invoice):
attach_invoice = fexml.attach_invoice(invoice)
attached_document = AttachedDocument(
invoice, '123', attach_invoice)
return attached_document
def set_supplier(fexml, invoice): def set_supplier(fexml, invoice):
fexml.placeholder_for('./cac:AccountingSupplierParty') fexml.placeholder_for('./cac:AccountingSupplierParty')
# DIAN 1.7.-2020: CAJ02 # DIAN 1.7.-2020: CAJ02
# DIAN 1.7.-2020: FAJ02 # DIAN 1.7.-2020: FAJ02
fexml.set_element('./cac:AccountingSupplierParty/cbc:AdditionalAccountID', fexml.set_element(
'./cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code) invoice.invoice_supplier.organization_code)
# DIAN 1.7.-2020: CAJ06 # DIAN 1.7.-2020: CAJ06
# DIAN 1.7.-2020: FAJ06 # DIAN 1.7.-2020: FAJ06
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_supplier.name) invoice.invoice_supplier.name)
# DIAN 1.7.-2020: CAJ07, CAJ08 # DIAN 1.7.-2020: CAJ07, CAJ08
# DIAN 1.7.-2020: FAJ07 # DIAN 1.7.-2020: FAJ07
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address')
# DIAN 1.7.-2020: FAJ08 # DIAN 1.7.-2020: FAJ08
# DIAN 1.7.-2020: CAJ09 # DIAN 1.7.-2020: CAJ09
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_supplier.address.city.code) invoice.invoice_supplier.address.city.code)
# DIAN 1.7.-2020: FAJ09 # DIAN 1.7.-2020: FAJ09
# DIAN 1.7.-2020: CAJ10 # DIAN 1.7.-2020: CAJ10
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName',
invoice.invoice_supplier.address.city.name) invoice.invoice_supplier.address.city.name)
# DIAN 1.7.-2020: FAJ11 # DIAN 1.7.-2020: FAJ11
# DIAN 1.7.-2020: CAJ11 # DIAN 1.7.-2020: CAJ11
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name) invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.7.-2020: FAJ12 # DIAN 1.7.-2020: FAJ12
# DIAN 1.7.-2020: CAJ12 # DIAN 1.7.-2020: CAJ12
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code) invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.7.-2020: FAJ14 # DIAN 1.7.-2020: FAJ14
# DIAN 1.7.-2020: CAJ13, CAJ14 # DIAN 1.7.-2020: CAJ13, CAJ14
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street) invoice.invoice_supplier.address.street)
# DIAN 1.7.-2020: FAJ16 # DIAN 1.7.-2020: FAJ16
# DIAN 1.7.-2020: CAJ16, CAJ16 # DIAN 1.7.-2020: CAJ16, CAJ16
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code) invoice.invoice_supplier.address.country.code)
# DIAN 1.7.-2020: FAJ17 # DIAN 1.7.-2020: FAJ17
# DIAN 1.7.-2020: CAJ17 # DIAN 1.7.-2020: CAJ17
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name, invoice.invoice_supplier.address.country.name,
# DIAN 1.7.-2020: FAJ18 # DIAN 1.7.-2020: FAJ18
languageID='es') languageID='es')
supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv, supplier_company_id_attrs.update({
'schemeName': invoice.invoice_supplier.ident.type_fiscal}) 'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal
})
# DIAN 1.7.-2020: FAJ19 # DIAN 1.7.-2020: FAJ19
# DIAN 1.7.-2020: CAJ19 # DIAN 1.7.-2020: CAJ19
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.7.-2020: FAJ20 # DIAN 1.7.-2020: FAJ20
# DIAN 1.7.-2020: CAJ20 # DIAN 1.7.-2020: CAJ20
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_supplier.legal_name) invoice.invoice_supplier.legal_name)
# DIAN 1.7.-2020: FAJ21 # DIAN 1.7.-2020: FAJ21
# DIAN 1.7.-2020: CAJ21 # DIAN 1.7.-2020: CAJ21
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_supplier.ident, invoice.invoice_supplier.ident,
# DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25 # DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25
**supplier_company_id_attrs) **supplier_company_id_attrs)
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
# DIAN 1.7.-2020: FAJ26 # DIAN 1.7.-2020: FAJ26
# DIAN 1.7.-2020: CAJ26 # DIAN 1.7.-2020: CAJ26
invoice.invoice_supplier.responsability_code, invoice.invoice_supplier.responsability_code,
@ -101,30 +171,36 @@ class DIANInvoiceXML(fe.FeXML):
listName=invoice.invoice_supplier.responsability_regime_code) listName=invoice.invoice_supplier.responsability_regime_code)
# DIAN 1.7.-2020: FAJ28 # DIAN 1.7.-2020: FAJ28
# DIAN 1.7.-2020: CAJ28 # DIAN 1.7.-2020: CAJ28
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress')
# DIAN 1.7.-2020: FAJ29 # DIAN 1.7.-2020: FAJ29
# DIAN 1.7.-2020: CAJ29 # DIAN 1.7.-2020: CAJ29
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID',
invoice.invoice_supplier.address.city.code) invoice.invoice_supplier.address.city.code)
# DIAN 1.7.-2020: FAJ30 # DIAN 1.7.-2020: FAJ30
# DIAN 1.7.-2020: CAJ30 # DIAN 1.7.-2020: CAJ30
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city.name) fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city.name)
# DIAN 1.7.-2020: FAJ31 # DIAN 1.7.-2020: FAJ31
# DIAN 1.7.-2020: CAJ31 # DIAN 1.7.-2020: CAJ31
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name) invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.7.-2020: FAJ32 # DIAN 1.7.-2020: FAJ32
# DIAN 1.7.-2020: CAJ32 # DIAN 1.7.-2020: CAJ32
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code) invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.7.-2020: FAJ33,FAJ34 # DIAN 1.7.-2020: FAJ33,FAJ34
# DIAN 1.7.-2020: CAJ33,CAJ34 # DIAN 1.7.-2020: CAJ33,CAJ34
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street) invoice.invoice_supplier.address.street)
# DIAN 1.7.-2020: FAJ35,FAJ36 # DIAN 1.7.-2020: FAJ35,FAJ36
@ -134,107 +210,129 @@ class DIANInvoiceXML(fe.FeXML):
# DIAN 1.7.-2020: FAJ37,FAJ38 # DIAN 1.7.-2020: FAJ37,FAJ38
# DIAN 1.7.-2020: CAJ37,CAJ38 # DIAN 1.7.-2020: CAJ37,CAJ38
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name, invoice.invoice_supplier.address.country.name,
languageID='es') languageID='es')
# DIAN 1.7.-2020: FAJ39 # DIAN 1.7.-2020: FAJ39
# DIAN 1.7.-2020: CAJ39 # DIAN 1.7.-2020: CAJ39
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.7.-2020: CAJ40 # DIAN 1.7.-2020: CAJ40
# DIAN 1.7.-2020: FAJ40 # DIAN 1.7.-2020: FAJ40
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code) invoice.invoice_customer.tax_scheme.code)
# DIAN 1.7.-2020: CAJ41 # DIAN 1.7.-2020: CAJ41
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name) invoice.invoice_customer.tax_scheme.name)
# DIAN 1.7.-2020: FAJ42 # DIAN 1.7.-2020: FAJ42
# DIAN 1.7.-2020: CAJ42 # DIAN 1.7.-2020: CAJ42
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity')
# DIAN 1.7.-2020: FAJ43 # DIAN 1.7.-2020: FAJ43
# DIAN 1.7.-2020: CAJ43 # DIAN 1.7.-2020: CAJ43
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_supplier.legal_name) invoice.invoice_supplier.legal_name)
# DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48 # DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48
# DIAN 1.7.-2020: CAJ44,CAJ45,CAJ46,CAJ47,CAJ48 # DIAN 1.7.-2020: CAJ44,CAJ45,CAJ46,CAJ47,CAJ48
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID',
invoice.invoice_supplier.ident, invoice.invoice_supplier.ident,
**supplier_company_id_attrs) **supplier_company_id_attrs)
# DIAN 1.7.-2020: FAJ49 # DIAN 1.7.-2020: FAJ49
# DIAN 1.7.-2020: CAJ49 # DIAN 1.7.-2020: CAJ49
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme')
# DIAN 1.7.-2020: FAJ50 # DIAN 1.7.-2020: FAJ50
# DIAN 1.7.-2020: CAJ50 # DIAN 1.7.-2020: CAJ50
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID',
invoice.invoice_ident_prefix) invoice.invoice_ident_prefix)
# DIAN 1.7.-2020: CAJ67 # DIAN 1.7.-2020: CAJ67
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:Contact') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:Contact')
# DIAN 1.7.-2020: FAJ71 # DIAN 1.7.-2020: FAJ71
# DIAN 1.7.-2020: CAJ71 # DIAN 1.7.-2020: CAJ71
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail',
invoice.invoice_supplier.email) invoice.invoice_supplier.email)
def set_customer(fexml, invoice): def set_customer(fexml, invoice):
fexml.placeholder_for('./cac:AccountingCustomerParty') fexml.placeholder_for('./cac:AccountingCustomerParty')
fexml.set_element('./cac:AccountingCustomerParty/cbc:AdditionalAccountID', fexml.set_element(
'./cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code) invoice.invoice_customer.organization_code)
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID',
invoice.invoice_customer.ident) invoice.invoice_customer.ident)
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_customer.name) invoice.invoice_customer.name)
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation')
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
# DIAN 1.7.-2020: FAK25 # DIAN 1.7.-2020: FAK25
# DIAN 1.7.-2020: CAK25 # DIAN 1.7.-2020: CAK25
customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv, customer_company_id_attrs.update({
'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal}) 'schemeName': invoice.invoice_customer.ident.type_fiscal})
# DIAN 1.7.-2020: FAK07 # DIAN 1.7.-2020: FAK07
# DIAN 1.7.-2020: CAK07 # DIAN 1.7.-2020: CAK07
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address')
# DIAN 1.7.-2020: FAK08 # DIAN 1.7.-2020: FAK08
# DIAN 1.7.-2020: CAK08 # DIAN 1.7.-2020: CAK08
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_customer.address.city.code) invoice.invoice_customer.address.city.code)
# DIAN 1.7.-2020: FAK09 # DIAN 1.7.-2020: FAK09
# DIAN 1.7.-2020: CAK09 # DIAN 1.7.-2020: CAK09
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', invoice.invoice_customer.address.city.name) fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', invoice.invoice_customer.address.city.name)
# DIAN 1.7.-2020: FAK11 # DIAN 1.7.-2020: FAK11
# DIAN 1.7.-2020: CAK11 # DIAN 1.7.-2020: CAK11
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name) invoice.invoice_customer.address.countrysubentity.name)
# DIAN 1.7.-2020: FAK12 # DIAN 1.7.-2020: FAK12
# DIAN 1.7.-2020: CAK12 # DIAN 1.7.-2020: CAK12
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code) invoice.invoice_customer.address.countrysubentity.code)
# DIAN 1.7.-2020: CAK13, CAK14 # DIAN 1.7.-2020: CAK13, CAK14
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street) invoice.invoice_customer.address.street)
# DIAN 1.7.-2020: CAK16 # DIAN 1.7.-2020: CAK16
# DIAN 1.7.-2020: FAK16 # DIAN 1.7.-2020: FAK16
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code) invoice.invoice_customer.address.country.code)
# DIAN 1.7.-2020: FAK17 # DIAN 1.7.-2020: FAK17
# DIAN 1.7.-2020: CAK17 # DIAN 1.7.-2020: CAK17
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_customer.address.country.name, invoice.invoice_customer.address.country.name,
# DIAN 1.7.-2020: FAK18 # DIAN 1.7.-2020: FAK18
# DIAN 1.7.-2020: CAK18 # DIAN 1.7.-2020: CAK18
@ -242,22 +340,26 @@ class DIANInvoiceXML(fe.FeXML):
# DIAN 1.7.-2020: FAK17,FAK19 # DIAN 1.7.-2020: FAK17,FAK19
# DIAN 1.7.-2020: CAK19 # DIAN 1.7.-2020: CAK19
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.7.-2020: FAK17,FAK20 # DIAN 1.7.-2020: FAK17,FAK20
# DIAN 1.7.-2020: CAK20 # DIAN 1.7.-2020: CAK20
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_customer.legal_name) invoice.invoice_customer.legal_name)
# DIAN 1.7.-2020: CAK21 # DIAN 1.7.-2020: CAK21
# DIAN 1.7.-2020: FAK21 # DIAN 1.7.-2020: FAK21
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_customer.ident, invoice.invoice_customer.ident,
# DIAN 1.7.-2020: CAK22, CAK23, CAK24, CAK25 # DIAN 1.7.-2020: CAK22, CAK23, CAK24, CAK25
**customer_company_id_attrs) **customer_company_id_attrs)
# DIAN 1.7.-2020: CAK26 # DIAN 1.7.-2020: CAK26
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
# DIAN 1.7.-2020: FAK26 # DIAN 1.7.-2020: FAK26
invoice.invoice_customer.responsability_code, invoice.invoice_customer.responsability_code,
# DIAN 1.7.-2020: FAK27 # DIAN 1.7.-2020: FAK27
@ -266,98 +368,121 @@ class DIANInvoiceXML(fe.FeXML):
# DIAN 1.7.-2020: FAK28 # DIAN 1.7.-2020: FAK28
# DIAN 1.7.-2020: CAK28 # DIAN 1.7.-2020: CAK28
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress')
# DIAN 1.7.-2020: FAK29 # DIAN 1.7.-2020: FAK29
# DIAN 1.7.-2020: CAK29 # DIAN 1.7.-2020: CAK29
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID',
invoice.invoice_customer.address.city.code) invoice.invoice_customer.address.city.code)
# DIAN 1.7.-2020: FAK30 # DIAN 1.7.-2020: FAK30
# DIAN 1.7.-2020: CAK30 # DIAN 1.7.-2020: CAK30
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName',
invoice.invoice_customer.address.city.name) invoice.invoice_customer.address.city.name)
# DIAN 1.7.-2020: FAK31 # DIAN 1.7.-2020: FAK31
# DIAN 1.7.-2020: CAK31 # DIAN 1.7.-2020: CAK31
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name) invoice.invoice_customer.address.countrysubentity.name)
# DIAN 1.7.-2020: FAK32 # DIAN 1.7.-2020: FAK32
# DIAN 1.7.-2020: CAK32 # DIAN 1.7.-2020: CAK32
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code) invoice.invoice_customer.address.countrysubentity.code)
# DIAN 1.7.-2020: FAK33 # DIAN 1.7.-2020: FAK33
# DIAN 1.7.-2020: CAK33 # DIAN 1.7.-2020: CAK33
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine')
# DIAN 1.7.-2020: FAK34 # DIAN 1.7.-2020: FAK34
# DIAN 1.7.-2020: CAK34 # DIAN 1.7.-2020: CAK34
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street) invoice.invoice_customer.address.street)
# DIAN 1.7.-2020: CAK35 # DIAN 1.7.-2020: CAK35
# DIAN 1.7.-2020: FAK35 # DIAN 1.7.-2020: FAK35
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country')
# DIAN 1.7.-2020: CAK36 # DIAN 1.7.-2020: CAK36
# DIAN 1.7.-2020: FAK36 # DIAN 1.7.-2020: FAK36
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code) invoice.invoice_customer.address.country.code)
# DIAN 1.7.-2020: CAK37 # DIAN 1.7.-2020: CAK37
# DIAN 1.7.-2020: FAK37 # DIAN 1.7.-2020: FAK37
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name) fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name)
# DIAN 1.7.-2020: FAK38 # DIAN 1.7.-2020: FAK38
# DIAN 1.7.-2020: CAK38 # DIAN 1.7.-2020: CAK38
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code, invoice.invoice_customer.address.country.code,
languageID='es') languageID='es')
# DIAN 1.7.-2020: CAK39 # DIAN 1.7.-2020: CAK39
# DIAN 1.7.-2020: FAK39 # DIAN 1.7.-2020: FAK39
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.7.-2020: CAK40 Machete Construir Validación # DIAN 1.7.-2020: CAK40 Machete Construir Validación
# DIAN 1.7.-2020: FAK40 # DIAN 1.7.-2020: FAK40
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code) invoice.invoice_customer.tax_scheme.code)
# DIAN 1.7.-2020: FAK41 # DIAN 1.7.-2020: FAK41
# DIAN 1.7.-2020: CAK41 Machete Construir Validación # DIAN 1.7.-2020: CAK41 Machete Construir Validación
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name) invoice.invoice_customer.tax_scheme.name)
# DIAN 1.7.-2020: FAK42 # DIAN 1.7.-2020: FAK42
# DIAN 1.7.-2020: CAK42 # DIAN 1.7.-2020: CAK42
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
# DIAN 1.7.-2020: FAK43 # DIAN 1.7.-2020: FAK43
# DIAN 1.7.-2020: CAK43 # DIAN 1.7.-2020: CAK43
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_customer.legal_name) invoice.invoice_customer.legal_name)
# DIAN 1.7.-2020: CAK44 # DIAN 1.7.-2020: CAK44
# DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48 # DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID',
invoice.invoice_customer.ident, invoice.invoice_customer.ident,
**customer_company_id_attrs) **customer_company_id_attrs)
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
# DIAN 1.7.-2020: FAK55 # DIAN 1.7.-2020: FAK55
# DIAN 1.7.-2020: CAK51, CAK55 # DIAN 1.7.-2020: CAK51, CAK55
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail',
invoice.invoice_customer.email) invoice.invoice_customer.email)
def set_payment_mean(fexml, invoice): def set_payment_mean(fexml, invoice):
payment_mean = invoice.invoice_payment_mean payment_mean = invoice.invoice_payment_mean
fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id) fexml.set_element(
fexml.set_element('./cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code) './cac:PaymentMeans/cbc:ID', payment_mean.id)
fexml.set_element('./cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d')) fexml.set_element(
fexml.set_element('./cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id) './cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code)
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d'))
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id)
def set_element_amount_for(fexml, xml, xpath, amount): def set_element_amount_for(fexml, xml, xpath, amount):
if not isinstance(amount, Amount): if not isinstance(amount, Amount):
@ -421,42 +546,55 @@ class DIANInvoiceXML(fe.FeXML):
def set_invoice_totals(fexml, invoice): def set_invoice_totals(fexml, invoice):
tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0)))
withholding_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0)))
percent_for = defaultdict(lambda: None) percent_for = defaultdict(lambda: None)
#requeridos para CUFE #requeridos para CUFE
#tax_amount_for['01']['tax_amount'] = Amount(0.0) #tax_amount_for['01']['tax_amount'] = Amount(0.0)
#tax_amount_for['01']['taxable_amount'] = Amount(0.0) #tax_amount_for['01']['taxable_amount'] = Amount(0.0)
#DIAN 1.7.-2020: FAS07 => Se debe construir estrategia para su manejo #DIAN 1.7.-2020: FAS07 => Se debe construir estrategia para su manejo
#tax_amount_for['04']['tax_amount'] = 0.0 #tax_amount_for['04']['tax_amount'] += 0.0
#tax_amount_for['04']['taxable_amount'] = 0.0 #tax_amount_for['04']['taxable_amount'] += 0.0
#tax_amount_for['03']['tax_amount'] = 0.0 #tax_amount_for['03']['tax_amount'] += 0.0
#tax_amount_for['03']['taxable_amount'] = 0.0 #tax_amount_for['03']['taxable_amount'] += 0.0
total_tax_amount = Amount(0.0) total_tax_amount = Amount(0.0)
total_withholding_amount = Amount(0.0)
for invoice_line in invoice.invoice_lines: for invoice_line in invoice.invoice_lines:
for subtotal in invoice_line.tax.subtotals: for subtotal in invoice_line.tax.subtotals:
if subtotal.scheme is not None: if subtotal.scheme is not None:
tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount
tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount
tax_amount_for[subtotal.scheme.code]['name'] = subtotal.scheme.name
# MACHETE ojo InvoiceLine.tax pasar a Invoice # MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal.scheme.code] = subtotal.percent percent_for[subtotal.scheme.code] = subtotal.percent
total_tax_amount += subtotal.tax_amount total_tax_amount += subtotal.tax_amount
for subtotal_withholding in invoice_line.withholding.subtotals:
if subtotal_withholding.scheme is not None:
withholding_amount_for[subtotal_withholding.scheme.code]['tax_amount'] += subtotal_withholding.tax_amount
withholding_amount_for[subtotal_withholding.scheme.code]['taxable_amount'] += invoice_line.withholding_taxable_amount
# MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal_withholding.scheme.code] = subtotal_withholding.percent
total_withholding_amount += subtotal_withholding.tax_amount
if total_tax_amount != Amount(0.0):
fexml.placeholder_for('./cac:TaxTotal') fexml.placeholder_for('./cac:TaxTotal')
fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount',
total_tax_amount) total_tax_amount)
for index, item in enumerate(tax_amount_for.items()): for index, item in enumerate(tax_amount_for.items()):
cod_impuesto, amount_of = item cod_impuesto, amount_of = item
next_append = index > 0 next_append = index > 0
#DIAN 1.7.-2020: FAS01 #DIAN 1.7.-2020: FAS01
line = fexml.fragment('./cac:TaxTotal', append=next_append) line = fexml.fragment('./cac:TaxTotal', append=next_append)
#DIAN 1.7.-2020: FAU06 #DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount'] tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line, fexml.set_element_amount_for(line,
@ -482,8 +620,55 @@ class DIANInvoiceXML(fe.FeXML):
if percent_for[cod_impuesto]: if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto]) percent_for[cod_impuesto])
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto) cod_impuesto)
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
amount_of['name'])
for index, item in enumerate(withholding_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
# DIAN 1.7.-2020: FAS01
line = fexml.fragment(
'./cac:WithholdingTaxTotal', append=next_append)
# DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(
line,
'/cac:WithholdingTaxTotal/cbc:TaxAmount',
tax_amount)
# DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(
line,
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount'])
# DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(
line,
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount'])
# DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]:
line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto])
if percent_for[cod_impuesto]:
line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto])
line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto)
line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
'ReteRenta')
# abstract method # abstract method
def tag_document(fexml): def tag_document(fexml):
@ -494,94 +679,141 @@ class DIANInvoiceXML(fe.FeXML):
return 'Invoiced' return 'Invoiced'
def set_invoice_line_tax(fexml, line, invoice_line): def set_invoice_line_tax(fexml, line, invoice_line):
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'./cac:TaxTotal/cbc:TaxAmount', './cac:TaxTotal/cbc:TaxAmount',
invoice_line.tax_amount) invoice_line.tax_amount)
# DIAN 1.7.-2020: FAX05 # DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.taxable_amount) invoice_line.taxable_amount)
for subtotal in invoice_line.tax.subtotals: for subtotal in invoice_line.tax.subtotals:
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
subtotal.tax_amount,
currencyID='COP')
if subtotal.percent is not None: if subtotal.percent is not None:
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2)) line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2))
if subtotal.scheme is not None: if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15 # DIAN 1.7.-2020: FAX15
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) line.set_element(
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code)
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name)
def set_invoice_line_withholding(fexml, line, invoice_line):
fexml.set_element_amount_for(
line,
'./cac:WithholdingTaxTotal/cbc:TaxAmount',
invoice_line.withholding_amount)
# DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(
line,
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.withholding_taxable_amount)
for subtotal in invoice_line.withholding.subtotals:
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
subtotal.tax_amount,
currencyID='COP')
if subtotal.percent is not None:
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2))
if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code)
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name)
def set_invoice_lines(fexml, invoice): def set_invoice_lines(fexml, invoice):
next_append = False next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines): for index, invoice_line in enumerate(invoice.invoice_lines):
line = fexml.fragment('./cac:%sLine' % (fexml.tag_document()), append=next_append) line = fexml.fragment(
'./cac:%sLine' % (fexml.tag_document()),
append=next_append)
next_append = True next_append = True
line.set_element('./cbc:ID', index + 1) line.set_element('./cbc:ID', index + 1)
line.set_element('./cbc:%sQuantity' % (fexml.tag_document_concilied()), invoice_line.quantity, unitCode = 'NAR') line.set_element(
fexml.set_element_amount_for(line, './cbc:%sQuantity' % (
fexml.tag_document_concilied()),
invoice_line.quantity,
unitCode='NAR')
fexml.set_element_amount_for(
line,
'./cbc:LineExtensionAmount', './cbc:LineExtensionAmount',
invoice_line.total_amount) invoice_line.total_amount)
if not isinstance(invoice_line.tax, TaxTotalOmit): if not isinstance(
invoice_line.tax, TaxTotalOmit):
fexml.set_invoice_line_tax(line, invoice_line) fexml.set_invoice_line_tax(line, invoice_line)
line.set_element('./cac:Item/cbc:Description', invoice_line.item.description) if not isinstance(
invoice_line.withholding, WithholdingTaxTotalOmit):
fexml.set_invoice_line_withholding(line, invoice_line)
line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', line.set_element(
'./cac:Item/cbc:Description', invoice_line.item.description)
line.set_element(
'./cac:Item/cac:StandardItemIdentification/cbc:ID',
invoice_line.item.id, invoice_line.item.id,
schemeID=invoice_line.item.scheme_id, schemeID=invoice_line.item.scheme_id,
schemeName=invoice_line.item.scheme_name, schemeName=invoice_line.item.scheme_name,
schemeAgencyID=invoice_line.item.scheme_agency_id) 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) line.set_element(
'./cac:Price/cbc:PriceAmount',
invoice_line.price.amount,
currencyID=invoice_line.price.amount.currency.code)
# DIAN 1.7.-2020: FBB04 # DIAN 1.7.-2020: FBB04
line.set_element('./cac:Price/cbc:BaseQuantity', line.set_element(
invoice_line.quantity, './cac:Price/cbc:BaseQuantity',
invoice_line.price.quantity,
unitCode=invoice_line.quantity.code) 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 attach_invoice(fexml, invoice): def set_allowance_charge(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML for idx, charge in enumerate(invoice.invoice_allowance_charge):
en caso de fallar validacion retorna None""" next_append = idx > 0
fexml.append_allowance_charge(
fexml, idx + 1, charge, append=next_append)
fexml.placeholder_for('./ext:UBLExtensions') def append_allowance_charge(fexml, parent, idx, charge, append=False):
fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1') line = parent.fragment('./cac:AllowanceCharge', append=append)
fexml.set_element('./cbc:CustomizationID', invoice.invoice_operation_type) # DIAN 1.7.-2020: FAQ02
fexml.placeholder_for('./cbc:ProfileID') line.set_element('./cbc:ID', idx)
fexml.placeholder_for('./cbc:ProfileExecutionID') # DIAN 1.7.-2020: FAQ03
fexml.set_element('./cbc:ID', invoice.invoice_ident) line.set_element('./cbc:ChargeIndicator', str(
fexml.placeholder_for('./cbc:UUID') charge.charge_indicator).lower())
fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') if charge.reason:
fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) line.set_element(
#DIAN 1.7.-2020: FAD10 './cbc:AllowanceChargeReasonCode', charge.reason.code)
fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) line.set_element(
fexml.set_element('./cbc:%sTypeCode' % (fexml.tag_document()), './cbc:allowanceChargeReason', charge.reason.reason)
invoice.invoice_type_code, line.set_element(
listAgencyID='195', './cbc:MultiplierFactorNumeric', str(
listAgencyName='No matching global declaration available for the validation root', round(charge.multiplier_factor_numeric, 2)))
listURI='http://www.dian.gov.co') fexml.set_element_amount_for(
fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) line, './cbc:Amount', charge.amount)
fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (fexml.tag_document()), fexml.set_element_amount_for(
invoice.invoice_period_start.strftime('%Y-%m-%d')) line, './cbc:BaseAmount', charge.base_amount)
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_billing_reference(invoice)
return fexml
def customize(fexml, invoice): def customize(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML """adiciona etiquetas a FEXML y retorna FEXML

View File

@ -0,0 +1,647 @@
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

@ -0,0 +1,25 @@
# 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,14 +2,30 @@ from .. import fe
__all__ = ['DIANWrite', 'DIANWriteSigned'] __all__ = ['DIANWrite', 'DIANWriteSigned']
def DIANWrite(xml, filename): def DIANWrite(xml, filename):
document = xml.tostring(xml_declaration=True, encoding='UTF-8') document = xml.tostring(xml_declaration=True, encoding='UTF-8')
with open(filename, 'w') as f: with open(filename, 'w') as f:
f.write(document) f.write(document)
def DIANWriteSigned(xml, filename, private_key, passphrase, use_cache_policy=False): def DIANWriteSigned(
document = xml.tostring(xml_declaration=True, encoding='UTF-8').encode('utf-8') xml,
signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase, mockpolicy=use_cache_policy) 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)
with open(filename, 'w') as f: with open(filename, 'w') as f:
f.write(signer.sign_xml_string(document)) f.write(signer.sign_xml_string(document))

651
facho/fe/nomina/__init__.py Normal file
View File

@ -0,0 +1,651 @@
#
# Para esta implementacion se usa BDD
# ver **test_nomina.py**.
#
# La idea en general es validar comportamiento desde el XML,
# creando las estructuras minimas necesaras.
from dataclasses import dataclass
from datetime import datetime
import hashlib
import typing
from .. import fe
from .. import form
from ..data.dian import codelist
from .devengado import *
from .deduccion import *
from .trabajador import *
from .empleador import *
from .pago import *
from .lugar import Lugar
from .amount import Amount
from .exception import *
class Fecha:
def __init__(self, fecha):
try:
datetime.strptime(fecha, "%Y-%m-%d")
except ValueError:
raise ValueError("fecha debe ser formato YYYY-MM-DD")
self.value = fecha
@classmethod
def cast(cls, data, optional=False):
if isinstance(data, str):
return cls(data)
elif isinstance(data, cls):
return data
elif data is None and optional:
return None
else:
raise ValueError('no se logra hacer casting a Fecha')
def __str__(self):
return self.value
class FechaPago(Fecha):
def apply(self, fragment):
fragment.set_element('./FechaPago', self.value)
@dataclass
class Novedad:
# cune de nomina a relacionar
# NIE204
cune: str
# NIE199
activa: bool = False
def apply(self, fragment):
if self.cune != "":
fragment.set_attributes('./Novedad',
CUNENov=self.cune,
)
def post_apply(self, fexml, scopexml, fragment):
scopexml.set_element('./Novedad', self.activa)
@dataclass
class NumeroSecuencia:
consecutivo: int
prefijo: str
def apply(self, fragment):
numero = f"{self.prefijo}{self.consecutivo}"
fragment.set_attributes('./NumeroSecuenciaXML',
# NIE010
Prefijo=self.prefijo,
# NIE011
Consecutivo=self.consecutivo,
# NIE012
Numero = numero)
@dataclass
class Periodo:
fecha_ingreso: typing.Union[str, Fecha]
fecha_liquidacion_inicio: typing.Union[str, Fecha]
fecha_liquidacion_fin: typing.Union[str, Fecha]
fecha_generacion: typing.Union[str, Fecha]
tiempo_laborado: int = 1
fecha_retiro: typing.Union[str, Fecha] = None
def __post_init__(self):
self.fecha_ingreso = Fecha.cast(self.fecha_ingreso)
self.fecha_liquidacion_inicio = Fecha.cast(self.fecha_liquidacion_inicio)
self.fecha_liquidacion_fin = Fecha.cast(self.fecha_liquidacion_fin)
self.fecha_retiro = Fecha.cast(self.fecha_retiro, optional=True)
def apply(self, fragment):
fragment.set_attributes('./Periodo',
#NIE002
FechaIngreso=self.fecha_ingreso,
#NIE003
FechaRetiro=self.fecha_retiro,
#NIE004
FechaLiquidacionInicio=self.fecha_liquidacion_inicio,
#NIE005
FechaLiquidacionFin=self.fecha_liquidacion_fin,
#NIE006
TiempoLaborado=self.tiempo_laborado,
#NIE008
FechaGen=self.fecha_generacion)
@dataclass
class Proveedor:
razon_social: str
nit: str
dv: int
software_id: str
software_pin: str
def apply(self, fragment):
fragment.set_attributes('./ProveedorXML',
# NIE017
NIT=self.nit,
# NIE018
DV=self.dv,
# NIE019
SoftwareID=self.software_id,
SoftwareSC=None,
# NIE025
RazonSocial=self.razon_social
)
def post_apply(self, fexml, scopexml, fragment):
cune_xpath = scopexml.xpath_from_root('/InformacionGeneral')
cune = fexml.get_element_attribute(cune_xpath, 'CUNE')
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:
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)
# NIE020
software_code = self._software_security_code(fexml, scopexml)
fexml.set_attributes(scopexml.xpath_from_root('/ProveedorXML'), SoftwareSC=software_code)
def _software_security_code(self, fexml, scopexml):
# 8.2
numero = fexml.get_element_attribute(scopexml.xpath_from_root('/NumeroSecuenciaXML'), 'Numero')
if numero is None:
raise RuntimeError('fallo obtener NumeroSequenciaXML/@Numero')
id_software = self.software_id
software_pin = self.software_pin
code = "".join([id_software, software_pin, numero])
fexml.set_attributes(scopexml.xpath_from_root('/ProveedorXML'))
h = hashlib.sha384()
h.update(code.encode('utf-8'))
return h.hexdigest()
@dataclass
class Metadata:
novedad: Novedad
secuencia: NumeroSecuencia
# NIE013, NIE014, NIE015, NIE016
lugar_generacion: Lugar
proveedor: Proveedor
def apply(self, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
if 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)
@dataclass
class PeriodoNomina:
code: str
name: str = ''
def __post_init__(self):
if self.code not in codelist.PeriodoNomina:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.PeriodoNomina[self.code]['name']
@dataclass
class TipoMoneda:
code: str
name: str = ''
def __post_init__(self):
if self.code not in codelist.TipoMoneda:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoMoneda[self.code]['name']
@dataclass
class InformacionGeneral:
@dataclass
class TIPO_AMBIENTE:
valor: str
def __eq__(self, other):
return self.valor == str(other)
# TABLA 5.1.1
@dataclass
class AMBIENTE_PRODUCCION(TIPO_AMBIENTE):
valor: str = '1'
def __str__(self):
self.valor
@dataclass
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
fecha_generacion: typing.Union[str, Fecha]
hora_generacion: str
periodo_nomina: PeriodoNomina
tipo_moneda: TipoMoneda
tipo_ambiente: TIPO_AMBIENTE
tipo_xml: TIPO_XML
software_pin: str
def __post_init__(self):
self.fecha_generacion = Fecha.cast(self.fecha_generacion)
def apply(self, fragment, version):
fragment.set_attributes('./InformacionGeneral',
# NIE022
Version = version,
# NIE023
Ambiente = self.tipo_ambiente.valor,
# NIE202
# TABLA 5.5.2
# TODO(bit4bit) solo NominaIndividual
TipoXML = self.tipo_xml.valor,
# NIE024
CUNE = None,
# NIE025
EncripCUNE = 'CUNE-SHA384',
# NIE026
FechaGen = self.fecha_generacion,
# NIE027
HoraGen = self.hora_generacion,
# NIE029
PeriodoNomina = self.periodo_nomina.code,
# NIE030
TipoMoneda = self.tipo_moneda.code,
TRM = 0
# TODO(bit4bit) resto...
# .....
)
def post_apply(self, fexml, scopexml, fragment):
# generar cune
# ver 8.1.1.1
xpaths = [
scopexml.xpath_from_root('/NumeroSecuenciaXML/@Numero'),
scopexml.xpath_from_root('/InformacionGeneral/@FechaGen'),
scopexml.xpath_from_root('/InformacionGeneral/@HoraGen'),
scopexml.xpath_from_root('/DevengadosTotal'),
scopexml.xpath_from_root('/DeduccionesTotal'),
scopexml.xpath_from_root('/ComprobanteTotal'),
scopexml.xpath_from_root('/Empleador/@NIT'),
scopexml.xpath_from_root('/Trabajador/@NumeroDocumento'),
scopexml.xpath_from_root('/InformacionGeneral/@TipoXML'),
tuple([self.software_pin]),
scopexml.xpath_from_root('/InformacionGeneral/@Ambiente')
]
campos = fexml.get_elements_text_or_attributes(xpaths)
cune = "".join(campos)
h = hashlib.sha384()
h.update(cune.encode('utf-8'))
cune_hash = h.hexdigest()
fragment.set_attributes(
'./InformacionGeneral',
# NIE024
CUNE = cune_hash
)
class DianXMLExtensionSigner(fe.DianXMLExtensionSigner):
def __init__(self, pkcs12_path, passphrase=None, localpolicy=True):
super().__init__(pkcs12_path, passphrase=passphrase, localpolicy=localpolicy)
def _element_extension_content(self, fachoxml):
return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
class DIANNominaXML:
def __init__(self, tag_document, xpath_ajuste=None, schemaLocation=None, namespace_ajuste=None):
self.informacion_general_version = None
self.tag_document = tag_document
if namespace_ajuste:
self.fexml = fe.FeXML(tag_document, namespace_ajuste)
else:
self.fexml = fe.FeXML(tag_document, 'dian:gov:co:facturaelectronica:NominaIndividual')
self.fexml.root.set("SchemaLocation", "")
self.fexml.root.set("schemaLocation", schemaLocation)
# layout, la dian requiere que los elementos
# esten ordenados segun el anexo tecnico
self.fexml.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
self.fexml.placeholder_for('./TipoNota', optional=True)
self.root_fragment = self.fexml
if xpath_ajuste is not None:
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('./Periodo')
self.root_fragment.placeholder_for('./NumeroSecuenciaXML')
self.root_fragment.placeholder_for('./LugarGeneracionXML')
self.root_fragment.placeholder_for('./ProveedorXML')
self.root_fragment.placeholder_for('./CodigoQR')
self.root_fragment.placeholder_for('./InformacionGeneral')
self.root_fragment.placeholder_for('./Empleador')
self.root_fragment.placeholder_for('./Trabajador')
self.root_fragment.placeholder_for('./Pago')
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.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')
self.numero_secuencia_xml = self.root_fragment.fragment('./NumeroSecuenciaXML')
self.lugar_generacion_xml = self.root_fragment.fragment('./LugarGeneracionXML')
self.proveedor_xml = self.root_fragment.fragment('./ProveedorXML')
self.empleador = self.root_fragment.fragment('./Empleador')
self.trabajador = self.root_fragment.fragment('./Trabajador')
self.pago_xml = self.root_fragment.fragment('./Pago')
self.devengados = self.root_fragment.fragment('./Devengados')
self.deducciones = self.root_fragment.fragment('./Deducciones')
self.informacion_general = None
self.metadata = None
def asignar_metadata(self, metadata):
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):
if not isinstance(general, InformacionGeneral):
raise ValueError('se espera tipo InformacionGeneral')
self.informacion_general = general
self.informacion_general.apply(self.informacion_general_xml, self.informacion_general_version)
def asignar_periodo(self, periodo):
if not isinstance(periodo, Periodo):
raise ValueError('se espera tipo Periodo')
periodo.apply(self.periodo_xml)
def asignar_pago(self, pago):
if not isinstance(pago, Pago):
raise ValueError('se espera tipo Pago')
pago.apply(self.pago_xml)
def asignar_fecha_pago(self, data):
if isinstance(data, str):
fecha = FechaPago(data)
elif isinstance(data, FechaPago):
fecha = data
fecha.apply(self.fecha_pagos_xml)
def asignar_empleador(self, empleador):
if not isinstance(empleador, Empleador):
raise ValueError('se espera tipo Empleador')
empleador.apply(self.empleador)
def asignar_trabajador(self, trabajador):
if not isinstance(trabajador, Trabajador):
raise ValueError('se espera tipo Trabajador')
trabajador.apply(self.trabajador)
def adicionar_devengado(self, devengado):
if not isinstance(devengado, Devengado):
raise ValueError('se espera tipo Devengado')
devengado.apply(self.devengados)
def adicionar_deduccion(self, deduccion):
if not isinstance(deduccion, Deduccion):
raise ValueError('se espera tipo Devengado')
deduccion.apply(self.deducciones)
def validate(self):
"""
Valida requisitos segun anexo tecnico
"""
errors = []
def check_element(xpath, msg):
if not self.fexml.exist_element(xpath):
errors.append(DIANNominaIndividualError(msg))
def check_attribute(xpath, key, msg):
err = DIANNominaIndividualError(msg)
elem = self.fexml.get_element(xpath)
if elem is None:
return errors.append(err)
if elem.get(key, None) is None:
return errors.append(err)
check_attribute(
self.fexml.xpath_from_root('/Periodo'),
'FechaIngreso',
'se requiere Periodo')
check_element(
self.fexml.xpath_from_root('/Pago'),
'se requiere Pago'
)
check_element(
self.fexml.xpath_from_root('/Devengados/Basico'),
'se requiere DevengadoBasico'
)
check_element(
self.fexml.xpath_from_root('/Deducciones/Salud'),
'se requiere DeduccionSalud'
)
check_element(
self.fexml.xpath_from_root('/Deducciones/FondoPension'),
'se requiere DeduccionFondoPension'
)
return errors
def informacion_general(self):
xpath = self.root_fragment.xpath_from_root('/InformacionGeneral')
return {
'cune': self.fexml.get_element_attribute(cune_xpath, 'CUNE'),
'fecha_generacion': self.fexml.get_element_attribute(xpath, 'FechaGen'),
'numero': self.fexml.get_element_attribute(self.root_fragment('/NumeroSecuenciaXML', 'Numero'))
}
def toFachoXML(self):
self._devengados_total()
self._deducciones_total()
self._comprobante_total()
if self.informacion_general is not None:
#TODO(bit4bit) acoplamiento temporal
# es importante el orden de ejecucion
self.informacion_general.post_apply(self.fexml, self.root_fragment, self.informacion_general_xml)
if self.metadata is not None:
self.metadata.post_apply(self.fexml, self.root_fragment, self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
return self.fexml
def _comprobante_total(self):
devengados_total = self.root_fragment.get_element_text_or_attribute('./DevengadosTotal', '0.0')
deducciones_total = self.root_fragment.get_element_text_or_attribute('./DeduccionesTotal', '0.0')
comprobante_total = Amount(devengados_total) - Amount(deducciones_total)
self.root_fragment.set_element('./ComprobanteTotal', str(round(comprobante_total, 2)))
def _deducciones_total(self):
xpaths = [
self.root_fragment.xpath_from_root('/Deducciones/Salud/@Deduccion'),
self.root_fragment.xpath_from_root('/Deducciones/FondoPension/@Deduccion')
]
deducciones = map(lambda valor: Amount(valor),
self._values_of_xpaths(xpaths))
deducciones_total = Amount(0.0)
for deduccion in deducciones:
deducciones_total += deduccion
self.root_fragment.set_element('./DeduccionesTotal', str(round(deducciones_total, 2)))
def _devengados_total(self):
xpaths = [
self.root_fragment.xpath_from_root('/Devengados/Basico/@SueldoTrabajado'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@AuxilioTransporte'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@ViaticoManuAlojS'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@ViaticoManuAlojNS')
]
devengados = map(lambda valor: Amount(valor),
self._values_of_xpaths(xpaths))
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)))
def _values_of_xpaths(self, xpaths):
xpaths_values_of_values = map(lambda val: self.fexml.get_element_text_or_attribute(val, multiple=True), xpaths)
xpaths_values = []
# toda esta carreta para hacer un aplano de lista
for xpath_values in xpaths_values_of_values:
if xpath_values is None:
continue
for xpath_value in xpath_values:
xpaths_values.append(xpath_value)
return filter(lambda val: val is not None, xpaths_values)
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:
numero: str
cune: str
fecha_generacion: str
def apply(self, fragment):
# NIAE214
fragment.set_element('./TipoNota', '1')
fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None,
# NIAE090
NumeroPred = self.numero,
# NIAE191
CUNEPred = self.cune,
# NIAE192
FechaGenPred = self.fecha_generacion
)
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'
def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor):
raise ValueError("se espera tipo Predecesor")
predecesor.apply(self.fexml)
class Eliminar(DIANNominaXML):
@dataclass
class Predecesor:
numero: str
cune: str
fecha_generacion: str
def apply(self, fragment):
fragment.set_element('./TipoNota', '2')
fragment.set_element('./Eliminar/EliminandoPredecesor', None,
# NIAE090
NumeroPred = self.numero,
# NIAE191
CUNEPred = self.cune,
# NIAE192
FechaGenPred = self.fecha_generacion
)
def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste', './Eliminar', 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"
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')

View File

@ -0,0 +1,4 @@
from .. import form
class Amount(form.Amount):
pass

View File

@ -0,0 +1,3 @@
# contributing
crear nuevo objeto de valor y exportar en **__init__.py** atributo **__all__**.

View File

@ -0,0 +1,13 @@
#
# al crear objetos de valor
# se debe exportar en __all__
from .deduccion import *
from .salud import *
from .fondo_pension import *
__all__ = [
'Deduccion',
'DeduccionSalud',
'DeduccionFondoPension'
]

View File

@ -0,0 +1,2 @@
class Deduccion:
pass

View File

@ -0,0 +1,18 @@
from dataclasses import dataclass
from ..amount import Amount
from .deduccion import Deduccion
@dataclass
class DeduccionFondoPension(Deduccion):
porcentaje: Amount
deduccion: Amount
def apply(self, fragment):
fragment.set_element('./FondoPension', None,
append_ = True,
# NIE164
Porcentaje = str(round(self.porcentaje, 2)),
# NIE166
Deduccion = self.deduccion
)

View File

@ -0,0 +1,19 @@
from dataclasses import dataclass
from ..amount import Amount
from .deduccion import Deduccion
@dataclass
class DeduccionSalud(Deduccion):
porcentaje: Amount
deduccion: Amount
def apply(self, fragment):
fragment.set_element('./Salud', None,
append_ = True,
# NIE161
Porcentaje = str(round(self.porcentaje, 2)),
# NIE163
Deduccion = self.deduccion
)

View File

@ -0,0 +1,4 @@
from .. import form
class Departamento(form.CountrySubentity):
pass

View File

@ -0,0 +1,3 @@
# contributing
crear nuevo objeto de valor y exportar en **__init__.py** atributo **__all__**.

View File

@ -0,0 +1,19 @@
from .basico import *
from .transporte import *
from .devengado import *
from .horas_extras import *
__all__ = [
'Devengado',
'DevengadoBasico',
'DevengadoTransporte',
'DevengadoHoraExtra',
'DevengadoHorasExtrasDiarias',
'DevengadoHorasExtrasNocturnas',
'DevengadoHorasRecargoNocturno',
'DevengadoHorasExtrasDiariasDominicalesYFestivos',
'DevengadoHorasRecargoDiariasDominicalesYFestivos',
'DevengadoHorasExtrasNocturnasDominicalesYFestivos',
'DevengadoHorasRecargoNocturnoDominicalesYFestivos'
]

View File

@ -0,0 +1,20 @@
from dataclasses import dataclass
from ..amount import Amount
from .devengado import Devengado
@dataclass
class DevengadoBasico(Devengado):
dias_trabajados: int
sueldo_trabajado: Amount
def apply(self, fragment):
fragment.find_or_create_element('./Basico')
fragment.set_attributes('/Basico',
# NIE069
DiasTrabajados = str(self.dias_trabajados),
# NIE070
SueldoTrabajado = round(self.sueldo_trabajado, 2)
)

View File

@ -0,0 +1,2 @@
class Devengado:
pass

View File

@ -0,0 +1,93 @@
from dataclasses import dataclass
from typing import List
from ..amount import Amount
from .devengado import Devengado
@dataclass
class DevengadoHoraExtra:
hora_inicio: str
hora_fin: str
cantidad: int
porcentaje: Amount
pago: Amount
def apply(self, child_path, fragment):
fragment.set_element(child_path, None,
append_=True,
# NIE074
HoraInicio=self.hora_inicio,
# NIE075
HoraFin=self.hora_fin,
# NIE076
Cantidad=self.cantidad,
# NIE077
Porcentaje=str(round(self.porcentaje, 2)),
# NIE078
Pago=str(round(self.pago, 2)))
@dataclass
class DevengadoHorasExtrasDiarias(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HEDs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HED', hora_extra_xml)
@dataclass
class DevengadoHorasExtrasNocturnas(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HENs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HEN', hora_extra_xml)
@dataclass
class DevengadoHorasRecargoNocturno(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HRNs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HRN', hora_extra_xml)
@dataclass
class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HEDDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HEDDF', hora_extra_xml)
@dataclass
class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HRDDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HRDDF', hora_extra_xml)
@dataclass
class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HENDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HENDF', hora_extra_xml)
@dataclass
class DevengadoHorasRecargoNocturnoDominicalesYFestivos(Devengado):
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HRNDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HRNDF', hora_extra_xml)

View File

@ -0,0 +1,21 @@
from dataclasses import dataclass
from ..amount import Amount
from .devengado import Devengado
@dataclass
class DevengadoTransporte(Devengado):
auxilio_transporte: Amount = None
viatico_manutencion: Amount = None
viatico_manutencion_no_salarial: Amount = None
def apply(self, fragment):
fragment.set_element('./Transporte', None,
append_ = True,
# NIE071
AuxilioTransporte = self.auxilio_transporte,
# NIE072
ViaticoManuAlojS = self.viatico_manutencion,
# NIE073
ViaticoManuAlojNS = self.viatico_manutencion_no_salarial
)

View File

@ -0,0 +1,35 @@
from dataclasses import dataclass
from ..pais import Pais
from ..departamento import Departamento
from ..municipio import Municipio
@dataclass
class Empleador:
razon_social: str
nit: str
dv: str
pais: Pais
departamento: Departamento
municipio: Municipio
direccion: str
def apply(self, fragment):
fragment.set_attributes('./Empleador',
# NIE033
NIT = self.nit,
# NIE034
DV = self.dv,
# NIE035
Pais = self.pais.code,
# NIE036
DepartamentoEstado = self.departamento.code,
# NIE037
MunicipioCiudad = self.municipio.code,
# NIE038
Direccion = self.direccion,
RazonSocial=self.razon_social
)

View File

@ -0,0 +1,2 @@
class DIANNominaIndividualError(Exception):
pass

View File

@ -0,0 +1,155 @@
from dataclasses import dataclass
import datetime
from facho import fe
class Habilitacion:
@dataclass
class Metadata:
software_pin: str
software_id: str
nit: str
dv: str
def __init__(self, metadata):
self.metadata = metadata
def generar(self, zipname, fecha):
nominas = []
dianzip = fe.DianZIP(open(zipname, 'w'))
fechabase = datetime.datetime.now()
consecutivo = 0
for _ in range(1, 11):
consecutivo += 1
fechabase += datetime.timedelta(days=1)
nomina = self._crear_nomina_individual()
# pag 96
nombre = "nie%010d%s%08x.xml" % (int(self.nit), fecha.strftime('%s'), consecutivo)
def _crear_nomina_individual_reemplazar(self, nomina, fechabase):
metadata = self.metadata
fecha = fechabase.strftime('%Y-%m-%d')
nomina_ajuste = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
self._poblar_nomina(nomina_ajuste, metadata, fecha, prefijo='R')
informacion_general = nomina.informacion_general()
def _poblar_nomina(self, nomina, metadata, fecha, prefijo='N', consecutivo='0001'):
nomina.asignar_fecha_pago(fecha)
nomina.asignar_metadata(fe.nomina.Metadata(
secuencia=fe.nomina.NumeroSecuencia(
prefijo=prefijo,
consecutivo=consecutivo
),
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=metadata.nit,
dv=metadata.dv,
software_id=metadata.software_id,
software_pin=metadata.software_pin
)
))
nomina.asignar_periodo(fe.nomina.Periodo(
fecha_ingreso=fecha,
fecha_liquidacion_inicio=fecha,
fecha_liquidacion_fin=fecha,
fecha_generacion=fecha,
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = fecha,
hora_generacion = '20:09:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = metadata.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(
nit = metadata.nit,
dv = '0',
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 = metadata.nit,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(3_500_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
porcentaje=fe.nomina.Amount(1),
deduccion=fe.nomina.Amount(10)
))
def _crear_nomina_individual(self, fechabase):
metadata = self.metadata
fecha = fechabase.strftime('%Y-%m-%d')
nomina = fe.nomina.DIANNominaIndividual()
self._poblar_nomina(nomina, metadata, fecha)

25
facho/fe/nomina/lugar.py Normal file
View File

@ -0,0 +1,25 @@
from dataclasses import dataclass
from .pais import Pais
from .departamento import Departamento
from .municipio import Municipio
from facho.fe.data.dian import codelist
@dataclass
class Lugar:
pais: Pais
departamento: Departamento
municipio: Municipio
idioma: str = 'es'
def __post_init__(self):
if self.idioma not in codelist.IdiomaISO6391:
raise ValueError("idioma [%s] not found" % (self.code))
codelist.IdiomaISO6391[self.idioma]['iso-639-1']
def apply(self, fragment, root):
fragment.set_attributes(root,
Pais=self.pais.code,
DepartamentoEstado=self.departamento.code,
MunicipioCiudad=self.municipio.code,
Idioma=self.idioma)

View File

@ -0,0 +1,5 @@
from .. import form
class Municipio(form.City):
pass

Some files were not shown because too many files have changed in this diff Show More