@ekwo-ai/vd 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 +122 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +204 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -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,122 @@
|
|
|
1
|
+
# `@ekwo-ai/vd`
|
|
2
|
+
|
|
3
|
+
The Estonian **form VD** — *ühendusesisese käibe aruanne*, the report of
|
|
4
|
+
intra-Community turnover — in TypeScript, with no dependencies.
|
|
5
|
+
|
|
6
|
+
Give it the rows of a recapitulative statement and it gives you the XML the
|
|
7
|
+
e-MTA loads, a name for it, and the list of what could not be put in it.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { generateVd } from '@ekwo-ai/vd';
|
|
11
|
+
|
|
12
|
+
const { file, filename, violations } = generateVd(rows, {
|
|
13
|
+
registryCode: '19999999',
|
|
14
|
+
year: 2026,
|
|
15
|
+
month: 2,
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`StatementRow` is the row shape of the `ec_sales_list(company, from, to)`
|
|
20
|
+
function of [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os), declared here so
|
|
21
|
+
that nothing is imported from it. Any book-keeping system that can produce
|
|
22
|
+
those columns can use this package; it reads no database and knows no
|
|
23
|
+
accounting.
|
|
24
|
+
|
|
25
|
+
## One line per acquirer, not one per nature
|
|
26
|
+
|
|
27
|
+
This is the thing to know before anything else. Form VD prints one row per
|
|
28
|
+
customer with three amount columns beside it, so an acquirer who was supplied
|
|
29
|
+
goods and services appears **once**, with two amounts. The instructions say it
|
|
30
|
+
in as many words: *not a single VAT identification number shall recur in the
|
|
31
|
+
column*, and the loader rejects a duplicate by row number.
|
|
32
|
+
|
|
33
|
+
Every other recapitulative statement in Europe does the opposite — the Belgian
|
|
34
|
+
listing prints the same number three times with three operation codes. So this
|
|
35
|
+
package merges the rows it is given, and a package for another format must not.
|
|
36
|
+
|
|
37
|
+
| Column | What it holds | Element |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| 1 | Country code of the acquirer | `riik`, an attribute of `kmkrKood` |
|
|
40
|
+
| 2 | VAT number, **without the country prefix** | the text of `kmkrKood` |
|
|
41
|
+
| 3 | Taxable value of the goods | `kaup` |
|
|
42
|
+
| 4 | Value of triangular transactions | `kolmnurktehing` |
|
|
43
|
+
| 5 | Taxable value of the services | `teenusteMyyk` |
|
|
44
|
+
|
|
45
|
+
A row carries at least one of the three amounts, which this package enforces by
|
|
46
|
+
leaving out an acquirer whose every amount rounds to nothing, and saying so.
|
|
47
|
+
|
|
48
|
+
## The specification this was written from
|
|
49
|
+
|
|
50
|
+
| What | Where |
|
|
51
|
+
|---|---|
|
|
52
|
+
| The technical section that carries them all | <https://www.emta.ee/ariklient/e-teenused-koolitused/e-teenuste-kasutamine/teenuste-tehniline-info#vd-aruanne> |
|
|
53
|
+
| The schema, served with an `.xml` extension | <https://www.emta.ee/sites/default/files/documents/2021-06/vorm_vd_xsd_2011.xml> |
|
|
54
|
+
| Description of the XML file | <https://www.emta.ee/sites/default/files/documents/2021-06/vorm_vd_xml_kirjeldus_2011%281%29.rtf> |
|
|
55
|
+
| A complete example | <https://www.emta.ee/sites/default/files/documents/2023-04/vorm_vd_xml_2011.xml> |
|
|
56
|
+
| The error messages the loader gives | <https://www.emta.ee/sites/default/files/documents/2023-01/vd_vdp_veateated_12012023.xlsx> |
|
|
57
|
+
| The regulation behind the form, consolidated | <https://www.riigiteataja.ee/akt/107012021006> |
|
|
58
|
+
|
|
59
|
+
The output of this package was checked against that schema with `xmllint`
|
|
60
|
+
before it was committed. The schema is not shipped here — it belongs to the
|
|
61
|
+
administration and it moves — so the test suite proves the shape of the
|
|
62
|
+
document and not its validity.
|
|
63
|
+
|
|
64
|
+
Two things worth knowing about those documents:
|
|
65
|
+
|
|
66
|
+
- **The specification is only on the Estonian page.** The English technical
|
|
67
|
+
page carries no VD section at all; its VAT page points at the KMD instead.
|
|
68
|
+
- **`elementFormDefault` is `unqualified`**, so only the root carries the
|
|
69
|
+
namespace prefix and its children are bare. The published example shows it,
|
|
70
|
+
and this package writes it that way.
|
|
71
|
+
|
|
72
|
+
## Amounts
|
|
73
|
+
|
|
74
|
+
**Whole euros**, rounded away from zero at the half, with no decimals and no
|
|
75
|
+
separator — the form says `täiseurodes` under each column, the schema types the
|
|
76
|
+
three as `xs:integer`, and the loader refuses `1.5` by name. **Negative values
|
|
77
|
+
are allowed**: a cancelled invoice or a credit note is reported on form VD for
|
|
78
|
+
the period of the credit note, with a minus sign.
|
|
79
|
+
|
|
80
|
+
## What it refuses and what it reports
|
|
81
|
+
|
|
82
|
+
An exception, because it would make the whole file meaningless: a month outside
|
|
83
|
+
1 to 12, or a registry code longer than the eleven characters the schema holds.
|
|
84
|
+
|
|
85
|
+
A violation, because one line is wrong and the rest of the file is not:
|
|
86
|
+
whatever the producer flagged, `unknown_nature`, `wrong_currency`,
|
|
87
|
+
`no_vat_number`, `country_code_length`, `vat_number_is_the_declarer`,
|
|
88
|
+
`vat_number_shape` — the schema allows twelve characters of letters, digits,
|
|
89
|
+
`+` and `*` — and `nil_amounts`.
|
|
90
|
+
|
|
91
|
+
The loader also validates the structure of each number against the Member State
|
|
92
|
+
it claims, which this package does not: the table of per-country shapes is
|
|
93
|
+
published with the form and belongs to whoever wants that check, not to a file
|
|
94
|
+
writer that would then hold a list of Member States.
|
|
95
|
+
|
|
96
|
+
## The file name
|
|
97
|
+
|
|
98
|
+
**The administration imposes none.** The technical description prescribes no
|
|
99
|
+
convention: the e-MTA checks the header of the file — declarer code, year,
|
|
100
|
+
month — against the report it is being loaded into, and nothing else.
|
|
101
|
+
`vdFileName` is a name of ours, and a caller is free to pick another.
|
|
102
|
+
|
|
103
|
+
## Two things about loading it
|
|
104
|
+
|
|
105
|
+
- **A load replaces everything already in the report**, whether it was loaded
|
|
106
|
+
before or typed in, and only works on a report that has not been confirmed.
|
|
107
|
+
- **There is no nil report.** Where there was no intra-Community supply, form VD
|
|
108
|
+
is not filed at all.
|
|
109
|
+
|
|
110
|
+
## What is out of scope, on purpose
|
|
111
|
+
|
|
112
|
+
- **Form VDP, the corrections.** It exists, it is described, and it has **no
|
|
113
|
+
file format**: the e-MTA offers loading from a file for VD and only manual
|
|
114
|
+
entry for VDP. Nothing here can be written against a specification that does
|
|
115
|
+
not exist.
|
|
116
|
+
- **Call-off stock**, column 6 of the form. It is a separate schema with its own
|
|
117
|
+
namespace, `VD_NV_toimingud`, and it reports movements of stock rather than
|
|
118
|
+
supplies.
|
|
119
|
+
- **Filing.** This package writes bytes; the e-MTA screen is what turns them
|
|
120
|
+
into a report, and confirming it is irreversible.
|
|
121
|
+
|
|
122
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Estonian **form VD** — *ühendusesisese käibe aruanne*, the report of
|
|
3
|
+
* intra-Community turnover — as the e-MTA loads it from a file.
|
|
4
|
+
*
|
|
5
|
+
* One XML document, `VD_deklaratsioon`, described by the schema the tax and
|
|
6
|
+
* customs board publishes beside the form. Give it the rows of a
|
|
7
|
+
* recapitulative statement and it writes the file, a name for it, and the list
|
|
8
|
+
* of what could not be put in it.
|
|
9
|
+
*
|
|
10
|
+
* **One line per customer, not one per nature.** Estonia is the odd one out:
|
|
11
|
+
* where the Belgian listing prints the same number three times with three
|
|
12
|
+
* codes, form VD prints it once with three amount columns. So this package
|
|
13
|
+
* merges the rows it is given, which the others must not do.
|
|
14
|
+
*
|
|
15
|
+
* Sources are in the README. This package depends on nothing, reads no
|
|
16
|
+
* database and knows no accounting.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* One line of a recapitulative statement, as `ec_sales_list(company, from,
|
|
20
|
+
* to)` of [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os) returns it. Declared
|
|
21
|
+
* here so that nothing is imported from it.
|
|
22
|
+
*/
|
|
23
|
+
export interface StatementRow {
|
|
24
|
+
/** Country of the customer's VAT identification number, ISO 3166-1 alpha-2. */
|
|
25
|
+
vat_country: string | null;
|
|
26
|
+
/** The number itself, without its country prefix — which is how form VD wants it. */
|
|
27
|
+
vat_number: string | null;
|
|
28
|
+
/** `goods`, `services`, `triangular`. */
|
|
29
|
+
nature: string;
|
|
30
|
+
/** Taxable amount, credit notes already deducted. */
|
|
31
|
+
amount: string | number;
|
|
32
|
+
/** ISO 4217 code the amount is in. */
|
|
33
|
+
currency_code: string;
|
|
34
|
+
/** Why this line cannot be declared as it stands, or null when it can. */
|
|
35
|
+
issue?: string | null;
|
|
36
|
+
/** Names of the customers behind the line, for a message a human reads. */
|
|
37
|
+
contact_names?: string[] | null;
|
|
38
|
+
}
|
|
39
|
+
export interface VdOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The declarer's registry code, at most eleven characters. The e-MTA checks
|
|
42
|
+
* it against the client the report is opened for.
|
|
43
|
+
*/
|
|
44
|
+
registryCode: string;
|
|
45
|
+
year: number;
|
|
46
|
+
/** 1 to 12. Form VD is filed monthly, with the VAT return. */
|
|
47
|
+
month: number;
|
|
48
|
+
}
|
|
49
|
+
/** Something that does not add up, said in the terms of this format. */
|
|
50
|
+
export interface Violation {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
vatNumber?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface Vd {
|
|
56
|
+
/** The XML, as text. */
|
|
57
|
+
file: string;
|
|
58
|
+
/** A name for it. See the README: the administration imposes none. */
|
|
59
|
+
filename: string;
|
|
60
|
+
/** Everything that could not be put in the file, and why. */
|
|
61
|
+
violations: Violation[];
|
|
62
|
+
}
|
|
63
|
+
/** The three amount columns of the form, by what the statement calls each. */
|
|
64
|
+
export declare const COLUMNS: Readonly<Record<string, string>>;
|
|
65
|
+
/** Form VD is in euro; it carries no currency field. */
|
|
66
|
+
export declare const CURRENCY = "EUR";
|
|
67
|
+
export declare class VdError extends Error {
|
|
68
|
+
name: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whole euros, rounded away from zero at the half.
|
|
72
|
+
*
|
|
73
|
+
* The form says `täiseurodes` under each of its three amount columns, the XML
|
|
74
|
+
* description types them `Number(20)`, and the published error messages refuse
|
|
75
|
+
* `1.5` by name. `Math.round` would send −0,5 to −0, which is the wrong
|
|
76
|
+
* direction for a credit note, so the sign is taken out and put back.
|
|
77
|
+
*/
|
|
78
|
+
export declare function roundToEuro(value: string | number): number;
|
|
79
|
+
/**
|
|
80
|
+
* A name for the file.
|
|
81
|
+
*
|
|
82
|
+
* **The administration imposes none.** The technical description prescribes no
|
|
83
|
+
* naming convention: the e-MTA checks the header of the file against the report
|
|
84
|
+
* it is being loaded into, and nothing else. So this is a name of ours, and a
|
|
85
|
+
* caller is free to pick another.
|
|
86
|
+
*/
|
|
87
|
+
export declare function vdFileName(registryCode: string, year: number, month: number): string;
|
|
88
|
+
/**
|
|
89
|
+
* Writes the report.
|
|
90
|
+
*
|
|
91
|
+
* Refuses, by exception, what would make the whole file meaningless: a month
|
|
92
|
+
* outside the year, or a registry code the schema cannot hold. Everything
|
|
93
|
+
* wrong with one line comes back in `violations`.
|
|
94
|
+
*
|
|
95
|
+
* Note for whoever uses the result: loading a file into the e-MTA **replaces
|
|
96
|
+
* every row already there**, whether it was loaded or typed in, and only works
|
|
97
|
+
* on a report that has not been confirmed.
|
|
98
|
+
*/
|
|
99
|
+
export declare function generateVd(rows: StatementRow[], options: VdOptions): Vd;
|
|
100
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qFAAqF;IACrF,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,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;CACf;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,EAAE;IACjB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,8EAA8E;AAC9E,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAInD,CAAC;AAEH,wDAAwD;AACxD,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAO9B,qBAAa,OAAQ,SAAQ,KAAK;IACvB,IAAI,SAAa;CAC3B;AAWD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAI1D;AA4GD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,SAAS,GAAG,EAAE,CA6CvE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Estonian **form VD** — *ühendusesisese käibe aruanne*, the report of
|
|
3
|
+
* intra-Community turnover — as the e-MTA loads it from a file.
|
|
4
|
+
*
|
|
5
|
+
* One XML document, `VD_deklaratsioon`, described by the schema the tax and
|
|
6
|
+
* customs board publishes beside the form. Give it the rows of a
|
|
7
|
+
* recapitulative statement and it writes the file, a name for it, and the list
|
|
8
|
+
* of what could not be put in it.
|
|
9
|
+
*
|
|
10
|
+
* **One line per customer, not one per nature.** Estonia is the odd one out:
|
|
11
|
+
* where the Belgian listing prints the same number three times with three
|
|
12
|
+
* codes, form VD prints it once with three amount columns. So this package
|
|
13
|
+
* merges the rows it is given, which the others must not do.
|
|
14
|
+
*
|
|
15
|
+
* Sources are in the README. This package depends on nothing, reads no
|
|
16
|
+
* database and knows no accounting.
|
|
17
|
+
*/
|
|
18
|
+
/** The three amount columns of the form, by what the statement calls each. */
|
|
19
|
+
export const COLUMNS = Object.freeze({
|
|
20
|
+
goods: 'kaup',
|
|
21
|
+
triangular: 'kolmnurktehing',
|
|
22
|
+
services: 'teenusteMyyk',
|
|
23
|
+
});
|
|
24
|
+
/** Form VD is in euro; it carries no currency field. */
|
|
25
|
+
export const CURRENCY = 'EUR';
|
|
26
|
+
const NAMESPACE = 'http://www.emta.ee/VD/xsd/webimport/v1';
|
|
27
|
+
/** The schema's own pattern for a customer's registration number. */
|
|
28
|
+
const CUSTOMER_NUMBER = /^[A-Za-z0-9+*]{1,12}$/;
|
|
29
|
+
export class VdError extends Error {
|
|
30
|
+
name = 'VdError';
|
|
31
|
+
}
|
|
32
|
+
function escapeXml(value) {
|
|
33
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
34
|
+
}
|
|
35
|
+
function normalise(value) {
|
|
36
|
+
// `+` and `*` are part of the alphabet the schema allows, so they survive.
|
|
37
|
+
return value.replace(/[^A-Za-z0-9+*]/g, '').toUpperCase();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whole euros, rounded away from zero at the half.
|
|
41
|
+
*
|
|
42
|
+
* The form says `täiseurodes` under each of its three amount columns, the XML
|
|
43
|
+
* description types them `Number(20)`, and the published error messages refuse
|
|
44
|
+
* `1.5` by name. `Math.round` would send −0,5 to −0, which is the wrong
|
|
45
|
+
* direction for a credit note, so the sign is taken out and put back.
|
|
46
|
+
*/
|
|
47
|
+
export function roundToEuro(value) {
|
|
48
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
49
|
+
if (!Number.isFinite(n))
|
|
50
|
+
throw new VdError(`invalid amount: ${String(value)}`);
|
|
51
|
+
return Math.sign(n) * Math.round(Math.abs(n));
|
|
52
|
+
}
|
|
53
|
+
function linesOf(rows, declarer) {
|
|
54
|
+
const byCustomer = new Map();
|
|
55
|
+
const violations = [];
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
const country = row.vat_country === null ? '' : normalise(row.vat_country);
|
|
58
|
+
const number = row.vat_number === null ? '' : normalise(row.vat_number);
|
|
59
|
+
const named = row.contact_names?.join(', ');
|
|
60
|
+
const about = named === undefined || named === '' ? `${country}${number}` : named;
|
|
61
|
+
if (row.issue !== null && row.issue !== undefined && row.issue !== '') {
|
|
62
|
+
violations.push({
|
|
63
|
+
code: row.issue,
|
|
64
|
+
message: `${about}: this supply cannot be reported — ${row.issue.replace(/_/g, ' ')}.`,
|
|
65
|
+
...(number === '' ? {} : { vatNumber: `${country}${number}` }),
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const column = COLUMNS[row.nature];
|
|
70
|
+
if (column === undefined) {
|
|
71
|
+
violations.push({
|
|
72
|
+
code: 'unknown_nature',
|
|
73
|
+
message: `${about}: form VD has a column for goods, triangular operations and services, and none for "${row.nature}".`,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (row.currency_code.toUpperCase() !== CURRENCY) {
|
|
78
|
+
violations.push({
|
|
79
|
+
code: 'wrong_currency',
|
|
80
|
+
message: `${about}: form VD is filed in ${CURRENCY} and this line is in ${row.currency_code}.`,
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (country === '' || number === '') {
|
|
85
|
+
violations.push({
|
|
86
|
+
code: 'no_vat_number',
|
|
87
|
+
message: `${about}: a row of form VD is identified by the acquirer's VAT number and this one has none.`,
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (country.length !== 2) {
|
|
92
|
+
violations.push({
|
|
93
|
+
code: 'country_code_length',
|
|
94
|
+
message: `${about}: column 1 of form VD holds a two-character country code and this one is "${country}".`,
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (number === declarer) {
|
|
99
|
+
violations.push({
|
|
100
|
+
code: 'vat_number_is_the_declarer',
|
|
101
|
+
message: `${about}: form VD reports supplies to other Member States, and this is the declarer's own number.`,
|
|
102
|
+
vatNumber: `${country}${number}`,
|
|
103
|
+
});
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!CUSTOMER_NUMBER.test(number)) {
|
|
107
|
+
violations.push({
|
|
108
|
+
code: 'vat_number_shape',
|
|
109
|
+
message: `${about}: the schema holds twelve characters of letters, digits, + and * for a number, and this one is "${number}".`,
|
|
110
|
+
vatNumber: `${country}${number}`,
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const value = roundToEuro(row.amount);
|
|
115
|
+
const key = `${country}|${number}`;
|
|
116
|
+
// The instructions are explicit: a VAT number may not appear twice in the
|
|
117
|
+
// column, so goods and services for the same acquirer are one row with two
|
|
118
|
+
// amounts. That is the aggregation this format asks for and no other does.
|
|
119
|
+
const held = byCustomer.get(key) ?? { country, number, amounts: new Map() };
|
|
120
|
+
held.amounts.set(column, (held.amounts.get(column) ?? 0) + value);
|
|
121
|
+
byCustomer.set(key, held);
|
|
122
|
+
}
|
|
123
|
+
const lines = [];
|
|
124
|
+
for (const line of byCustomer.values()) {
|
|
125
|
+
for (const [column, value] of [...line.amounts]) {
|
|
126
|
+
if (value === 0)
|
|
127
|
+
line.amounts.delete(column);
|
|
128
|
+
}
|
|
129
|
+
if (line.amounts.size === 0) {
|
|
130
|
+
violations.push({
|
|
131
|
+
code: 'nil_amounts',
|
|
132
|
+
message: `${line.country}${line.number}: every amount of this acquirer rounds to nothing, and a row of form VD carries at least one.`,
|
|
133
|
+
vatNumber: `${line.country}${line.number}`,
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
lines.push(line);
|
|
138
|
+
}
|
|
139
|
+
lines.sort((a, b) => `${a.country}${a.number}`.localeCompare(`${b.country}${b.number}`));
|
|
140
|
+
return { lines, violations };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A name for the file.
|
|
144
|
+
*
|
|
145
|
+
* **The administration imposes none.** The technical description prescribes no
|
|
146
|
+
* naming convention: the e-MTA checks the header of the file against the report
|
|
147
|
+
* it is being loaded into, and nothing else. So this is a name of ours, and a
|
|
148
|
+
* caller is free to pick another.
|
|
149
|
+
*/
|
|
150
|
+
export function vdFileName(registryCode, year, month) {
|
|
151
|
+
return `VD-${normalise(registryCode)}-${year}${String(month).padStart(2, '0')}.xml`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Writes the report.
|
|
155
|
+
*
|
|
156
|
+
* Refuses, by exception, what would make the whole file meaningless: a month
|
|
157
|
+
* outside the year, or a registry code the schema cannot hold. Everything
|
|
158
|
+
* wrong with one line comes back in `violations`.
|
|
159
|
+
*
|
|
160
|
+
* Note for whoever uses the result: loading a file into the e-MTA **replaces
|
|
161
|
+
* every row already there**, whether it was loaded or typed in, and only works
|
|
162
|
+
* on a report that has not been confirmed.
|
|
163
|
+
*/
|
|
164
|
+
export function generateVd(rows, options) {
|
|
165
|
+
if (!Number.isInteger(options.month) || options.month < 1 || options.month > 12) {
|
|
166
|
+
throw new VdError(`month out of range: ${options.month}`);
|
|
167
|
+
}
|
|
168
|
+
if (!Number.isInteger(options.year) || options.year < 1000 || options.year > 9999) {
|
|
169
|
+
throw new VdError(`year out of range: ${options.year}`);
|
|
170
|
+
}
|
|
171
|
+
const declarer = normalise(options.registryCode);
|
|
172
|
+
if (declarer === '' || declarer.length > 11) {
|
|
173
|
+
throw new VdError(`the declarer's registry code is one to eleven characters: "${options.registryCode}"`);
|
|
174
|
+
}
|
|
175
|
+
const { lines, violations } = linesOf(rows, declarer);
|
|
176
|
+
const parts = [];
|
|
177
|
+
parts.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
178
|
+
parts.push(`<v1:VD_deklaratsioon xmlns:v1="${NAMESPACE}">`);
|
|
179
|
+
parts.push(` <deklareerijaKood>${escapeXml(declarer)}</deklareerijaKood>`);
|
|
180
|
+
parts.push(` <perioodAasta>${options.year}</perioodAasta>`);
|
|
181
|
+
parts.push(` <perioodKuu>${options.month}</perioodKuu>`);
|
|
182
|
+
parts.push(' <aruandeRead>');
|
|
183
|
+
for (const line of lines) {
|
|
184
|
+
parts.push(' <aruandeRida>');
|
|
185
|
+
parts.push(` <kmkrKood riik="${escapeXml(line.country)}">${escapeXml(line.number)}</kmkrKood>`);
|
|
186
|
+
// The schema fixes the order of the three columns, and a line carries only
|
|
187
|
+
// the ones that came to something.
|
|
188
|
+
for (const nature of ['goods', 'triangular', 'services']) {
|
|
189
|
+
const column = COLUMNS[nature];
|
|
190
|
+
const value = line.amounts.get(column);
|
|
191
|
+
if (value !== undefined)
|
|
192
|
+
parts.push(` <${column}>${value}</${column}>`);
|
|
193
|
+
}
|
|
194
|
+
parts.push(' </aruandeRida>');
|
|
195
|
+
}
|
|
196
|
+
parts.push(' </aruandeRead>');
|
|
197
|
+
parts.push('</v1:VD_deklaratsioon>');
|
|
198
|
+
return {
|
|
199
|
+
file: `${parts.join('\n')}\n`,
|
|
200
|
+
filename: vdFileName(declarer, options.year, options.month),
|
|
201
|
+
violations,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# 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,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ekwo-ai/vd",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "The Estonian form VD — uhendusesisese kaibe aruanne — in TypeScript: the XML the e-MTA loads, from plain statement rows. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vd",
|
|
7
|
+
"emta",
|
|
8
|
+
"estonia",
|
|
9
|
+
"ec-sales-list",
|
|
10
|
+
"uhendusesisese-kaibe-aruanne",
|
|
11
|
+
"intra-community",
|
|
12
|
+
"vat",
|
|
13
|
+
"xml"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Ekwo AI",
|
|
17
|
+
"homepage": "https://github.com/Ekwo-ai/ekwo-os/tree/main/packages/formats/vd#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Ekwo-ai/ekwo-os.git",
|
|
21
|
+
"directory": "packages/formats/vd"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Ekwo-ai/ekwo-os/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"main": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.build.json",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
47
|
+
"test": "vitest run"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^24.0.0",
|
|
51
|
+
"typescript": "^5.9.0",
|
|
52
|
+
"vitest": "^3.2.0"
|
|
53
|
+
}
|
|
54
|
+
}
|