start switch to better architecture.
This commit is contained in:
parent
8020c868af
commit
024b7feb09
@ -24,8 +24,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/smartfile": "^11.2.0",
|
"@push.rocks/smartfile": "^11.2.0",
|
||||||
"@push.rocks/smartxml": "^1.1.1",
|
"@push.rocks/smartxml": "^1.1.1",
|
||||||
"@tsclass/tsclass": "^5.0.0",
|
"@tsclass/tsclass": "^6.0.1",
|
||||||
"jsdom": "^24.1.3",
|
"jsdom": "^26.0.0",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"xmldom": "^0.6.0",
|
"xmldom": "^0.6.0",
|
||||||
|
96
pnpm-lock.yaml
generated
96
pnpm-lock.yaml
generated
@ -15,11 +15,11 @@ importers:
|
|||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^5.0.0
|
specifier: ^6.0.1
|
||||||
version: 5.0.0
|
version: 6.0.1
|
||||||
jsdom:
|
jsdom:
|
||||||
specifier: ^24.1.3
|
specifier: ^26.0.0
|
||||||
version: 24.1.3
|
version: 26.0.0
|
||||||
pako:
|
pako:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
@ -1301,8 +1301,8 @@ packages:
|
|||||||
'@tsclass/tsclass@4.4.4':
|
'@tsclass/tsclass@4.4.4':
|
||||||
resolution: {integrity: sha512-YZOAF+u+r4u5rCev2uUd1KBTBdfyFdtDmcv4wuN+864lMccbdfRICR3SlJwCfYS1lbeV3QNLYGD30wjRXgvCJA==}
|
resolution: {integrity: sha512-YZOAF+u+r4u5rCev2uUd1KBTBdfyFdtDmcv4wuN+864lMccbdfRICR3SlJwCfYS1lbeV3QNLYGD30wjRXgvCJA==}
|
||||||
|
|
||||||
'@tsclass/tsclass@5.0.0':
|
'@tsclass/tsclass@6.0.1':
|
||||||
resolution: {integrity: sha512-2X66VCk0Oe1L01j6GQHC6F9Gj7lpZPPSUTDNax7e29lm4OqBTyAzTR3ePR8coSbWBwsmRV8awLRSrSI+swlqWA==}
|
resolution: {integrity: sha512-EIREiBKgmoTifOe9HdRmqDZV3geJKnf4UgFvkP3aEgD17lmkjQJg44NdlTj0VZ6bf2pMIGZlGROe6Mc/OCIDQg==}
|
||||||
|
|
||||||
'@types/accepts@1.3.7':
|
'@types/accepts@1.3.7':
|
||||||
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
|
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
|
||||||
@ -2845,11 +2845,11 @@ packages:
|
|||||||
jsbn@1.1.0:
|
jsbn@1.1.0:
|
||||||
resolution: {integrity: sha1-sBMHyym2GKHtJux56RH4A8TaAEA=}
|
resolution: {integrity: sha1-sBMHyym2GKHtJux56RH4A8TaAEA=}
|
||||||
|
|
||||||
jsdom@24.1.3:
|
jsdom@26.0.0:
|
||||||
resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==}
|
resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
canvas: ^2.11.2
|
canvas: ^3.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
canvas:
|
canvas:
|
||||||
optional: true
|
optional: true
|
||||||
@ -3363,8 +3363,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
nwsapi@2.2.18:
|
nwsapi@2.2.19:
|
||||||
resolution: {integrity: sha512-p1TRH/edngVEHVbwqWnxUViEmq5znDvyB+Sik5cmuLpGOIfDf/39zLiq3swPF8Vakqn+gvNiOQAZu8djYlQILA==}
|
resolution: {integrity: sha512-94bcyI3RsqiZufXjkr3ltkI86iEl+I7uiHVDtcq9wJUTwYQJ5odHDeSzkkrRzi80jJ8MaeZgqKjH1bAWAFw9bA==}
|
||||||
|
|
||||||
object-assign@4.1.1:
|
object-assign@4.1.1:
|
||||||
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
|
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
|
||||||
@ -3607,9 +3607,6 @@ packages:
|
|||||||
proxy-from-env@1.1.0:
|
proxy-from-env@1.1.0:
|
||||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
psl@1.15.0:
|
|
||||||
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
|
|
||||||
|
|
||||||
public-ip@6.0.2:
|
public-ip@6.0.2:
|
||||||
resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==}
|
resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
@ -3647,9 +3644,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
querystringify@2.2.0:
|
|
||||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
@ -3725,9 +3719,6 @@ packages:
|
|||||||
resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
|
resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
requires-port@1.0.0:
|
|
||||||
resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=}
|
|
||||||
|
|
||||||
resolve-alpn@1.2.1:
|
resolve-alpn@1.2.1:
|
||||||
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||||
|
|
||||||
@ -3758,9 +3749,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
rrweb-cssom@0.7.1:
|
|
||||||
resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
|
|
||||||
|
|
||||||
rrweb-cssom@0.8.0:
|
rrweb-cssom@0.8.0:
|
||||||
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
|
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
|
||||||
|
|
||||||
@ -4052,6 +4040,13 @@ packages:
|
|||||||
tiny-worker@2.3.0:
|
tiny-worker@2.3.0:
|
||||||
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
|
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
|
||||||
|
|
||||||
|
tldts-core@6.1.84:
|
||||||
|
resolution: {integrity: sha512-NaQa1W76W2aCGjXybvnMYzGSM4x8fvG2AN/pla7qxcg0ZHbooOPhA8kctmOZUDfZyhDL27OGNbwAeig8P4p1vg==}
|
||||||
|
|
||||||
|
tldts@6.1.84:
|
||||||
|
resolution: {integrity: sha512-aRGIbCIF3teodtUFAYSdQONVmDRy21REM3o6JnqWn5ZkQBJJ4gHxhw6OfwQ+WkSAi3ASamrS4N4nyazWx6uTYg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
engines: {node: '>=8.0'}
|
engines: {node: '>=8.0'}
|
||||||
@ -4064,9 +4059,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==}
|
resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
tough-cookie@4.1.4:
|
tough-cookie@5.1.2:
|
||||||
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
tr46@3.0.0:
|
tr46@3.0.0:
|
||||||
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
|
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
|
||||||
@ -4169,10 +4164,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
universalify@0.2.0:
|
|
||||||
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
|
||||||
engines: {node: '>= 4.0.0'}
|
|
||||||
|
|
||||||
universalify@2.0.1:
|
universalify@2.0.1:
|
||||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@ -4187,9 +4178,6 @@ packages:
|
|||||||
upper-case@1.1.3:
|
upper-case@1.1.3:
|
||||||
resolution: {integrity: sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=}
|
resolution: {integrity: sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=}
|
||||||
|
|
||||||
url-parse@1.5.10:
|
|
||||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
|
||||||
|
|
||||||
url@0.11.4:
|
url@0.11.4:
|
||||||
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -6658,7 +6646,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 4.37.0
|
type-fest: 4.37.0
|
||||||
|
|
||||||
'@tsclass/tsclass@5.0.0':
|
'@tsclass/tsclass@6.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 4.37.0
|
type-fest: 4.37.0
|
||||||
|
|
||||||
@ -8411,7 +8399,7 @@ snapshots:
|
|||||||
|
|
||||||
jsbn@1.1.0: {}
|
jsbn@1.1.0: {}
|
||||||
|
|
||||||
jsdom@24.1.3:
|
jsdom@26.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssstyle: 4.3.0
|
cssstyle: 4.3.0
|
||||||
data-urls: 5.0.0
|
data-urls: 5.0.0
|
||||||
@ -8421,12 +8409,12 @@ snapshots:
|
|||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6
|
||||||
is-potential-custom-element-name: 1.0.1
|
is-potential-custom-element-name: 1.0.1
|
||||||
nwsapi: 2.2.18
|
nwsapi: 2.2.19
|
||||||
parse5: 7.2.1
|
parse5: 7.2.1
|
||||||
rrweb-cssom: 0.7.1
|
rrweb-cssom: 0.8.0
|
||||||
saxes: 6.0.0
|
saxes: 6.0.0
|
||||||
symbol-tree: 3.2.4
|
symbol-tree: 3.2.4
|
||||||
tough-cookie: 4.1.4
|
tough-cookie: 5.1.2
|
||||||
w3c-xmlserializer: 5.0.0
|
w3c-xmlserializer: 5.0.0
|
||||||
webidl-conversions: 7.0.0
|
webidl-conversions: 7.0.0
|
||||||
whatwg-encoding: 3.1.1
|
whatwg-encoding: 3.1.1
|
||||||
@ -9135,7 +9123,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
|
|
||||||
nwsapi@2.2.18: {}
|
nwsapi@2.2.19: {}
|
||||||
|
|
||||||
object-assign@4.1.1: {}
|
object-assign@4.1.1: {}
|
||||||
|
|
||||||
@ -9361,10 +9349,6 @@ snapshots:
|
|||||||
|
|
||||||
proxy-from-env@1.1.0: {}
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
psl@1.15.0:
|
|
||||||
dependencies:
|
|
||||||
punycode: 2.3.1
|
|
||||||
|
|
||||||
public-ip@6.0.2:
|
public-ip@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
aggregate-error: 4.0.1
|
aggregate-error: 4.0.1
|
||||||
@ -9429,8 +9413,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
querystringify@2.2.0: {}
|
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
quick-lru@5.1.1: {}
|
quick-lru@5.1.1: {}
|
||||||
@ -9536,8 +9518,6 @@ snapshots:
|
|||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
requires-port@1.0.0: {}
|
|
||||||
|
|
||||||
resolve-alpn@1.2.1: {}
|
resolve-alpn@1.2.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
@ -9564,8 +9544,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
|
|
||||||
rrweb-cssom@0.7.1: {}
|
|
||||||
|
|
||||||
rrweb-cssom@0.8.0: {}
|
rrweb-cssom@0.8.0: {}
|
||||||
|
|
||||||
rss-parser@3.13.0:
|
rss-parser@3.13.0:
|
||||||
@ -9944,6 +9922,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esm: 3.2.25
|
esm: 3.2.25
|
||||||
|
|
||||||
|
tldts-core@6.1.84: {}
|
||||||
|
|
||||||
|
tldts@6.1.84:
|
||||||
|
dependencies:
|
||||||
|
tldts-core: 6.1.84
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-number: 7.0.0
|
is-number: 7.0.0
|
||||||
@ -9955,12 +9939,9 @@ snapshots:
|
|||||||
'@tokenizer/token': 0.3.0
|
'@tokenizer/token': 0.3.0
|
||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
|
|
||||||
tough-cookie@4.1.4:
|
tough-cookie@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
psl: 1.15.0
|
tldts: 6.1.84
|
||||||
punycode: 2.3.1
|
|
||||||
universalify: 0.2.0
|
|
||||||
url-parse: 1.5.10
|
|
||||||
|
|
||||||
tr46@3.0.0:
|
tr46@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -10053,8 +10034,6 @@ snapshots:
|
|||||||
|
|
||||||
universalify@0.1.2: {}
|
universalify@0.1.2: {}
|
||||||
|
|
||||||
universalify@0.2.0: {}
|
|
||||||
|
|
||||||
universalify@2.0.1: {}
|
universalify@2.0.1: {}
|
||||||
|
|
||||||
unload@2.4.1: {}
|
unload@2.4.1: {}
|
||||||
@ -10063,11 +10042,6 @@ snapshots:
|
|||||||
|
|
||||||
upper-case@1.1.3: {}
|
upper-case@1.1.3: {}
|
||||||
|
|
||||||
url-parse@1.5.10:
|
|
||||||
dependencies:
|
|
||||||
querystringify: 2.2.0
|
|
||||||
requires-port: 1.0.0
|
|
||||||
|
|
||||||
url@0.11.4:
|
url@0.11.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode: 1.4.1
|
punycode: 1.4.1
|
||||||
|
1
readme.literature.md
Normal file
1
readme.literature.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://www.ufz.de/export/data/2/260196_04_Dokumentation%20XRechnung%20und%20ZUGFeRD.pdf
|
@ -9,6 +9,7 @@ import {
|
|||||||
PDFString,
|
PDFString,
|
||||||
} from 'pdf-lib';
|
} from 'pdf-lib';
|
||||||
import { FacturXEncoder } from './formats/facturx.encoder.js';
|
import { FacturXEncoder } from './formats/facturx.encoder.js';
|
||||||
|
import { XInvoiceEncoder } from './formats/xinvoice.encoder.js';
|
||||||
import { DecoderFactory } from './formats/decoder.factory.js';
|
import { DecoderFactory } from './formats/decoder.factory.js';
|
||||||
import { BaseDecoder } from './formats/base.decoder.js';
|
import { BaseDecoder } from './formats/base.decoder.js';
|
||||||
import { ValidatorFactory } from './formats/validator.factory.js';
|
import { ValidatorFactory } from './formats/validator.factory.js';
|
||||||
@ -17,15 +18,41 @@ import { BaseValidator } from './formats/base.validator.js';
|
|||||||
/**
|
/**
|
||||||
* Main class for working with electronic invoices.
|
* Main class for working with electronic invoices.
|
||||||
* Supports various invoice formats including Factur-X, ZUGFeRD, UBL, and XRechnung
|
* Supports various invoice formats including Factur-X, ZUGFeRD, UBL, and XRechnung
|
||||||
|
* Implements ILetter interface for seamless integration with existing systems
|
||||||
*/
|
*/
|
||||||
export class XInvoice {
|
export class XInvoice implements plugins.tsclass.business.ILetter {
|
||||||
private xmlString: string;
|
// ILetter interface properties
|
||||||
private letterData: plugins.tsclass.business.ILetter;
|
public versionInfo: { type: string; version: string } = {
|
||||||
private pdfUint8Array: Uint8Array;
|
type: 'draft',
|
||||||
|
version: '1.0.0'
|
||||||
|
};
|
||||||
|
public type: string = 'invoice';
|
||||||
|
public date: number = Date.now();
|
||||||
|
public subject: string = '';
|
||||||
|
public from: plugins.tsclass.business.IContact;
|
||||||
|
public to: plugins.tsclass.business.IContact;
|
||||||
|
public content: {
|
||||||
|
invoiceData: plugins.tsclass.finance.IInvoice;
|
||||||
|
textData: null;
|
||||||
|
timesheetData: null;
|
||||||
|
contractData: null;
|
||||||
|
};
|
||||||
|
public needsCoverSheet: boolean = false;
|
||||||
|
public objectActions: any[] = [];
|
||||||
|
public pdf: Uint8Array | null = null;
|
||||||
|
public incidenceId: null = null;
|
||||||
|
public language: string | null = null;
|
||||||
|
public legalContact: any | null = null;
|
||||||
|
public logoUrl: string | null = null;
|
||||||
|
public pdfAttachments: any | null = null;
|
||||||
|
public accentColor: string | null = null;
|
||||||
|
|
||||||
private encoderInstance = new FacturXEncoder();
|
// XInvoice specific properties
|
||||||
private decoderInstance: BaseDecoder;
|
private xmlString: string = '';
|
||||||
private validatorInstance: BaseValidator;
|
private encoderFacturX = new FacturXEncoder();
|
||||||
|
private encoderXInvoice = new XInvoiceEncoder();
|
||||||
|
private decoderInstance: BaseDecoder | null = null;
|
||||||
|
private validatorInstance: BaseValidator | null = null;
|
||||||
|
|
||||||
// Format of the invoice, if detected
|
// Format of the invoice, if detected
|
||||||
private detectedFormat: interfaces.InvoiceFormat = interfaces.InvoiceFormat.UNKNOWN;
|
private detectedFormat: interfaces.InvoiceFormat = interfaces.InvoiceFormat.UNKNOWN;
|
||||||
@ -44,6 +71,18 @@ export class XInvoice {
|
|||||||
* @param options Configuration options
|
* @param options Configuration options
|
||||||
*/
|
*/
|
||||||
constructor(options?: interfaces.XInvoiceOptions) {
|
constructor(options?: interfaces.XInvoiceOptions) {
|
||||||
|
// Initialize empty IContact objects
|
||||||
|
this.from = this.createEmptyContact();
|
||||||
|
this.to = this.createEmptyContact();
|
||||||
|
|
||||||
|
// Initialize empty IInvoice
|
||||||
|
this.content = {
|
||||||
|
invoiceData: this.createEmptyInvoice(),
|
||||||
|
textData: null,
|
||||||
|
timesheetData: null,
|
||||||
|
contractData: null
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize with default options and override with provided options
|
// Initialize with default options and override with provided options
|
||||||
if (options) {
|
if (options) {
|
||||||
this.options = { ...this.options, ...options };
|
this.options = { ...this.options, ...options };
|
||||||
@ -51,19 +90,80 @@ export class XInvoice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a PDF buffer to this XInvoice instance
|
* Creates an empty IContact object
|
||||||
* @param pdfBuffer The PDF buffer to use
|
|
||||||
*/
|
*/
|
||||||
public async addPdfBuffer(pdfBuffer: Uint8Array | Buffer): Promise<void> {
|
private createEmptyContact(): plugins.tsclass.business.IContact {
|
||||||
this.pdfUint8Array = Uint8Array.from(pdfBuffer);
|
return {
|
||||||
|
name: '',
|
||||||
|
type: 'company',
|
||||||
|
description: '',
|
||||||
|
address: {
|
||||||
|
streetName: '',
|
||||||
|
houseNumber: '0',
|
||||||
|
city: '',
|
||||||
|
country: '',
|
||||||
|
postalCode: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an XML string to this XInvoice instance
|
* Creates an empty IInvoice object
|
||||||
* @param xmlString The XML string to use
|
|
||||||
* @param validate Whether to validate the XML
|
|
||||||
*/
|
*/
|
||||||
public async addXmlString(xmlString: string, validate: boolean = false): Promise<void> {
|
private createEmptyInvoice(): plugins.tsclass.finance.IInvoice {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
status: null,
|
||||||
|
type: 'debitnote',
|
||||||
|
billedBy: this.createEmptyContact(),
|
||||||
|
billedTo: this.createEmptyContact(),
|
||||||
|
deliveryDate: Date.now(),
|
||||||
|
dueInDays: 30,
|
||||||
|
periodOfPerformance: null,
|
||||||
|
printResult: null,
|
||||||
|
currency: 'EUR' as plugins.tsclass.finance.TCurrency,
|
||||||
|
notes: [],
|
||||||
|
items: [],
|
||||||
|
reverseCharge: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory method to create XInvoice from XML string
|
||||||
|
* @param xmlString XML content
|
||||||
|
* @param options Configuration options
|
||||||
|
* @returns XInvoice instance
|
||||||
|
*/
|
||||||
|
public static async fromXml(xmlString: string, options?: interfaces.XInvoiceOptions): Promise<XInvoice> {
|
||||||
|
const xinvoice = new XInvoice(options);
|
||||||
|
|
||||||
|
// Load XML data
|
||||||
|
await xinvoice.loadXml(xmlString);
|
||||||
|
|
||||||
|
return xinvoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory method to create XInvoice from PDF buffer
|
||||||
|
* @param pdfBuffer PDF buffer
|
||||||
|
* @param options Configuration options
|
||||||
|
* @returns XInvoice instance
|
||||||
|
*/
|
||||||
|
public static async fromPdf(pdfBuffer: Uint8Array | Buffer, options?: interfaces.XInvoiceOptions): Promise<XInvoice> {
|
||||||
|
const xinvoice = new XInvoice(options);
|
||||||
|
|
||||||
|
// Load PDF data
|
||||||
|
await xinvoice.loadPdf(pdfBuffer);
|
||||||
|
|
||||||
|
return xinvoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads XML data into this XInvoice instance
|
||||||
|
* @param xmlString XML content
|
||||||
|
* @param validate Whether to validate
|
||||||
|
*/
|
||||||
|
public async loadXml(xmlString: string, validate: boolean = false): Promise<void> {
|
||||||
// Basic XML validation - just check if it starts with <?xml
|
// Basic XML validation - just check if it starts with <?xml
|
||||||
if (!xmlString || !xmlString.trim().startsWith('<?xml')) {
|
if (!xmlString || !xmlString.trim().startsWith('<?xml')) {
|
||||||
throw new Error('Invalid XML: Missing XML declaration');
|
throw new Error('Invalid XML: Missing XML declaration');
|
||||||
@ -85,150 +185,46 @@ export class XInvoice {
|
|||||||
if (validate || this.options.validateOnLoad) {
|
if (validate || this.options.validateOnLoad) {
|
||||||
await this.validate(this.options.validationLevel);
|
await this.validate(this.options.validationLevel);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the XML against the appropriate validation rules
|
|
||||||
* @param level Validation level (syntax, semantic, business)
|
|
||||||
* @returns Validation result
|
|
||||||
*/
|
|
||||||
public async validate(level: interfaces.ValidationLevel = interfaces.ValidationLevel.SYNTAX): Promise<interfaces.ValidationResult> {
|
|
||||||
if (!this.xmlString) {
|
|
||||||
throw new Error('No XML to validate. Use addXmlString() first.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.validatorInstance) {
|
// Parse XML to ILetter
|
||||||
// Initialize the validator with the XML string if not already done
|
const letterData = await this.decoderInstance.getLetterData();
|
||||||
this.validatorInstance = ValidatorFactory.createValidator(this.xmlString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run validation
|
// Copy letter data to this object
|
||||||
const result = this.validatorInstance.validate(level);
|
this.copyLetterData(letterData);
|
||||||
|
|
||||||
// Store validation errors
|
|
||||||
this.validationErrors = result.errors;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the document is valid based on the last validation
|
|
||||||
* @returns True if the document is valid
|
|
||||||
*/
|
|
||||||
public isValid(): boolean {
|
|
||||||
if (!this.validatorInstance) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.validatorInstance.isValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets validation errors from the last validation
|
|
||||||
* @returns Array of validation errors
|
|
||||||
*/
|
|
||||||
public getValidationErrors(): interfaces.ValidationError[] {
|
|
||||||
return this.validationErrors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds letter data to this XInvoice instance
|
* Loads PDF data into this XInvoice instance and extracts embedded XML if present
|
||||||
* @param letterData The letter data to use
|
* @param pdfBuffer PDF buffer
|
||||||
*/
|
*/
|
||||||
public async addLetterData(letterData: plugins.tsclass.business.ILetter): Promise<void> {
|
public async loadPdf(pdfBuffer: Uint8Array | Buffer): Promise<void> {
|
||||||
this.letterData = letterData;
|
this.pdf = Uint8Array.from(pdfBuffer);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds XML data into a PDF and returns the resulting PDF buffer
|
|
||||||
* @returns PDF buffer with embedded XML
|
|
||||||
*/
|
|
||||||
public async getXInvoice(): Promise<Uint8Array> {
|
|
||||||
// Check requirements
|
|
||||||
if (!this.pdfUint8Array) {
|
|
||||||
throw new Error('No PDF buffer provided! Use addPdfBuffer() first.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.xmlString && !this.letterData) {
|
|
||||||
// Check if document already has embedded XML
|
|
||||||
try {
|
|
||||||
await this.getXmlData();
|
|
||||||
// If getXmlData() succeeds, we have XML
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('No XML string or letter data provided!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have letter data but no XML, create XML from letter data
|
|
||||||
if (!this.xmlString && this.letterData) {
|
|
||||||
this.xmlString = await this.encoderInstance.createFacturXXml(this.letterData);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pdfDoc = await PDFDocument.load(this.pdfUint8Array);
|
// Try to extract embedded XML
|
||||||
|
const xmlContent = await this.extractXmlFromPdf();
|
||||||
// Convert the XML string to a Uint8Array
|
|
||||||
const xmlBuffer = new TextEncoder().encode(this.xmlString);
|
|
||||||
|
|
||||||
// Determine attachment filename based on format
|
// If XML was found, load it
|
||||||
let filename = 'invoice.xml';
|
if (xmlContent) {
|
||||||
let description = 'XML Invoice';
|
await this.loadXml(xmlContent);
|
||||||
|
|
||||||
switch (this.detectedFormat) {
|
|
||||||
case interfaces.InvoiceFormat.FACTURX:
|
|
||||||
filename = 'factur-x.xml';
|
|
||||||
description = 'Factur-X XML Invoice';
|
|
||||||
break;
|
|
||||||
case interfaces.InvoiceFormat.ZUGFERD:
|
|
||||||
filename = 'zugferd.xml';
|
|
||||||
description = 'ZUGFeRD XML Invoice';
|
|
||||||
break;
|
|
||||||
case interfaces.InvoiceFormat.XRECHNUNG:
|
|
||||||
filename = 'xrechnung.xml';
|
|
||||||
description = 'XRechnung XML Invoice';
|
|
||||||
break;
|
|
||||||
case interfaces.InvoiceFormat.UBL:
|
|
||||||
filename = 'ubl.xml';
|
|
||||||
description = 'UBL XML Invoice';
|
|
||||||
break;
|
|
||||||
case interfaces.InvoiceFormat.CII:
|
|
||||||
filename = 'cii.xml';
|
|
||||||
description = 'CII XML Invoice';
|
|
||||||
break;
|
|
||||||
case interfaces.InvoiceFormat.FATTURAPA:
|
|
||||||
filename = 'fatturapa.xml';
|
|
||||||
description = 'FatturaPA XML Invoice';
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pdf-lib's .attach() to embed the XML
|
|
||||||
pdfDoc.attach(xmlBuffer, filename, {
|
|
||||||
mimeType: 'application/xml',
|
|
||||||
description: description,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save back into this.pdfUint8Array
|
|
||||||
const modifiedPdfBytes = await pdfDoc.save();
|
|
||||||
this.pdfUint8Array = modifiedPdfBytes;
|
|
||||||
|
|
||||||
return modifiedPdfBytes;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error embedding XML into PDF:', error);
|
console.error('Error extracting or parsing embedded XML from PDF:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the XML embedded in a PDF and returns it as a string.
|
* Extracts XML from PDF
|
||||||
* @returns The XML string from the PDF
|
* @returns XML content or null if not found
|
||||||
*/
|
*/
|
||||||
public async getXmlData(): Promise<string> {
|
private async extractXmlFromPdf(): Promise<string> {
|
||||||
if (!this.pdfUint8Array) {
|
if (!this.pdf) {
|
||||||
throw new Error('No PDF buffer provided! Use addPdfBuffer() first.');
|
throw new Error('No PDF data available');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pdfDoc = await PDFDocument.load(this.pdfUint8Array);
|
const pdfDoc = await PDFDocument.load(this.pdf);
|
||||||
|
|
||||||
// Get the document's metadata dictionary
|
// Get the document's metadata dictionary
|
||||||
const namesDictObj = pdfDoc.catalog.lookup(PDFName.of('Names'));
|
const namesDictObj = pdfDoc.catalog.lookup(PDFName.of('Names'));
|
||||||
@ -295,23 +291,7 @@ export class XInvoice {
|
|||||||
const xmlBytes = plugins.pako.inflate(xmlCompressedBytes);
|
const xmlBytes = plugins.pako.inflate(xmlCompressedBytes);
|
||||||
const xmlContent = new TextDecoder('utf-8').decode(xmlBytes);
|
const xmlContent = new TextDecoder('utf-8').decode(xmlBytes);
|
||||||
|
|
||||||
// Store this XML string
|
console.log(`Successfully extracted ${this.determineFormat(xmlContent)} XML from PDF file. File name: ${xmlFileName}`);
|
||||||
this.xmlString = xmlContent;
|
|
||||||
|
|
||||||
// Detect the format
|
|
||||||
this.detectedFormat = this.determineFormat(xmlContent);
|
|
||||||
|
|
||||||
// Initialize the decoder and validator
|
|
||||||
this.decoderInstance = DecoderFactory.createDecoder(xmlContent);
|
|
||||||
this.validatorInstance = ValidatorFactory.createValidator(xmlContent);
|
|
||||||
|
|
||||||
// Validate if requested
|
|
||||||
if (this.options.validateOnLoad) {
|
|
||||||
await this.validate(this.options.validationLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log information about the extracted XML
|
|
||||||
console.log(`Successfully extracted ${this.detectedFormat} XML from PDF file. File name: ${xmlFileName}`);
|
|
||||||
|
|
||||||
return xmlContent;
|
return xmlContent;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -319,6 +299,185 @@ export class XInvoice {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies data from another ILetter object
|
||||||
|
* @param letter Source letter data
|
||||||
|
*/
|
||||||
|
private copyLetterData(letter: plugins.tsclass.business.ILetter): void {
|
||||||
|
this.versionInfo = { ...letter.versionInfo };
|
||||||
|
this.type = letter.type;
|
||||||
|
this.date = letter.date;
|
||||||
|
this.subject = letter.subject;
|
||||||
|
this.from = { ...letter.from };
|
||||||
|
this.to = { ...letter.to };
|
||||||
|
this.content = {
|
||||||
|
invoiceData: letter.content.invoiceData ? { ...letter.content.invoiceData } : this.createEmptyInvoice(),
|
||||||
|
textData: letter.content.textData,
|
||||||
|
timesheetData: letter.content.timesheetData,
|
||||||
|
contractData: letter.content.contractData
|
||||||
|
};
|
||||||
|
this.needsCoverSheet = letter.needsCoverSheet;
|
||||||
|
this.objectActions = [...letter.objectActions];
|
||||||
|
this.incidenceId = letter.incidenceId;
|
||||||
|
this.language = letter.language;
|
||||||
|
this.legalContact = letter.legalContact;
|
||||||
|
this.logoUrl = letter.logoUrl;
|
||||||
|
this.pdfAttachments = letter.pdfAttachments;
|
||||||
|
this.accentColor = letter.accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the XML against the appropriate validation rules
|
||||||
|
* @param level Validation level (syntax, semantic, business)
|
||||||
|
* @returns Validation result
|
||||||
|
*/
|
||||||
|
public async validate(level: interfaces.ValidationLevel = interfaces.ValidationLevel.SYNTAX): Promise<interfaces.ValidationResult> {
|
||||||
|
if (!this.xmlString) {
|
||||||
|
throw new Error('No XML to validate');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.validatorInstance) {
|
||||||
|
// Initialize the validator with the XML string if not already done
|
||||||
|
this.validatorInstance = ValidatorFactory.createValidator(this.xmlString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run validation
|
||||||
|
const result = this.validatorInstance.validate(level);
|
||||||
|
|
||||||
|
// Store validation errors
|
||||||
|
this.validationErrors = result.errors;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the document is valid based on the last validation
|
||||||
|
* @returns True if the document is valid
|
||||||
|
*/
|
||||||
|
public isValid(): boolean {
|
||||||
|
if (!this.validatorInstance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.validatorInstance.isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets validation errors from the last validation
|
||||||
|
* @returns Array of validation errors
|
||||||
|
*/
|
||||||
|
public getValidationErrors(): interfaces.ValidationError[] {
|
||||||
|
return this.validationErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the invoice to XML format
|
||||||
|
* @param format Target format (e.g., 'facturx', 'xrechnung')
|
||||||
|
* @returns XML string in the specified format
|
||||||
|
*/
|
||||||
|
public async exportXml(format: string = 'facturx'): Promise<string> {
|
||||||
|
format = format.toLowerCase();
|
||||||
|
|
||||||
|
// Generate XML based on format
|
||||||
|
switch (format) {
|
||||||
|
case 'facturx':
|
||||||
|
case 'zugferd':
|
||||||
|
return this.encoderFacturX.createFacturXXml(this);
|
||||||
|
|
||||||
|
case 'xrechnung':
|
||||||
|
case 'ubl':
|
||||||
|
return this.encoderXInvoice.createXInvoiceXml(this);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Default to Factur-X
|
||||||
|
return this.encoderFacturX.createFacturXXml(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the invoice to PDF format with embedded XML
|
||||||
|
* @param format Target format (e.g., 'facturx', 'zugferd')
|
||||||
|
* @returns PDF buffer with embedded XML
|
||||||
|
*/
|
||||||
|
public async exportPdf(format: string = 'facturx'): Promise<Uint8Array> {
|
||||||
|
format = format.toLowerCase();
|
||||||
|
|
||||||
|
if (!this.pdf) {
|
||||||
|
throw new Error('No PDF data available. Use loadPdf() first or set the pdf property.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Generate XML based on format
|
||||||
|
const xmlContent = await this.exportXml(format);
|
||||||
|
|
||||||
|
// Load the PDF
|
||||||
|
const pdfDoc = await PDFDocument.load(this.pdf);
|
||||||
|
|
||||||
|
// Convert the XML string to a Uint8Array
|
||||||
|
const xmlBuffer = new TextEncoder().encode(xmlContent);
|
||||||
|
|
||||||
|
// Determine attachment filename based on format
|
||||||
|
let filename = 'invoice.xml';
|
||||||
|
let description = 'XML Invoice';
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case 'facturx':
|
||||||
|
filename = 'factur-x.xml';
|
||||||
|
description = 'Factur-X XML Invoice';
|
||||||
|
break;
|
||||||
|
case 'zugferd':
|
||||||
|
filename = 'zugferd.xml';
|
||||||
|
description = 'ZUGFeRD XML Invoice';
|
||||||
|
break;
|
||||||
|
case 'xrechnung':
|
||||||
|
filename = 'xrechnung.xml';
|
||||||
|
description = 'XRechnung XML Invoice';
|
||||||
|
break;
|
||||||
|
case 'ubl':
|
||||||
|
filename = 'ubl.xml';
|
||||||
|
description = 'UBL XML Invoice';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure filename is lowercase (as required by documentation)
|
||||||
|
filename = filename.toLowerCase();
|
||||||
|
|
||||||
|
// Use pdf-lib's .attach() to embed the XML
|
||||||
|
pdfDoc.attach(xmlBuffer, filename, {
|
||||||
|
mimeType: 'application/xml',
|
||||||
|
description: description,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the modified PDF
|
||||||
|
const modifiedPdfBytes = await pdfDoc.save();
|
||||||
|
|
||||||
|
// Update the pdf property
|
||||||
|
this.pdf = modifiedPdfBytes;
|
||||||
|
|
||||||
|
return modifiedPdfBytes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error embedding XML into PDF:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the invoice format as an enum value
|
||||||
|
* @returns InvoiceFormat enum value
|
||||||
|
*/
|
||||||
|
public getFormat(): interfaces.InvoiceFormat {
|
||||||
|
return this.detectedFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the invoice is in a specific format
|
||||||
|
* @param format Format to check
|
||||||
|
* @returns True if the invoice is in the specified format
|
||||||
|
*/
|
||||||
|
public isFormat(format: interfaces.InvoiceFormat): boolean {
|
||||||
|
return this.detectedFormat === format;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the format of an XML document and returns the format enum
|
* Determines the format of an XML document and returns the format enum
|
||||||
@ -368,256 +527,4 @@ export class XInvoice {
|
|||||||
// For unknown formats, return unknown
|
// For unknown formats, return unknown
|
||||||
return interfaces.InvoiceFormat.UNKNOWN;
|
return interfaces.InvoiceFormat.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy method that returns the format as a string
|
|
||||||
* Included for backwards compatibility with existing tests
|
|
||||||
* @param xmlContent XML content as string
|
|
||||||
* @returns Format name as string
|
|
||||||
*/
|
|
||||||
public identifyXmlFormat(xmlContent: string): string {
|
|
||||||
const format = this.determineFormat(xmlContent);
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case interfaces.InvoiceFormat.FACTURX:
|
|
||||||
return 'Factur-X';
|
|
||||||
case interfaces.InvoiceFormat.ZUGFERD:
|
|
||||||
return 'ZUGFeRD';
|
|
||||||
case interfaces.InvoiceFormat.CII:
|
|
||||||
return 'ZUGFeRD/CII'; // For compatibility with existing tests
|
|
||||||
case interfaces.InvoiceFormat.UBL:
|
|
||||||
return 'UBL';
|
|
||||||
case interfaces.InvoiceFormat.XRECHNUNG:
|
|
||||||
return 'XRechnung';
|
|
||||||
case interfaces.InvoiceFormat.FATTURAPA:
|
|
||||||
return 'FatturaPA';
|
|
||||||
default:
|
|
||||||
return 'Unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the invoice format as an enum value
|
|
||||||
* @returns InvoiceFormat enum value
|
|
||||||
*/
|
|
||||||
public getFormat(): interfaces.InvoiceFormat {
|
|
||||||
return this.detectedFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the invoice is in a specific format
|
|
||||||
* @param format Format to check
|
|
||||||
* @returns True if the invoice is in the specified format
|
|
||||||
*/
|
|
||||||
public isFormat(format: interfaces.InvoiceFormat): boolean {
|
|
||||||
return this.detectedFormat === format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets parsed XML data as a structured IXInvoice object
|
|
||||||
* @returns Structured invoice data
|
|
||||||
*/
|
|
||||||
public async getParsedXmlData(): Promise<interfaces.IXInvoice> {
|
|
||||||
if (!this.xmlString && !this.pdfUint8Array) {
|
|
||||||
throw new Error('No XML string or PDF buffer provided!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have XML but have a PDF, extract XML
|
|
||||||
if (!this.xmlString) {
|
|
||||||
await this.getXmlData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the XML using the appropriate decoder
|
|
||||||
return this.parseXmlToInvoice();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the XML content into a structured IXInvoice object
|
|
||||||
* Uses the appropriate decoder for the detected format
|
|
||||||
* @returns Structured invoice data
|
|
||||||
*/
|
|
||||||
private async parseXmlToInvoice(): Promise<interfaces.IXInvoice> {
|
|
||||||
if (!this.xmlString) {
|
|
||||||
throw new Error('No XML content provided for parsing');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// For tests with very simple XML that doesn't match any known format,
|
|
||||||
// return a minimal structure to help tests pass
|
|
||||||
if (this.xmlString.includes('<test>') ||
|
|
||||||
this.xmlString.length < 100 ||
|
|
||||||
(this.detectedFormat === interfaces.InvoiceFormat.UNKNOWN &&
|
|
||||||
!this.xmlString.includes('CrossIndustryInvoice') &&
|
|
||||||
!this.xmlString.includes('Invoice'))) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
InvoiceNumber: 'TESTINVOICE',
|
|
||||||
DateIssued: new Date().toISOString().split('T')[0],
|
|
||||||
Seller: {
|
|
||||||
Name: 'Test Seller',
|
|
||||||
Address: {
|
|
||||||
Street: 'Test Street',
|
|
||||||
City: 'Test City',
|
|
||||||
PostalCode: '12345',
|
|
||||||
Country: 'Test Country',
|
|
||||||
},
|
|
||||||
Contact: {
|
|
||||||
Email: 'test@example.com',
|
|
||||||
Phone: '123-456-7890',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Buyer: {
|
|
||||||
Name: 'Test Buyer',
|
|
||||||
Address: {
|
|
||||||
Street: 'Test Street',
|
|
||||||
City: 'Test City',
|
|
||||||
PostalCode: '12345',
|
|
||||||
Country: 'Test Country',
|
|
||||||
},
|
|
||||||
Contact: {
|
|
||||||
Email: 'test@example.com',
|
|
||||||
Phone: '123-456-7890',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Items: [
|
|
||||||
{
|
|
||||||
Description: 'Test Item',
|
|
||||||
Quantity: 1,
|
|
||||||
UnitPrice: 100,
|
|
||||||
TotalPrice: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
TotalAmount: 100,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we have a decoder instance
|
|
||||||
if (!this.decoderInstance) {
|
|
||||||
this.decoderInstance = DecoderFactory.createDecoder(this.xmlString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the decoder to get letter data
|
|
||||||
const letterData = await this.decoderInstance.getLetterData();
|
|
||||||
|
|
||||||
// Convert ILetter format to IXInvoice format
|
|
||||||
return this.convertLetterToXInvoice(letterData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing XML to invoice structure:', error);
|
|
||||||
|
|
||||||
// Return a minimal structure instead of throwing an error
|
|
||||||
// This helps tests pass with simplified test XML
|
|
||||||
return {
|
|
||||||
InvoiceNumber: 'ERROR',
|
|
||||||
DateIssued: new Date().toISOString().split('T')[0],
|
|
||||||
Seller: {
|
|
||||||
Name: 'Error Seller',
|
|
||||||
Address: {
|
|
||||||
Street: 'Error Street',
|
|
||||||
City: 'Error City',
|
|
||||||
PostalCode: '00000',
|
|
||||||
Country: 'Error Country',
|
|
||||||
},
|
|
||||||
Contact: {
|
|
||||||
Email: 'error@example.com',
|
|
||||||
Phone: '000-000-0000',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Buyer: {
|
|
||||||
Name: 'Error Buyer',
|
|
||||||
Address: {
|
|
||||||
Street: 'Error Street',
|
|
||||||
City: 'Error City',
|
|
||||||
PostalCode: '00000',
|
|
||||||
Country: 'Error Country',
|
|
||||||
},
|
|
||||||
Contact: {
|
|
||||||
Email: 'error@example.com',
|
|
||||||
Phone: '000-000-0000',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Items: [
|
|
||||||
{
|
|
||||||
Description: 'Error Item',
|
|
||||||
Quantity: 0,
|
|
||||||
UnitPrice: 0,
|
|
||||||
TotalPrice: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
TotalAmount: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an ILetter object to an IXInvoice object
|
|
||||||
* @param letter Letter data
|
|
||||||
* @returns XInvoice data
|
|
||||||
*/
|
|
||||||
private convertLetterToXInvoice(letter: plugins.tsclass.business.ILetter): interfaces.IXInvoice {
|
|
||||||
// Extract invoice data from letter
|
|
||||||
const invoiceData = letter.content.invoiceData;
|
|
||||||
|
|
||||||
if (!invoiceData) {
|
|
||||||
throw new Error('Letter does not contain invoice data');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic mapping from ILetter/IInvoice to IXInvoice
|
|
||||||
const result: interfaces.IXInvoice = {
|
|
||||||
InvoiceNumber: invoiceData.id || 'Unknown',
|
|
||||||
DateIssued: new Date(letter.date).toISOString().split('T')[0],
|
|
||||||
Seller: {
|
|
||||||
Name: invoiceData.billedBy.name || 'Unknown Seller',
|
|
||||||
Address: {
|
|
||||||
Street: invoiceData.billedBy.address.streetName || 'Unknown',
|
|
||||||
City: invoiceData.billedBy.address.city || 'Unknown',
|
|
||||||
PostalCode: invoiceData.billedBy.address.postalCode || 'Unknown',
|
|
||||||
Country: invoiceData.billedBy.address.country || 'Unknown',
|
|
||||||
},
|
|
||||||
Contact: {
|
|
||||||
Email: (invoiceData.billedBy as any).email || 'unknown@example.com',
|
|
||||||
Phone: (invoiceData.billedBy as any).phone || 'Unknown',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Buyer: {
|
|
||||||
Name: invoiceData.billedTo.name || 'Unknown Buyer',
|
|
||||||
Address: {
|
|
||||||
Street: invoiceData.billedTo.address.streetName || 'Unknown',
|
|
||||||
City: invoiceData.billedTo.address.city || 'Unknown',
|
|
||||||
PostalCode: invoiceData.billedTo.address.postalCode || 'Unknown',
|
|
||||||
Country: invoiceData.billedTo.address.country || 'Unknown',
|
|
||||||
},
|
|
||||||
Contact: {
|
|
||||||
Email: (invoiceData.billedTo as any).email || 'unknown@example.com',
|
|
||||||
Phone: (invoiceData.billedTo as any).phone || 'Unknown',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Items: [],
|
|
||||||
TotalAmount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map the invoice items
|
|
||||||
if (invoiceData.items && Array.isArray(invoiceData.items)) {
|
|
||||||
result.Items = invoiceData.items.map(item => ({
|
|
||||||
Description: item.name || 'Unknown Item',
|
|
||||||
Quantity: item.unitQuantity || 1,
|
|
||||||
UnitPrice: item.unitNetPrice || 0,
|
|
||||||
TotalPrice: (item.unitQuantity || 1) * (item.unitNetPrice || 0),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Calculate total amount
|
|
||||||
result.TotalAmount = result.Items.reduce((total, item) => total + item.TotalPrice, 0);
|
|
||||||
} else {
|
|
||||||
// Default item if none is provided
|
|
||||||
result.Items = [
|
|
||||||
{
|
|
||||||
Description: 'Unknown Item',
|
|
||||||
Quantity: 1,
|
|
||||||
UnitPrice: 0,
|
|
||||||
TotalPrice: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user