@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 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.
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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
+ }