@dwast/verifactu-lib 0.1.0-beta.0 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/aeat/client.d.ts +162 -0
- package/dist/cjs/aeat/client.d.ts.map +1 -0
- package/dist/cjs/aeat/client.js +267 -0
- package/dist/cjs/aeat/client.js.map +1 -0
- package/dist/cjs/aeat/index.d.ts +3 -0
- package/dist/cjs/aeat/index.d.ts.map +1 -0
- package/dist/cjs/aeat/index.js +19 -0
- package/dist/cjs/aeat/index.js.map +1 -0
- package/dist/cjs/aeat/response.d.ts +78 -0
- package/dist/cjs/aeat/response.d.ts.map +1 -0
- package/dist/cjs/aeat/response.js +117 -0
- package/dist/cjs/aeat/response.js.map +1 -0
- package/dist/{core → cjs/core}/hash/chain.d.ts +42 -0
- package/dist/cjs/core/hash/chain.d.ts.map +1 -0
- package/dist/cjs/core/hash/chain.js +167 -0
- package/dist/cjs/core/hash/chain.js.map +1 -0
- package/dist/cjs/core/hash/index.d.ts.map +1 -0
- package/dist/cjs/core/hash/index.js +18 -0
- package/dist/cjs/core/hash/index.js.map +1 -0
- package/dist/{core → cjs/core}/index.d.ts +2 -0
- package/dist/cjs/core/index.d.ts.map +1 -0
- package/dist/cjs/core/index.js +23 -0
- package/dist/cjs/core/index.js.map +1 -0
- package/dist/cjs/core/models/event.d.ts.map +1 -0
- package/dist/cjs/core/models/event.js +3 -0
- package/dist/{core → cjs/core}/models/event.js.map +1 -1
- package/dist/cjs/core/models/index.d.ts.map +1 -0
- package/dist/cjs/core/models/index.js +19 -0
- package/dist/cjs/core/models/index.js.map +1 -0
- package/dist/{core → cjs/core}/models/invoice.d.ts +42 -17
- package/dist/cjs/core/models/invoice.d.ts.map +1 -0
- package/dist/cjs/core/models/invoice.js +3 -0
- package/dist/{core → cjs/core}/models/invoice.js.map +1 -1
- package/dist/cjs/core/qr/index.d.ts.map +1 -0
- package/dist/cjs/core/qr/index.js +18 -0
- package/dist/cjs/core/qr/index.js.map +1 -0
- package/dist/cjs/core/qr/qr.d.ts.map +1 -0
- package/dist/cjs/core/qr/qr.js +19 -0
- package/dist/cjs/core/qr/qr.js.map +1 -0
- package/dist/cjs/core/utils/date.d.ts +16 -0
- package/dist/cjs/core/utils/date.d.ts.map +1 -0
- package/dist/cjs/core/utils/date.js +27 -0
- package/dist/cjs/core/utils/date.js.map +1 -0
- package/dist/cjs/core/utils/index.d.ts +2 -0
- package/dist/cjs/core/utils/index.d.ts.map +1 -0
- package/dist/cjs/core/utils/index.js +18 -0
- package/dist/cjs/core/utils/index.js.map +1 -0
- package/dist/cjs/core/validation/index.d.ts +2 -0
- package/dist/cjs/core/validation/index.d.ts.map +1 -0
- package/dist/cjs/core/validation/index.js +18 -0
- package/dist/cjs/core/validation/index.js.map +1 -0
- package/dist/cjs/core/validation/validate.d.ts +18 -0
- package/dist/cjs/core/validation/validate.d.ts.map +1 -0
- package/dist/cjs/core/validation/validate.js +65 -0
- package/dist/cjs/core/validation/validate.js.map +1 -0
- package/dist/cjs/core/xml/generator.d.ts +140 -0
- package/dist/cjs/core/xml/generator.d.ts.map +1 -0
- package/dist/cjs/core/xml/generator.js +269 -0
- package/dist/cjs/core/xml/generator.js.map +1 -0
- package/dist/cjs/core/xml/index.d.ts.map +1 -0
- package/dist/cjs/core/xml/index.js +18 -0
- package/dist/cjs/core/xml/index.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/integration/accountability.d.ts +41 -0
- package/dist/cjs/integration/accountability.d.ts.map +1 -0
- package/dist/cjs/integration/accountability.js +92 -0
- package/dist/cjs/integration/accountability.js.map +1 -0
- package/dist/cjs/integration/index.d.ts +5 -0
- package/dist/cjs/integration/index.d.ts.map +1 -0
- package/dist/cjs/integration/index.js +21 -0
- package/dist/cjs/integration/index.js.map +1 -0
- package/dist/cjs/integration/p12.d.ts +58 -0
- package/dist/cjs/integration/p12.d.ts.map +1 -0
- package/dist/cjs/integration/p12.js +127 -0
- package/dist/cjs/integration/p12.js.map +1 -0
- package/dist/{integration → cjs/integration}/repository.d.ts +2 -0
- package/dist/cjs/integration/repository.d.ts.map +1 -0
- package/dist/cjs/integration/repository.js +3 -0
- package/dist/cjs/integration/repository.js.map +1 -0
- package/dist/{integration → cjs/integration}/service.d.ts +11 -2
- package/dist/cjs/integration/service.d.ts.map +1 -0
- package/dist/cjs/integration/service.js +230 -0
- package/dist/cjs/integration/service.js.map +1 -0
- package/dist/cjs/integration/tenant-credentials.d.ts +44 -0
- package/dist/cjs/integration/tenant-credentials.d.ts.map +1 -0
- package/dist/cjs/integration/tenant-credentials.js +62 -0
- package/dist/cjs/integration/tenant-credentials.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/aeat/client.d.ts +162 -0
- package/dist/esm/aeat/client.d.ts.map +1 -0
- package/dist/esm/aeat/client.js +259 -0
- package/dist/esm/aeat/client.js.map +1 -0
- package/dist/esm/aeat/index.d.ts +3 -0
- package/dist/esm/aeat/index.d.ts.map +1 -0
- package/dist/esm/aeat/index.js +3 -0
- package/dist/esm/aeat/index.js.map +1 -0
- package/dist/esm/aeat/response.d.ts +78 -0
- package/dist/esm/aeat/response.d.ts.map +1 -0
- package/dist/esm/aeat/response.js +114 -0
- package/dist/esm/aeat/response.js.map +1 -0
- package/dist/esm/core/hash/chain.d.ts +139 -0
- package/dist/esm/core/hash/chain.d.ts.map +1 -0
- package/dist/{core → esm/core}/hash/chain.js +37 -0
- package/dist/esm/core/hash/chain.js.map +1 -0
- package/dist/esm/core/hash/index.d.ts +2 -0
- package/dist/esm/core/hash/index.d.ts.map +1 -0
- package/dist/esm/core/hash/index.js.map +1 -0
- package/dist/esm/core/index.d.ts +7 -0
- package/dist/esm/core/index.d.ts.map +1 -0
- package/dist/{core → esm/core}/index.js +2 -0
- package/dist/esm/core/index.js.map +1 -0
- package/dist/esm/core/models/event.d.ts +25 -0
- package/dist/esm/core/models/event.d.ts.map +1 -0
- package/dist/esm/core/models/event.js.map +1 -0
- package/dist/esm/core/models/index.d.ts +3 -0
- package/dist/esm/core/models/index.d.ts.map +1 -0
- package/dist/esm/core/models/index.js.map +1 -0
- package/dist/esm/core/models/invoice.d.ts +115 -0
- package/dist/esm/core/models/invoice.d.ts.map +1 -0
- package/dist/esm/core/models/invoice.js.map +1 -0
- package/dist/esm/core/qr/index.d.ts +2 -0
- package/dist/esm/core/qr/index.d.ts.map +1 -0
- package/dist/esm/core/qr/index.js.map +1 -0
- package/dist/esm/core/qr/qr.d.ts +13 -0
- package/dist/esm/core/qr/qr.d.ts.map +1 -0
- package/dist/esm/core/qr/qr.js.map +1 -0
- package/dist/esm/core/utils/date.d.ts +16 -0
- package/dist/esm/core/utils/date.d.ts.map +1 -0
- package/dist/esm/core/utils/date.js +24 -0
- package/dist/esm/core/utils/date.js.map +1 -0
- package/dist/esm/core/utils/index.d.ts +2 -0
- package/dist/esm/core/utils/index.d.ts.map +1 -0
- package/dist/esm/core/utils/index.js +2 -0
- package/dist/esm/core/utils/index.js.map +1 -0
- package/dist/esm/core/validation/index.d.ts +2 -0
- package/dist/esm/core/validation/index.d.ts.map +1 -0
- package/dist/esm/core/validation/index.js +2 -0
- package/dist/esm/core/validation/index.js.map +1 -0
- package/dist/esm/core/validation/validate.d.ts +18 -0
- package/dist/esm/core/validation/validate.d.ts.map +1 -0
- package/dist/esm/core/validation/validate.js +62 -0
- package/dist/esm/core/validation/validate.js.map +1 -0
- package/dist/esm/core/xml/generator.d.ts +140 -0
- package/dist/esm/core/xml/generator.d.ts.map +1 -0
- package/dist/esm/core/xml/generator.js +259 -0
- package/dist/esm/core/xml/generator.js.map +1 -0
- package/dist/esm/core/xml/index.d.ts +2 -0
- package/dist/esm/core/xml/index.d.ts.map +1 -0
- package/dist/esm/core/xml/index.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/integration/accountability.d.ts +41 -0
- package/dist/esm/integration/accountability.d.ts.map +1 -0
- package/dist/esm/integration/accountability.js +88 -0
- package/dist/esm/integration/accountability.js.map +1 -0
- package/dist/esm/integration/index.d.ts +5 -0
- package/dist/esm/integration/index.d.ts.map +1 -0
- package/dist/esm/integration/index.js +5 -0
- package/dist/esm/integration/index.js.map +1 -0
- package/dist/esm/integration/p12.d.ts +58 -0
- package/dist/esm/integration/p12.d.ts.map +1 -0
- package/dist/esm/integration/p12.js +119 -0
- package/dist/esm/integration/p12.js.map +1 -0
- package/dist/esm/integration/repository.d.ts +79 -0
- package/dist/esm/integration/repository.d.ts.map +1 -0
- package/dist/esm/integration/repository.js.map +1 -0
- package/dist/esm/integration/service.d.ts +45 -0
- package/dist/esm/integration/service.d.ts.map +1 -0
- package/dist/{integration → esm/integration}/service.js +101 -15
- package/dist/esm/integration/service.js.map +1 -0
- package/dist/esm/integration/tenant-credentials.d.ts +44 -0
- package/dist/esm/integration/tenant-credentials.d.ts.map +1 -0
- package/dist/esm/integration/tenant-credentials.js +58 -0
- package/dist/esm/integration/tenant-credentials.js.map +1 -0
- package/package.json +45 -13
- package/readme.md +566 -42
- package/src/aeat/client.ts +296 -55
- package/src/aeat/index.ts +1 -0
- package/src/aeat/response.ts +195 -0
- package/src/core/hash/chain.ts +68 -0
- package/src/core/index.ts +2 -0
- package/src/core/models/invoice.ts +40 -18
- package/src/core/utils/date.ts +27 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/validation/index.ts +1 -0
- package/src/core/validation/validate.ts +71 -0
- package/src/core/xml/generator.ts +306 -73
- package/src/integration/accountability.ts +125 -0
- package/src/integration/index.ts +2 -0
- package/src/integration/p12.ts +155 -0
- package/src/integration/repository.ts +2 -0
- package/src/integration/service.ts +113 -14
- package/dist/aeat/client.d.ts +0 -57
- package/dist/aeat/client.d.ts.map +0 -1
- package/dist/aeat/client.js +0 -87
- package/dist/aeat/client.js.map +0 -1
- package/dist/aeat/index.d.ts +0 -2
- package/dist/aeat/index.d.ts.map +0 -1
- package/dist/aeat/index.js +0 -2
- package/dist/aeat/index.js.map +0 -1
- package/dist/core/hash/chain.d.ts.map +0 -1
- package/dist/core/hash/chain.js.map +0 -1
- package/dist/core/hash/index.d.ts.map +0 -1
- package/dist/core/hash/index.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/core/models/event.d.ts.map +0 -1
- package/dist/core/models/index.d.ts.map +0 -1
- package/dist/core/models/index.js.map +0 -1
- package/dist/core/models/invoice.d.ts.map +0 -1
- package/dist/core/qr/index.d.ts.map +0 -1
- package/dist/core/qr/index.js.map +0 -1
- package/dist/core/qr/qr.d.ts.map +0 -1
- package/dist/core/qr/qr.js.map +0 -1
- package/dist/core/xml/generator.d.ts +0 -32
- package/dist/core/xml/generator.d.ts.map +0 -1
- package/dist/core/xml/generator.js +0 -103
- package/dist/core/xml/generator.js.map +0 -1
- package/dist/core/xml/index.d.ts.map +0 -1
- package/dist/core/xml/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integration/index.d.ts +0 -3
- package/dist/integration/index.d.ts.map +0 -1
- package/dist/integration/index.js +0 -3
- package/dist/integration/index.js.map +0 -1
- package/dist/integration/repository.d.ts.map +0 -1
- package/dist/integration/repository.js.map +0 -1
- package/dist/integration/service.d.ts.map +0 -1
- package/dist/integration/service.js.map +0 -1
- /package/dist/{core → cjs/core}/hash/index.d.ts +0 -0
- /package/dist/{core → cjs/core}/models/event.d.ts +0 -0
- /package/dist/{core → cjs/core}/models/index.d.ts +0 -0
- /package/dist/{core → cjs/core}/qr/index.d.ts +0 -0
- /package/dist/{core → cjs/core}/qr/qr.d.ts +0 -0
- /package/dist/{core → cjs/core}/xml/index.d.ts +0 -0
- /package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
- /package/dist/{core → esm/core}/hash/index.js +0 -0
- /package/dist/{core → esm/core}/models/event.js +0 -0
- /package/dist/{core → esm/core}/models/index.js +0 -0
- /package/dist/{core → esm/core}/models/invoice.js +0 -0
- /package/dist/{core → esm/core}/qr/index.js +0 -0
- /package/dist/{core → esm/core}/qr/qr.js +0 -0
- /package/dist/{core → esm/core}/xml/index.js +0 -0
- /package/dist/{index.js → esm/index.js} +0 -0
- /package/dist/{integration → esm/integration}/repository.js +0 -0
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import type { Invoice } from '../models/invoice.js';
|
|
1
|
+
import type { Invoice, SistemaInformatico } from '../models/invoice.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Datos de
|
|
4
|
+
* Datos de un registro de anulación (RegistroFacturacionAnulacionType).
|
|
5
5
|
*/
|
|
6
6
|
export interface AnulacionInput {
|
|
7
7
|
/** NIF del emisor de la factura anulada. */
|
|
8
8
|
idEmisorFacturaAnulada: string;
|
|
9
9
|
/** Número y serie de la factura anulada. */
|
|
10
10
|
numSerieFacturaAnulada: string;
|
|
11
|
-
/** Fecha de expedición de la factura anulada. */
|
|
11
|
+
/** Fecha de expedición de la factura anulada (DD-MM-YYYY). */
|
|
12
12
|
fechaExpedicionFacturaAnulada: string;
|
|
13
13
|
/** Huella (hash) de la factura anulada. */
|
|
14
14
|
huellaFacturaAnulada: string;
|
|
15
|
+
/** Sistema informático que genera el registro. */
|
|
16
|
+
sistemaInformatico: SistemaInformatico;
|
|
17
|
+
/** Fecha/hora de generación con huso horario. */
|
|
18
|
+
fechaHoraHusoGenRegistro: string;
|
|
19
|
+
/** Huella (hash) del propio registro de anulación. */
|
|
20
|
+
huella: string;
|
|
21
|
+
/** Versión del esquema (por defecto '1.0'). */
|
|
22
|
+
idVersion?: string;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
/**
|
|
@@ -34,64 +42,85 @@ function formatAmount(value: number): string {
|
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
|
-
* Genera el
|
|
45
|
+
* Genera el elemento `<RegistroAlta>` de una factura (registro de alta).
|
|
38
46
|
*
|
|
39
|
-
* Estructura
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* TipoFactura, TipoDesglose, Desglose, CuotaTotal, Encadenamiento, Huella).
|
|
47
|
+
* Estructura según el XSD `SuministroInformacion.xsd` de la AEAT
|
|
48
|
+
* (`RegistroFacturacionAltaType`). NOTA: validar contra el XSD oficial
|
|
49
|
+
* antes de producción.
|
|
43
50
|
*/
|
|
44
|
-
export function
|
|
45
|
-
const { id, emisor, desglose, tipoFactura,
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
export function generateRegistroAltaXml(invoice: Invoice, previousHash: string | null): string {
|
|
52
|
+
const { id, emisor, desglose, tipoFactura, sistemaInformatico } = invoice;
|
|
53
|
+
const idVersion = invoice.idVersion ?? '1.0';
|
|
54
|
+
const descripcion = invoice.descripcion ?? '';
|
|
55
|
+
|
|
56
|
+
const detalles = desglose.detalles
|
|
57
|
+
.map(
|
|
58
|
+
(d) =>
|
|
59
|
+
` <sf:DetalleDesglose>` +
|
|
60
|
+
(d.claveRegimen !== undefined
|
|
61
|
+
? `<sf:ClaveRegimen>${escapeXml(d.claveRegimen)}</sf:ClaveRegimen>`
|
|
62
|
+
: '') +
|
|
63
|
+
`<sf:CalificacionOperacion>${escapeXml(d.calificacionOperacion)}</sf:CalificacionOperacion>` +
|
|
64
|
+
(d.tipoImpositivo !== undefined
|
|
65
|
+
? `<sf:TipoImpositivo>${formatAmount(d.tipoImpositivo)}</sf:TipoImpositivo>`
|
|
66
|
+
: '') +
|
|
67
|
+
`<sf:BaseImponibleOimporteNoSujeto>${formatAmount(d.baseImponible)}</sf:BaseImponibleOimporteNoSujeto>` +
|
|
68
|
+
(d.cuotaRepercutida !== undefined
|
|
69
|
+
? `<sf:CuotaRepercutida>${formatAmount(d.cuotaRepercutida)}</sf:CuotaRepercutida>`
|
|
70
|
+
: '') +
|
|
71
|
+
`</sf:DetalleDesglose>`,
|
|
72
|
+
)
|
|
66
73
|
.join('\n');
|
|
67
74
|
|
|
75
|
+
const encadenamiento = previousHash
|
|
76
|
+
? ` <sf:RegistroAnterior><sf:IDEmisorFactura>${escapeXml(emisor.nif)}</sf:IDEmisorFactura><sf:NumSerieFactura>${escapeXml(id.serie ?? '')}${escapeXml(id.numero)}</sf:NumSerieFactura><sf:FechaExpedicionFactura>${id.fechaExpedicion}</sf:FechaExpedicionFactura><sf:Huella>${previousHash}</sf:Huella></sf:RegistroAnterior>`
|
|
77
|
+
: ` <sf:PrimerRegistro>S</sf:PrimerRegistro>`;
|
|
78
|
+
|
|
68
79
|
const xml = [
|
|
69
80
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
'
|
|
73
|
-
`
|
|
74
|
-
`
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
`<sf:RegistroAlta xmlns:sf="${AEAT_INFORMACION_NAMESPACE}">`,
|
|
82
|
+
` <sf:IDVersion>${idVersion}</sf:IDVersion>`,
|
|
83
|
+
' <sf:IDFactura>',
|
|
84
|
+
` <sf:IDEmisorFactura>${escapeXml(emisor.nif)}</sf:IDEmisorFactura>`,
|
|
85
|
+
` <sf:NumSerieFactura>${escapeXml(id.serie ?? '')}${escapeXml(id.numero)}</sf:NumSerieFactura>`,
|
|
86
|
+
` <sf:FechaExpedicionFactura>${id.fechaExpedicion}</sf:FechaExpedicionFactura>`,
|
|
87
|
+
' </sf:IDFactura>',
|
|
88
|
+
` <sf:NombreRazonEmisor>${escapeXml(emisor.nombreRazon)}</sf:NombreRazonEmisor>`,
|
|
89
|
+
` <sf:TipoFactura>${tipoFactura}</sf:TipoFactura>`,
|
|
90
|
+
` <sf:DescripcionOperacion>${escapeXml(descripcion)}</sf:DescripcionOperacion>`,
|
|
91
|
+
...(invoice.destinatarios && invoice.destinatarios.length
|
|
92
|
+
? [
|
|
93
|
+
' <sf:Destinatarios>',
|
|
94
|
+
...invoice.destinatarios.map(
|
|
95
|
+
(d) =>
|
|
96
|
+
` <sf:IDDestinatario><sf:NombreRazon>${escapeXml(d.nombreRazon)}</sf:NombreRazon><sf:NIF>${escapeXml(d.nif)}</sf:NIF></sf:IDDestinatario>`,
|
|
97
|
+
),
|
|
98
|
+
' </sf:Destinatarios>',
|
|
99
|
+
]
|
|
100
|
+
: []),
|
|
101
|
+
' <sf:Desglose>',
|
|
88
102
|
detalles,
|
|
89
|
-
' </Desglose>',
|
|
90
|
-
` <CuotaTotal>${formatAmount(desglose.cuotaTotal)}</CuotaTotal>`,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
'</
|
|
103
|
+
' </sf:Desglose>',
|
|
104
|
+
` <sf:CuotaTotal>${formatAmount(desglose.cuotaTotal)}</sf:CuotaTotal>`,
|
|
105
|
+
` <sf:ImporteTotal>${formatAmount(desglose.importeTotal)}</sf:ImporteTotal>`,
|
|
106
|
+
' <sf:Encadenamiento>',
|
|
107
|
+
encadenamiento,
|
|
108
|
+
' </sf:Encadenamiento>',
|
|
109
|
+
' <sf:SistemaInformatico>',
|
|
110
|
+
` <sf:NombreRazon>${escapeXml(sistemaInformatico.nombreRazon)}</sf:NombreRazon>`,
|
|
111
|
+
` <sf:NIF>${escapeXml(sistemaInformatico.nif)}</sf:NIF>`,
|
|
112
|
+
` <sf:NombreSistemaInformatico>${escapeXml(sistemaInformatico.nombreSistemaInformatico)}</sf:NombreSistemaInformatico>`,
|
|
113
|
+
` <sf:IdSistemaInformatico>${escapeXml(sistemaInformatico.idSistemaInformatico)}</sf:IdSistemaInformatico>`,
|
|
114
|
+
` <sf:Version>${escapeXml(sistemaInformatico.version)}</sf:Version>`,
|
|
115
|
+
` <sf:NumeroInstalacion>${escapeXml(sistemaInformatico.numeroInstalacion)}</sf:NumeroInstalacion>`,
|
|
116
|
+
' <sf:TipoUsoPosibleSoloVerifactu>S</sf:TipoUsoPosibleSoloVerifactu>',
|
|
117
|
+
' <sf:TipoUsoPosibleMultiOT>N</sf:TipoUsoPosibleMultiOT>',
|
|
118
|
+
' <sf:IndicadorMultiplesOT>N</sf:IndicadorMultiplesOT>',
|
|
119
|
+
' </sf:SistemaInformatico>',
|
|
120
|
+
` <sf:FechaHoraHusoGenRegistro>${invoice.fechaHoraHusoGenRegistro}</sf:FechaHoraHusoGenRegistro>`,
|
|
121
|
+
' <sf:TipoHuella>01</sf:TipoHuella>',
|
|
122
|
+
` <sf:Huella>${invoice.huella}</sf:Huella>`,
|
|
123
|
+
'</sf:RegistroAlta>',
|
|
95
124
|
]
|
|
96
125
|
.filter((line) => line !== '')
|
|
97
126
|
.join('\n');
|
|
@@ -100,34 +129,238 @@ export function generateInvoiceXml(invoice: Invoice, previousHash: string | null
|
|
|
100
129
|
}
|
|
101
130
|
|
|
102
131
|
/**
|
|
103
|
-
*
|
|
132
|
+
* Cabecera del suministro (CabeceraType del XSD).
|
|
133
|
+
*/
|
|
134
|
+
export interface Cabecera {
|
|
135
|
+
/** Nombre o razón social del obligado a emitir. */
|
|
136
|
+
nombreRazon: string;
|
|
137
|
+
/** NIF del obligado a emitir. */
|
|
138
|
+
nif: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Namespace del esquema `SuministroLR.xsd` de la AEAT.
|
|
143
|
+
*/
|
|
144
|
+
export const AEAT_SUMINISTRO_NAMESPACE =
|
|
145
|
+
'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd';
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Namespace del esquema `SuministroInformacion.xsd` de la AEAT.
|
|
149
|
+
*/
|
|
150
|
+
export const AEAT_INFORMACION_NAMESPACE =
|
|
151
|
+
'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd';
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Namespace del esquema `EventosSIF.xsd` de la AEAT (registro de eventos).
|
|
155
|
+
*/
|
|
156
|
+
export const AEAT_EVENTO_NAMESPACE =
|
|
157
|
+
'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/EventosSIF.xsd';
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Cabecera del suministro (CabeceraType del XSD).
|
|
161
|
+
*/
|
|
162
|
+
export interface Cabecera {
|
|
163
|
+
/** Nombre o razón social del obligado a emitir. */
|
|
164
|
+
nombreRazon: string;
|
|
165
|
+
/** NIF del obligado a emitir. */
|
|
166
|
+
nif: string;
|
|
167
|
+
/** Representante del obligado (solo si el envío lo hace un representante). */
|
|
168
|
+
representante?: {
|
|
169
|
+
/** Nombre o razón social del representante. */
|
|
170
|
+
nombreRazon: string;
|
|
171
|
+
/** NIF del representante. */
|
|
172
|
+
nif: string;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Genera el elemento `<RegFactuSistemaFacturacion>` completo (operación SOAP),
|
|
178
|
+
* con la `Cabecera` y el `RegistroFactura` que envuelve el registro.
|
|
179
|
+
*/
|
|
180
|
+
export function generateSuministroXml(registroXml: string, cabecera: Cabecera): string {
|
|
181
|
+
const body = registroXml.replace(/^<\?xml[^>]*\?>\s*/, '');
|
|
182
|
+
const representante = cabecera.representante
|
|
183
|
+
? [
|
|
184
|
+
' <sf:Representante>',
|
|
185
|
+
` <sf:NombreRazon>${escapeXml(cabecera.representante.nombreRazon)}</sf:NombreRazon>`,
|
|
186
|
+
` <sf:NIF>${escapeXml(cabecera.representante.nif)}</sf:NIF>`,
|
|
187
|
+
' </sf:Representante>',
|
|
188
|
+
].join('\n')
|
|
189
|
+
: '';
|
|
190
|
+
return [
|
|
191
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
192
|
+
`<sfLR:RegFactuSistemaFacturacion xmlns:sfLR="${AEAT_SUMINISTRO_NAMESPACE}" xmlns:sf="${AEAT_INFORMACION_NAMESPACE}">`,
|
|
193
|
+
' <sfLR:Cabecera>',
|
|
194
|
+
' <sf:ObligadoEmision>',
|
|
195
|
+
` <sf:NombreRazon>${escapeXml(cabecera.nombreRazon)}</sf:NombreRazon>`,
|
|
196
|
+
` <sf:NIF>${escapeXml(cabecera.nif)}</sf:NIF>`,
|
|
197
|
+
' </sf:ObligadoEmision>',
|
|
198
|
+
representante,
|
|
199
|
+
' </sfLR:Cabecera>',
|
|
200
|
+
' <sfLR:RegistroFactura>',
|
|
201
|
+
body,
|
|
202
|
+
' </sfLR:RegistroFactura>',
|
|
203
|
+
'</sfLR:RegFactuSistemaFacturacion>',
|
|
204
|
+
].join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Construye la `Cabecera` del suministro, detectando si el certificado es de
|
|
209
|
+
* un representante.
|
|
210
|
+
*
|
|
211
|
+
* Si se proporciona el certificado en PEM y su NIF difiere del NIF del
|
|
212
|
+
* obligado, se añade el bloque `Representante` (el certificado actúa como
|
|
213
|
+
* representante del obligado), usando el nombre del titular del certificado.
|
|
214
|
+
*/
|
|
215
|
+
export function buildCabecera(
|
|
216
|
+
obligado: { nombreRazon: string; nif: string },
|
|
217
|
+
certificatePem?: string,
|
|
218
|
+
extractInfo?: (pem: string) => { nif: string; nombreRazon: string } | null,
|
|
219
|
+
): Cabecera {
|
|
220
|
+
const cabecera: Cabecera = { nombreRazon: obligado.nombreRazon, nif: obligado.nif };
|
|
221
|
+
if (certificatePem && extractInfo) {
|
|
222
|
+
const info = extractInfo(certificatePem);
|
|
223
|
+
if (info && info.nif !== obligado.nif) {
|
|
224
|
+
cabecera.representante = { nombreRazon: info.nombreRazon, nif: info.nif };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return cabecera;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Genera el XML Verifactu de una factura (registro de alta).
|
|
104
232
|
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
233
|
+
* Alias de `generateRegistroAltaXml` por compatibilidad.
|
|
234
|
+
*/
|
|
235
|
+
export function generateInvoiceXml(invoice: Invoice, previousHash: string | null): string {
|
|
236
|
+
return generateRegistroAltaXml(invoice, previousHash);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Genera el XML Verifactu de un registro de anulación (`<sf:RegistroAnulacion>`).
|
|
241
|
+
*
|
|
242
|
+
* Referencia la factura anulada (IDFactura) y encadena con el hash anterior.
|
|
243
|
+
* Estructura según el XSD `SuministroInformacion.xsd` (`RegistroFacturacionAnulacionType`).
|
|
108
244
|
*/
|
|
109
245
|
export function generateAnulacionXml(
|
|
110
246
|
anulacion: AnulacionInput,
|
|
111
247
|
previousHash: string | null,
|
|
112
248
|
): string {
|
|
249
|
+
const idVersion = anulacion.idVersion ?? '1.0';
|
|
250
|
+
const si = anulacion.sistemaInformatico;
|
|
251
|
+
const encadenamiento = previousHash
|
|
252
|
+
? ` <sf:RegistroAnterior><sf:IDEmisorFactura>${escapeXml(anulacion.idEmisorFacturaAnulada)}</sf:IDEmisorFactura><sf:NumSerieFactura>${escapeXml(anulacion.numSerieFacturaAnulada)}</sf:NumSerieFactura><sf:FechaExpedicionFactura>${anulacion.fechaExpedicionFacturaAnulada}</sf:FechaExpedicionFactura><sf:Huella>${previousHash}</sf:Huella></sf:RegistroAnterior>`
|
|
253
|
+
: ` <sf:PrimerRegistro>S</sf:PrimerRegistro>`;
|
|
254
|
+
|
|
255
|
+
return [
|
|
256
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
257
|
+
`<sf:RegistroAnulacion xmlns:sf="${AEAT_INFORMACION_NAMESPACE}">`,
|
|
258
|
+
` <sf:IDVersion>${idVersion}</sf:IDVersion>`,
|
|
259
|
+
' <sf:IDFactura>',
|
|
260
|
+
` <sf:IDEmisorFacturaAnulada>${escapeXml(anulacion.idEmisorFacturaAnulada)}</sf:IDEmisorFacturaAnulada>`,
|
|
261
|
+
` <sf:NumSerieFacturaAnulada>${escapeXml(anulacion.numSerieFacturaAnulada)}</sf:NumSerieFacturaAnulada>`,
|
|
262
|
+
` <sf:FechaExpedicionFacturaAnulada>${anulacion.fechaExpedicionFacturaAnulada}</sf:FechaExpedicionFacturaAnulada>`,
|
|
263
|
+
' </sf:IDFactura>',
|
|
264
|
+
' <sf:Encadenamiento>',
|
|
265
|
+
encadenamiento,
|
|
266
|
+
' </sf:Encadenamiento>',
|
|
267
|
+
' <sf:SistemaInformatico>',
|
|
268
|
+
` <sf:NombreRazon>${escapeXml(si.nombreRazon)}</sf:NombreRazon>`,
|
|
269
|
+
` <sf:NIF>${escapeXml(si.nif)}</sf:NIF>`,
|
|
270
|
+
` <sf:NombreSistemaInformatico>${escapeXml(si.nombreSistemaInformatico)}</sf:NombreSistemaInformatico>`,
|
|
271
|
+
` <sf:IdSistemaInformatico>${escapeXml(si.idSistemaInformatico)}</sf:IdSistemaInformatico>`,
|
|
272
|
+
` <sf:Version>${escapeXml(si.version)}</sf:Version>`,
|
|
273
|
+
` <sf:NumeroInstalacion>${escapeXml(si.numeroInstalacion)}</sf:NumeroInstalacion>`,
|
|
274
|
+
' <sf:TipoUsoPosibleSoloVerifactu>S</sf:TipoUsoPosibleSoloVerifactu>',
|
|
275
|
+
' <sf:TipoUsoPosibleMultiOT>N</sf:TipoUsoPosibleMultiOT>',
|
|
276
|
+
' <sf:IndicadorMultiplesOT>N</sf:IndicadorMultiplesOT>',
|
|
277
|
+
' </sf:SistemaInformatico>',
|
|
278
|
+
` <sf:FechaHoraHusoGenRegistro>${anulacion.fechaHoraHusoGenRegistro}</sf:FechaHoraHusoGenRegistro>`,
|
|
279
|
+
' <sf:TipoHuella>01</sf:TipoHuella>',
|
|
280
|
+
` <sf:Huella>${anulacion.huella}</sf:Huella>`,
|
|
281
|
+
'</sf:RegistroAnulacion>',
|
|
282
|
+
].join('\n');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Datos de un registro de evento (RegistroEvento).
|
|
287
|
+
*
|
|
288
|
+
* Nota: el `NIF` aparece dos veces: `nifSistemaInformatico` (del sistema
|
|
289
|
+
* informático) y `nifObligado` (del obligado a emitir).
|
|
290
|
+
*/
|
|
291
|
+
/**
|
|
292
|
+
* Datos de un registro de evento (RegistroEvento, EventosSIF.xsd).
|
|
293
|
+
*/
|
|
294
|
+
export interface EventoInput {
|
|
295
|
+
/** Sistema informático que genera el evento. */
|
|
296
|
+
sistemaInformatico: SistemaInformatico;
|
|
297
|
+
/** Obligado a emitir (persona física o jurídica española). */
|
|
298
|
+
obligadoEmision: { nombreRazon: string; nif: string };
|
|
299
|
+
/** Fecha/hora de generación con huso horario (ISO 8601). */
|
|
300
|
+
fechaHoraHusoGenEvento: string;
|
|
301
|
+
/** Tipo de evento (01–10, 90). */
|
|
302
|
+
tipoEvento: string;
|
|
303
|
+
/** Huella (hash) del evento. */
|
|
304
|
+
huellaEvento: string;
|
|
305
|
+
/** Versión del esquema (por defecto '1.0'). */
|
|
306
|
+
idVersion?: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Genera el XML Verifactu de un registro de evento (RegistroEvento).
|
|
311
|
+
*
|
|
312
|
+
* Estructura basada en `EventosSIF.xsd` (namespace `sf`). El evento se envía
|
|
313
|
+
* como un suministro independiente (no dentro de `RegFactuSistemaFacturacion`).
|
|
314
|
+
*/
|
|
315
|
+
export function generateEventoXml(evento: EventoInput): string {
|
|
316
|
+
const si = evento.sistemaInformatico;
|
|
113
317
|
const xml = [
|
|
114
318
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
`
|
|
120
|
-
|
|
121
|
-
` <
|
|
122
|
-
` <
|
|
123
|
-
|
|
124
|
-
`
|
|
125
|
-
'
|
|
126
|
-
'
|
|
127
|
-
|
|
128
|
-
'
|
|
129
|
-
'
|
|
319
|
+
`<sf:RegistroEvento xmlns:sf="${AEAT_EVENTO_NAMESPACE}">`,
|
|
320
|
+
` <sf:IDVersion>${escapeXml(evento.idVersion ?? '1.0')}</sf:IDVersion>`,
|
|
321
|
+
' <sf:Evento>',
|
|
322
|
+
' <sf:SistemaInformatico>',
|
|
323
|
+
` <sf:NombreRazon>${escapeXml(si.nombreRazon)}</sf:NombreRazon>`,
|
|
324
|
+
` <sf:NIF>${escapeXml(si.nif)}</sf:NIF>`,
|
|
325
|
+
` <sf:NombreSistemaInformatico>${escapeXml(si.nombreSistemaInformatico)}</sf:NombreSistemaInformatico>`,
|
|
326
|
+
` <sf:IdSistemaInformatico>${escapeXml(si.idSistemaInformatico)}</sf:IdSistemaInformatico>`,
|
|
327
|
+
` <sf:Version>${escapeXml(si.version)}</sf:Version>`,
|
|
328
|
+
` <sf:NumeroInstalacion>${escapeXml(si.numeroInstalacion)}</sf:NumeroInstalacion>`,
|
|
329
|
+
' <sf:TipoUsoPosibleSoloVerifactu>S</sf:TipoUsoPosibleSoloVerifactu>',
|
|
330
|
+
' <sf:TipoUsoPosibleMultiOT>N</sf:TipoUsoPosibleMultiOT>',
|
|
331
|
+
' <sf:IndicadorMultiplesOT>N</sf:IndicadorMultiplesOT>',
|
|
332
|
+
' </sf:SistemaInformatico>',
|
|
333
|
+
' <sf:ObligadoEmision>',
|
|
334
|
+
` <sf:NombreRazon>${escapeXml(evento.obligadoEmision.nombreRazon)}</sf:NombreRazon>`,
|
|
335
|
+
` <sf:NIF>${escapeXml(evento.obligadoEmision.nif)}</sf:NIF>`,
|
|
336
|
+
' </sf:ObligadoEmision>',
|
|
337
|
+
` <sf:FechaHoraHusoGenEvento>${evento.fechaHoraHusoGenEvento}</sf:FechaHoraHusoGenEvento>`,
|
|
338
|
+
` <sf:TipoEvento>${escapeXml(evento.tipoEvento)}</sf:TipoEvento>`,
|
|
339
|
+
' <sf:Encadenamiento>',
|
|
340
|
+
' <sf:PrimerEvento>S</sf:PrimerEvento>',
|
|
341
|
+
' </sf:Encadenamiento>',
|
|
342
|
+
' <sf:TipoHuella>01</sf:TipoHuella>',
|
|
343
|
+
` <sf:HuellaEvento>${evento.huellaEvento}</sf:HuellaEvento>`,
|
|
344
|
+
' </sf:Evento>',
|
|
345
|
+
'</sf:RegistroEvento>',
|
|
130
346
|
].join('\n');
|
|
131
347
|
|
|
132
348
|
return xml;
|
|
133
349
|
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Envuelve un elemento de operación Verifactu en el sobre SOAP que la AEAT
|
|
353
|
+
* espera en el envío. El XML de entrada DEBE ser ya el elemento de operación
|
|
354
|
+
* (p. ej. `<RegFactuSistemaFacturacion>` o `<ConsultaFactuSistemaFacturacion>`).
|
|
355
|
+
*/
|
|
356
|
+
export function buildRemisionEnvelope(operationXml: string): string {
|
|
357
|
+
const body = operationXml.replace(/^<\?xml[^>]*\?>\s*/, '');
|
|
358
|
+
return [
|
|
359
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
360
|
+
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">',
|
|
361
|
+
' <soap:Body>',
|
|
362
|
+
body,
|
|
363
|
+
' </soap:Body>',
|
|
364
|
+
'</soap:Envelope>',
|
|
365
|
+
].join('\n');
|
|
366
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Invoice } from '../core/models/invoice.js';
|
|
2
|
+
import { generateInvoiceXml } from '../core/xml/generator.js';
|
|
3
|
+
import { computeRegistroAlta } from '../core/hash/chain.js';
|
|
4
|
+
import { buildQrPayload } from '../core/qr/qr.js';
|
|
5
|
+
import { nowIsoWithOffset } from '../core/utils/date.js';
|
|
6
|
+
import { VerifactuClient } from '../aeat/client.js';
|
|
7
|
+
import { readP12 } from './p12.js';
|
|
8
|
+
import type { VerifactuRepository, StoredRecord } from './repository.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuración de la integración de accountability (contabilidad).
|
|
12
|
+
*
|
|
13
|
+
* Se puede aportar el material TLS ya extraído (`cert`/`key`) o el `.p12`
|
|
14
|
+
* en bruto (`p12Buffer` + `p12Password`). El NIF del emisor se aporta
|
|
15
|
+
* siempre en `nif` (la librería no interpreta los campos del certificado).
|
|
16
|
+
*/
|
|
17
|
+
export interface AccountabilityConfig {
|
|
18
|
+
/** URL base del servicio AEAT. */
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
/** NIF del emisor. */
|
|
21
|
+
nif: string;
|
|
22
|
+
/** Certificado de cliente en PEM (mTLS). */
|
|
23
|
+
cert?: string;
|
|
24
|
+
/** Clave privada del cliente en PEM (mTLS). */
|
|
25
|
+
key?: string;
|
|
26
|
+
/** Contenido del `.p12` (alternativa a `cert`/`key`). */
|
|
27
|
+
p12Buffer?: Buffer;
|
|
28
|
+
/** Contraseña del `.p12`. */
|
|
29
|
+
p12Password?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Integración de accountability (contabilidad) para Verifactu.
|
|
34
|
+
*
|
|
35
|
+
* Une las credenciales del certificado (`.p12`) con el cliente mTLS y la
|
|
36
|
+
* persistencia, para registrar facturas de forma verificable ante la AEAT.
|
|
37
|
+
*/
|
|
38
|
+
export class AccountabilityService {
|
|
39
|
+
private readonly client: VerifactuClient;
|
|
40
|
+
private readonly nif: string;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
private readonly repository: VerifactuRepository,
|
|
44
|
+
config: AccountabilityConfig,
|
|
45
|
+
) {
|
|
46
|
+
let cert: string | undefined;
|
|
47
|
+
let key: string | undefined;
|
|
48
|
+
|
|
49
|
+
if (config.p12Buffer && config.p12Password) {
|
|
50
|
+
const content = readP12(config.p12Buffer, config.p12Password);
|
|
51
|
+
cert = content.certificates[0];
|
|
52
|
+
key = content.keys[0];
|
|
53
|
+
} else if (config.cert && config.key) {
|
|
54
|
+
cert = config.cert;
|
|
55
|
+
key = config.key;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!cert || !key) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'AccountabilityService requiere un .p12 (p12Buffer + p12Password) o cert/key PEM.',
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.nif = config.nif;
|
|
65
|
+
this.client = new VerifactuClient({ baseUrl: config.baseUrl, cert, key });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Registra una factura (alta) ante la AEAT, usando el certificado del
|
|
70
|
+
* emisor para autenticar el envío.
|
|
71
|
+
*/
|
|
72
|
+
async registerInvoice(invoice: Invoice): Promise<StoredRecord> {
|
|
73
|
+
// El NIF del emisor se toma del certificado (o del config).
|
|
74
|
+
const invoiceConNif: Invoice = {
|
|
75
|
+
...invoice,
|
|
76
|
+
emisor: { ...invoice.emisor, nif: this.nif },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const last = await this.repository.getLastRecord();
|
|
80
|
+
const previousHash = last?.hash ?? null;
|
|
81
|
+
|
|
82
|
+
const xml = generateInvoiceXml(invoiceConNif, previousHash);
|
|
83
|
+
const fechaHoraHusoGenRegistro = nowIsoWithOffset();
|
|
84
|
+
const hash = computeRegistroAlta({
|
|
85
|
+
idEmisorFactura: this.nif,
|
|
86
|
+
numSerieFactura: `${invoice.id.serie ?? ''}${invoice.id.numero}`,
|
|
87
|
+
fechaExpedicionFactura: invoice.id.fechaExpedicion,
|
|
88
|
+
tipoFactura: invoice.tipoFactura,
|
|
89
|
+
cuotaTotal: invoice.desglose.cuotaTotal.toFixed(2),
|
|
90
|
+
importeTotal: invoice.desglose.importeTotal.toFixed(2),
|
|
91
|
+
huellaAnterior: previousHash,
|
|
92
|
+
fechaHoraHusoGenRegistro,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const now = new Date().toISOString();
|
|
96
|
+
const record: StoredRecord = {
|
|
97
|
+
id: `${invoice.id.serie ?? ''}${invoice.id.numero}`,
|
|
98
|
+
invoiceId: `${invoice.id.serie ?? ''}${invoice.id.numero}`,
|
|
99
|
+
emisorNif: this.nif,
|
|
100
|
+
xml,
|
|
101
|
+
hash,
|
|
102
|
+
previousHash,
|
|
103
|
+
qrPayload: buildQrPayload(hash),
|
|
104
|
+
status: 'PENDING',
|
|
105
|
+
createdAt: now,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
};
|
|
108
|
+
await this.repository.saveRecord(record);
|
|
109
|
+
|
|
110
|
+
const result = await this.client.sendRecord(xml);
|
|
111
|
+
await this.repository.saveResponse({
|
|
112
|
+
id: `${record.id}-${Date.now()}`,
|
|
113
|
+
recordId: record.id,
|
|
114
|
+
operationId: result.operationId,
|
|
115
|
+
status: result.status,
|
|
116
|
+
message: result.message,
|
|
117
|
+
receivedAt: new Date().toISOString(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const status = result.status === 'OK' ? 'CONFIRMED' : 'REJECTED';
|
|
121
|
+
await this.repository.updateRecordStatus(record.id, status);
|
|
122
|
+
|
|
123
|
+
return { ...record, status };
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/integration/index.ts
CHANGED