@fin.cx/compatibility 1.1.0 → 2.0.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/extf/index.d.ts +31 -3
- package/dist_ts/extf/index.js +68 -4
- package/package.json +2 -2
- package/readme.md +4 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/extf/index.ts +93 -5
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/compatibility',
|
|
6
|
-
version: '
|
|
6
|
+
version: '2.0.0',
|
|
7
7
|
description: 'Exchange formats of third-party accounting systems: the EXTF Buchungsstapel in Windows-1252 on the rows of @fin.cx/skr, and the document-image archive (document.xml and ZIP)'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx1QkFBdUI7SUFDN0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLCtLQUErSztDQUM3TCxDQUFBIn0=
|
package/dist_ts/extf/index.d.ts
CHANGED
|
@@ -33,7 +33,9 @@ export declare const bookingsOfRows: (rowsArg: readonly IDatevBuchungsstapelRow[
|
|
|
33
33
|
export declare const differingBookingCount: (leftArg: readonly IExportBooking[], rightArg: readonly IExportBooking[]) => number;
|
|
34
34
|
/**
|
|
35
35
|
* Checks every draft against the account policy and turns the drafts into
|
|
36
|
-
* Buchungsstapel rows (`@fin.cx/skr` `journalDraftsToDatevRows
|
|
36
|
+
* Buchungsstapel rows (`@fin.cx/skr` `journalDraftsToDatevRows` under that
|
|
37
|
+
* policy, not fixed): its Automatikkonten and VAT accounts decide which VAT
|
|
38
|
+
* lines fold into their base.
|
|
37
39
|
* A draft the policy refuses throws `invalid_bookings`, naming the draft; a
|
|
38
40
|
* failure of the conversion throws `export_failed`.
|
|
39
41
|
*/
|
|
@@ -47,6 +49,32 @@ export interface IBuchungsstapelOptions {
|
|
|
47
49
|
/** the file's name, for example `extfFilename('2026-03')` */
|
|
48
50
|
filename: string;
|
|
49
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* A character a Buchungsstapel cannot hold in Windows-1252, written as `?`,
|
|
54
|
+
* with where it stands: `offset` in the whole text, and for a character of a
|
|
55
|
+
* booking row the index of that row in `rows` (from 0) and the number of its
|
|
56
|
+
* DATEV field (from 1, 14 is the Buchungstext). A character of the header
|
|
57
|
+
* line has neither.
|
|
58
|
+
*/
|
|
59
|
+
export interface IBuchungsstapelReplacement extends IReplacedCharacter {
|
|
60
|
+
row?: number;
|
|
61
|
+
field?: number;
|
|
62
|
+
}
|
|
63
|
+
/** A character of one booking row the file cannot hold, as `replacedCharactersOfRows` reports it. */
|
|
64
|
+
export interface IRowReplacement {
|
|
65
|
+
/** the index of the row in the rows given, from 0 */
|
|
66
|
+
row: number;
|
|
67
|
+
/** the number of the DATEV field it stands in, from 1 (14 is the Buchungstext) */
|
|
68
|
+
field: number;
|
|
69
|
+
character: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The characters of each row that a Buchungsstapel in Windows-1252 cannot
|
|
73
|
+
* hold, in row order, without writing a file: the rows' lines as the writer
|
|
74
|
+
* writes them (`@fin.cx/skr` `datevRowFields`), so a character a field is cut
|
|
75
|
+
* off before is not named. A row the writer refuses throws as it does.
|
|
76
|
+
*/
|
|
77
|
+
export declare const replacedCharactersOfRows: (rowsArg: readonly IDatevBuchungsstapelRow[]) => IRowReplacement[];
|
|
50
78
|
export interface IBuchungsstapelFile {
|
|
51
79
|
filename: string;
|
|
52
80
|
/** the file as text, lines ending in CRLF */
|
|
@@ -55,8 +83,8 @@ export interface IBuchungsstapelFile {
|
|
|
55
83
|
bytes: Uint8Array;
|
|
56
84
|
rows: IDatevBuchungsstapelRow[];
|
|
57
85
|
bookings: IExportBooking[];
|
|
58
|
-
/** the characters Windows-1252 cannot write, each written as
|
|
59
|
-
replacedCharacters:
|
|
86
|
+
/** the characters Windows-1252 cannot write, each written as `?`, with the row and field it stands in */
|
|
87
|
+
replacedCharacters: IBuchungsstapelReplacement[];
|
|
60
88
|
}
|
|
61
89
|
/** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
|
|
62
90
|
export declare const writeBuchungsstapel: (optionsArg: IBuchungsstapelOptions) => IBuchungsstapelFile;
|
package/dist_ts/extf/index.js
CHANGED
|
@@ -50,7 +50,9 @@ export const differingBookingCount = (leftArg, rightArg) => {
|
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
52
|
* Checks every draft against the account policy and turns the drafts into
|
|
53
|
-
* Buchungsstapel rows (`@fin.cx/skr` `journalDraftsToDatevRows
|
|
53
|
+
* Buchungsstapel rows (`@fin.cx/skr` `journalDraftsToDatevRows` under that
|
|
54
|
+
* policy, not fixed): its Automatikkonten and VAT accounts decide which VAT
|
|
55
|
+
* lines fold into their base.
|
|
54
56
|
* A draft the policy refuses throws `invalid_bookings`, naming the draft; a
|
|
55
57
|
* failure of the conversion throws `export_failed`.
|
|
56
58
|
*/
|
|
@@ -62,17 +64,79 @@ export const datevRowsOf = (draftsArg, policyArg) => {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
try {
|
|
65
|
-
|
|
67
|
+
// the rows consolidate under the same policy the drafts are checked against, not the chart's default one
|
|
68
|
+
return plugins.skrCore.journalDraftsToDatevRows([...draftsArg], { festschreibung: false, policy: policyArg });
|
|
66
69
|
}
|
|
67
70
|
catch (error) {
|
|
68
71
|
throw new CompatibilityError('export_failed', error instanceof Error ? error.message : String(error));
|
|
69
72
|
}
|
|
70
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Where the characters of one row stand in its line: the line is the fields
|
|
76
|
+
* joined with `;` (`@fin.cx/skr` `datevRowFields`), so field n begins after
|
|
77
|
+
* the lengths of the fields before it and one separator each.
|
|
78
|
+
*/
|
|
79
|
+
const fieldStartsOf = (fieldsArg) => {
|
|
80
|
+
const starts = [];
|
|
81
|
+
let offset = 0;
|
|
82
|
+
for (const field of fieldsArg) {
|
|
83
|
+
starts.push(offset);
|
|
84
|
+
offset += field.length + 1;
|
|
85
|
+
}
|
|
86
|
+
return starts;
|
|
87
|
+
};
|
|
88
|
+
const fieldAt = (startsArg, offsetArg) => {
|
|
89
|
+
let field = 0;
|
|
90
|
+
while (field + 1 < startsArg.length && startsArg[field + 1] <= offsetArg) {
|
|
91
|
+
field += 1;
|
|
92
|
+
}
|
|
93
|
+
return field + 1;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* The characters of each row that a Buchungsstapel in Windows-1252 cannot
|
|
97
|
+
* hold, in row order, without writing a file: the rows' lines as the writer
|
|
98
|
+
* writes them (`@fin.cx/skr` `datevRowFields`), so a character a field is cut
|
|
99
|
+
* off before is not named. A row the writer refuses throws as it does.
|
|
100
|
+
*/
|
|
101
|
+
export const replacedCharactersOfRows = (rowsArg) => {
|
|
102
|
+
const replacements = [];
|
|
103
|
+
rowsArg.forEach((rowArg, rowIndex) => {
|
|
104
|
+
const fields = plugins.skrCore.datevRowFields(rowArg);
|
|
105
|
+
const starts = fieldStartsOf(fields);
|
|
106
|
+
for (const replaced of encodeWindows1252(fields.join(';')).replaced) {
|
|
107
|
+
replacements.push({ row: rowIndex, field: fieldAt(starts, replaced.offset), character: replaced.character });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return replacements;
|
|
111
|
+
};
|
|
71
112
|
/** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
|
|
72
113
|
export const writeBuchungsstapel = (optionsArg) => {
|
|
73
114
|
const rows = datevRowsOf(optionsArg.drafts, optionsArg.policy);
|
|
74
115
|
const { csv } = plugins.skrCore.writeExtfBuchungsstapel(rows, optionsArg.header);
|
|
75
116
|
const { bytes, replaced } = encodeWindows1252(csv);
|
|
76
|
-
|
|
117
|
+
// the file is the header line, the caption line and one line per row, each ending in CRLF (`writeExtfBuchungsstapel`)
|
|
118
|
+
const rowLines = rows.map((rowArg) => plugins.skrCore.datevRowFields(rowArg));
|
|
119
|
+
const [headerLine = '', captionLine = ''] = csv.split('\r\n', 2);
|
|
120
|
+
const rowsStart = headerLine.length + 2 + captionLine.length + 2;
|
|
121
|
+
const lineStarts = [];
|
|
122
|
+
let offset = rowsStart;
|
|
123
|
+
for (const fields of rowLines) {
|
|
124
|
+
lineStarts.push(offset);
|
|
125
|
+
offset += fields.join(';').length + 2;
|
|
126
|
+
}
|
|
127
|
+
if (offset !== csv.length || rowLines.some((fieldsArg, indexArg) => csv.slice(lineStarts[indexArg], lineStarts[indexArg] + fieldsArg.join(';').length) !== fieldsArg.join(';'))) {
|
|
128
|
+
throw new CompatibilityError('export_failed', 'the rows of the Buchungsstapel are not the lines @fin.cx/skr writes for them');
|
|
129
|
+
}
|
|
130
|
+
const replacedCharacters = replaced.map((replacedArg) => {
|
|
131
|
+
if (replacedArg.offset < rowsStart) {
|
|
132
|
+
return { ...replacedArg };
|
|
133
|
+
}
|
|
134
|
+
let row = 0;
|
|
135
|
+
while (row + 1 < lineStarts.length && lineStarts[row + 1] <= replacedArg.offset) {
|
|
136
|
+
row += 1;
|
|
137
|
+
}
|
|
138
|
+
return { ...replacedArg, row, field: fieldAt(fieldStartsOf(rowLines[row]), replacedArg.offset - lineStarts[row]) };
|
|
139
|
+
});
|
|
140
|
+
return { filename: optionsArg.filename, csv, bytes, rows, bookings: bookingsOfRows(rows), replacedCharacters };
|
|
77
141
|
};
|
|
78
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
142
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9leHRmL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNqRCxPQUFPLEVBQUUsaUJBQWlCLEVBQTJCLE1BQU0sc0JBQXNCLENBQUM7QUFFbEYsT0FBTyxFQUFFLGtCQUFrQixFQUFnQyxNQUFNLGFBQWEsQ0FBQztBQUsvRSxzRkFBc0Y7QUFDdEYsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsZ0NBQWdDLENBQUM7QUFFbEUsd0ZBQXdGO0FBQ3hGLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLFFBQWdCLEVBQVUsRUFBRSxDQUFDLHVCQUF1QixRQUFRLE1BQU0sQ0FBQztBQWtCaEcsMEVBQTBFO0FBQzFFLE1BQU0sVUFBVSxHQUFHLENBQUMsT0FBZSxFQUFVLEVBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0FBRWxILDRDQUE0QztBQUM1QyxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUEyQyxFQUFvQixFQUFFLENBQzlGLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDdkIsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXO0lBQy9CLFNBQVMsRUFBRSxNQUFNLENBQUMsU0FBUztJQUMzQixLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUs7SUFDbkIsVUFBVSxFQUFFLE1BQU0sQ0FBQyxVQUFVO0lBQzdCLEtBQUssRUFBRSxNQUFNLENBQUMsWUFBWTtJQUMxQixJQUFJLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxXQUFXLEVB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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fin.cx/compatibility",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Exchange formats of third-party accounting systems: the EXTF Buchungsstapel in Windows-1252 on the rows of @fin.cx/skr, and the document-image archive (document.xml and ZIP)",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://code.foss.global/fin.cx/compatibility#readme",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@fin.cx/skr": "^
|
|
45
|
+
"@fin.cx/skr": "^5.2.0",
|
|
46
46
|
"@push.rocks/smartarchive": "^5.4.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
package/readme.md
CHANGED
|
@@ -41,9 +41,12 @@ const file = extf.writeBuchungsstapel({
|
|
|
41
41
|
});
|
|
42
42
|
file.bytes; // Uint8Array in Windows-1252, served as extf.EXTF_CONTENT_TYPE
|
|
43
43
|
file.bookings; // what each row says, for keeping and comparing
|
|
44
|
-
file.replacedCharacters; // [{ offset, character: 'ő' }, …]: written as "?"
|
|
44
|
+
file.replacedCharacters; // [{ offset, character: 'ő', row: 1, field: 14 }, …]: written as "?"
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
- **Where a character that became `?` stands.** Each entry of `replacedCharacters` names its `offset` in the text and, for a booking row, the index of that row in `rows` (from 0) and the number of its DATEV field (from 1; 14 is the Buchungstext). A character of the header line (the label) has neither.
|
|
48
|
+
- **The same without a file.** `extf.replacedCharactersOfRows(rows)` names the characters of each row the file could not hold (`{ row, field, character }`), without a header and without writing anything: for a preview, before the consultant and client numbers of the header are known. It reads each row's line as the writer writes it (`@fin.cx/skr` `datevRowFields`), so a character a field is cut off before (the Buchungstext keeps 60 characters) is not named.
|
|
49
|
+
|
|
47
50
|
- **A draft the account policy refuses** throws `CompatibilityError` with code `invalid_bookings` and a message naming the draft.
|
|
48
51
|
- **A failed conversion** throws code `export_failed`.
|
|
49
52
|
- **`differingBookingCount(left, right)`** counts the rows two Stapel of one period do not share. It compares each booking's day, amount, side, accounts, BU key and Belegfeld 1, as a multiset, and ignores wording and document links.
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/compatibility',
|
|
6
|
-
version: '
|
|
6
|
+
version: '2.0.0',
|
|
7
7
|
description: 'Exchange formats of third-party accounting systems: the EXTF Buchungsstapel in Windows-1252 on the rows of @fin.cx/skr, and the document-image archive (document.xml and ZIP)'
|
|
8
8
|
}
|
package/ts/extf/index.ts
CHANGED
|
@@ -77,7 +77,9 @@ export const differingBookingCount = (leftArg: readonly IExportBooking[], rightA
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Checks every draft against the account policy and turns the drafts into
|
|
80
|
-
* Buchungsstapel rows (`@fin.cx/skr` `journalDraftsToDatevRows
|
|
80
|
+
* Buchungsstapel rows (`@fin.cx/skr` `journalDraftsToDatevRows` under that
|
|
81
|
+
* policy, not fixed): its Automatikkonten and VAT accounts decide which VAT
|
|
82
|
+
* lines fold into their base.
|
|
81
83
|
* A draft the policy refuses throws `invalid_bookings`, naming the draft; a
|
|
82
84
|
* failure of the conversion throws `export_failed`.
|
|
83
85
|
*/
|
|
@@ -89,7 +91,8 @@ export const datevRowsOf = (draftsArg: readonly plugins.skrCore.IJournalDraft[],
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
try {
|
|
92
|
-
|
|
94
|
+
// the rows consolidate under the same policy the drafts are checked against, not the chart's default one
|
|
95
|
+
return plugins.skrCore.journalDraftsToDatevRows([...draftsArg], { festschreibung: false, policy: policyArg });
|
|
93
96
|
} catch (error) {
|
|
94
97
|
throw new CompatibilityError('export_failed', error instanceof Error ? error.message : String(error));
|
|
95
98
|
}
|
|
@@ -105,6 +108,68 @@ export interface IBuchungsstapelOptions {
|
|
|
105
108
|
filename: string;
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
/**
|
|
112
|
+
* A character a Buchungsstapel cannot hold in Windows-1252, written as `?`,
|
|
113
|
+
* with where it stands: `offset` in the whole text, and for a character of a
|
|
114
|
+
* booking row the index of that row in `rows` (from 0) and the number of its
|
|
115
|
+
* DATEV field (from 1, 14 is the Buchungstext). A character of the header
|
|
116
|
+
* line has neither.
|
|
117
|
+
*/
|
|
118
|
+
export interface IBuchungsstapelReplacement extends IReplacedCharacter {
|
|
119
|
+
row?: number;
|
|
120
|
+
field?: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A character of one booking row the file cannot hold, as `replacedCharactersOfRows` reports it. */
|
|
124
|
+
export interface IRowReplacement {
|
|
125
|
+
/** the index of the row in the rows given, from 0 */
|
|
126
|
+
row: number;
|
|
127
|
+
/** the number of the DATEV field it stands in, from 1 (14 is the Buchungstext) */
|
|
128
|
+
field: number;
|
|
129
|
+
character: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Where the characters of one row stand in its line: the line is the fields
|
|
134
|
+
* joined with `;` (`@fin.cx/skr` `datevRowFields`), so field n begins after
|
|
135
|
+
* the lengths of the fields before it and one separator each.
|
|
136
|
+
*/
|
|
137
|
+
const fieldStartsOf = (fieldsArg: readonly string[]): number[] => {
|
|
138
|
+
const starts: number[] = [];
|
|
139
|
+
let offset = 0;
|
|
140
|
+
for (const field of fieldsArg) {
|
|
141
|
+
starts.push(offset);
|
|
142
|
+
offset += field.length + 1;
|
|
143
|
+
}
|
|
144
|
+
return starts;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const fieldAt = (startsArg: readonly number[], offsetArg: number): number => {
|
|
148
|
+
let field = 0;
|
|
149
|
+
while (field + 1 < startsArg.length && startsArg[field + 1] <= offsetArg) {
|
|
150
|
+
field += 1;
|
|
151
|
+
}
|
|
152
|
+
return field + 1;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The characters of each row that a Buchungsstapel in Windows-1252 cannot
|
|
157
|
+
* hold, in row order, without writing a file: the rows' lines as the writer
|
|
158
|
+
* writes them (`@fin.cx/skr` `datevRowFields`), so a character a field is cut
|
|
159
|
+
* off before is not named. A row the writer refuses throws as it does.
|
|
160
|
+
*/
|
|
161
|
+
export const replacedCharactersOfRows = (rowsArg: readonly IDatevBuchungsstapelRow[]): IRowReplacement[] => {
|
|
162
|
+
const replacements: IRowReplacement[] = [];
|
|
163
|
+
rowsArg.forEach((rowArg, rowIndex) => {
|
|
164
|
+
const fields = plugins.skrCore.datevRowFields(rowArg);
|
|
165
|
+
const starts = fieldStartsOf(fields);
|
|
166
|
+
for (const replaced of encodeWindows1252(fields.join(';')).replaced) {
|
|
167
|
+
replacements.push({ row: rowIndex, field: fieldAt(starts, replaced.offset), character: replaced.character });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return replacements;
|
|
171
|
+
};
|
|
172
|
+
|
|
108
173
|
export interface IBuchungsstapelFile {
|
|
109
174
|
filename: string;
|
|
110
175
|
/** the file as text, lines ending in CRLF */
|
|
@@ -113,8 +178,8 @@ export interface IBuchungsstapelFile {
|
|
|
113
178
|
bytes: Uint8Array;
|
|
114
179
|
rows: IDatevBuchungsstapelRow[];
|
|
115
180
|
bookings: IExportBooking[];
|
|
116
|
-
/** the characters Windows-1252 cannot write, each written as
|
|
117
|
-
replacedCharacters:
|
|
181
|
+
/** the characters Windows-1252 cannot write, each written as `?`, with the row and field it stands in */
|
|
182
|
+
replacedCharacters: IBuchungsstapelReplacement[];
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
/** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
|
|
@@ -122,5 +187,28 @@ export const writeBuchungsstapel = (optionsArg: IBuchungsstapelOptions): IBuchun
|
|
|
122
187
|
const rows = datevRowsOf(optionsArg.drafts, optionsArg.policy);
|
|
123
188
|
const { csv } = plugins.skrCore.writeExtfBuchungsstapel(rows, optionsArg.header);
|
|
124
189
|
const { bytes, replaced } = encodeWindows1252(csv);
|
|
125
|
-
|
|
190
|
+
// the file is the header line, the caption line and one line per row, each ending in CRLF (`writeExtfBuchungsstapel`)
|
|
191
|
+
const rowLines = rows.map((rowArg) => plugins.skrCore.datevRowFields(rowArg));
|
|
192
|
+
const [headerLine = '', captionLine = ''] = csv.split('\r\n', 2);
|
|
193
|
+
const rowsStart = headerLine.length + 2 + captionLine.length + 2;
|
|
194
|
+
const lineStarts: number[] = [];
|
|
195
|
+
let offset = rowsStart;
|
|
196
|
+
for (const fields of rowLines) {
|
|
197
|
+
lineStarts.push(offset);
|
|
198
|
+
offset += fields.join(';').length + 2;
|
|
199
|
+
}
|
|
200
|
+
if (offset !== csv.length || rowLines.some((fieldsArg, indexArg) => csv.slice(lineStarts[indexArg], lineStarts[indexArg] + fieldsArg.join(';').length) !== fieldsArg.join(';'))) {
|
|
201
|
+
throw new CompatibilityError('export_failed', 'the rows of the Buchungsstapel are not the lines @fin.cx/skr writes for them');
|
|
202
|
+
}
|
|
203
|
+
const replacedCharacters = replaced.map((replacedArg): IBuchungsstapelReplacement => {
|
|
204
|
+
if (replacedArg.offset < rowsStart) {
|
|
205
|
+
return { ...replacedArg };
|
|
206
|
+
}
|
|
207
|
+
let row = 0;
|
|
208
|
+
while (row + 1 < lineStarts.length && lineStarts[row + 1] <= replacedArg.offset) {
|
|
209
|
+
row += 1;
|
|
210
|
+
}
|
|
211
|
+
return { ...replacedArg, row, field: fieldAt(fieldStartsOf(rowLines[row]), replacedArg.offset - lineStarts[row]) };
|
|
212
|
+
});
|
|
213
|
+
return { filename: optionsArg.filename, csv, bytes, rows, bookings: bookingsOfRows(rows), replacedCharacters };
|
|
126
214
|
};
|