@ekwo-ai/ecdf 0.4.0
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/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +264 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ekwo AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# `@ekwo-ai/ecdf`
|
|
2
|
+
|
|
3
|
+
The Luxembourg **eCDF** interface file, version 2.0, in TypeScript, with no
|
|
4
|
+
dependencies — and the four forms it carries that are a recapitulative
|
|
5
|
+
statement of intra-Community supplies.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { generateEcdf } from '@ekwo-ai/ecdf';
|
|
9
|
+
|
|
10
|
+
const { file, filename, declarations, violations } = generateEcdf(rows, {
|
|
11
|
+
prefix: '000000',
|
|
12
|
+
interfaceId: 'DEMO',
|
|
13
|
+
agent: { matrNbr: '19999999999', rcsNbr: 'B999999', vatNbr: '99999999' },
|
|
14
|
+
declarer: { matrNbr: '19999999999', rcsNbr: 'B999999', vatNbr: '99999999' },
|
|
15
|
+
cadence: 'quarter',
|
|
16
|
+
year: 2026,
|
|
17
|
+
period: 3,
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`StatementRow` is the row shape of the `ec_sales_list(company, from, to)`
|
|
22
|
+
function of [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os), declared here so
|
|
23
|
+
that nothing is imported from it. Any book-keeping system that can produce
|
|
24
|
+
those columns can use this package; it reads no database and knows no
|
|
25
|
+
accounting.
|
|
26
|
+
|
|
27
|
+
## eCDF is an envelope, not a form
|
|
28
|
+
|
|
29
|
+
One XML file carries any number of declarations, each named by a `type` and
|
|
30
|
+
filled with fields identified by number. This package writes the envelope and
|
|
31
|
+
knows the four types that carry a statement:
|
|
32
|
+
|
|
33
|
+
| `type` | What it declares | Filed |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `TVA_LICM` | Supplies of goods and triangular operations | Monthly |
|
|
36
|
+
| `TVA_LICT` | The same | Quarterly |
|
|
37
|
+
| `TVA_PSIM` | Supplies of services | Monthly |
|
|
38
|
+
| `TVA_PSIT` | The same | Quarterly |
|
|
39
|
+
|
|
40
|
+
**One call writes both forms** where the period carries both natures: goods and
|
|
41
|
+
services are separate filings in Luxembourg, and the envelope is made to hold
|
|
42
|
+
several. A nature with nothing in it produces no declaration, because eCDF
|
|
43
|
+
refuses a statement whose every table is empty — and `generateEcdf` says so in
|
|
44
|
+
a violation rather than handing back a file that will be rejected.
|
|
45
|
+
|
|
46
|
+
The field identifiers are the ones the forms print: state I of the goods form
|
|
47
|
+
is 01 country, 02 number, 03 amount, with 04 as its total; state II — the
|
|
48
|
+
triangular operations — is 05, 06, 07 with total 08; the services form uses 01,
|
|
49
|
+
02, 03 with total 04. Field 16 is the total of the corrections, written as zero
|
|
50
|
+
here because corrections are out of scope.
|
|
51
|
+
|
|
52
|
+
## The specification this was written from
|
|
53
|
+
|
|
54
|
+
| What | Where |
|
|
55
|
+
|---|---|
|
|
56
|
+
| The schema, `eCDF_file_v2.0-XML_schema.xsd` | <https://ecdf-developer.b2g.etat.lu/ecdf/formdocs/eCDF_file_v2.0-XML_documentation-01-EN.pdf> |
|
|
57
|
+
| Documentation of the XML file, v2.0, CTIE, 24 November 2022 | <https://ecdf-developer.b2g.etat.lu/ecdf/formdocs/eCDF_file_v2.0-XML_documentation-01-EN.pdf> |
|
|
58
|
+
| A complete example, with `TVA_LICM`, `TVA_LICT`, `TVA_PSIM` and `TVA_PSIT` | <https://ecdf.b2g.etat.lu/ecdf/formdocs/eCDF_XML_file_example-FR.pdf> |
|
|
59
|
+
| The blank forms and the filing rules, 2026 | `https://ecdf.b2g.etat.lu/ecdf/formdocs/2026/TVA_LICM/2026M1V001/` and the three siblings |
|
|
60
|
+
|
|
61
|
+
The schema itself is at
|
|
62
|
+
<https://ecdf-developer.b2g.etat.lu/ecdf/formdocs/eCDF_file_v2.0-XML_schema.xsd>.
|
|
63
|
+
|
|
64
|
+
The output of this package was checked against that schema with `xmllint`
|
|
65
|
+
before it was committed, with one caveat worth writing down: libxml2 cannot
|
|
66
|
+
evaluate the `[\P{Cc}]+` pattern the schema puts on every `TextField`, and
|
|
67
|
+
rejects `DE` against it. Replacing that one pattern with `.+` makes the file
|
|
68
|
+
validate. The schema is not shipped here — it belongs to the administration
|
|
69
|
+
and it moves.
|
|
70
|
+
|
|
71
|
+
Three things about it that an example does not show:
|
|
72
|
+
|
|
73
|
+
- **Tables come last.** Every simple field of a `FormData` is written before the
|
|
74
|
+
first `Table`, and a file that interleaves them is refused.
|
|
75
|
+
- **`id="352"` is not `id="0352"`.** The identifier is a string, and a leading
|
|
76
|
+
zero makes a different field.
|
|
77
|
+
- **There is no schema per form.** The XSD accepts any `id` under `FormData`;
|
|
78
|
+
which identifiers a given `type` allows is checked by the server. The
|
|
79
|
+
validation-rules document that would state each field's type formally sits
|
|
80
|
+
behind authentication, so the field types here come from the published
|
|
81
|
+
example and the printed forms.
|
|
82
|
+
|
|
83
|
+
## Amounts and numbers
|
|
84
|
+
|
|
85
|
+
**A comma, never a point** — not even as a thousands separator. Up to two
|
|
86
|
+
decimals, and a minus sign where a credit note has taken the line below zero.
|
|
87
|
+
Everything is in euro; the forms carry no currency field.
|
|
88
|
+
|
|
89
|
+
**The customer's country and number are two separate fields**, which is exactly
|
|
90
|
+
the shape the statement rows already have. Do not concatenate them. The
|
|
91
|
+
declarer's own VAT number is eight digits without `LU`, or `NE`.
|
|
92
|
+
|
|
93
|
+
## The file name
|
|
94
|
+
|
|
95
|
+
eCDF fixes it: the file on disk is called `<FileReference>.xml`, where the
|
|
96
|
+
reference is `<prefix>X<yyyymmdd>T<hhmmss><nn>` — the agent's six-character
|
|
97
|
+
eCDF prefix, an `X` for an XML file, the moment it was built, and two digits
|
|
98
|
+
that separate two files of the same second. A fresh reference is generated for
|
|
99
|
+
every file. `generateEcdf` returns both the name and the reference.
|
|
100
|
+
|
|
101
|
+
## What it refuses and what it reports
|
|
102
|
+
|
|
103
|
+
An exception, because it would make the whole file meaningless: a prefix that
|
|
104
|
+
is not six characters, a missing interface identifier, a `MatrNbr`, `VATNbr` or
|
|
105
|
+
`RCSNbr` the schema's own patterns reject, or a period that does not fit the
|
|
106
|
+
cadence.
|
|
107
|
+
|
|
108
|
+
A violation, because one line is wrong and the rest of the file is not:
|
|
109
|
+
whatever the producer flagged, `unknown_nature`, `wrong_currency`,
|
|
110
|
+
`no_vat_number`, `vat_number_is_the_declarer`, `nil_amount`, and
|
|
111
|
+
`nothing_to_declare` when no form came out at all.
|
|
112
|
+
|
|
113
|
+
## What is out of scope, on purpose
|
|
114
|
+
|
|
115
|
+
- **Corrections.** States III and V of the goods form and state II of the
|
|
116
|
+
services form carry them, with the period being corrected and, on the goods
|
|
117
|
+
side, a localised "Oui"/"Ja" in field 15. Producing one means knowing what
|
|
118
|
+
was filed before, which is a question for the books.
|
|
119
|
+
- **Call-off stock**, states IV and V of the goods form. The published example
|
|
120
|
+
predates them and the type of their boolean fields could not be read from any
|
|
121
|
+
document available without an account, so nothing is guessed at here.
|
|
122
|
+
- **Filing.** `Interface` must hold an identifier the CTIE attributes after an
|
|
123
|
+
access request and an interface validation. A file without one is refused.
|
|
124
|
+
Writing valid XML is possible today; depositing it is not, until that
|
|
125
|
+
agreement exists.
|
|
126
|
+
|
|
127
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Luxembourg **eCDF** interface file, version 2.0.
|
|
3
|
+
*
|
|
4
|
+
* eCDF is not a form: it is the envelope the CTIE publishes, one XML file that
|
|
5
|
+
* carries any number of declarations, each named by a `type` and filled with
|
|
6
|
+
* numbered fields. This package writes that envelope, and knows the four types
|
|
7
|
+
* that carry a recapitulative statement — supplies of goods and supplies of
|
|
8
|
+
* services, each monthly or quarterly.
|
|
9
|
+
*
|
|
10
|
+
* Give it the rows of a statement and it writes the file, its name, and the
|
|
11
|
+
* list of what could not be put in it.
|
|
12
|
+
*
|
|
13
|
+
* Sources are in the README. This package depends on nothing, reads no
|
|
14
|
+
* database and knows no accounting.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* One line of a recapitulative statement, as `ec_sales_list(company, from,
|
|
18
|
+
* to)` of [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os) returns it. Declared
|
|
19
|
+
* here so that nothing is imported from it.
|
|
20
|
+
*/
|
|
21
|
+
export interface StatementRow {
|
|
22
|
+
/** Country of the customer's VAT identification number, ISO 3166-1 alpha-2. */
|
|
23
|
+
vat_country: string | null;
|
|
24
|
+
/** The number itself, without its country prefix — which is how eCDF wants it. */
|
|
25
|
+
vat_number: string | null;
|
|
26
|
+
/** `goods`, `services`, `triangular`. */
|
|
27
|
+
nature: string;
|
|
28
|
+
/** Taxable amount, credit notes already deducted. */
|
|
29
|
+
amount: string | number;
|
|
30
|
+
/** ISO 4217 code the amount is in. */
|
|
31
|
+
currency_code: string;
|
|
32
|
+
/** Why this line cannot be declared as it stands, or null when it can. */
|
|
33
|
+
issue?: string | null;
|
|
34
|
+
/** Names of the customers behind the line, for a message a human reads. */
|
|
35
|
+
contact_names?: string[] | null;
|
|
36
|
+
}
|
|
37
|
+
/** A party of the envelope: the agent who files, or the taxpayer filed for. */
|
|
38
|
+
export interface Party {
|
|
39
|
+
/** National identifier, 11 to 13 digits. */
|
|
40
|
+
matrNbr: string;
|
|
41
|
+
/** Trade register number, or `NE` where there is none. */
|
|
42
|
+
rcsNbr: string;
|
|
43
|
+
/** VAT number, eight digits without `LU`, or `NE`. */
|
|
44
|
+
vatNbr: string;
|
|
45
|
+
}
|
|
46
|
+
/** How often the statement is filed. eCDF has a form for each. */
|
|
47
|
+
export type Cadence = 'month' | 'quarter';
|
|
48
|
+
export interface EcdfOptions {
|
|
49
|
+
/** The eCDF prefix of the agent, six characters, from the file transfer menu. */
|
|
50
|
+
prefix: string;
|
|
51
|
+
/** The interface identifier the CTIE attributes. Without one, eCDF refuses the file. */
|
|
52
|
+
interfaceId: string;
|
|
53
|
+
/** Who files. */
|
|
54
|
+
agent: Party;
|
|
55
|
+
/** Who is filed for. The agent again, where they are the same. */
|
|
56
|
+
declarer: Party;
|
|
57
|
+
cadence: Cadence;
|
|
58
|
+
year: number;
|
|
59
|
+
/** 1 to 12 for a month, 1 to 4 for a quarter. */
|
|
60
|
+
period: number;
|
|
61
|
+
/** `FR`, `DE` or `EN`. The labels of the form, not of this file. */
|
|
62
|
+
language?: 'FR' | 'DE' | 'EN';
|
|
63
|
+
/** Moment the reference is built from. Now, when left out. */
|
|
64
|
+
createdAt?: Date;
|
|
65
|
+
/** Two digits distinguishing two files of the same second. Default `01`. */
|
|
66
|
+
sequence?: number;
|
|
67
|
+
}
|
|
68
|
+
/** Something that does not add up, said in the terms of this format. */
|
|
69
|
+
export interface Violation {
|
|
70
|
+
code: string;
|
|
71
|
+
message: string;
|
|
72
|
+
vatNumber?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface EcdfFile {
|
|
75
|
+
/** The XML, as text. */
|
|
76
|
+
file: string;
|
|
77
|
+
/** `<FileReference>.xml`, which eCDF requires the file to be called. */
|
|
78
|
+
filename: string;
|
|
79
|
+
/** The reference itself, which also identifies the filing. */
|
|
80
|
+
fileReference: string;
|
|
81
|
+
/** The declaration types written, in order. */
|
|
82
|
+
declarations: string[];
|
|
83
|
+
/** Everything that could not be put in the file, and why. */
|
|
84
|
+
violations: Violation[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The four forms that carry a recapitulative statement, by what they carry and
|
|
88
|
+
* how often it is filed. `LIC` is *livraisons intracommunautaires* and holds
|
|
89
|
+
* goods and triangular operations in two tables of its own; `PSI` is
|
|
90
|
+
* *prestations de services intracommunautaires*.
|
|
91
|
+
*/
|
|
92
|
+
export declare const FORMS: Readonly<Record<string, Readonly<Record<Cadence, string>>>>;
|
|
93
|
+
/** The eCDF forms are in euro; they carry no currency field. */
|
|
94
|
+
export declare const CURRENCY = "EUR";
|
|
95
|
+
export declare class EcdfError extends Error {
|
|
96
|
+
name: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* An amount as eCDF writes one: a comma, at most two decimals, and no
|
|
100
|
+
* thousands separator. A point is refused, including as a grouping mark.
|
|
101
|
+
*/
|
|
102
|
+
export declare function formatAmount(value: string | number): string;
|
|
103
|
+
/**
|
|
104
|
+
* `<prefix>X<yyyymmdd>T<hhmmss><nn>`, which is both the identifier of the
|
|
105
|
+
* filing and the name the file has to have on disk.
|
|
106
|
+
*/
|
|
107
|
+
export declare function fileReference(prefix: string, at: Date, sequence: number): string;
|
|
108
|
+
/**
|
|
109
|
+
* Writes the file.
|
|
110
|
+
*
|
|
111
|
+
* Two declarations come out of one call where the period carries both natures:
|
|
112
|
+
* Luxembourg files goods and services on separate forms, and the envelope is
|
|
113
|
+
* made to hold several. A nature with nothing in it produces no declaration,
|
|
114
|
+
* because eCDF refuses a statement whose every table is empty.
|
|
115
|
+
*
|
|
116
|
+
* Refuses, by exception, what would make the whole file meaningless: a period
|
|
117
|
+
* that does not fit the cadence, or a party the schema's own patterns reject.
|
|
118
|
+
* Everything wrong with one line comes back in `violations`.
|
|
119
|
+
*/
|
|
120
|
+
export declare function generateEcdf(rows: StatementRow[], options: EcdfOptions): EcdfFile;
|
|
121
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kFAAkF;IAClF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACjC;AAED,+EAA+E;AAC/E,MAAM,WAAW,KAAK;IACpB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kEAAkE;AAClE,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,KAAK,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9B,8DAA8D;IAC9D,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,6DAA6D;IAC7D,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAG5E,CAAC;AAEH,gEAAgE;AAChE,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAU9B,qBAAa,SAAU,SAAQ,KAAK;IACzB,IAAI,SAAe;CAC7B;AAUD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAI3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMhF;AA6ID;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,QAAQ,CAsFjF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Luxembourg **eCDF** interface file, version 2.0.
|
|
3
|
+
*
|
|
4
|
+
* eCDF is not a form: it is the envelope the CTIE publishes, one XML file that
|
|
5
|
+
* carries any number of declarations, each named by a `type` and filled with
|
|
6
|
+
* numbered fields. This package writes that envelope, and knows the four types
|
|
7
|
+
* that carry a recapitulative statement — supplies of goods and supplies of
|
|
8
|
+
* services, each monthly or quarterly.
|
|
9
|
+
*
|
|
10
|
+
* Give it the rows of a statement and it writes the file, its name, and the
|
|
11
|
+
* list of what could not be put in it.
|
|
12
|
+
*
|
|
13
|
+
* Sources are in the README. This package depends on nothing, reads no
|
|
14
|
+
* database and knows no accounting.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The four forms that carry a recapitulative statement, by what they carry and
|
|
18
|
+
* how often it is filed. `LIC` is *livraisons intracommunautaires* and holds
|
|
19
|
+
* goods and triangular operations in two tables of its own; `PSI` is
|
|
20
|
+
* *prestations de services intracommunautaires*.
|
|
21
|
+
*/
|
|
22
|
+
export const FORMS = Object.freeze({
|
|
23
|
+
goods: Object.freeze({ month: 'TVA_LICM', quarter: 'TVA_LICT' }),
|
|
24
|
+
services: Object.freeze({ month: 'TVA_PSIM', quarter: 'TVA_PSIT' }),
|
|
25
|
+
});
|
|
26
|
+
/** The eCDF forms are in euro; they carry no currency field. */
|
|
27
|
+
export const CURRENCY = 'EUR';
|
|
28
|
+
const NAMESPACE = 'http://www.ctie.etat.lu/2011/ecdf';
|
|
29
|
+
/** The schema's own patterns, so a refusal here is the one eCDF would give. */
|
|
30
|
+
const PREFIX = /^[0-9A-Z]{6}$/;
|
|
31
|
+
const MATR = /^[0-9]{11,13}$/;
|
|
32
|
+
const VAT = /^(?:[0-9]{8}|NE)$/;
|
|
33
|
+
const RCS = /^(?:[A-Z][^\s]{0,6}|NE)$/;
|
|
34
|
+
export class EcdfError extends Error {
|
|
35
|
+
name = 'EcdfError';
|
|
36
|
+
}
|
|
37
|
+
function escapeXml(value) {
|
|
38
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
39
|
+
}
|
|
40
|
+
function normalise(value) {
|
|
41
|
+
return value.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* An amount as eCDF writes one: a comma, at most two decimals, and no
|
|
45
|
+
* thousands separator. A point is refused, including as a grouping mark.
|
|
46
|
+
*/
|
|
47
|
+
export function formatAmount(value) {
|
|
48
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
49
|
+
if (!Number.isFinite(n))
|
|
50
|
+
throw new EcdfError(`invalid amount: ${String(value)}`);
|
|
51
|
+
return n.toFixed(2).replace('.', ',');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `<prefix>X<yyyymmdd>T<hhmmss><nn>`, which is both the identifier of the
|
|
55
|
+
* filing and the name the file has to have on disk.
|
|
56
|
+
*/
|
|
57
|
+
export function fileReference(prefix, at, sequence) {
|
|
58
|
+
const two = (n) => String(n).padStart(2, '0');
|
|
59
|
+
const stamp = `${at.getUTCFullYear()}${two(at.getUTCMonth() + 1)}${two(at.getUTCDate())}` +
|
|
60
|
+
`T${two(at.getUTCHours())}${two(at.getUTCMinutes())}${two(at.getUTCSeconds())}`;
|
|
61
|
+
return `${prefix}X${stamp}${two(sequence)}`;
|
|
62
|
+
}
|
|
63
|
+
function sortRows(rows, declarerVat) {
|
|
64
|
+
const out = { goods: [], triangular: [], services: [], violations: [] };
|
|
65
|
+
for (const row of rows) {
|
|
66
|
+
const country = row.vat_country === null ? '' : normalise(row.vat_country);
|
|
67
|
+
const number = row.vat_number === null ? '' : normalise(row.vat_number);
|
|
68
|
+
const named = row.contact_names?.join(', ');
|
|
69
|
+
const about = named === undefined || named === '' ? `${country}${number}` : named;
|
|
70
|
+
if (row.issue !== null && row.issue !== undefined && row.issue !== '') {
|
|
71
|
+
out.violations.push({
|
|
72
|
+
code: row.issue,
|
|
73
|
+
message: `${about}: this supply cannot be declared — ${row.issue.replace(/_/g, ' ')}.`,
|
|
74
|
+
...(number === '' ? {} : { vatNumber: `${country}${number}` }),
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const bucket = row.nature === 'goods' ? out.goods : row.nature === 'triangular' ? out.triangular : row.nature === 'services' ? out.services : null;
|
|
79
|
+
if (bucket === null) {
|
|
80
|
+
out.violations.push({
|
|
81
|
+
code: 'unknown_nature',
|
|
82
|
+
message: `${about}: the forms carry supplies of goods, triangular operations and services, and nothing called "${row.nature}".`,
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (row.currency_code.toUpperCase() !== CURRENCY) {
|
|
87
|
+
out.violations.push({
|
|
88
|
+
code: 'wrong_currency',
|
|
89
|
+
message: `${about}: the eCDF forms are filed in ${CURRENCY} and this line is in ${row.currency_code}.`,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (country === '' || number === '') {
|
|
94
|
+
out.violations.push({
|
|
95
|
+
code: 'no_vat_number',
|
|
96
|
+
message: `${about}: a line of the statement is identified by the customer's VAT number and this one has none.`,
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// The declarer's own number carries no country, so the comparison is on
|
|
101
|
+
// the number: a customer identified under the same registration as the
|
|
102
|
+
// declarer is not a customer in another Member State.
|
|
103
|
+
if (number === declarerVat) {
|
|
104
|
+
out.violations.push({
|
|
105
|
+
code: 'vat_number_is_the_declarer',
|
|
106
|
+
message: `${about}: the statement carries supplies to other Member States, and this is the declarer's own number.`,
|
|
107
|
+
vatNumber: `${country}${number}`,
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const amount = formatAmount(row.amount);
|
|
112
|
+
if (Number(amount.replace(',', '.')) === 0) {
|
|
113
|
+
out.violations.push({
|
|
114
|
+
code: 'nil_amount',
|
|
115
|
+
message: `${about}: a line whose amount is nil is not carried.`,
|
|
116
|
+
vatNumber: `${country}${number}`,
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
bucket.push({ country, number, amount });
|
|
121
|
+
}
|
|
122
|
+
const order = (a, b) => `${a.country}${a.number}`.localeCompare(`${b.country}${b.number}`);
|
|
123
|
+
out.goods.sort(order);
|
|
124
|
+
out.triangular.sort(order);
|
|
125
|
+
out.services.sort(order);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
function total(clients) {
|
|
129
|
+
return formatAmount(clients.reduce((sum, client) => sum + Number(client.amount.replace(',', '.')), 0));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* One `Table` of a form: a line per client, with the field identifiers that
|
|
133
|
+
* table uses. A table with no line is left out — the total beside it stays, at
|
|
134
|
+
* zero — which is what the published example does.
|
|
135
|
+
*/
|
|
136
|
+
function table(clients, ids, indent) {
|
|
137
|
+
if (clients.length === 0)
|
|
138
|
+
return [];
|
|
139
|
+
const [country, number, amount] = ids;
|
|
140
|
+
const lines = [`${indent}<Table>`];
|
|
141
|
+
for (const [index, client] of clients.entries()) {
|
|
142
|
+
lines.push(`${indent} <Line num="${index + 1}">`);
|
|
143
|
+
lines.push(`${indent} <TextField id="${country}">${escapeXml(client.country)}</TextField>`);
|
|
144
|
+
lines.push(`${indent} <TextField id="${number}">${escapeXml(client.number)}</TextField>`);
|
|
145
|
+
lines.push(`${indent} <NumericField id="${amount}">${client.amount}</NumericField>`);
|
|
146
|
+
lines.push(`${indent} </Line>`);
|
|
147
|
+
}
|
|
148
|
+
lines.push(`${indent}</Table>`);
|
|
149
|
+
return lines;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The three identifiers of a party. `Agent` closes on itself; `Declarer` does
|
|
153
|
+
* not, because the declarations it files are its own children.
|
|
154
|
+
*/
|
|
155
|
+
function partyOpening(tag, value, indent) {
|
|
156
|
+
return [
|
|
157
|
+
`${indent}<${tag}>`,
|
|
158
|
+
`${indent} <MatrNbr>${escapeXml(value.matrNbr)}</MatrNbr>`,
|
|
159
|
+
`${indent} <RCSNbr>${escapeXml(value.rcsNbr)}</RCSNbr>`,
|
|
160
|
+
`${indent} <VATNbr>${escapeXml(value.vatNbr)}</VATNbr>`,
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
function checkParty(name, value) {
|
|
164
|
+
if (!MATR.test(value.matrNbr)) {
|
|
165
|
+
throw new EcdfError(`${name}: MatrNbr is eleven to thirteen digits, got "${value.matrNbr}"`);
|
|
166
|
+
}
|
|
167
|
+
if (!VAT.test(value.vatNbr)) {
|
|
168
|
+
throw new EcdfError(`${name}: VATNbr is eight digits without LU, or NE, got "${value.vatNbr}"`);
|
|
169
|
+
}
|
|
170
|
+
if (!RCS.test(value.rcsNbr)) {
|
|
171
|
+
throw new EcdfError(`${name}: RCSNbr is at most seven characters, or NE, got "${value.rcsNbr}"`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Writes the file.
|
|
176
|
+
*
|
|
177
|
+
* Two declarations come out of one call where the period carries both natures:
|
|
178
|
+
* Luxembourg files goods and services on separate forms, and the envelope is
|
|
179
|
+
* made to hold several. A nature with nothing in it produces no declaration,
|
|
180
|
+
* because eCDF refuses a statement whose every table is empty.
|
|
181
|
+
*
|
|
182
|
+
* Refuses, by exception, what would make the whole file meaningless: a period
|
|
183
|
+
* that does not fit the cadence, or a party the schema's own patterns reject.
|
|
184
|
+
* Everything wrong with one line comes back in `violations`.
|
|
185
|
+
*/
|
|
186
|
+
export function generateEcdf(rows, options) {
|
|
187
|
+
const prefix = options.prefix.toUpperCase();
|
|
188
|
+
if (!PREFIX.test(prefix)) {
|
|
189
|
+
throw new EcdfError(`the eCDF prefix is six characters, digits and capitals: "${options.prefix}"`);
|
|
190
|
+
}
|
|
191
|
+
if (options.interfaceId === '' || options.interfaceId.length > 20) {
|
|
192
|
+
throw new EcdfError('the interface identifier is one to twenty characters, attributed by the CTIE');
|
|
193
|
+
}
|
|
194
|
+
checkParty('Agent', options.agent);
|
|
195
|
+
checkParty('Declarer', options.declarer);
|
|
196
|
+
const last = options.cadence === 'month' ? 12 : 4;
|
|
197
|
+
if (!Number.isInteger(options.period) || options.period < 1 || options.period > last) {
|
|
198
|
+
throw new EcdfError(`a ${options.cadence} is 1 to ${last}, got ${options.period}`);
|
|
199
|
+
}
|
|
200
|
+
if (!Number.isInteger(options.year) || options.year < 2000 || options.year > 9999) {
|
|
201
|
+
throw new EcdfError(`year out of range: ${options.year}`);
|
|
202
|
+
}
|
|
203
|
+
const sorted = sortRows(rows, normalise(options.declarer.vatNbr));
|
|
204
|
+
const reference = fileReference(prefix, options.createdAt ?? new Date(), options.sequence ?? 1);
|
|
205
|
+
const language = options.language ?? 'FR';
|
|
206
|
+
const parts = [];
|
|
207
|
+
parts.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
208
|
+
parts.push(`<eCDFDeclarations xmlns="${NAMESPACE}">`);
|
|
209
|
+
parts.push(` <FileReference>${reference}</FileReference>`);
|
|
210
|
+
parts.push(' <eCDFFileVersion>2.0</eCDFFileVersion>');
|
|
211
|
+
parts.push(` <Interface>${escapeXml(options.interfaceId)}</Interface>`);
|
|
212
|
+
parts.push(...partyOpening('Agent', options.agent, ' '));
|
|
213
|
+
parts.push(' </Agent>');
|
|
214
|
+
parts.push(' <Declarations>');
|
|
215
|
+
parts.push(...partyOpening('Declarer', options.declarer, ' '));
|
|
216
|
+
const written = [];
|
|
217
|
+
// The goods form: state I is the supplies, state II the triangular
|
|
218
|
+
// operations, and the totals print above both.
|
|
219
|
+
if (sorted.goods.length > 0 || sorted.triangular.length > 0) {
|
|
220
|
+
const type = FORMS['goods']?.[options.cadence];
|
|
221
|
+
written.push(type);
|
|
222
|
+
parts.push(` <Declaration type="${type}" model="1" language="${language}">`);
|
|
223
|
+
parts.push(` <Year>${options.year}</Year>`);
|
|
224
|
+
parts.push(` <Period>${options.period}</Period>`);
|
|
225
|
+
parts.push(' <FormData>');
|
|
226
|
+
parts.push(` <NumericField id="04">${total(sorted.goods)}</NumericField>`);
|
|
227
|
+
parts.push(` <NumericField id="08">${total(sorted.triangular)}</NumericField>`);
|
|
228
|
+
parts.push(` <NumericField id="16">${formatAmount(0)}</NumericField>`);
|
|
229
|
+
parts.push(...table(sorted.goods, ['01', '02', '03'], ' '));
|
|
230
|
+
parts.push(...table(sorted.triangular, ['05', '06', '07'], ' '));
|
|
231
|
+
parts.push(' </FormData>');
|
|
232
|
+
parts.push(' </Declaration>');
|
|
233
|
+
}
|
|
234
|
+
if (sorted.services.length > 0) {
|
|
235
|
+
const type = FORMS['services']?.[options.cadence];
|
|
236
|
+
written.push(type);
|
|
237
|
+
parts.push(` <Declaration type="${type}" model="1" language="${language}">`);
|
|
238
|
+
parts.push(` <Year>${options.year}</Year>`);
|
|
239
|
+
parts.push(` <Period>${options.period}</Period>`);
|
|
240
|
+
parts.push(' <FormData>');
|
|
241
|
+
parts.push(` <NumericField id="04">${total(sorted.services)}</NumericField>`);
|
|
242
|
+
parts.push(` <NumericField id="16">${formatAmount(0)}</NumericField>`);
|
|
243
|
+
parts.push(...table(sorted.services, ['01', '02', '03'], ' '));
|
|
244
|
+
parts.push(' </FormData>');
|
|
245
|
+
parts.push(' </Declaration>');
|
|
246
|
+
}
|
|
247
|
+
parts.push(' </Declarer>');
|
|
248
|
+
parts.push(' </Declarations>');
|
|
249
|
+
parts.push('</eCDFDeclarations>');
|
|
250
|
+
if (written.length === 0) {
|
|
251
|
+
sorted.violations.push({
|
|
252
|
+
code: 'nothing_to_declare',
|
|
253
|
+
message: 'eCDF refuses a statement whose tables are all empty, so this file carries no declaration. Do not file it.',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
file: `${parts.join('\n')}\n`,
|
|
258
|
+
filename: `${reference}.xml`,
|
|
259
|
+
fileReference: reference,
|
|
260
|
+
declarations: written,
|
|
261
|
+
violations: sorted.violations,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ekwo-ai/ecdf",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "The Luxembourg eCDF XML interface file (v2.0) in TypeScript, with the four recapitulative statement forms it carries. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ecdf",
|
|
7
|
+
"b2g",
|
|
8
|
+
"luxembourg",
|
|
9
|
+
"ctie",
|
|
10
|
+
"aed",
|
|
11
|
+
"ec-sales-list",
|
|
12
|
+
"etat-recapitulatif",
|
|
13
|
+
"vat",
|
|
14
|
+
"xml"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Ekwo AI",
|
|
18
|
+
"homepage": "https://github.com/Ekwo-ai/ekwo-os/tree/main/packages/formats/ecdf#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Ekwo-ai/ekwo-os.git",
|
|
22
|
+
"directory": "packages/formats/ecdf"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Ekwo-ai/ekwo-os/issues"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"main": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"import": "./dist/index.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.build.json",
|
|
47
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^24.0.0",
|
|
52
|
+
"typescript": "^5.9.0",
|
|
53
|
+
"vitest": "^3.2.0"
|
|
54
|
+
}
|
|
55
|
+
}
|