@ekwo-ai/journal-items 0.6.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 +100 -0
- package/dist/csv.d.ts +25 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/csv.js +125 -0
- package/dist/csv.js.map +1 -0
- package/dist/decimal.d.ts +25 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +55 -0
- package/dist/decimal.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +288 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Karuna Co OÜ, trading as Ekwo
|
|
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,100 @@
|
|
|
1
|
+
# `@ekwo-ai/journal-items`
|
|
2
|
+
|
|
3
|
+
Reads an **export of journal items** — the lines of every entry, one row per
|
|
4
|
+
line, saved as CSV from the list view of an ERP whose ledger is a table of
|
|
5
|
+
lines — into plain objects, in TypeScript, with no dependencies. Beside it, the
|
|
6
|
+
chart of accounts and the partners exported from the same screen.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { readJournalItems } from '@ekwo-ai/journal-items';
|
|
10
|
+
|
|
11
|
+
const books = readJournalItems([items, accounts, partners]); // any order; the last two are optional
|
|
12
|
+
|
|
13
|
+
books.entries; // [{ number: 'INV/2025/00001', journal: 'Customer Invoices', date: '2025-01-15', lines: […] }, …]
|
|
14
|
+
books.accounts; // [{ code: '400000', name: 'Receivable', type: 'Receivable' }, …]
|
|
15
|
+
books.contacts; // the partners, with their tax ID and e-mail when the partners file has them
|
|
16
|
+
books.currency; // the company currency the export states, or null
|
|
17
|
+
books.violations; // an entry that does not balance, a line of a draft entry
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## The files
|
|
21
|
+
|
|
22
|
+
Each file is recognised by its header, so they may be given in any order.
|
|
23
|
+
|
|
24
|
+
**The journal items** (required). The columns are found by name, under either
|
|
25
|
+
spelling the export uses — the label a person reads, or the technical name of
|
|
26
|
+
the field an export for re-import writes:
|
|
27
|
+
|
|
28
|
+
| Column | Labels | Field names |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| the entry | `Number`, or `Journal Entry` | `move_name`, or `move_id` |
|
|
31
|
+
| journal | `Journal` | `journal_id` |
|
|
32
|
+
| date | `Date` | `date` |
|
|
33
|
+
| account | `Account` | `account_id` |
|
|
34
|
+
| partner | `Partner` | `partner_id` |
|
|
35
|
+
| label | `Label` | `name` |
|
|
36
|
+
| reference | `Reference` | `ref` |
|
|
37
|
+
| debit, credit | `Debit`, `Credit` | `debit`, `credit` |
|
|
38
|
+
| currency, amount | `Currency`, `Amount in Currency` | `currency_id`, `amount_currency` |
|
|
39
|
+
| company currency | `Company Currency` | `company_currency_id` |
|
|
40
|
+
| due date | `Due Date` | `date_maturity` |
|
|
41
|
+
| matching | `Matching #` | `matching_number` |
|
|
42
|
+
| status | `Status` | `parent_state` |
|
|
43
|
+
|
|
44
|
+
- Lines belong to one entry when they share its **number**; without that
|
|
45
|
+
column, its display name.
|
|
46
|
+
- An **account** arrives as it is displayed, its code and then its name
|
|
47
|
+
(`400000 Receivable`). With the chart beside it the display is matched whole;
|
|
48
|
+
without, the code is what comes before the first space.
|
|
49
|
+
- A **partner** arrives as its name, and is identified by it.
|
|
50
|
+
- A line whose entry is **not posted** is refused in `violations`: filter the
|
|
51
|
+
export on posted entries, which is what books are made of.
|
|
52
|
+
- **Dates** are ISO, `YYYY-MM-DD`; **amounts** have a point for decimal mark,
|
|
53
|
+
a minus for a negative. A negative debit is a credit, and the other way
|
|
54
|
+
round.
|
|
55
|
+
- An export with a **group-by** active cannot be saved as CSV; remove the
|
|
56
|
+
grouping first. A spreadsheet export is saved as CSV before it is read.
|
|
57
|
+
|
|
58
|
+
**The chart of accounts** (optional): `Code`, `Account Name` or `Name`, `Type`
|
|
59
|
+
(or `code`, `name`, `account_type`). The type is kept in the source's words.
|
|
60
|
+
|
|
61
|
+
**The partners** (optional): `Name`, `Tax ID`, `Email`, `Company ID`, and the
|
|
62
|
+
country as a two-letter code (`Country/Country Code`) — a country written as
|
|
63
|
+
its name is not turned into a code.
|
|
64
|
+
|
|
65
|
+
## What it returns
|
|
66
|
+
|
|
67
|
+
The shape declared in [`src/types.ts`](./src/types.ts), which is, field for
|
|
68
|
+
field, the shape every reader of an accounting export in this repository
|
|
69
|
+
declares for itself. Amounts are decimal strings, never negative; the side is
|
|
70
|
+
the sign. What does not add up comes back in `violations` and the entry comes
|
|
71
|
+
back as written; what is not an export of journal items is thrown as a
|
|
72
|
+
`BooksFileError` with a `code`.
|
|
73
|
+
|
|
74
|
+
It knows no chart of accounts. Which account of another chart each code
|
|
75
|
+
becomes is the importer's business: in [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os),
|
|
76
|
+
`ekwo import journal-items <files…>`.
|
|
77
|
+
|
|
78
|
+
## Sources
|
|
79
|
+
|
|
80
|
+
This reader was written against the export of the **Journal Items** of Odoo
|
|
81
|
+
(Accounting), versions 17 and 18, read on 22 September 2026 from:
|
|
82
|
+
|
|
83
|
+
- [Export and import data — Odoo 17.0 documentation](https://www.odoo.com/documentation/17.0/applications/essentials/export_import_data.html)
|
|
84
|
+
and [18.0](https://www.odoo.com/documentation/18.0/applications/essentials/export_import_data.html):
|
|
85
|
+
the two modes of an export, the ISO dates, the field names of an export for
|
|
86
|
+
re-import;
|
|
87
|
+
- the source of the accounting module, branch 17.0:
|
|
88
|
+
[`account_move_line.py`](https://github.com/odoo/odoo/blob/17.0/addons/account/models/account_move_line.py),
|
|
89
|
+
[`account_account.py`](https://github.com/odoo/odoo/blob/17.0/addons/account/models/account_account.py),
|
|
90
|
+
[`res_partner.py`](https://github.com/odoo/odoo/blob/17.0/odoo/addons/base/models/res_partner.py) and
|
|
91
|
+
[`export.py`](https://github.com/odoo/odoo/blob/17.0/addons/web/controllers/export.py):
|
|
92
|
+
the label of every field above, the display name of an account, and the way a
|
|
93
|
+
CSV is written — comma, double quotes, UTF-8, a point for decimals.
|
|
94
|
+
|
|
95
|
+
`ekwo import odoo <files…>` is the same as `ekwo import journal-items`.
|
|
96
|
+
|
|
97
|
+
The fixtures under `test/fixtures/` are written by hand from those pages and
|
|
98
|
+
hold no real data.
|
|
99
|
+
|
|
100
|
+
MIT.
|
package/dist/csv.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The comma-separated values a spreadsheet saves, as RFC 4180 describes them:
|
|
3
|
+
* fields between double quotes may hold the separator, a line break and a
|
|
4
|
+
* doubled quote. Nothing else about the file is assumed.
|
|
5
|
+
*
|
|
6
|
+
* The separator is **read from the header**, which this format requires: the
|
|
7
|
+
* first of `,`, `;` and a tab that the header row contains outside quotes. A
|
|
8
|
+
* spreadsheet saves with a comma or a semicolon depending on the language of
|
|
9
|
+
* the machine, and the header is the one row whose content is known, so this
|
|
10
|
+
* is reading the file rather than guessing at it.
|
|
11
|
+
*/
|
|
12
|
+
import type { Encoding } from './types.js';
|
|
13
|
+
export type Separator = ',' | ';' | '\t';
|
|
14
|
+
/** Bytes to text, in the encoding the caller said, refusing what is not that. */
|
|
15
|
+
export declare function decode(input: string | Uint8Array, encoding: Encoding, maxBytes: number): string;
|
|
16
|
+
/** The separator of a header line: the first of the three found outside quotes. */
|
|
17
|
+
export declare function separatorOf(headerLine: string): Separator;
|
|
18
|
+
/** Every row of the file, each an array of fields, with the row number it starts on. */
|
|
19
|
+
export declare function parseCsv(text: string, separator: Separator): {
|
|
20
|
+
row: number;
|
|
21
|
+
fields: string[];
|
|
22
|
+
}[];
|
|
23
|
+
/** A header cell as it is compared: trimmed, lower case, without the `*` some exports put on a required column. */
|
|
24
|
+
export declare function headerKey(cell: string): string;
|
|
25
|
+
//# sourceMappingURL=csv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;AAEzC,iFAAiF;AACjF,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA0B/F;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAQzD;AAED,wFAAwF;AACxF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAqDhG;AAED,mHAAmH;AACnH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|
package/dist/csv.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The comma-separated values a spreadsheet saves, as RFC 4180 describes them:
|
|
3
|
+
* fields between double quotes may hold the separator, a line break and a
|
|
4
|
+
* doubled quote. Nothing else about the file is assumed.
|
|
5
|
+
*
|
|
6
|
+
* The separator is **read from the header**, which this format requires: the
|
|
7
|
+
* first of `,`, `;` and a tab that the header row contains outside quotes. A
|
|
8
|
+
* spreadsheet saves with a comma or a semicolon depending on the language of
|
|
9
|
+
* the machine, and the header is the one row whose content is known, so this
|
|
10
|
+
* is reading the file rather than guessing at it.
|
|
11
|
+
*/
|
|
12
|
+
import { BooksFileError } from './errors.js';
|
|
13
|
+
const BYTE_ORDER_MARK = 0xfeff;
|
|
14
|
+
/** Bytes to text, in the encoding the caller said, refusing what is not that. */
|
|
15
|
+
export function decode(input, encoding, maxBytes) {
|
|
16
|
+
let text;
|
|
17
|
+
if (typeof input === 'string') {
|
|
18
|
+
if (input.length > maxBytes)
|
|
19
|
+
throw new BooksFileError('too_large', `the file is longer than ${maxBytes} bytes`);
|
|
20
|
+
text = input;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
if (input.byteLength > maxBytes) {
|
|
24
|
+
throw new BooksFileError('too_large', `the file is ${input.byteLength} bytes, over the limit of ${maxBytes}`);
|
|
25
|
+
}
|
|
26
|
+
if (encoding === 'iso-8859-1') {
|
|
27
|
+
const parts = [];
|
|
28
|
+
for (let i = 0; i < input.length; i += 8192)
|
|
29
|
+
parts.push(String.fromCharCode(...input.subarray(i, i + 8192)));
|
|
30
|
+
text = parts.join('');
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
try {
|
|
34
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(input);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new BooksFileError('unsupported_encoding', "the bytes are not UTF-8; if the file was saved in Latin-1, say so with { encoding: 'iso-8859-1' } — it is not guessed, because every sequence of bytes is valid Latin-1");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (text.charCodeAt(0) === BYTE_ORDER_MARK)
|
|
42
|
+
text = text.slice(1);
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
/** The separator of a header line: the first of the three found outside quotes. */
|
|
46
|
+
export function separatorOf(headerLine) {
|
|
47
|
+
let quoted = false;
|
|
48
|
+
for (const char of headerLine) {
|
|
49
|
+
if (char === '"')
|
|
50
|
+
quoted = !quoted;
|
|
51
|
+
else if (!quoted && (char === ',' || char === ';' || char === '\t'))
|
|
52
|
+
return char;
|
|
53
|
+
}
|
|
54
|
+
// A header of one column has no separator; any will do, and a comma is it.
|
|
55
|
+
return ',';
|
|
56
|
+
}
|
|
57
|
+
/** Every row of the file, each an array of fields, with the row number it starts on. */
|
|
58
|
+
export function parseCsv(text, separator) {
|
|
59
|
+
const rows = [];
|
|
60
|
+
let fields = [];
|
|
61
|
+
let field = '';
|
|
62
|
+
let quoted = false;
|
|
63
|
+
let line = 1;
|
|
64
|
+
let startedOn = 1;
|
|
65
|
+
let fieldStarted = false;
|
|
66
|
+
const endRow = () => {
|
|
67
|
+
fields.push(field);
|
|
68
|
+
// A blank line is no row; a row of empty fields is kept and read.
|
|
69
|
+
if (!(fields.length === 1 && fields[0] === ''))
|
|
70
|
+
rows.push({ row: startedOn, fields });
|
|
71
|
+
fields = [];
|
|
72
|
+
field = '';
|
|
73
|
+
fieldStarted = false;
|
|
74
|
+
};
|
|
75
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
76
|
+
const char = text[i];
|
|
77
|
+
if (quoted) {
|
|
78
|
+
if (char === '"') {
|
|
79
|
+
if (text[i + 1] === '"') {
|
|
80
|
+
field += '"';
|
|
81
|
+
i += 1;
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
quoted = false;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (char === '\n')
|
|
88
|
+
line += 1;
|
|
89
|
+
field += char;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (char === '"' && !fieldStarted) {
|
|
94
|
+
quoted = true;
|
|
95
|
+
fieldStarted = true;
|
|
96
|
+
}
|
|
97
|
+
else if (char === separator) {
|
|
98
|
+
fields.push(field);
|
|
99
|
+
field = '';
|
|
100
|
+
fieldStarted = false;
|
|
101
|
+
}
|
|
102
|
+
else if (char === '\r' && text[i + 1] === '\n') {
|
|
103
|
+
// The \n that follows ends the row.
|
|
104
|
+
}
|
|
105
|
+
else if (char === '\n' || char === '\r') {
|
|
106
|
+
endRow();
|
|
107
|
+
line += 1;
|
|
108
|
+
startedOn = line;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
field += char;
|
|
112
|
+
fieldStarted = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (quoted)
|
|
116
|
+
throw new BooksFileError('malformed_csv', 'a quoted field is never closed', startedOn);
|
|
117
|
+
if (field !== '' || fields.length > 0)
|
|
118
|
+
endRow();
|
|
119
|
+
return rows;
|
|
120
|
+
}
|
|
121
|
+
/** A header cell as it is compared: trimmed, lower case, without the `*` some exports put on a required column. */
|
|
122
|
+
export function headerKey(cell) {
|
|
123
|
+
return cell.trim().replace(/^\*/, '').trim().toLowerCase();
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=csv.js.map
|
package/dist/csv.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as the file wrote them, added without a float.
|
|
3
|
+
*
|
|
4
|
+
* An amount is held as a `bigint` of millionths: enough for any currency and
|
|
5
|
+
* any rate a spreadsheet prints, and exact. What leaves is a string with two
|
|
6
|
+
* decimals, and more when the file wrote more that are not zero — dropping a
|
|
7
|
+
* written zero is not rounding, dropping anything else would be, and is never
|
|
8
|
+
* done here.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The decimal marks a number may be written with. The comma is accepted only
|
|
12
|
+
* where it cannot be a separator of thousands — in a file whose fields are
|
|
13
|
+
* separated by something else — and a number never carries both marks.
|
|
14
|
+
*/
|
|
15
|
+
export type DecimalMark = '.' | ',';
|
|
16
|
+
/**
|
|
17
|
+
* A written amount as millionths, or null when it is not a number this reader
|
|
18
|
+
* accepts: digits, one decimal mark, an optional leading minus or a pair of
|
|
19
|
+
* brackets for a negative. No thousands separator, no currency symbol, no
|
|
20
|
+
* exponent — each of those is refused rather than read one way or another.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseDecimal(text: string, marks: readonly DecimalMark[]): bigint | null;
|
|
23
|
+
/** Millionths as a decimal string: signed, two decimals, more when they are written. */
|
|
24
|
+
export declare function formatDecimal(value: bigint): string;
|
|
25
|
+
//# sourceMappingURL=decimal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decimal.d.ts","sourceRoot":"","sources":["../src/decimal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,GAAG,CAAC;AAEpC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAuBvF;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMnD"}
|
package/dist/decimal.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as the file wrote them, added without a float.
|
|
3
|
+
*
|
|
4
|
+
* An amount is held as a `bigint` of millionths: enough for any currency and
|
|
5
|
+
* any rate a spreadsheet prints, and exact. What leaves is a string with two
|
|
6
|
+
* decimals, and more when the file wrote more that are not zero — dropping a
|
|
7
|
+
* written zero is not rounding, dropping anything else would be, and is never
|
|
8
|
+
* done here.
|
|
9
|
+
*/
|
|
10
|
+
const SCALE = 6;
|
|
11
|
+
const UNIT = 10n ** BigInt(SCALE);
|
|
12
|
+
/**
|
|
13
|
+
* A written amount as millionths, or null when it is not a number this reader
|
|
14
|
+
* accepts: digits, one decimal mark, an optional leading minus or a pair of
|
|
15
|
+
* brackets for a negative. No thousands separator, no currency symbol, no
|
|
16
|
+
* exponent — each of those is refused rather than read one way or another.
|
|
17
|
+
*/
|
|
18
|
+
export function parseDecimal(text, marks) {
|
|
19
|
+
let value = text.trim();
|
|
20
|
+
if (value === '')
|
|
21
|
+
return null;
|
|
22
|
+
let negative = false;
|
|
23
|
+
if (value.startsWith('(') && value.endsWith(')')) {
|
|
24
|
+
negative = true;
|
|
25
|
+
value = value.slice(1, -1).trim();
|
|
26
|
+
}
|
|
27
|
+
if (value.startsWith('-')) {
|
|
28
|
+
if (negative)
|
|
29
|
+
return null;
|
|
30
|
+
negative = true;
|
|
31
|
+
value = value.slice(1);
|
|
32
|
+
}
|
|
33
|
+
else if (value.startsWith('+')) {
|
|
34
|
+
value = value.slice(1);
|
|
35
|
+
}
|
|
36
|
+
const pattern = marks.includes(',') ? /^([0-9]+)(?:[.,]([0-9]*))?$|^[.,]([0-9]+)$/ : /^([0-9]+)(?:\.([0-9]*))?$|^\.([0-9]+)$/;
|
|
37
|
+
const match = pattern.exec(value);
|
|
38
|
+
if (match === null)
|
|
39
|
+
return null;
|
|
40
|
+
const whole = match[1] ?? '0';
|
|
41
|
+
const fraction = match[2] ?? match[3] ?? '';
|
|
42
|
+
if (fraction.length > SCALE)
|
|
43
|
+
return null;
|
|
44
|
+
const units = BigInt(whole) * UNIT + BigInt(fraction.padEnd(SCALE, '0') || '0');
|
|
45
|
+
return negative ? -units : units;
|
|
46
|
+
}
|
|
47
|
+
/** Millionths as a decimal string: signed, two decimals, more when they are written. */
|
|
48
|
+
export function formatDecimal(value) {
|
|
49
|
+
const negative = value < 0n;
|
|
50
|
+
const digits = (negative ? -value : value).toString().padStart(SCALE + 1, '0');
|
|
51
|
+
const whole = digits.slice(0, -SCALE);
|
|
52
|
+
const fraction = digits.slice(-SCALE).replace(/0+$/, '').padEnd(2, '0');
|
|
53
|
+
return `${negative ? '-' : ''}${whole}.${fraction}`;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=decimal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decimal.js","sourceRoot":"","sources":["../src/decimal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,KAAK,GAAG,CAAC,CAAC;AAChB,MAAM,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;AASlC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAA6B;IACtE,IAAI,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACxB,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,QAAQ,GAAG,IAAI,CAAC;QAChB,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,IAAI,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1B,QAAQ,GAAG,IAAI,CAAC;QAChB,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;SAAM,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,wCAAwC,CAAC;IAC9H,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,QAAQ,CAAC,MAAM,GAAG,KAAK;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;IAChF,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;AACnC,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,QAAQ,GAAG,KAAK,GAAG,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC;AACtD,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What makes a file unreadable, as opposed to books that read and do not add
|
|
3
|
+
* up. The second comes back in `violations`; the first is thrown, because a
|
|
4
|
+
* file with no header the reader recognises has no row to hang a violation on.
|
|
5
|
+
*/
|
|
6
|
+
export type BooksFileErrorCode =
|
|
7
|
+
/** Larger than `maxBytes`. Refused before a single character is read. */
|
|
8
|
+
'too_large'
|
|
9
|
+
/** Bytes that are not the encoding announced. */
|
|
10
|
+
| 'unsupported_encoding'
|
|
11
|
+
/** Nothing in the file, or a header and nothing under it. */
|
|
12
|
+
| 'empty_file'
|
|
13
|
+
/** A header that lacks a column the format needs, or names one twice. */
|
|
14
|
+
| 'missing_column'
|
|
15
|
+
/** A quote that is never closed. */
|
|
16
|
+
| 'malformed_csv'
|
|
17
|
+
/** A value the format does not allow where it is: a date, an amount. */
|
|
18
|
+
| 'invalid_value';
|
|
19
|
+
export declare class BooksFileError extends Error {
|
|
20
|
+
readonly code: BooksFileErrorCode;
|
|
21
|
+
/** The row the reader stopped on, from 1, when there is one. */
|
|
22
|
+
readonly row: number | null;
|
|
23
|
+
constructor(code: BooksFileErrorCode, message: string, row?: number | null);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,kBAAkB;AAC5B,yEAAyE;AACvE,WAAW;AACb,iDAAiD;GAC/C,sBAAsB;AACxB,6DAA6D;GAC3D,YAAY;AACd,yEAAyE;GACvE,gBAAgB;AAClB,oCAAoC;GAClC,eAAe;AACjB,wEAAwE;GACtE,eAAe,CAAC;AAEpB,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEhB,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,GAAG,IAAW;CAMjF"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class BooksFileError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
/** The row the reader stopped on, from 1, when there is one. */
|
|
4
|
+
row;
|
|
5
|
+
constructor(code, message, row = null) {
|
|
6
|
+
super(row === null ? message : `row ${row}: ${message}`);
|
|
7
|
+
this.name = 'BooksFileError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.row = row;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAmBA,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAqB;IAClC,gEAAgE;IACvD,GAAG,CAAgB;IAE5B,YAAY,IAAwB,EAAE,OAAe,EAAE,MAAqB,IAAI;QAC9E,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An export of journal items: the list view of the lines of every entry,
|
|
3
|
+
* saved as CSV, one row per line. It is what an ERP whose ledger is a table of
|
|
4
|
+
* lines hands over when it is asked for its books — and beside it, from the
|
|
5
|
+
* same export screen, the chart of accounts and the partners.
|
|
6
|
+
*
|
|
7
|
+
* The columns are found **by their names in the header**, under either of the
|
|
8
|
+
* two spellings such an export uses: the label a person reads (`Journal Entry`,
|
|
9
|
+
* `Account`, `Partner`, `Debit`) or the technical name of the field
|
|
10
|
+
* (`move_id`, `account_id`, `partner_id`, `debit`). An account arrives as it
|
|
11
|
+
* is displayed, its code and then its name (`400000 Customers`); a partner as
|
|
12
|
+
* its name. Dates are ISO, amounts carry a point.
|
|
13
|
+
*
|
|
14
|
+
* It depends on nothing, reads no database and knows no chart of accounts.
|
|
15
|
+
*/
|
|
16
|
+
import type { ImportedBooks, ReadOptions } from './types.js';
|
|
17
|
+
export * from './types.js';
|
|
18
|
+
export { BooksFileError, type BooksFileErrorCode } from './errors.js';
|
|
19
|
+
/** The columns of the lines, each under the names the export may give it. */
|
|
20
|
+
export declare const JOURNAL_ITEM_COLUMNS: {
|
|
21
|
+
readonly number: readonly ["number", "move_name"];
|
|
22
|
+
readonly entry: readonly ["journal entry", "move_id", "entry"];
|
|
23
|
+
readonly journal: readonly ["journal", "journal_id"];
|
|
24
|
+
readonly date: readonly ["date"];
|
|
25
|
+
readonly account: readonly ["account", "account_id"];
|
|
26
|
+
readonly partner: readonly ["partner", "partner_id"];
|
|
27
|
+
readonly label: readonly ["label", "name"];
|
|
28
|
+
readonly reference: readonly ["reference", "ref"];
|
|
29
|
+
readonly debit: readonly ["debit"];
|
|
30
|
+
readonly credit: readonly ["credit"];
|
|
31
|
+
readonly currency: readonly ["currency", "currency_id"];
|
|
32
|
+
readonly amountCurrency: readonly ["amount in currency", "amount_currency"];
|
|
33
|
+
readonly companyCurrency: readonly ["company currency", "company_currency_id"];
|
|
34
|
+
readonly dueDate: readonly ["due date", "date_maturity"];
|
|
35
|
+
readonly matching: readonly ["matching #", "matching_number"];
|
|
36
|
+
readonly status: readonly ["status", "parent_state"];
|
|
37
|
+
};
|
|
38
|
+
/** The columns of the chart of accounts exported beside them. */
|
|
39
|
+
export declare const ACCOUNT_COLUMNS: {
|
|
40
|
+
readonly code: readonly ["code"];
|
|
41
|
+
readonly name: readonly ["account name", "name"];
|
|
42
|
+
readonly type: readonly ["type", "account_type"];
|
|
43
|
+
};
|
|
44
|
+
/** The columns of the partners exported beside them. */
|
|
45
|
+
export declare const PARTNER_COLUMNS: {
|
|
46
|
+
readonly name: readonly ["name", "display name", "display_name"];
|
|
47
|
+
readonly vat: readonly ["tax id", "vat"];
|
|
48
|
+
readonly registration: readonly ["company id", "company_registry"];
|
|
49
|
+
readonly email: readonly ["email"];
|
|
50
|
+
readonly country: readonly ["country/country code", "country_id/code", "country code"];
|
|
51
|
+
readonly reference: readonly ["reference", "ref"];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Reads the files — the journal items, and optionally the chart of accounts
|
|
55
|
+
* and the partners — in any order: each is recognised by its header, and a
|
|
56
|
+
* file that is none of the three is refused by name.
|
|
57
|
+
*
|
|
58
|
+
* Lines belong to one entry when they share its number (`Number`, `move_name`)
|
|
59
|
+
* or, without that column, its display name (`Journal Entry`, `move_id`). A
|
|
60
|
+
* line whose entry is not posted is refused in `violations`: export the posted
|
|
61
|
+
* entries, which is what books are made of.
|
|
62
|
+
*/
|
|
63
|
+
export declare function readJournalItems(inputs: readonly (string | Uint8Array)[], options?: ReadOptions): ImportedBooks;
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAEV,aAAa,EAIb,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItE,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;CAiBvB,CAAC;AAEX,iEAAiE;AACjE,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,wDAAwD;AACxD,eAAO,MAAM,eAAe;;;;;;;CAOlB,CAAC;AAuCX;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,EAAE,OAAO,GAAE,WAAgB,GAAG,aAAa,CAqLnH"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An export of journal items: the list view of the lines of every entry,
|
|
3
|
+
* saved as CSV, one row per line. It is what an ERP whose ledger is a table of
|
|
4
|
+
* lines hands over when it is asked for its books — and beside it, from the
|
|
5
|
+
* same export screen, the chart of accounts and the partners.
|
|
6
|
+
*
|
|
7
|
+
* The columns are found **by their names in the header**, under either of the
|
|
8
|
+
* two spellings such an export uses: the label a person reads (`Journal Entry`,
|
|
9
|
+
* `Account`, `Partner`, `Debit`) or the technical name of the field
|
|
10
|
+
* (`move_id`, `account_id`, `partner_id`, `debit`). An account arrives as it
|
|
11
|
+
* is displayed, its code and then its name (`400000 Customers`); a partner as
|
|
12
|
+
* its name. Dates are ISO, amounts carry a point.
|
|
13
|
+
*
|
|
14
|
+
* It depends on nothing, reads no database and knows no chart of accounts.
|
|
15
|
+
*/
|
|
16
|
+
import { decode, headerKey, parseCsv, separatorOf } from './csv.js';
|
|
17
|
+
import { formatDecimal, parseDecimal } from './decimal.js';
|
|
18
|
+
import { BooksFileError } from './errors.js';
|
|
19
|
+
export * from './types.js';
|
|
20
|
+
export { BooksFileError } from './errors.js';
|
|
21
|
+
const DEFAULTS = { encoding: 'utf-8', maxBytes: 256 * 1024 * 1024 };
|
|
22
|
+
/** The columns of the lines, each under the names the export may give it. */
|
|
23
|
+
export const JOURNAL_ITEM_COLUMNS = {
|
|
24
|
+
number: ['number', 'move_name'],
|
|
25
|
+
entry: ['journal entry', 'move_id', 'entry'],
|
|
26
|
+
journal: ['journal', 'journal_id'],
|
|
27
|
+
date: ['date'],
|
|
28
|
+
account: ['account', 'account_id'],
|
|
29
|
+
partner: ['partner', 'partner_id'],
|
|
30
|
+
label: ['label', 'name'],
|
|
31
|
+
reference: ['reference', 'ref'],
|
|
32
|
+
debit: ['debit'],
|
|
33
|
+
credit: ['credit'],
|
|
34
|
+
currency: ['currency', 'currency_id'],
|
|
35
|
+
amountCurrency: ['amount in currency', 'amount_currency'],
|
|
36
|
+
companyCurrency: ['company currency', 'company_currency_id'],
|
|
37
|
+
dueDate: ['due date', 'date_maturity'],
|
|
38
|
+
matching: ['matching #', 'matching_number'],
|
|
39
|
+
status: ['status', 'parent_state'],
|
|
40
|
+
};
|
|
41
|
+
/** The columns of the chart of accounts exported beside them. */
|
|
42
|
+
export const ACCOUNT_COLUMNS = {
|
|
43
|
+
code: ['code'],
|
|
44
|
+
name: ['account name', 'name'],
|
|
45
|
+
type: ['type', 'account_type'],
|
|
46
|
+
};
|
|
47
|
+
/** The columns of the partners exported beside them. */
|
|
48
|
+
export const PARTNER_COLUMNS = {
|
|
49
|
+
name: ['name', 'display name', 'display_name'],
|
|
50
|
+
vat: ['tax id', 'vat'],
|
|
51
|
+
registration: ['company id', 'company_registry'],
|
|
52
|
+
email: ['email'],
|
|
53
|
+
country: ['country/country code', 'country_id/code', 'country code'],
|
|
54
|
+
reference: ['reference', 'ref'],
|
|
55
|
+
};
|
|
56
|
+
function locate(keys, spec) {
|
|
57
|
+
const at = {};
|
|
58
|
+
for (const [column, names] of Object.entries(spec)) {
|
|
59
|
+
const found = keys.findIndex((key) => names.includes(key));
|
|
60
|
+
if (found !== -1)
|
|
61
|
+
at[column] = found;
|
|
62
|
+
}
|
|
63
|
+
return at;
|
|
64
|
+
}
|
|
65
|
+
function table(input, options, file) {
|
|
66
|
+
const text = decode(input, options.encoding ?? DEFAULTS.encoding, options.maxBytes ?? DEFAULTS.maxBytes);
|
|
67
|
+
const separator = separatorOf(text.split(/\r?\n/, 1)[0] ?? '');
|
|
68
|
+
const rows = parseCsv(text, separator);
|
|
69
|
+
const header = rows.shift();
|
|
70
|
+
if (header === undefined)
|
|
71
|
+
throw new BooksFileError('empty_file', `file ${file} is empty`);
|
|
72
|
+
return { rows, keys: header.fields.map(headerKey), marks: separator === ',' ? ['.'] : ['.', ','], file };
|
|
73
|
+
}
|
|
74
|
+
/** What a file is, from its header: the lines, the chart, or the partners. */
|
|
75
|
+
function kindOf(keys) {
|
|
76
|
+
const has = (names) => keys.some((key) => names.includes(key));
|
|
77
|
+
if (has(JOURNAL_ITEM_COLUMNS.debit) && has(JOURNAL_ITEM_COLUMNS.credit) && has(JOURNAL_ITEM_COLUMNS.account))
|
|
78
|
+
return 'items';
|
|
79
|
+
if (has(ACCOUNT_COLUMNS.code) && has(ACCOUNT_COLUMNS.type))
|
|
80
|
+
return 'accounts';
|
|
81
|
+
if (has(PARTNER_COLUMNS.name))
|
|
82
|
+
return 'partners';
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reads the files — the journal items, and optionally the chart of accounts
|
|
87
|
+
* and the partners — in any order: each is recognised by its header, and a
|
|
88
|
+
* file that is none of the three is refused by name.
|
|
89
|
+
*
|
|
90
|
+
* Lines belong to one entry when they share its number (`Number`, `move_name`)
|
|
91
|
+
* or, without that column, its display name (`Journal Entry`, `move_id`). A
|
|
92
|
+
* line whose entry is not posted is refused in `violations`: export the posted
|
|
93
|
+
* entries, which is what books are made of.
|
|
94
|
+
*/
|
|
95
|
+
export function readJournalItems(inputs, options = {}) {
|
|
96
|
+
const tables = inputs.map((input, index) => table(input, options, index + 1));
|
|
97
|
+
const kinds = tables.map((t) => ({ table: t, kind: kindOf(t.keys) }));
|
|
98
|
+
for (const { table: t, kind } of kinds) {
|
|
99
|
+
if (kind === null) {
|
|
100
|
+
throw new BooksFileError('missing_column', `file ${t.file} is neither journal items (account, debit, credit), a chart of accounts (code, type) nor partners (name)`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const itemTables = kinds.filter((k) => k.kind === 'items').map((k) => k.table);
|
|
104
|
+
if (itemTables.length === 0)
|
|
105
|
+
throw new BooksFileError('missing_column', 'no file holds journal items: one needs an account, a debit and a credit column');
|
|
106
|
+
const violations = [];
|
|
107
|
+
const accounts = new Map();
|
|
108
|
+
const contacts = new Map();
|
|
109
|
+
for (const t of kinds.filter((k) => k.kind === 'accounts').map((k) => k.table)) {
|
|
110
|
+
const at = locate(t.keys, ACCOUNT_COLUMNS);
|
|
111
|
+
for (const { fields } of t.rows) {
|
|
112
|
+
const code = (fields[at.code] ?? '').trim();
|
|
113
|
+
if (code === '')
|
|
114
|
+
continue;
|
|
115
|
+
const name = at.name === undefined ? '' : (fields[at.name] ?? '').trim();
|
|
116
|
+
const type = at.type === undefined ? '' : (fields[at.type] ?? '').trim();
|
|
117
|
+
accounts.set(code, { code, name: name === '' ? null : name, type: type === '' ? null : type });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const t of kinds.filter((k) => k.kind === 'partners').map((k) => k.table)) {
|
|
121
|
+
const at = locate(t.keys, PARTNER_COLUMNS);
|
|
122
|
+
for (const { fields } of t.rows) {
|
|
123
|
+
const cell = (column) => {
|
|
124
|
+
const index = at[column];
|
|
125
|
+
return index === undefined ? '' : (fields[index] ?? '').trim();
|
|
126
|
+
};
|
|
127
|
+
const name = cell('name');
|
|
128
|
+
if (name === '')
|
|
129
|
+
continue;
|
|
130
|
+
const country = cell('country').toUpperCase();
|
|
131
|
+
contacts.set(name, {
|
|
132
|
+
code: name,
|
|
133
|
+
name,
|
|
134
|
+
vatNumber: cell('vat') === '' ? null : cell('vat'),
|
|
135
|
+
registrationNumber: cell('registration') === '' ? null : cell('registration'),
|
|
136
|
+
email: cell('email') === '' ? null : cell('email'),
|
|
137
|
+
country: /^[A-Z]{2}$/.test(country) ? country : null,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// An account arrives as displayed, "code name". With the chart beside it the
|
|
142
|
+
// display is matched whole; without, the code is what comes before the
|
|
143
|
+
// first space.
|
|
144
|
+
const displays = new Map([...accounts.values()].map((a) => [`${a.code} ${a.name ?? ''}`.trim(), a.code]));
|
|
145
|
+
const accountOf = (display) => {
|
|
146
|
+
const known = displays.get(display);
|
|
147
|
+
if (known !== undefined)
|
|
148
|
+
return known;
|
|
149
|
+
const space = display.indexOf(' ');
|
|
150
|
+
return space === -1 ? display : display.slice(0, space);
|
|
151
|
+
};
|
|
152
|
+
const entries = new Map();
|
|
153
|
+
const currencies = new Set();
|
|
154
|
+
for (const t of itemTables) {
|
|
155
|
+
const at = locate(t.keys, JOURNAL_ITEM_COLUMNS);
|
|
156
|
+
const key = at.number ?? at.entry;
|
|
157
|
+
if (key === undefined) {
|
|
158
|
+
throw new BooksFileError('missing_column', `file ${t.file} names no entry for its lines: it needs a Number (move_name) or a Journal Entry (move_id) column`);
|
|
159
|
+
}
|
|
160
|
+
if (at.date === undefined)
|
|
161
|
+
throw new BooksFileError('missing_column', `file ${t.file} has no Date column`);
|
|
162
|
+
for (const { row, fields } of t.rows) {
|
|
163
|
+
const cell = (column) => {
|
|
164
|
+
const index = at[column];
|
|
165
|
+
return index === undefined ? '' : (fields[index] ?? '').trim();
|
|
166
|
+
};
|
|
167
|
+
const amount = (column) => {
|
|
168
|
+
const raw = cell(column);
|
|
169
|
+
if (raw === '')
|
|
170
|
+
return 0n;
|
|
171
|
+
const value = parseDecimal(raw, t.marks);
|
|
172
|
+
if (value === null)
|
|
173
|
+
throw new BooksFileError('invalid_value', `"${raw}" in ${column} is not an amount`, row);
|
|
174
|
+
return value;
|
|
175
|
+
};
|
|
176
|
+
const status = cell('status').toLowerCase();
|
|
177
|
+
if (status !== '' && status !== 'posted') {
|
|
178
|
+
violations.push({ rule: 'not_posted', message: `a line of an entry that is ${status}, not posted; export the posted entries only`, row });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const number = (fields[key] ?? '').trim();
|
|
182
|
+
const display = cell('account');
|
|
183
|
+
if (number === '' || display === '') {
|
|
184
|
+
violations.push({ rule: 'required', message: 'a line names no entry or no account and was left out', row });
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const date = isoDate(cell('date'), row);
|
|
188
|
+
const account = accountOf(display);
|
|
189
|
+
if (!accounts.has(account)) {
|
|
190
|
+
const space = display.indexOf(' ');
|
|
191
|
+
accounts.set(account, { code: account, name: space === -1 ? null : display.slice(space + 1).trim() || null, type: null });
|
|
192
|
+
}
|
|
193
|
+
const partner = cell('partner');
|
|
194
|
+
if (partner !== '' && !contacts.has(partner)) {
|
|
195
|
+
contacts.set(partner, { code: partner, name: partner, vatNumber: null, registrationNumber: null, email: null, country: null });
|
|
196
|
+
}
|
|
197
|
+
const companyCurrency = cell('companyCurrency');
|
|
198
|
+
if (companyCurrency !== '')
|
|
199
|
+
currencies.add(companyCurrency.toUpperCase());
|
|
200
|
+
let debit = amount('debit');
|
|
201
|
+
let credit = amount('credit');
|
|
202
|
+
if (debit !== 0n && credit !== 0n)
|
|
203
|
+
violations.push({ rule: 'one_side', message: 'a line carries both a debit and a credit', row });
|
|
204
|
+
if (debit < 0n) {
|
|
205
|
+
credit -= debit;
|
|
206
|
+
debit = 0n;
|
|
207
|
+
}
|
|
208
|
+
if (credit < 0n) {
|
|
209
|
+
debit -= credit;
|
|
210
|
+
credit = 0n;
|
|
211
|
+
}
|
|
212
|
+
const currency = cell('currency').toUpperCase();
|
|
213
|
+
const inCurrency = cell('amountCurrency') === '' ? null : amount('amountCurrency');
|
|
214
|
+
const label = cell('label');
|
|
215
|
+
const line = {
|
|
216
|
+
account,
|
|
217
|
+
contact: partner === '' ? null : partner,
|
|
218
|
+
label: label === '' ? null : label,
|
|
219
|
+
debit: formatDecimal(debit),
|
|
220
|
+
credit: formatDecimal(credit),
|
|
221
|
+
currency: currency === '' ? null : currency,
|
|
222
|
+
amountCurrency: inCurrency === null ? null : formatDecimal(inCurrency < 0n ? -inCurrency : inCurrency),
|
|
223
|
+
dueDate: cell('dueDate') === '' ? null : isoDate(cell('dueDate'), row),
|
|
224
|
+
matching: cell('matching') === '' ? null : cell('matching'),
|
|
225
|
+
};
|
|
226
|
+
let entry = entries.get(number);
|
|
227
|
+
if (entry === undefined) {
|
|
228
|
+
const journal = cell('journal');
|
|
229
|
+
const reference = cell('reference');
|
|
230
|
+
entry = {
|
|
231
|
+
journal: journal === '' ? null : journal,
|
|
232
|
+
journalName: journal === '' ? null : journal,
|
|
233
|
+
number,
|
|
234
|
+
date,
|
|
235
|
+
reference: reference === '' ? null : reference,
|
|
236
|
+
description: line.label,
|
|
237
|
+
lines: [],
|
|
238
|
+
row,
|
|
239
|
+
debits: 0n,
|
|
240
|
+
credits: 0n,
|
|
241
|
+
};
|
|
242
|
+
entries.set(number, entry);
|
|
243
|
+
}
|
|
244
|
+
else if (entry.date !== date) {
|
|
245
|
+
violations.push({ rule: 'one_date', message: `entry ${number} is dated ${entry.date} on its first line and ${date} here`, row });
|
|
246
|
+
}
|
|
247
|
+
entry.lines.push(line);
|
|
248
|
+
entry.debits += debit;
|
|
249
|
+
entry.credits += credit;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const read = [];
|
|
253
|
+
for (const { debits, credits, ...entry } of entries.values()) {
|
|
254
|
+
if (debits !== credits) {
|
|
255
|
+
violations.push({
|
|
256
|
+
rule: 'balance',
|
|
257
|
+
message: `entry ${entry.number} has debit ${formatDecimal(debits)} and credit ${formatDecimal(credits)}`,
|
|
258
|
+
row: entry.row,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
read.push(entry);
|
|
262
|
+
}
|
|
263
|
+
if (currencies.size > 1) {
|
|
264
|
+
violations.push({ rule: 'one_currency', message: `the lines are in several company currencies (${[...currencies].join(', ')}); export one company at a time`, row: null });
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
format: 'journal-items',
|
|
268
|
+
currency: currencies.size === 1 ? [...currencies][0] : null,
|
|
269
|
+
accounts: [...accounts.values()],
|
|
270
|
+
contacts: [...contacts.values()],
|
|
271
|
+
entries: read,
|
|
272
|
+
opening: [],
|
|
273
|
+
violations,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** `YYYY-MM-DD`, or the same followed by a time, refusing anything else. */
|
|
277
|
+
function isoDate(value, row) {
|
|
278
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:[ T].*)?$/.exec(value);
|
|
279
|
+
if (match === null)
|
|
280
|
+
throw new BooksFileError('invalid_value', `"${value}" is not a date written YYYY-MM-DD`, row);
|
|
281
|
+
const iso = `${match[1]}-${match[2]}-${match[3]}`;
|
|
282
|
+
const parsed = new Date(`${iso}T00:00:00Z`);
|
|
283
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== iso) {
|
|
284
|
+
throw new BooksFileError('invalid_value', `${value} is not a date of the calendar`, row);
|
|
285
|
+
}
|
|
286
|
+
return iso;
|
|
287
|
+
}
|
|
288
|
+
//# 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/dist/types.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a set of books is once it has been read out of the software that kept
|
|
3
|
+
* them: accounts, parties, entries and an opening balance. The shape is
|
|
4
|
+
* declared here and imported from nowhere — it names no table and no ledger.
|
|
5
|
+
*
|
|
6
|
+
* It is, field for field, the shape every reader of an accounting export in
|
|
7
|
+
* this repository declares for itself (`@ekwo-ai/fec`, and the readers beside
|
|
8
|
+
* this one), so whatever takes one takes the others. It is not a shared
|
|
9
|
+
* abstraction: none imports another, and a reader that has nothing to say for
|
|
10
|
+
* a part of it returns that part empty.
|
|
11
|
+
*
|
|
12
|
+
* Every amount is a **decimal string**, never negative: a line carries a debit
|
|
13
|
+
* or a credit, and the side is the sign. No figure passes through a `number`.
|
|
14
|
+
*/
|
|
15
|
+
/** ISO 8601 calendar date, `YYYY-MM-DD`. */
|
|
16
|
+
export type IsoDate = string;
|
|
17
|
+
/** A fixed-point number, `1234.50`, never negative. */
|
|
18
|
+
export type Decimal = string;
|
|
19
|
+
/** One account of the chart the books were kept on, in the words of that software. */
|
|
20
|
+
export interface ImportedAccount {
|
|
21
|
+
code: string;
|
|
22
|
+
name: string | null;
|
|
23
|
+
/** What the source calls its kind, verbatim. Null when it says nothing. */
|
|
24
|
+
type: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** A customer, a supplier or anybody else a line names. */
|
|
27
|
+
export interface ImportedContact {
|
|
28
|
+
/** How the source identifies the party on a line: its code, or its name where it has no code. */
|
|
29
|
+
code: string;
|
|
30
|
+
name: string;
|
|
31
|
+
vatNumber: string | null;
|
|
32
|
+
registrationNumber: string | null;
|
|
33
|
+
email: string | null;
|
|
34
|
+
/** ISO 3166-1 alpha-2, when the source writes one. Never inferred. */
|
|
35
|
+
country: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ImportedLine {
|
|
38
|
+
/** The account code in the source's chart. */
|
|
39
|
+
account: string;
|
|
40
|
+
/** The `code` of an {@link ImportedContact}, or null. */
|
|
41
|
+
contact: string | null;
|
|
42
|
+
label: string | null;
|
|
43
|
+
debit: Decimal;
|
|
44
|
+
credit: Decimal;
|
|
45
|
+
/** A foreign currency the line was in, when the source says so. */
|
|
46
|
+
currency: string | null;
|
|
47
|
+
/** The amount in that currency, never negative. */
|
|
48
|
+
amountCurrency: Decimal | null;
|
|
49
|
+
dueDate: IsoDate | null;
|
|
50
|
+
/** The reconciliation mark the source put on the line, as written. */
|
|
51
|
+
matching: string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface ImportedEntry {
|
|
54
|
+
/** The journal code in the source, or null when it has none. */
|
|
55
|
+
journal: string | null;
|
|
56
|
+
journalName: string | null;
|
|
57
|
+
/** The entry's number in the source. */
|
|
58
|
+
number: string | null;
|
|
59
|
+
date: IsoDate;
|
|
60
|
+
reference: string | null;
|
|
61
|
+
description: string | null;
|
|
62
|
+
lines: ImportedLine[];
|
|
63
|
+
/** The row of the file the entry starts on, from 1, for a message. */
|
|
64
|
+
row: number | null;
|
|
65
|
+
}
|
|
66
|
+
export interface Violation {
|
|
67
|
+
rule: string;
|
|
68
|
+
message: string;
|
|
69
|
+
/** The row of the file, from 1, or null for a rule about the whole file. */
|
|
70
|
+
row: number | null;
|
|
71
|
+
}
|
|
72
|
+
export interface ImportedBooks {
|
|
73
|
+
/** Which reader: `journal-items` here. */
|
|
74
|
+
format: string;
|
|
75
|
+
/** The currency the file says its amounts are in. Null when it says none: never guessed. */
|
|
76
|
+
currency: string | null;
|
|
77
|
+
accounts: ImportedAccount[];
|
|
78
|
+
contacts: ImportedContact[];
|
|
79
|
+
entries: ImportedEntry[];
|
|
80
|
+
/** A trial balance: one line per account, to become the opening entry of a year. */
|
|
81
|
+
opening: ImportedLine[];
|
|
82
|
+
violations: Violation[];
|
|
83
|
+
}
|
|
84
|
+
export type Encoding = 'utf-8' | 'iso-8859-1';
|
|
85
|
+
export interface ReadOptions {
|
|
86
|
+
/**
|
|
87
|
+
* How bytes are decoded. Default `utf-8`, fatally: bytes that are not UTF-8
|
|
88
|
+
* are refused by name, never read as something else. A spreadsheet that
|
|
89
|
+
* saves in Latin-1 is said to be so, because every sequence of bytes is
|
|
90
|
+
* valid Latin-1 and a guess would always succeed.
|
|
91
|
+
*/
|
|
92
|
+
encoding?: Encoding;
|
|
93
|
+
/** Refused beyond this many bytes, before anything is read. Default 64 MiB. */
|
|
94
|
+
maxBytes?: number;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,uDAAuD;AACvD,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sEAAsE;IACtE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mDAAmD;IACnD,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,wCAAwC;IACxC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,sEAAsE;IACtE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,oFAAoF;IACpF,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a set of books is once it has been read out of the software that kept
|
|
3
|
+
* them: accounts, parties, entries and an opening balance. The shape is
|
|
4
|
+
* declared here and imported from nowhere — it names no table and no ledger.
|
|
5
|
+
*
|
|
6
|
+
* It is, field for field, the shape every reader of an accounting export in
|
|
7
|
+
* this repository declares for itself (`@ekwo-ai/fec`, and the readers beside
|
|
8
|
+
* this one), so whatever takes one takes the others. It is not a shared
|
|
9
|
+
* abstraction: none imports another, and a reader that has nothing to say for
|
|
10
|
+
* a part of it returns that part empty.
|
|
11
|
+
*
|
|
12
|
+
* Every amount is a **decimal string**, never negative: a line carries a debit
|
|
13
|
+
* or a credit, and the side is the sign. No figure passes through a `number`.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ekwo-ai/journal-items",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Reads an export of journal items as CSV — one row per line of an entry, with its entry, journal, date, account, partner, debit and credit — with the chart of accounts and the partners exported beside it, into plain objects in TypeScript: amounts as decimal strings, and what does not add up. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"journal-items",
|
|
7
|
+
"general-ledger",
|
|
8
|
+
"csv",
|
|
9
|
+
"accounting",
|
|
10
|
+
"bookkeeping",
|
|
11
|
+
"migration",
|
|
12
|
+
"import",
|
|
13
|
+
"parser"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Ekwo AI",
|
|
17
|
+
"homepage": "https://github.com/Ekwo-ai/ekwo-os/tree/main/packages/formats/journal-items#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Ekwo-ai/ekwo-os.git",
|
|
21
|
+
"directory": "packages/formats/journal-items"
|
|
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
|
+
}
|