@fin.cx/compatibility 4.0.0 → 5.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/error.d.ts +11 -3
- package/dist_ts/error.js +1 -1
- package/dist_ts/extf/index.d.ts +117 -3
- package/dist_ts/extf/index.js +301 -27
- package/package.json +3 -3
- package/readme.md +9 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/error.ts +11 -3
- package/ts/extf/index.ts +396 -27
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/compatibility',
|
|
6
|
-
version: '
|
|
6
|
+
version: '5.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/error.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Why an exchange file could not be written:
|
|
3
|
-
* - `invalid_bookings`: a draft the account policy refuses
|
|
3
|
+
* - `invalid_bookings`: a draft the account policy refuses, the message names it; a general reversal among the
|
|
4
|
+
* bookings handed over to a correction Buchungsstapel (a correction file passed as the state handed over); or a row
|
|
5
|
+
* or booking a Buchungsstapel does not hold (no day, an amount that is not positive) or whose Belegdatum lies
|
|
6
|
+
* outside the period of the header, named;
|
|
7
|
+
* - `invalid_header`: a header whose period ends before it begins or does not lie within the one Wirtschaftsjahr
|
|
8
|
+
* beginning on its Wirtschaftsjahr-Beginn;
|
|
4
9
|
* - `export_failed`: the conversion into rows failed;
|
|
5
|
-
* - `invalid_documents`: a document of a document-image archive DATEV does not take (its GUID, its format), or none
|
|
10
|
+
* - `invalid_documents`: a document of a document-image archive DATEV does not take (its GUID, its format), or none;
|
|
11
|
+
* - `nothing_to_correct`: a correction Buchungsstapel whose current bookings are the ones handed over;
|
|
12
|
+
* - `handed_over_cost_centres_unknown`: a correction of a period whose bookings handed over include one whose cost
|
|
13
|
+
* centres its record does not state (stored before 4.1) and no rows sent were given; the message names it.
|
|
6
14
|
*/
|
|
7
|
-
export type TCompatibilityErrorCode = 'invalid_bookings' | 'export_failed' | 'invalid_documents';
|
|
15
|
+
export type TCompatibilityErrorCode = 'invalid_bookings' | 'invalid_header' | 'export_failed' | 'invalid_documents' | 'nothing_to_correct' | 'handed_over_cost_centres_unknown';
|
|
8
16
|
export declare class CompatibilityError extends Error {
|
|
9
17
|
readonly code: TCompatibilityErrorCode;
|
|
10
18
|
constructor(codeArg: TCompatibilityErrorCode, messageArg: string);
|
package/dist_ts/error.js
CHANGED
|
@@ -5,4 +5,4 @@ export class CompatibilityError extends Error {
|
|
|
5
5
|
this.code = codeArg;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9lcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFnQkEsTUFBTSxPQUFPLGtCQUFtQixTQUFRLEtBQUs7SUFHM0MsWUFBWSxPQUFnQyxFQUFFLFVBQWtCO1FBQzlELEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNsQixJQUFJLENBQUMsSUFBSSxHQUFHLG9CQUFvQixDQUFDO1FBQ2pDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDO0lBQ3RCLENBQUM7Q0FDRiJ9
|
package/dist_ts/extf/index.d.ts
CHANGED
|
@@ -21,14 +21,32 @@ export interface IExportBooking {
|
|
|
21
21
|
text: string;
|
|
22
22
|
/** the document GUID the row links to (field 20, `BEDI "GUID"`) */
|
|
23
23
|
documentGuid?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The cost centres of the row (fields 37 and 38, KOST1 and KOST2), empty where it states none. `bookingsOfRows`
|
|
26
|
+
* records both on every booking; a booking stored before 4.1 has neither, although the row sent stated them: its
|
|
27
|
+
* cost centres are not known. `differingBookingCount` compares it without them; a correction refuses it
|
|
28
|
+
* (`handed_over_cost_centres_unknown`) unless it is given the rows sent (`handedOverRows`).
|
|
29
|
+
*/
|
|
30
|
+
kost1?: string;
|
|
31
|
+
kost2?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The row is a general reversal (field 118 "Generalumkehr", written "1"): it takes back the booking of the same
|
|
34
|
+
* fields on DATEV's side. Absent on an ordinary row.
|
|
35
|
+
*/
|
|
36
|
+
generalumkehr?: true;
|
|
24
37
|
}
|
|
25
38
|
/** The bookings of rows, in their order. */
|
|
26
39
|
export declare const bookingsOfRows: (rowsArg: readonly IDatevBuchungsstapelRow[]) => IExportBooking[];
|
|
27
40
|
/**
|
|
28
41
|
* How many rows two Buchungsstapel of one period do not share, compared on
|
|
29
|
-
* what a booking says (its day, amount, side, accounts, BU key
|
|
30
|
-
*
|
|
31
|
-
* multiset: a row that stands twice in one and once in the other differs once.
|
|
42
|
+
* what a booking says (its day, amount, side, accounts, BU key, Belegfeld 1,
|
|
43
|
+
* cost centres, and whether it is a general reversal) and not on its wording or its document link. The rows are
|
|
44
|
+
* compared as a multiset: a row that stands twice in one and once in the other differs once. Both sides are compared
|
|
45
|
+
* as a file states them (`comparableOf`: a `"` as `'`, Belegfeld 1 cut to 36 characters, a character Windows-1252
|
|
46
|
+
* cannot hold as `?`), so bookings stored of rows before writing and bookings read from a file compare alike. A
|
|
47
|
+
* booking stored before 4.1, which does not record its cost centres, never differs on them (`pairBookings`); compare
|
|
48
|
+
* `bookingsOfRows` of the rows sent for an answer that counts them. A booking a Buchungsstapel cannot hold (no day,
|
|
49
|
+
* no positive amount) is refused (`invalid_bookings`).
|
|
32
50
|
*/
|
|
33
51
|
export declare const differingBookingCount: (leftArg: readonly IExportBooking[], rightArg: readonly IExportBooking[]) => number;
|
|
34
52
|
/**
|
|
@@ -81,10 +99,106 @@ export interface IBuchungsstapelFile {
|
|
|
81
99
|
csv: string;
|
|
82
100
|
/** the file in Windows-1252, as it is handed over */
|
|
83
101
|
bytes: Uint8Array;
|
|
102
|
+
/** the rows as they were given to the writer; the file states them as `rowsAsWritten` does */
|
|
84
103
|
rows: IDatevBuchungsstapelRow[];
|
|
104
|
+
/**
|
|
105
|
+
* The bookings the file states, read back from its bytes (`@fin.cx/skr` `readExtfBuchungsstapel`): a `"` in a
|
|
106
|
+
* text field as `'`, Belegfeld 1 cut to 36 characters, a character Windows-1252 cannot hold as `?`. What a file
|
|
107
|
+
* read back is compared with, and what a correction's `handedOver` is (4.1 and before: the rows' bookings before
|
|
108
|
+
* writing).
|
|
109
|
+
*/
|
|
85
110
|
bookings: IExportBooking[];
|
|
86
111
|
/** the characters Windows-1252 cannot write, each written as `?`, with the row and field it stands in */
|
|
87
112
|
replacedCharacters: IBuchungsstapelReplacement[];
|
|
88
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Rows as a Buchungsstapel file states them: each row written by the writer (`@fin.cx/skr` `writeExtfBuchungsstapel`,
|
|
116
|
+
* which writes a `"` in a text field as `'` and cuts a field to its DATEV length), encoded in Windows-1252 (a
|
|
117
|
+
* character it cannot hold written `?`, `encodeWindows1252`), and read back (`@fin.cx/skr` `readExtfBuchungsstapel`),
|
|
118
|
+
* in the order given. Every rule of the writer, the encoding and the reader applies because the rows go through them,
|
|
119
|
+
* not through a copy of their rules. The rows of each calendar year are written as one file whose header spans their
|
|
120
|
+
* days; the header states nothing a row is read by beyond its days. A row the writer or the reader refuses throws as
|
|
121
|
+
* it does.
|
|
122
|
+
*/
|
|
123
|
+
export declare const rowsAsWritten: (rowsArg: readonly IDatevBuchungsstapelRow[]) => IDatevBuchungsstapelRow[];
|
|
124
|
+
/**
|
|
125
|
+
* The bookings of rows as a Buchungsstapel file states them (`rowsAsWritten`): what a file handed over, read back
|
|
126
|
+
* with `readExtfBuchungsstapel`, is compared with. `bookingsOfRows` of the rows before writing keeps what the file
|
|
127
|
+
* does not (a `"` in Belegfeld 1, a Belegfeld 1 longer than its 36 characters, a character Windows-1252 cannot
|
|
128
|
+
* hold), and Belegfeld 1 is compared, so such a booking would differ from the file forever.
|
|
129
|
+
*/
|
|
130
|
+
export declare const bookingsAsWritten: (rowsArg: readonly IDatevBuchungsstapelRow[]) => IExportBooking[];
|
|
89
131
|
/** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
|
|
90
132
|
export declare const writeBuchungsstapel: (optionsArg: IBuchungsstapelOptions) => IBuchungsstapelFile;
|
|
133
|
+
/**
|
|
134
|
+
* The row a booking handed over was written as, for a general reversal of it: every field the booking keeps
|
|
135
|
+
* (`IExportBooking`) in its place, the Belegdatum at midnight UTC as the writer reads it.
|
|
136
|
+
*/
|
|
137
|
+
export declare const rowOfBooking: (bookingArg: IExportBooking) => IDatevBuchungsstapelRow;
|
|
138
|
+
/**
|
|
139
|
+
* What a correction of a period handed over changes, compared as `differingBookingCount` compares: the bookings
|
|
140
|
+
* handed over that the current ones no longer hold (`reversed`, each once for each time it is missing), the ones they
|
|
141
|
+
* still hold (`kept`, as they were handed over) and the current rows the bookings handed over do not hold (`added`),
|
|
142
|
+
* each in its order. A booking that stands in both is kept: a changed text or document link is no changed booking,
|
|
143
|
+
* a changed cost centre is. The current rows are compared as a file states them (`bookingsAsWritten`), as the
|
|
144
|
+
* bookings handed over are; `added` are the rows themselves, which the writer writes. A booking handed over whose
|
|
145
|
+
* cost centres its record does not state (stored before 4.1) is refused (`handed_over_cost_centres_unknown`): pass
|
|
146
|
+
* `bookingsOfRows` of the rows sent instead.
|
|
147
|
+
*/
|
|
148
|
+
export declare const correctionOf: (handedOverArg: readonly IExportBooking[], currentArg: readonly IDatevBuchungsstapelRow[]) => {
|
|
149
|
+
reversed: IExportBooking[];
|
|
150
|
+
kept: IExportBooking[];
|
|
151
|
+
added: IDatevBuchungsstapelRow[];
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* The state of a period handed over, given once: the bookings stored of it (`handedOver`) or the rows of the Stapel
|
|
155
|
+
* sent (`handedOverRows`). A general reversal in it is refused: the state handed over is compared, not a correction
|
|
156
|
+
* file.
|
|
157
|
+
*/
|
|
158
|
+
export interface ICorrectionBuchungsstapelOptions {
|
|
159
|
+
/**
|
|
160
|
+
* The bookings of the period as they were handed over, as a whole: the bookings of its Buchungsstapel as stored
|
|
161
|
+
* (`IBuchungsstapelFile.bookings`), or after a correction the period's bookings as that correction left them
|
|
162
|
+
* (`bookingsAfter`). Each must state its cost centres: one stored before 4.1 does not, and is refused
|
|
163
|
+
* (`handed_over_cost_centres_unknown`); pass the rows sent for it. Bookings stored by any version, before or after
|
|
164
|
+
* writing, are compared as a file states them (`comparableOf`), so a reference the writer changed is no change.
|
|
165
|
+
*/
|
|
166
|
+
handedOver?: readonly IExportBooking[];
|
|
167
|
+
/**
|
|
168
|
+
* The rows of the Buchungsstapel as it was sent: the transmitted file read back (`@fin.cx/skr`
|
|
169
|
+
* `readExtfBuchungsstapel(bytes).rows`, skr 7.0.1 and later) or the rows given to the writer
|
|
170
|
+
* (`IBuchungsstapelFile.rows`), taken as the file states them (`bookingsAsWritten`). What DATEV received, cost
|
|
171
|
+
* centres (fields 37 and 38) included, so a reversal mirrors it exactly. For a Stapel whose bookings were stored
|
|
172
|
+
* before 4.1, which record no cost centres.
|
|
173
|
+
*/
|
|
174
|
+
handedOverRows?: readonly IDatevBuchungsstapelRow[];
|
|
175
|
+
/** the period's drafts as they stand now */
|
|
176
|
+
drafts: readonly plugins.skrCore.IJournalDraft[];
|
|
177
|
+
/** the policy the drafts are checked against */
|
|
178
|
+
policy: plugins.skrCore.IAccountPolicy;
|
|
179
|
+
/** the header of the file: the same period (`dateFrom`, `dateTo`) as the Stapel handed over, a label of its own */
|
|
180
|
+
header: IDatevExtfHeaderOptions;
|
|
181
|
+
filename: string;
|
|
182
|
+
}
|
|
183
|
+
export interface ICorrectionBuchungsstapelFile extends IBuchungsstapelFile {
|
|
184
|
+
/** the bookings handed over it takes back, each written as a general reversal */
|
|
185
|
+
reversed: IExportBooking[];
|
|
186
|
+
/** the rows it books anew */
|
|
187
|
+
added: IDatevBuchungsstapelRow[];
|
|
188
|
+
/**
|
|
189
|
+
* The period's bookings as DATEV holds them after this correction: each booking handed over that it keeps, as it
|
|
190
|
+
* was handed over (its text and document link too), then the rows it books anew. What the next correction is
|
|
191
|
+
* compared against.
|
|
192
|
+
*/
|
|
193
|
+
bookingsAfter: IExportBooking[];
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Writes the correction Buchungsstapel of a period handed over whose bookings changed since: only what changed. Each
|
|
197
|
+
* booking handed over that the drafts no longer hold is written again with field 118 "Generalumkehr" set, so DATEV
|
|
198
|
+
* takes it back on its side and day, and each row the drafts hold that was not handed over is written as it is; rows
|
|
199
|
+
* in both are not written. Every row keeps its Belegdatum, so it counts in the period it belongs to. A booking handed
|
|
200
|
+
* over is never changed or replaced (§ 146 Abs. 4 AO): it is taken back and booked anew. Throws `nothing_to_correct`
|
|
201
|
+
* when nothing changed; `invalid_bookings` for a general reversal in the state handed over, or for a state given
|
|
202
|
+
* twice or not at all; `handed_over_cost_centres_unknown` for a booking whose cost centres are not known.
|
|
203
|
+
*/
|
|
204
|
+
export declare const writeCorrectionBuchungsstapel: (optionsArg: ICorrectionBuchungsstapelOptions) => ICorrectionBuchungsstapelFile;
|
package/dist_ts/extf/index.js
CHANGED
|
@@ -20,33 +20,110 @@ export const bookingsOfRows = (rowsArg) => rowsArg.map((rowArg) => ({
|
|
|
20
20
|
...(rowArg.belegfeld2 ? { belegfeld2: rowArg.belegfeld2 } : {}),
|
|
21
21
|
text: rowArg.buchungstext,
|
|
22
22
|
...(rowArg.belegLink ? { documentGuid: guidOfLink(rowArg.belegLink) } : {}),
|
|
23
|
+
// recorded on every booking, empty where the row states none: a booking without them was stored before 4.1
|
|
24
|
+
kost1: rowArg.kost1 ?? '',
|
|
25
|
+
kost2: rowArg.kost2 ?? '',
|
|
26
|
+
...(rowArg.generalumkehr === true ? { generalumkehr: true } : {}),
|
|
23
27
|
}));
|
|
24
28
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* multiset: a row that stands twice in one and once in the other differs once.
|
|
29
|
+
* What a booking says, as two Stapel of one period are compared, apart from its cost centres: its day, amount, side,
|
|
30
|
+
* accounts, BU key, Belegfeld 1 and whether it is a general reversal, not its wording or document link. A reversal
|
|
31
|
+
* never matches an ordinary row.
|
|
29
32
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
const baseKeyOf = (bookingArg) => JSON.stringify([
|
|
34
|
+
bookingArg.date,
|
|
35
|
+
bookingArg.umsatzCents,
|
|
36
|
+
bookingArg.sollHaben,
|
|
37
|
+
bookingArg.konto,
|
|
38
|
+
bookingArg.gegenkonto,
|
|
39
|
+
bookingArg.buKey,
|
|
40
|
+
bookingArg.belegfeld1 ?? '',
|
|
41
|
+
bookingArg.generalumkehr ? 'GU' : '',
|
|
42
|
+
]);
|
|
43
|
+
/** Whether a booking records its cost centres: every booking read from rows since 4.1 does, one stored before does not. */
|
|
44
|
+
const recordsCostCentres = (bookingArg) => bookingArg.kost1 !== undefined || bookingArg.kost2 !== undefined;
|
|
45
|
+
/** What a booking that records its cost centres says, the cost centres included. */
|
|
46
|
+
const fullKeyOf = (bookingArg) => JSON.stringify([baseKeyOf(bookingArg), bookingArg.kost1 ?? '', bookingArg.kost2 ?? '']);
|
|
47
|
+
/**
|
|
48
|
+
* Pairs the bookings of two Stapel of one period that say the same, as a multiset: two bookings that record their
|
|
49
|
+
* cost centres pair on every key field, the cost centres included, so a changed cost centre is a changed booking. A
|
|
50
|
+
* booking that does not record them (stored before 4.1, although the row sent stated them) pairs with any booking of
|
|
51
|
+
* the same other key fields: its record says nothing of them, so they never make it differ. Unpaired recorded bookings take the
|
|
52
|
+
* unrecorded ones of the other side first, which pair with anything, so as many pairs as possible are made.
|
|
53
|
+
*/
|
|
54
|
+
const pairBookings = (leftArg, rightArg) => {
|
|
55
|
+
const leftPaired = leftArg.map(() => false);
|
|
56
|
+
const rightPaired = rightArg.map(() => false);
|
|
57
|
+
const take = (queuesArg, keyArg) => queuesArg.get(keyArg)?.shift();
|
|
58
|
+
const queue = (queuesArg, keyArg, indexArg) => {
|
|
59
|
+
queuesArg.set(keyArg, [...(queuesArg.get(keyArg) ?? []), indexArg]);
|
|
60
|
+
};
|
|
61
|
+
// recorded with recorded, on every key field
|
|
62
|
+
const recordedRight = new Map();
|
|
63
|
+
rightArg.forEach((bookingArg, indexArg) => {
|
|
64
|
+
if (recordsCostCentres(bookingArg)) {
|
|
65
|
+
queue(recordedRight, fullKeyOf(bookingArg), indexArg);
|
|
41
66
|
}
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
});
|
|
68
|
+
leftArg.forEach((bookingArg, indexArg) => {
|
|
69
|
+
const paired = recordsCostCentres(bookingArg) ? take(recordedRight, fullKeyOf(bookingArg)) : undefined;
|
|
70
|
+
if (paired !== undefined) {
|
|
71
|
+
leftPaired[indexArg] = true;
|
|
72
|
+
rightPaired[paired] = true;
|
|
44
73
|
}
|
|
74
|
+
});
|
|
75
|
+
// what is left, on the key fields without the cost centres, where one of the two does not record them
|
|
76
|
+
const unrecordedRight = new Map();
|
|
77
|
+
const openRecordedRight = new Map();
|
|
78
|
+
rightArg.forEach((bookingArg, indexArg) => {
|
|
79
|
+
if (!rightPaired[indexArg]) {
|
|
80
|
+
queue(recordsCostCentres(bookingArg) ? openRecordedRight : unrecordedRight, baseKeyOf(bookingArg), indexArg);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const pairLeft = (recordedArg) => {
|
|
84
|
+
leftArg.forEach((bookingArg, indexArg) => {
|
|
85
|
+
if (leftPaired[indexArg] || recordsCostCentres(bookingArg) !== recordedArg) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const key = baseKeyOf(bookingArg);
|
|
89
|
+
const paired = recordedArg ? take(unrecordedRight, key) : take(openRecordedRight, key) ?? take(unrecordedRight, key);
|
|
90
|
+
if (paired !== undefined) {
|
|
91
|
+
leftPaired[indexArg] = true;
|
|
92
|
+
rightPaired[paired] = true;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
pairLeft(true);
|
|
97
|
+
pairLeft(false);
|
|
98
|
+
return { leftPaired, rightPaired };
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Bookings as a Buchungsstapel file states them, for comparing: each written as the row it was written as
|
|
102
|
+
* (`rowOfBooking`) and read back (`bookingsAsWritten`), so bookings stored of rows before writing (4.1 and before)
|
|
103
|
+
* and bookings read from a file compare alike. One that records no cost centres (stored before 4.1) keeps recording
|
|
104
|
+
* none, so it still never differs on them. A booking a Buchungsstapel cannot hold is refused (`invalid_bookings`).
|
|
105
|
+
*/
|
|
106
|
+
const comparableOf = (bookingsArg) => bookingsAsWritten(bookingsArg.map((bookingArg) => rowOfBooking(bookingArg))).map((bookingArg, indexArg) => {
|
|
107
|
+
if (recordsCostCentres(bookingsArg[indexArg])) {
|
|
108
|
+
return bookingArg;
|
|
45
109
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
110
|
+
const { kost1: _kost1, kost2: _kost2, ...unrecorded } = bookingArg;
|
|
111
|
+
return unrecorded;
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* How many rows two Buchungsstapel of one period do not share, compared on
|
|
115
|
+
* what a booking says (its day, amount, side, accounts, BU key, Belegfeld 1,
|
|
116
|
+
* cost centres, and whether it is a general reversal) and not on its wording or its document link. The rows are
|
|
117
|
+
* compared as a multiset: a row that stands twice in one and once in the other differs once. Both sides are compared
|
|
118
|
+
* as a file states them (`comparableOf`: a `"` as `'`, Belegfeld 1 cut to 36 characters, a character Windows-1252
|
|
119
|
+
* cannot hold as `?`), so bookings stored of rows before writing and bookings read from a file compare alike. A
|
|
120
|
+
* booking stored before 4.1, which does not record its cost centres, never differs on them (`pairBookings`); compare
|
|
121
|
+
* `bookingsOfRows` of the rows sent for an answer that counts them. A booking a Buchungsstapel cannot hold (no day,
|
|
122
|
+
* no positive amount) is refused (`invalid_bookings`).
|
|
123
|
+
*/
|
|
124
|
+
export const differingBookingCount = (leftArg, rightArg) => {
|
|
125
|
+
const { leftPaired, rightPaired } = pairBookings(comparableOf(leftArg), comparableOf(rightArg));
|
|
126
|
+
return [...leftPaired, ...rightPaired].filter((pairedArg) => !pairedArg).length;
|
|
50
127
|
};
|
|
51
128
|
/**
|
|
52
129
|
* Checks every draft against the account policy and turns the drafts into
|
|
@@ -109,10 +186,11 @@ export const replacedCharactersOfRows = (rowsArg) => {
|
|
|
109
186
|
});
|
|
110
187
|
return replacements;
|
|
111
188
|
};
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
189
|
+
/** The file of rows: the header and the caption row, the rows, encoded in Windows-1252, with where each character stands. */
|
|
190
|
+
const fileOfRows = (rows, headerArg, filenameArg) => {
|
|
191
|
+
refuseUnwritableRows(rows);
|
|
192
|
+
refuseOutsideHeader(rows, headerArg);
|
|
193
|
+
const { csv } = plugins.skrCore.writeExtfBuchungsstapel(rows, headerArg);
|
|
116
194
|
const { bytes, replaced } = encodeWindows1252(csv);
|
|
117
195
|
// the file is the header line, the caption line and one line per row, each ending in CRLF (`writeExtfBuchungsstapel`)
|
|
118
196
|
const rowLines = rows.map((rowArg) => plugins.skrCore.datevRowFields(rowArg));
|
|
@@ -137,6 +215,202 @@ export const writeBuchungsstapel = (optionsArg) => {
|
|
|
137
215
|
}
|
|
138
216
|
return { ...replacedArg, row, field: fieldAt(fieldStartsOf(rowLines[row]), replacedArg.offset - lineStarts[row]) };
|
|
139
217
|
});
|
|
140
|
-
|
|
218
|
+
// the bookings of what was written, as the file states them
|
|
219
|
+
const written = plugins.skrCore.readExtfBuchungsstapel(bytes).rows;
|
|
220
|
+
if (written.length !== rows.length) {
|
|
221
|
+
throw new CompatibilityError('export_failed', `${rows.length} row(s) written, ${written.length} read back`);
|
|
222
|
+
}
|
|
223
|
+
return { filename: filenameArg, csv, bytes, rows, bookings: bookingsOfRows(written), replacedCharacters };
|
|
224
|
+
};
|
|
225
|
+
/** The Belegdatum of a row as its day, midnight UTC, in epoch milliseconds. */
|
|
226
|
+
const dayOfRow = (rowArg) => Date.UTC(rowArg.belegDatum.getUTCFullYear(), rowArg.belegDatum.getUTCMonth(), rowArg.belegDatum.getUTCDate());
|
|
227
|
+
/** A row as a refusal names it: its place among the rows given, its day, amount, side, accounts and Belegfeld 1. */
|
|
228
|
+
const rowNameOf = (rowArg, indexArg) => `row ${indexArg + 1} (${Number.isNaN(rowArg.belegDatum.getTime()) ? 'no day' : rowArg.belegDatum.toISOString().slice(0, 10)}, ${rowArg.umsatzCents} ${rowArg.sollHaben} ${rowArg.konto}/${rowArg.gegenKonto}`
|
|
229
|
+
+ `${rowArg.belegfeld1 ? `, Belegfeld 1 ${rowArg.belegfeld1}` : ''})`;
|
|
230
|
+
/**
|
|
231
|
+
* Refuses a row a Buchungsstapel does not hold, before anything is written: a Belegdatum that is no day, and an
|
|
232
|
+
* amount that is not a positive whole number of cents (a Buchungsstapel states the side apart, field 2, and
|
|
233
|
+
* `readExtfBuchungsstapel` refuses any other). `invalid_bookings`, naming the row.
|
|
234
|
+
*/
|
|
235
|
+
const refuseUnwritableRows = (rowsArg) => {
|
|
236
|
+
rowsArg.forEach((rowArg, indexArg) => {
|
|
237
|
+
if (Number.isNaN(rowArg.belegDatum.getTime())) {
|
|
238
|
+
throw new CompatibilityError('invalid_bookings', `${rowNameOf(rowArg, indexArg)} has no Belegdatum`);
|
|
239
|
+
}
|
|
240
|
+
if (!Number.isSafeInteger(rowArg.umsatzCents) || rowArg.umsatzCents <= 0) {
|
|
241
|
+
throw new CompatibilityError('invalid_bookings', `${rowNameOf(rowArg, indexArg)}: a Buchungsstapel states a positive amount, the side apart`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Refuses a header whose period a Buchungsstapel cannot state (`invalid_header`): Datum von after Datum bis, or a
|
|
247
|
+
* period that does not lie within the one Wirtschaftsjahr beginning on the Wirtschaftsjahr-Beginn (a Belegdatum is
|
|
248
|
+
* written TTMM, and the reader takes its year from them); and every row whose Belegdatum lies outside the period
|
|
249
|
+
* (`invalid_bookings`, naming it). Checked before anything is written.
|
|
250
|
+
*/
|
|
251
|
+
const refuseOutsideHeader = (rowsArg, headerArg) => {
|
|
252
|
+
const day = (dateArg) => Date.UTC(dateArg.getUTCFullYear(), dateArg.getUTCMonth(), dateArg.getUTCDate());
|
|
253
|
+
const from = day(headerArg.dateFrom);
|
|
254
|
+
const to = day(headerArg.dateTo);
|
|
255
|
+
const start = day(headerArg.fiscalYearStart);
|
|
256
|
+
const end = Date.UTC(headerArg.fiscalYearStart.getUTCFullYear() + 1, headerArg.fiscalYearStart.getUTCMonth(), headerArg.fiscalYearStart.getUTCDate());
|
|
257
|
+
const iso = (timeArg) => new Date(timeArg).toISOString().slice(0, 10);
|
|
258
|
+
if ([from, to, start].some(Number.isNaN)) {
|
|
259
|
+
throw new CompatibilityError('invalid_header', 'the header states no day for the Wirtschaftsjahr-Beginn, Datum von or Datum bis');
|
|
260
|
+
}
|
|
261
|
+
if (to < from) {
|
|
262
|
+
throw new CompatibilityError('invalid_header', `the period of the header ends (${iso(to)}) before it begins (${iso(from)})`);
|
|
263
|
+
}
|
|
264
|
+
if (from < start || to >= end) {
|
|
265
|
+
throw new CompatibilityError('invalid_header', `the period of the header, ${iso(from)} to ${iso(to)}, does not lie within the Wirtschaftsjahr beginning ${iso(start)}`);
|
|
266
|
+
}
|
|
267
|
+
rowsArg.forEach((rowArg, indexArg) => {
|
|
268
|
+
const time = dayOfRow(rowArg);
|
|
269
|
+
if (time < from || time > to) {
|
|
270
|
+
throw new CompatibilityError('invalid_bookings', `${rowNameOf(rowArg, indexArg)} lies outside the period of the header, ${iso(from)} to ${iso(to)}`);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Rows as a Buchungsstapel file states them: each row written by the writer (`@fin.cx/skr` `writeExtfBuchungsstapel`,
|
|
276
|
+
* which writes a `"` in a text field as `'` and cuts a field to its DATEV length), encoded in Windows-1252 (a
|
|
277
|
+
* character it cannot hold written `?`, `encodeWindows1252`), and read back (`@fin.cx/skr` `readExtfBuchungsstapel`),
|
|
278
|
+
* in the order given. Every rule of the writer, the encoding and the reader applies because the rows go through them,
|
|
279
|
+
* not through a copy of their rules. The rows of each calendar year are written as one file whose header spans their
|
|
280
|
+
* days; the header states nothing a row is read by beyond its days. A row the writer or the reader refuses throws as
|
|
281
|
+
* it does.
|
|
282
|
+
*/
|
|
283
|
+
export const rowsAsWritten = (rowsArg) => {
|
|
284
|
+
refuseUnwritableRows(rowsArg);
|
|
285
|
+
const written = new Array(rowsArg.length);
|
|
286
|
+
// the indexes of the rows of each calendar year, and the first and last day among them
|
|
287
|
+
const years = new Map();
|
|
288
|
+
rowsArg.forEach((rowArg, indexArg) => {
|
|
289
|
+
const year = rowArg.belegDatum.getUTCFullYear();
|
|
290
|
+
const time = dayOfRow(rowArg);
|
|
291
|
+
const group = years.get(year);
|
|
292
|
+
if (group) {
|
|
293
|
+
group.indexes.push(indexArg);
|
|
294
|
+
group.first = Math.min(group.first, time);
|
|
295
|
+
group.last = Math.max(group.last, time);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
years.set(year, { indexes: [indexArg], first: time, last: time });
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
for (const [year, group] of years) {
|
|
302
|
+
const { csv } = plugins.skrCore.writeExtfBuchungsstapel(group.indexes.map((indexArg) => rowsArg[indexArg]), {
|
|
303
|
+
createdAt: new Date(Date.UTC(year, 0, 1)),
|
|
304
|
+
consultantNumber: 0,
|
|
305
|
+
clientNumber: 0,
|
|
306
|
+
// a Belegdatum is written TTMM: the reader takes its year from the Wirtschaftsjahr and the period of the header
|
|
307
|
+
fiscalYearStart: new Date(Date.UTC(year, 0, 1)),
|
|
308
|
+
dateFrom: new Date(group.first),
|
|
309
|
+
dateTo: new Date(group.last),
|
|
310
|
+
label: '',
|
|
311
|
+
festschreibung: false,
|
|
312
|
+
});
|
|
313
|
+
const read = plugins.skrCore.readExtfBuchungsstapel(encodeWindows1252(csv).bytes).rows;
|
|
314
|
+
if (read.length !== group.indexes.length) {
|
|
315
|
+
throw new CompatibilityError('export_failed', `${group.indexes.length} row(s) written, ${read.length} read back`);
|
|
316
|
+
}
|
|
317
|
+
group.indexes.forEach((indexArg, positionArg) => {
|
|
318
|
+
written[indexArg] = read[positionArg];
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return written;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* The bookings of rows as a Buchungsstapel file states them (`rowsAsWritten`): what a file handed over, read back
|
|
325
|
+
* with `readExtfBuchungsstapel`, is compared with. `bookingsOfRows` of the rows before writing keeps what the file
|
|
326
|
+
* does not (a `"` in Belegfeld 1, a Belegfeld 1 longer than its 36 characters, a character Windows-1252 cannot
|
|
327
|
+
* hold), and Belegfeld 1 is compared, so such a booking would differ from the file forever.
|
|
328
|
+
*/
|
|
329
|
+
export const bookingsAsWritten = (rowsArg) => bookingsOfRows(rowsAsWritten(rowsArg));
|
|
330
|
+
/** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
|
|
331
|
+
export const writeBuchungsstapel = (optionsArg) => fileOfRows(datevRowsOf(optionsArg.drafts, optionsArg.policy), optionsArg.header, optionsArg.filename);
|
|
332
|
+
/**
|
|
333
|
+
* The row a booking handed over was written as, for a general reversal of it: every field the booking keeps
|
|
334
|
+
* (`IExportBooking`) in its place, the Belegdatum at midnight UTC as the writer reads it.
|
|
335
|
+
*/
|
|
336
|
+
export const rowOfBooking = (bookingArg) => ({
|
|
337
|
+
umsatzCents: bookingArg.umsatzCents,
|
|
338
|
+
sollHaben: bookingArg.sollHaben,
|
|
339
|
+
konto: bookingArg.konto,
|
|
340
|
+
gegenKonto: bookingArg.gegenkonto,
|
|
341
|
+
buSchluessel: bookingArg.buKey,
|
|
342
|
+
belegDatum: new Date(`${bookingArg.date}T00:00:00.000Z`),
|
|
343
|
+
...(bookingArg.belegfeld1 ? { belegfeld1: bookingArg.belegfeld1 } : {}),
|
|
344
|
+
...(bookingArg.belegfeld2 ? { belegfeld2: bookingArg.belegfeld2 } : {}),
|
|
345
|
+
buchungstext: bookingArg.text,
|
|
346
|
+
...(bookingArg.documentGuid ? { belegLink: bookingArg.documentGuid } : {}),
|
|
347
|
+
...(bookingArg.kost1 ? { kost1: bookingArg.kost1 } : {}),
|
|
348
|
+
...(bookingArg.kost2 ? { kost2: bookingArg.kost2 } : {}),
|
|
349
|
+
festschreibung: false,
|
|
350
|
+
...(bookingArg.generalumkehr ? { generalumkehr: true } : {}),
|
|
351
|
+
});
|
|
352
|
+
/** A booking as a refusal names it: its place among the bookings handed over, day, amount, side, accounts and Belegfeld 1. */
|
|
353
|
+
const bookingNameOf = (bookingArg, indexArg) => `booking ${indexArg + 1} (${bookingArg.date}, ${bookingArg.umsatzCents} ${bookingArg.sollHaben} ${bookingArg.konto}/${bookingArg.gegenkonto}`
|
|
354
|
+
+ `${bookingArg.belegfeld1 ? `, Belegfeld 1 ${bookingArg.belegfeld1}` : ''})`;
|
|
355
|
+
/**
|
|
356
|
+
* Refuses a state handed over that holds a booking whose cost centres its record does not state (stored before 4.1).
|
|
357
|
+
* The Stapel sent stated them (fields 37 and 38), so DATEV holds them: whether the booking must be taken back turns on
|
|
358
|
+
* them, and a reversal that does not state them would leave them standing in DATEV's cost accounting. The rows sent
|
|
359
|
+
* tell them (`handedOverRows`).
|
|
360
|
+
*/
|
|
361
|
+
const refuseCostCentresUnknown = (handedOverArg) => {
|
|
362
|
+
const unknown = handedOverArg.findIndex((bookingArg) => !recordsCostCentres(bookingArg));
|
|
363
|
+
if (unknown >= 0) {
|
|
364
|
+
throw new CompatibilityError('handed_over_cost_centres_unknown', `${bookingNameOf(handedOverArg[unknown], unknown)} was stored without the cost centres it was sent with; pass the rows of the Stapel sent (handedOverRows)`);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* What a correction of a period handed over changes, compared as `differingBookingCount` compares: the bookings
|
|
369
|
+
* handed over that the current ones no longer hold (`reversed`, each once for each time it is missing), the ones they
|
|
370
|
+
* still hold (`kept`, as they were handed over) and the current rows the bookings handed over do not hold (`added`),
|
|
371
|
+
* each in its order. A booking that stands in both is kept: a changed text or document link is no changed booking,
|
|
372
|
+
* a changed cost centre is. The current rows are compared as a file states them (`bookingsAsWritten`), as the
|
|
373
|
+
* bookings handed over are; `added` are the rows themselves, which the writer writes. A booking handed over whose
|
|
374
|
+
* cost centres its record does not state (stored before 4.1) is refused (`handed_over_cost_centres_unknown`): pass
|
|
375
|
+
* `bookingsOfRows` of the rows sent instead.
|
|
376
|
+
*/
|
|
377
|
+
export const correctionOf = (handedOverArg, currentArg) => {
|
|
378
|
+
refuseCostCentresUnknown(handedOverArg);
|
|
379
|
+
// the current rows as a file states them, as the state handed over was: a reference the writer changes is the same
|
|
380
|
+
const { leftPaired, rightPaired } = pairBookings(comparableOf(handedOverArg), bookingsAsWritten(currentArg));
|
|
381
|
+
return {
|
|
382
|
+
reversed: handedOverArg.filter((_bookingArg, indexArg) => !leftPaired[indexArg]),
|
|
383
|
+
kept: handedOverArg.filter((_bookingArg, indexArg) => leftPaired[indexArg]),
|
|
384
|
+
added: currentArg.filter((_rowArg, indexArg) => !rightPaired[indexArg]),
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* Writes the correction Buchungsstapel of a period handed over whose bookings changed since: only what changed. Each
|
|
389
|
+
* booking handed over that the drafts no longer hold is written again with field 118 "Generalumkehr" set, so DATEV
|
|
390
|
+
* takes it back on its side and day, and each row the drafts hold that was not handed over is written as it is; rows
|
|
391
|
+
* in both are not written. Every row keeps its Belegdatum, so it counts in the period it belongs to. A booking handed
|
|
392
|
+
* over is never changed or replaced (§ 146 Abs. 4 AO): it is taken back and booked anew. Throws `nothing_to_correct`
|
|
393
|
+
* when nothing changed; `invalid_bookings` for a general reversal in the state handed over, or for a state given
|
|
394
|
+
* twice or not at all; `handed_over_cost_centres_unknown` for a booking whose cost centres are not known.
|
|
395
|
+
*/
|
|
396
|
+
export const writeCorrectionBuchungsstapel = (optionsArg) => {
|
|
397
|
+
// rows read back from the file sent are as written already; rows given to the writer become what it wrote
|
|
398
|
+
const handedOver = optionsArg.handedOverRows !== undefined ? bookingsAsWritten(optionsArg.handedOverRows) : optionsArg.handedOver;
|
|
399
|
+
if (handedOver === undefined || (optionsArg.handedOver !== undefined && optionsArg.handedOverRows !== undefined)) {
|
|
400
|
+
throw new CompatibilityError('invalid_bookings', 'pass the state handed over once: the bookings stored (handedOver) or the rows sent (handedOverRows)');
|
|
401
|
+
}
|
|
402
|
+
const reversal = handedOver.findIndex((bookingArg) => bookingArg.generalumkehr === true);
|
|
403
|
+
if (reversal >= 0) {
|
|
404
|
+
throw new CompatibilityError('invalid_bookings', `the state handed over holds a general reversal (booking ${reversal + 1}); pass the period's bookings as they stood, not a correction file`);
|
|
405
|
+
}
|
|
406
|
+
const current = datevRowsOf(optionsArg.drafts, optionsArg.policy);
|
|
407
|
+
const { reversed, kept, added } = correctionOf(handedOver, current);
|
|
408
|
+
if (reversed.length === 0 && added.length === 0) {
|
|
409
|
+
throw new CompatibilityError('nothing_to_correct', 'the bookings of the period are the ones handed over; there is nothing to correct');
|
|
410
|
+
}
|
|
411
|
+
const rows = [...reversed.map((bookingArg) => ({ ...rowOfBooking(bookingArg), generalumkehr: true })), ...added];
|
|
412
|
+
const file = fileOfRows(rows, optionsArg.header, optionsArg.filename);
|
|
413
|
+
// the rows booked anew as the correction file states them, after the reversals
|
|
414
|
+
return { ...file, reversed, added, bookingsAfter: [...kept, ...file.bookings.slice(reversed.length)] };
|
|
141
415
|
};
|
|
142
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9leHRmL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNqRCxPQUFPLEVBQUUsaUJBQWlCLEVBQTJCLE1BQU0sc0JBQXNCLENBQUM7QUFFbEYsT0FBTyxFQUFFLGtCQUFrQixFQUFnQyxNQUFNLGFBQWEsQ0FBQztBQUsvRSxzRkFBc0Y7QUFDdEYsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsZ0NBQWdDLENBQUM7QUFFbEUsd0ZBQXdGO0FBQ3hGLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLFFBQWdCLEVBQVUsRUFBRSxDQUFDLHVCQUF1QixRQUFRLE1BQU0sQ0FBQztBQWtCaEcsMEVBQTBFO0FBQzFFLE1BQU0sVUFBVSxHQUFHLENBQUMsT0FBZSxFQUFVLEVBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0FBRWxILDRDQUE0QztBQUM1QyxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUEyQyxFQUFvQixFQUFFLENBQzlGLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDdkIsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXO0lBQy9CLFNBQVMsRUFBRSxNQUFNLENBQUMsU0FBUztJQUMzQixLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUs7SUFDbkIsVUFBVSxFQUFFLE1BQU0sQ0FBQyxVQUFVO0lBQzdCLEtBQUssRUFBRSxNQUFNLENBQUMsWUFBWTtJQUMxQixJQUFJLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxXQUFXLEVB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
|
|
416
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9leHRmL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNqRCxPQUFPLEVBQUUsaUJBQWlCLEVBQTJCLE1BQU0sc0JBQXNCLENBQUM7QUFFbEYsT0FBTyxFQUFFLGtCQUFrQixFQUFnQyxNQUFNLGFBQWEsQ0FBQztBQUsvRSxzRkFBc0Y7QUFDdEYsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsZ0NBQWdDLENBQUM7QUFFbEUsd0ZBQXdGO0FBQ3hGLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLFFBQWdCLEVBQVUsRUFBRSxDQUFDLHVCQUF1QixRQUFRLE1BQU0sQ0FBQztBQStCaEcsMEVBQTBFO0FBQzFFLE1BQU0sVUFBVSxHQUFHLENBQUMsT0FBZSxFQUFVLEVBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0FBRWxILDRDQUE0QztBQUM1QyxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUEyQyxFQUFvQixFQUFFLENBQzlGLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDdkIsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXO0lBQy9CLFNBQVMsRUFBRSxNQUFNLENBQUMsU0FBUztJQUMzQixLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUs7SUFDbkIsVUFBVSxFQUFFLE1BQU0sQ0FBQyxVQUFVO0lBQzdCLEtBQUssRUFBRSxNQUFNLENBQUMsWUFBWTtJQUMxQixJQUFJLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxXQUFXLEVB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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fin.cx/compatibility",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.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",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@push.rocks/smartarchive": "^5.4.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@fin.cx/skr": "^
|
|
48
|
+
"@fin.cx/skr": "^7.0.1",
|
|
49
49
|
"@git.zone/tsbuild": "^5.0.0",
|
|
50
50
|
"@git.zone/tsrun": "^3.0.2",
|
|
51
51
|
"@git.zone/tstest": "^6.3.2",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"license.md"
|
|
59
59
|
],
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@fin.cx/skr": "^
|
|
61
|
+
"@fin.cx/skr": "^7.0.1"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"test": "(tstest test/ --verbose)",
|
package/readme.md
CHANGED
|
@@ -17,7 +17,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
17
17
|
pnpm install @fin.cx/compatibility @fin.cx/skr
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`@fin.cx/skr` (^
|
|
20
|
+
`@fin.cx/skr` (^7.0.1) is a peer dependency: the drafts and the account policy the EXTF writer takes are built by your copy of skr, and the writer turns them into rows with the same copy, so the rows follow the chart the drafts were booked on (which accounts are Automatikkonten, which carry VAT). A second copy of skr of another major would write rows by another chart; a package manager that finds your skr outside the range says so as an unmet peer.
|
|
21
21
|
|
|
22
22
|
## Usage
|
|
23
23
|
|
|
@@ -42,7 +42,7 @@ const file = extf.writeBuchungsstapel({
|
|
|
42
42
|
filename: extf.extfFilename('2026-03'), // EXTF_Buchungsstapel_2026-03.csv
|
|
43
43
|
});
|
|
44
44
|
file.bytes; // Uint8Array in Windows-1252, served as extf.EXTF_CONTENT_TYPE
|
|
45
|
-
file.bookings; // what each row says, for keeping and comparing
|
|
45
|
+
file.bookings; // what each row says as the file states it, for keeping and comparing
|
|
46
46
|
file.replacedCharacters; // [{ offset, character: 'ő', row: 1, field: 14 }, …]: written as "?"
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -50,9 +50,15 @@ file.replacedCharacters; // [{ offset, character: 'ő', row: 1, field: 14 }, …
|
|
|
50
50
|
- **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.
|
|
51
51
|
|
|
52
52
|
- **A draft the account policy refuses** throws `CompatibilityError` with code `invalid_bookings` and a message naming the draft.
|
|
53
|
+
- **What the reader would refuse is refused before writing.** A header whose period ends before it begins or leaves the Wirtschaftsjahr beginning on its Wirtschaftsjahr-Beginn throws code `invalid_header`; a row whose Belegdatum lies outside that period, is no day, or whose amount is not positive throws `invalid_bookings`, naming the row.
|
|
53
54
|
- **A failed conversion** throws code `export_failed`.
|
|
54
55
|
- **A document-image archive DATEV would not take** (no document, a GUID of another form, a format it does not take) throws code `invalid_documents`.
|
|
55
|
-
- **`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
|
|
56
|
+
- **`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, Belegfeld 1, cost centres (KOST1, KOST2) and whether it is a general reversal, as a multiset, and ignores wording and document links. A booking stored before 4.1 has no cost-centre fields: its record says nothing of them, and they never make it differ; compare `bookingsOfRows` of the rows sent for an answer that counts them.
|
|
57
|
+
- **The bookings as the file states them.** `extf.bookingsAsWritten(rows)` (and `extf.rowsAsWritten(rows)`) writes the rows with skr's writer, encodes them in Windows-1252 and reads them back with skr's reader: a `"` in a text field becomes `'`, Belegfeld 1 is cut to 36 characters, a line break becomes a space and a character Windows-1252 cannot hold becomes `?`, as in the file. `writeBuchungsstapel`'s `bookings` are read back from the bytes it wrote. `differingBookingCount` and a correction take both sides as a file states them, so bookings stored before writing (4.1 and before), bookings of this version and rows read back from the file sent compare alike; compare with them, not with `bookingsOfRows` of rows that were not written, which keep what the file does not.
|
|
58
|
+
- **A period whose bookings were stored before 4.1.** Such bookings record no cost centres, although the Stapel sent stated them (fields 37 and 38), and `differingBookingCount` never counts them as differing on cost centres alone: a change of a cost centre only is not seen. For such a period read the file handed over back (`readExtfBuchungsstapel(bytes).rows`) and compare `bookingsOfRows` of those rows, or pass them to a correction as `handedOverRows`.
|
|
59
|
+
- **A correction of a period handed over.** `writeCorrectionBuchungsstapel({ handedOver, drafts, policy, header, filename })` writes only what changed since the state handed over: each booking handed over that the drafts no longer hold is written again with field 118 "Generalumkehr" set, so DATEV takes it back on its own side and day, then each row of the drafts that was not handed over; what stands in both is not written. A changed text or document link is no change; a changed cost centre is, so the booking is taken back on the cost centre DATEV received and booked on the new one. Every row keeps its Belegdatum, so it counts in the period it belongs to; a booking handed over is never replaced (§ 146 Abs. 4 AO), which also holds once DATEV has fixed the period (Festschreibung). The file names what it takes back (`reversed`), what it books (`added`) and the bookings after it (`bookingsAfter`: each booking it keeps as it was handed over, text and document link included, then the rows it books anew as the correction file states them). Both sides are compared as a file states them, so a reference the writer changes (a `"`, more than 36 characters, a character Windows-1252 cannot hold, a line break) is no change. `correctionOf(handedOver, rows)` gives `reversed`, `kept` and `added` without a file, and `rowOfBooking(booking)` the row a booking was written as.
|
|
60
|
+
- **The state handed over** is given once: the bookings stored of it (`handedOver`: the `bookings` of its Buchungsstapel as they were stored, by this version or by 4.1, or the last correction's `bookingsAfter`), or the rows of the Stapel as it was sent (`handedOverRows`: the transmitted file read back with `@fin.cx/skr` `readExtfBuchungsstapel(bytes).rows`, or the `rows` given to the writer). Every form compares alike. A booking stored before 4.1 has no cost-centre fields, although the Stapel sent stated them (fields 37 and 38): a correction refuses it with code `handed_over_cost_centres_unknown`, naming it, since whether it must be taken back turns on the cost centres DATEV holds and a reversal without them would not mirror it; pass `handedOverRows` for such a period. Nothing changed is refused with `nothing_to_correct`; a general reversal in the state handed over (a correction file passed as the state), or a state given twice or not at all, with `invalid_bookings`.
|
|
61
|
+
- **A booking keeps its general reversal** (`IExportBooking.generalumkehr`, from field 118) and its cost centres (`kost1`, `kost2`, empty where the row states none).
|
|
56
62
|
|
|
57
63
|
```typescript
|
|
58
64
|
import { belegbilder } from '@fin.cx/compatibility';
|
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: '5.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/error.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Why an exchange file could not be written:
|
|
3
|
-
* - `invalid_bookings`: a draft the account policy refuses
|
|
3
|
+
* - `invalid_bookings`: a draft the account policy refuses, the message names it; a general reversal among the
|
|
4
|
+
* bookings handed over to a correction Buchungsstapel (a correction file passed as the state handed over); or a row
|
|
5
|
+
* or booking a Buchungsstapel does not hold (no day, an amount that is not positive) or whose Belegdatum lies
|
|
6
|
+
* outside the period of the header, named;
|
|
7
|
+
* - `invalid_header`: a header whose period ends before it begins or does not lie within the one Wirtschaftsjahr
|
|
8
|
+
* beginning on its Wirtschaftsjahr-Beginn;
|
|
4
9
|
* - `export_failed`: the conversion into rows failed;
|
|
5
|
-
* - `invalid_documents`: a document of a document-image archive DATEV does not take (its GUID, its format), or none
|
|
10
|
+
* - `invalid_documents`: a document of a document-image archive DATEV does not take (its GUID, its format), or none;
|
|
11
|
+
* - `nothing_to_correct`: a correction Buchungsstapel whose current bookings are the ones handed over;
|
|
12
|
+
* - `handed_over_cost_centres_unknown`: a correction of a period whose bookings handed over include one whose cost
|
|
13
|
+
* centres its record does not state (stored before 4.1) and no rows sent were given; the message names it.
|
|
6
14
|
*/
|
|
7
|
-
export type TCompatibilityErrorCode = 'invalid_bookings' | 'export_failed' | 'invalid_documents';
|
|
15
|
+
export type TCompatibilityErrorCode = 'invalid_bookings' | 'invalid_header' | 'export_failed' | 'invalid_documents' | 'nothing_to_correct' | 'handed_over_cost_centres_unknown';
|
|
8
16
|
|
|
9
17
|
export class CompatibilityError extends Error {
|
|
10
18
|
public readonly code: TCompatibilityErrorCode;
|
package/ts/extf/index.ts
CHANGED
|
@@ -27,6 +27,19 @@ export interface IExportBooking {
|
|
|
27
27
|
text: string;
|
|
28
28
|
/** the document GUID the row links to (field 20, `BEDI "GUID"`) */
|
|
29
29
|
documentGuid?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The cost centres of the row (fields 37 and 38, KOST1 and KOST2), empty where it states none. `bookingsOfRows`
|
|
32
|
+
* records both on every booking; a booking stored before 4.1 has neither, although the row sent stated them: its
|
|
33
|
+
* cost centres are not known. `differingBookingCount` compares it without them; a correction refuses it
|
|
34
|
+
* (`handed_over_cost_centres_unknown`) unless it is given the rows sent (`handedOverRows`).
|
|
35
|
+
*/
|
|
36
|
+
kost1?: string;
|
|
37
|
+
kost2?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The row is a general reversal (field 118 "Generalumkehr", written "1"): it takes back the booking of the same
|
|
40
|
+
* fields on DATEV's side. Absent on an ordinary row.
|
|
41
|
+
*/
|
|
42
|
+
generalumkehr?: true;
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
/** The GUID inside a Beleglink value (`BEDI "GUID"` or the bare GUID). */
|
|
@@ -45,34 +58,121 @@ export const bookingsOfRows = (rowsArg: readonly IDatevBuchungsstapelRow[]): IEx
|
|
|
45
58
|
...(rowArg.belegfeld2 ? { belegfeld2: rowArg.belegfeld2 } : {}),
|
|
46
59
|
text: rowArg.buchungstext,
|
|
47
60
|
...(rowArg.belegLink ? { documentGuid: guidOfLink(rowArg.belegLink) } : {}),
|
|
61
|
+
// recorded on every booking, empty where the row states none: a booking without them was stored before 4.1
|
|
62
|
+
kost1: rowArg.kost1 ?? '',
|
|
63
|
+
kost2: rowArg.kost2 ?? '',
|
|
64
|
+
...(rowArg.generalumkehr === true ? { generalumkehr: true as const } : {}),
|
|
48
65
|
}));
|
|
49
66
|
|
|
67
|
+
/**
|
|
68
|
+
* What a booking says, as two Stapel of one period are compared, apart from its cost centres: its day, amount, side,
|
|
69
|
+
* accounts, BU key, Belegfeld 1 and whether it is a general reversal, not its wording or document link. A reversal
|
|
70
|
+
* never matches an ordinary row.
|
|
71
|
+
*/
|
|
72
|
+
const baseKeyOf = (bookingArg: IExportBooking): string =>
|
|
73
|
+
JSON.stringify([
|
|
74
|
+
bookingArg.date,
|
|
75
|
+
bookingArg.umsatzCents,
|
|
76
|
+
bookingArg.sollHaben,
|
|
77
|
+
bookingArg.konto,
|
|
78
|
+
bookingArg.gegenkonto,
|
|
79
|
+
bookingArg.buKey,
|
|
80
|
+
bookingArg.belegfeld1 ?? '',
|
|
81
|
+
bookingArg.generalumkehr ? 'GU' : '',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
/** Whether a booking records its cost centres: every booking read from rows since 4.1 does, one stored before does not. */
|
|
85
|
+
const recordsCostCentres = (bookingArg: IExportBooking): boolean => bookingArg.kost1 !== undefined || bookingArg.kost2 !== undefined;
|
|
86
|
+
|
|
87
|
+
/** What a booking that records its cost centres says, the cost centres included. */
|
|
88
|
+
const fullKeyOf = (bookingArg: IExportBooking): string => JSON.stringify([baseKeyOf(bookingArg), bookingArg.kost1 ?? '', bookingArg.kost2 ?? '']);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pairs the bookings of two Stapel of one period that say the same, as a multiset: two bookings that record their
|
|
92
|
+
* cost centres pair on every key field, the cost centres included, so a changed cost centre is a changed booking. A
|
|
93
|
+
* booking that does not record them (stored before 4.1, although the row sent stated them) pairs with any booking of
|
|
94
|
+
* the same other key fields: its record says nothing of them, so they never make it differ. Unpaired recorded bookings take the
|
|
95
|
+
* unrecorded ones of the other side first, which pair with anything, so as many pairs as possible are made.
|
|
96
|
+
*/
|
|
97
|
+
const pairBookings = (
|
|
98
|
+
leftArg: readonly IExportBooking[],
|
|
99
|
+
rightArg: readonly IExportBooking[],
|
|
100
|
+
): { leftPaired: boolean[]; rightPaired: boolean[] } => {
|
|
101
|
+
const leftPaired = leftArg.map(() => false);
|
|
102
|
+
const rightPaired = rightArg.map(() => false);
|
|
103
|
+
const take = (queuesArg: Map<string, number[]>, keyArg: string): number | undefined => queuesArg.get(keyArg)?.shift();
|
|
104
|
+
const queue = (queuesArg: Map<string, number[]>, keyArg: string, indexArg: number): void => {
|
|
105
|
+
queuesArg.set(keyArg, [...(queuesArg.get(keyArg) ?? []), indexArg]);
|
|
106
|
+
};
|
|
107
|
+
// recorded with recorded, on every key field
|
|
108
|
+
const recordedRight = new Map<string, number[]>();
|
|
109
|
+
rightArg.forEach((bookingArg, indexArg) => {
|
|
110
|
+
if (recordsCostCentres(bookingArg)) {
|
|
111
|
+
queue(recordedRight, fullKeyOf(bookingArg), indexArg);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
leftArg.forEach((bookingArg, indexArg) => {
|
|
115
|
+
const paired = recordsCostCentres(bookingArg) ? take(recordedRight, fullKeyOf(bookingArg)) : undefined;
|
|
116
|
+
if (paired !== undefined) {
|
|
117
|
+
leftPaired[indexArg] = true;
|
|
118
|
+
rightPaired[paired] = true;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// what is left, on the key fields without the cost centres, where one of the two does not record them
|
|
122
|
+
const unrecordedRight = new Map<string, number[]>();
|
|
123
|
+
const openRecordedRight = new Map<string, number[]>();
|
|
124
|
+
rightArg.forEach((bookingArg, indexArg) => {
|
|
125
|
+
if (!rightPaired[indexArg]) {
|
|
126
|
+
queue(recordsCostCentres(bookingArg) ? openRecordedRight : unrecordedRight, baseKeyOf(bookingArg), indexArg);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const pairLeft = (recordedArg: boolean): void => {
|
|
130
|
+
leftArg.forEach((bookingArg, indexArg) => {
|
|
131
|
+
if (leftPaired[indexArg] || recordsCostCentres(bookingArg) !== recordedArg) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const key = baseKeyOf(bookingArg);
|
|
135
|
+
const paired = recordedArg ? take(unrecordedRight, key) : take(openRecordedRight, key) ?? take(unrecordedRight, key);
|
|
136
|
+
if (paired !== undefined) {
|
|
137
|
+
leftPaired[indexArg] = true;
|
|
138
|
+
rightPaired[paired] = true;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
pairLeft(true);
|
|
143
|
+
pairLeft(false);
|
|
144
|
+
return { leftPaired, rightPaired };
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Bookings as a Buchungsstapel file states them, for comparing: each written as the row it was written as
|
|
149
|
+
* (`rowOfBooking`) and read back (`bookingsAsWritten`), so bookings stored of rows before writing (4.1 and before)
|
|
150
|
+
* and bookings read from a file compare alike. One that records no cost centres (stored before 4.1) keeps recording
|
|
151
|
+
* none, so it still never differs on them. A booking a Buchungsstapel cannot hold is refused (`invalid_bookings`).
|
|
152
|
+
*/
|
|
153
|
+
const comparableOf = (bookingsArg: readonly IExportBooking[]): IExportBooking[] =>
|
|
154
|
+
bookingsAsWritten(bookingsArg.map((bookingArg) => rowOfBooking(bookingArg))).map((bookingArg, indexArg) => {
|
|
155
|
+
if (recordsCostCentres(bookingsArg[indexArg])) {
|
|
156
|
+
return bookingArg;
|
|
157
|
+
}
|
|
158
|
+
const { kost1: _kost1, kost2: _kost2, ...unrecorded } = bookingArg;
|
|
159
|
+
return unrecorded;
|
|
160
|
+
});
|
|
161
|
+
|
|
50
162
|
/**
|
|
51
163
|
* How many rows two Buchungsstapel of one period do not share, compared on
|
|
52
|
-
* what a booking says (its day, amount, side, accounts, BU key
|
|
53
|
-
*
|
|
54
|
-
* multiset: a row that stands twice in one and once in the other differs once.
|
|
164
|
+
* what a booking says (its day, amount, side, accounts, BU key, Belegfeld 1,
|
|
165
|
+
* cost centres, and whether it is a general reversal) and not on its wording or its document link. The rows are
|
|
166
|
+
* compared as a multiset: a row that stands twice in one and once in the other differs once. Both sides are compared
|
|
167
|
+
* as a file states them (`comparableOf`: a `"` as `'`, Belegfeld 1 cut to 36 characters, a character Windows-1252
|
|
168
|
+
* cannot hold as `?`), so bookings stored of rows before writing and bookings read from a file compare alike. A
|
|
169
|
+
* booking stored before 4.1, which does not record its cost centres, never differs on them (`pairBookings`); compare
|
|
170
|
+
* `bookingsOfRows` of the rows sent for an answer that counts them. A booking a Buchungsstapel cannot hold (no day,
|
|
171
|
+
* no positive amount) is refused (`invalid_bookings`).
|
|
55
172
|
*/
|
|
56
173
|
export const differingBookingCount = (leftArg: readonly IExportBooking[], rightArg: readonly IExportBooking[]): number => {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const unmatched = new Map<string, number>();
|
|
60
|
-
for (const booking of leftArg) {
|
|
61
|
-
unmatched.set(keyOf(booking), (unmatched.get(keyOf(booking)) ?? 0) + 1);
|
|
62
|
-
}
|
|
63
|
-
let differing = 0;
|
|
64
|
-
for (const booking of rightArg) {
|
|
65
|
-
const left = unmatched.get(keyOf(booking)) ?? 0;
|
|
66
|
-
if (left > 0) {
|
|
67
|
-
unmatched.set(keyOf(booking), left - 1);
|
|
68
|
-
} else {
|
|
69
|
-
differing += 1;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const left of unmatched.values()) {
|
|
73
|
-
differing += left;
|
|
74
|
-
}
|
|
75
|
-
return differing;
|
|
174
|
+
const { leftPaired, rightPaired } = pairBookings(comparableOf(leftArg), comparableOf(rightArg));
|
|
175
|
+
return [...leftPaired, ...rightPaired].filter((pairedArg) => !pairedArg).length;
|
|
76
176
|
};
|
|
77
177
|
|
|
78
178
|
/**
|
|
@@ -176,16 +276,24 @@ export interface IBuchungsstapelFile {
|
|
|
176
276
|
csv: string;
|
|
177
277
|
/** the file in Windows-1252, as it is handed over */
|
|
178
278
|
bytes: Uint8Array;
|
|
279
|
+
/** the rows as they were given to the writer; the file states them as `rowsAsWritten` does */
|
|
179
280
|
rows: IDatevBuchungsstapelRow[];
|
|
281
|
+
/**
|
|
282
|
+
* The bookings the file states, read back from its bytes (`@fin.cx/skr` `readExtfBuchungsstapel`): a `"` in a
|
|
283
|
+
* text field as `'`, Belegfeld 1 cut to 36 characters, a character Windows-1252 cannot hold as `?`. What a file
|
|
284
|
+
* read back is compared with, and what a correction's `handedOver` is (4.1 and before: the rows' bookings before
|
|
285
|
+
* writing).
|
|
286
|
+
*/
|
|
180
287
|
bookings: IExportBooking[];
|
|
181
288
|
/** the characters Windows-1252 cannot write, each written as `?`, with the row and field it stands in */
|
|
182
289
|
replacedCharacters: IBuchungsstapelReplacement[];
|
|
183
290
|
}
|
|
184
291
|
|
|
185
|
-
/**
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
292
|
+
/** The file of rows: the header and the caption row, the rows, encoded in Windows-1252, with where each character stands. */
|
|
293
|
+
const fileOfRows = (rows: IDatevBuchungsstapelRow[], headerArg: IDatevExtfHeaderOptions, filenameArg: string): IBuchungsstapelFile => {
|
|
294
|
+
refuseUnwritableRows(rows);
|
|
295
|
+
refuseOutsideHeader(rows, headerArg);
|
|
296
|
+
const { csv } = plugins.skrCore.writeExtfBuchungsstapel(rows, headerArg);
|
|
189
297
|
const { bytes, replaced } = encodeWindows1252(csv);
|
|
190
298
|
// the file is the header line, the caption line and one line per row, each ending in CRLF (`writeExtfBuchungsstapel`)
|
|
191
299
|
const rowLines = rows.map((rowArg) => plugins.skrCore.datevRowFields(rowArg));
|
|
@@ -210,5 +318,266 @@ export const writeBuchungsstapel = (optionsArg: IBuchungsstapelOptions): IBuchun
|
|
|
210
318
|
}
|
|
211
319
|
return { ...replacedArg, row, field: fieldAt(fieldStartsOf(rowLines[row]), replacedArg.offset - lineStarts[row]) };
|
|
212
320
|
});
|
|
213
|
-
|
|
321
|
+
// the bookings of what was written, as the file states them
|
|
322
|
+
const written = plugins.skrCore.readExtfBuchungsstapel(bytes).rows;
|
|
323
|
+
if (written.length !== rows.length) {
|
|
324
|
+
throw new CompatibilityError('export_failed', `${rows.length} row(s) written, ${written.length} read back`);
|
|
325
|
+
}
|
|
326
|
+
return { filename: filenameArg, csv, bytes, rows, bookings: bookingsOfRows(written), replacedCharacters };
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/** The Belegdatum of a row as its day, midnight UTC, in epoch milliseconds. */
|
|
330
|
+
const dayOfRow = (rowArg: IDatevBuchungsstapelRow): number =>
|
|
331
|
+
Date.UTC(rowArg.belegDatum.getUTCFullYear(), rowArg.belegDatum.getUTCMonth(), rowArg.belegDatum.getUTCDate());
|
|
332
|
+
|
|
333
|
+
/** A row as a refusal names it: its place among the rows given, its day, amount, side, accounts and Belegfeld 1. */
|
|
334
|
+
const rowNameOf = (rowArg: IDatevBuchungsstapelRow, indexArg: number): string =>
|
|
335
|
+
`row ${indexArg + 1} (${Number.isNaN(rowArg.belegDatum.getTime()) ? 'no day' : rowArg.belegDatum.toISOString().slice(0, 10)}, ${rowArg.umsatzCents} ${rowArg.sollHaben} ${rowArg.konto}/${rowArg.gegenKonto}`
|
|
336
|
+
+ `${rowArg.belegfeld1 ? `, Belegfeld 1 ${rowArg.belegfeld1}` : ''})`;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Refuses a row a Buchungsstapel does not hold, before anything is written: a Belegdatum that is no day, and an
|
|
340
|
+
* amount that is not a positive whole number of cents (a Buchungsstapel states the side apart, field 2, and
|
|
341
|
+
* `readExtfBuchungsstapel` refuses any other). `invalid_bookings`, naming the row.
|
|
342
|
+
*/
|
|
343
|
+
const refuseUnwritableRows = (rowsArg: readonly IDatevBuchungsstapelRow[]): void => {
|
|
344
|
+
rowsArg.forEach((rowArg, indexArg) => {
|
|
345
|
+
if (Number.isNaN(rowArg.belegDatum.getTime())) {
|
|
346
|
+
throw new CompatibilityError('invalid_bookings', `${rowNameOf(rowArg, indexArg)} has no Belegdatum`);
|
|
347
|
+
}
|
|
348
|
+
if (!Number.isSafeInteger(rowArg.umsatzCents) || rowArg.umsatzCents <= 0) {
|
|
349
|
+
throw new CompatibilityError('invalid_bookings', `${rowNameOf(rowArg, indexArg)}: a Buchungsstapel states a positive amount, the side apart`);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Refuses a header whose period a Buchungsstapel cannot state (`invalid_header`): Datum von after Datum bis, or a
|
|
356
|
+
* period that does not lie within the one Wirtschaftsjahr beginning on the Wirtschaftsjahr-Beginn (a Belegdatum is
|
|
357
|
+
* written TTMM, and the reader takes its year from them); and every row whose Belegdatum lies outside the period
|
|
358
|
+
* (`invalid_bookings`, naming it). Checked before anything is written.
|
|
359
|
+
*/
|
|
360
|
+
const refuseOutsideHeader = (rowsArg: readonly IDatevBuchungsstapelRow[], headerArg: IDatevExtfHeaderOptions): void => {
|
|
361
|
+
const day = (dateArg: Date): number => Date.UTC(dateArg.getUTCFullYear(), dateArg.getUTCMonth(), dateArg.getUTCDate());
|
|
362
|
+
const from = day(headerArg.dateFrom);
|
|
363
|
+
const to = day(headerArg.dateTo);
|
|
364
|
+
const start = day(headerArg.fiscalYearStart);
|
|
365
|
+
const end = Date.UTC(headerArg.fiscalYearStart.getUTCFullYear() + 1, headerArg.fiscalYearStart.getUTCMonth(), headerArg.fiscalYearStart.getUTCDate());
|
|
366
|
+
const iso = (timeArg: number): string => new Date(timeArg).toISOString().slice(0, 10);
|
|
367
|
+
if ([from, to, start].some(Number.isNaN)) {
|
|
368
|
+
throw new CompatibilityError('invalid_header', 'the header states no day for the Wirtschaftsjahr-Beginn, Datum von or Datum bis');
|
|
369
|
+
}
|
|
370
|
+
if (to < from) {
|
|
371
|
+
throw new CompatibilityError('invalid_header', `the period of the header ends (${iso(to)}) before it begins (${iso(from)})`);
|
|
372
|
+
}
|
|
373
|
+
if (from < start || to >= end) {
|
|
374
|
+
throw new CompatibilityError('invalid_header', `the period of the header, ${iso(from)} to ${iso(to)}, does not lie within the Wirtschaftsjahr beginning ${iso(start)}`);
|
|
375
|
+
}
|
|
376
|
+
rowsArg.forEach((rowArg, indexArg) => {
|
|
377
|
+
const time = dayOfRow(rowArg);
|
|
378
|
+
if (time < from || time > to) {
|
|
379
|
+
throw new CompatibilityError('invalid_bookings', `${rowNameOf(rowArg, indexArg)} lies outside the period of the header, ${iso(from)} to ${iso(to)}`);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Rows as a Buchungsstapel file states them: each row written by the writer (`@fin.cx/skr` `writeExtfBuchungsstapel`,
|
|
386
|
+
* which writes a `"` in a text field as `'` and cuts a field to its DATEV length), encoded in Windows-1252 (a
|
|
387
|
+
* character it cannot hold written `?`, `encodeWindows1252`), and read back (`@fin.cx/skr` `readExtfBuchungsstapel`),
|
|
388
|
+
* in the order given. Every rule of the writer, the encoding and the reader applies because the rows go through them,
|
|
389
|
+
* not through a copy of their rules. The rows of each calendar year are written as one file whose header spans their
|
|
390
|
+
* days; the header states nothing a row is read by beyond its days. A row the writer or the reader refuses throws as
|
|
391
|
+
* it does.
|
|
392
|
+
*/
|
|
393
|
+
export const rowsAsWritten = (rowsArg: readonly IDatevBuchungsstapelRow[]): IDatevBuchungsstapelRow[] => {
|
|
394
|
+
refuseUnwritableRows(rowsArg);
|
|
395
|
+
const written: IDatevBuchungsstapelRow[] = new Array(rowsArg.length);
|
|
396
|
+
// the indexes of the rows of each calendar year, and the first and last day among them
|
|
397
|
+
const years = new Map<number, { indexes: number[]; first: number; last: number }>();
|
|
398
|
+
rowsArg.forEach((rowArg, indexArg) => {
|
|
399
|
+
const year = rowArg.belegDatum.getUTCFullYear();
|
|
400
|
+
const time = dayOfRow(rowArg);
|
|
401
|
+
const group = years.get(year);
|
|
402
|
+
if (group) {
|
|
403
|
+
group.indexes.push(indexArg);
|
|
404
|
+
group.first = Math.min(group.first, time);
|
|
405
|
+
group.last = Math.max(group.last, time);
|
|
406
|
+
} else {
|
|
407
|
+
years.set(year, { indexes: [indexArg], first: time, last: time });
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
for (const [year, group] of years) {
|
|
411
|
+
const { csv } = plugins.skrCore.writeExtfBuchungsstapel(group.indexes.map((indexArg) => rowsArg[indexArg]), {
|
|
412
|
+
createdAt: new Date(Date.UTC(year, 0, 1)),
|
|
413
|
+
consultantNumber: 0,
|
|
414
|
+
clientNumber: 0,
|
|
415
|
+
// a Belegdatum is written TTMM: the reader takes its year from the Wirtschaftsjahr and the period of the header
|
|
416
|
+
fiscalYearStart: new Date(Date.UTC(year, 0, 1)),
|
|
417
|
+
dateFrom: new Date(group.first),
|
|
418
|
+
dateTo: new Date(group.last),
|
|
419
|
+
label: '',
|
|
420
|
+
festschreibung: false,
|
|
421
|
+
});
|
|
422
|
+
const read = plugins.skrCore.readExtfBuchungsstapel(encodeWindows1252(csv).bytes).rows;
|
|
423
|
+
if (read.length !== group.indexes.length) {
|
|
424
|
+
throw new CompatibilityError('export_failed', `${group.indexes.length} row(s) written, ${read.length} read back`);
|
|
425
|
+
}
|
|
426
|
+
group.indexes.forEach((indexArg, positionArg) => {
|
|
427
|
+
written[indexArg] = read[positionArg];
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return written;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* The bookings of rows as a Buchungsstapel file states them (`rowsAsWritten`): what a file handed over, read back
|
|
435
|
+
* with `readExtfBuchungsstapel`, is compared with. `bookingsOfRows` of the rows before writing keeps what the file
|
|
436
|
+
* does not (a `"` in Belegfeld 1, a Belegfeld 1 longer than its 36 characters, a character Windows-1252 cannot
|
|
437
|
+
* hold), and Belegfeld 1 is compared, so such a booking would differ from the file forever.
|
|
438
|
+
*/
|
|
439
|
+
export const bookingsAsWritten = (rowsArg: readonly IDatevBuchungsstapelRow[]): IExportBooking[] => bookingsOfRows(rowsAsWritten(rowsArg));
|
|
440
|
+
|
|
441
|
+
/** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
|
|
442
|
+
export const writeBuchungsstapel = (optionsArg: IBuchungsstapelOptions): IBuchungsstapelFile =>
|
|
443
|
+
fileOfRows(datevRowsOf(optionsArg.drafts, optionsArg.policy), optionsArg.header, optionsArg.filename);
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* The row a booking handed over was written as, for a general reversal of it: every field the booking keeps
|
|
447
|
+
* (`IExportBooking`) in its place, the Belegdatum at midnight UTC as the writer reads it.
|
|
448
|
+
*/
|
|
449
|
+
export const rowOfBooking = (bookingArg: IExportBooking): IDatevBuchungsstapelRow => ({
|
|
450
|
+
umsatzCents: bookingArg.umsatzCents,
|
|
451
|
+
sollHaben: bookingArg.sollHaben,
|
|
452
|
+
konto: bookingArg.konto,
|
|
453
|
+
gegenKonto: bookingArg.gegenkonto,
|
|
454
|
+
buSchluessel: bookingArg.buKey as IDatevBuchungsstapelRow['buSchluessel'],
|
|
455
|
+
belegDatum: new Date(`${bookingArg.date}T00:00:00.000Z`),
|
|
456
|
+
...(bookingArg.belegfeld1 ? { belegfeld1: bookingArg.belegfeld1 } : {}),
|
|
457
|
+
...(bookingArg.belegfeld2 ? { belegfeld2: bookingArg.belegfeld2 } : {}),
|
|
458
|
+
buchungstext: bookingArg.text,
|
|
459
|
+
...(bookingArg.documentGuid ? { belegLink: bookingArg.documentGuid } : {}),
|
|
460
|
+
...(bookingArg.kost1 ? { kost1: bookingArg.kost1 } : {}),
|
|
461
|
+
...(bookingArg.kost2 ? { kost2: bookingArg.kost2 } : {}),
|
|
462
|
+
festschreibung: false,
|
|
463
|
+
...(bookingArg.generalumkehr ? { generalumkehr: true } : {}),
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
/** A booking as a refusal names it: its place among the bookings handed over, day, amount, side, accounts and Belegfeld 1. */
|
|
467
|
+
const bookingNameOf = (bookingArg: IExportBooking, indexArg: number): string =>
|
|
468
|
+
`booking ${indexArg + 1} (${bookingArg.date}, ${bookingArg.umsatzCents} ${bookingArg.sollHaben} ${bookingArg.konto}/${bookingArg.gegenkonto}`
|
|
469
|
+
+ `${bookingArg.belegfeld1 ? `, Belegfeld 1 ${bookingArg.belegfeld1}` : ''})`;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Refuses a state handed over that holds a booking whose cost centres its record does not state (stored before 4.1).
|
|
473
|
+
* The Stapel sent stated them (fields 37 and 38), so DATEV holds them: whether the booking must be taken back turns on
|
|
474
|
+
* them, and a reversal that does not state them would leave them standing in DATEV's cost accounting. The rows sent
|
|
475
|
+
* tell them (`handedOverRows`).
|
|
476
|
+
*/
|
|
477
|
+
const refuseCostCentresUnknown = (handedOverArg: readonly IExportBooking[]): void => {
|
|
478
|
+
const unknown = handedOverArg.findIndex((bookingArg) => !recordsCostCentres(bookingArg));
|
|
479
|
+
if (unknown >= 0) {
|
|
480
|
+
throw new CompatibilityError(
|
|
481
|
+
'handed_over_cost_centres_unknown',
|
|
482
|
+
`${bookingNameOf(handedOverArg[unknown], unknown)} was stored without the cost centres it was sent with; pass the rows of the Stapel sent (handedOverRows)`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* What a correction of a period handed over changes, compared as `differingBookingCount` compares: the bookings
|
|
489
|
+
* handed over that the current ones no longer hold (`reversed`, each once for each time it is missing), the ones they
|
|
490
|
+
* still hold (`kept`, as they were handed over) and the current rows the bookings handed over do not hold (`added`),
|
|
491
|
+
* each in its order. A booking that stands in both is kept: a changed text or document link is no changed booking,
|
|
492
|
+
* a changed cost centre is. The current rows are compared as a file states them (`bookingsAsWritten`), as the
|
|
493
|
+
* bookings handed over are; `added` are the rows themselves, which the writer writes. A booking handed over whose
|
|
494
|
+
* cost centres its record does not state (stored before 4.1) is refused (`handed_over_cost_centres_unknown`): pass
|
|
495
|
+
* `bookingsOfRows` of the rows sent instead.
|
|
496
|
+
*/
|
|
497
|
+
export const correctionOf = (
|
|
498
|
+
handedOverArg: readonly IExportBooking[],
|
|
499
|
+
currentArg: readonly IDatevBuchungsstapelRow[],
|
|
500
|
+
): { reversed: IExportBooking[]; kept: IExportBooking[]; added: IDatevBuchungsstapelRow[] } => {
|
|
501
|
+
refuseCostCentresUnknown(handedOverArg);
|
|
502
|
+
// the current rows as a file states them, as the state handed over was: a reference the writer changes is the same
|
|
503
|
+
const { leftPaired, rightPaired } = pairBookings(comparableOf(handedOverArg), bookingsAsWritten(currentArg));
|
|
504
|
+
return {
|
|
505
|
+
reversed: handedOverArg.filter((_bookingArg, indexArg) => !leftPaired[indexArg]),
|
|
506
|
+
kept: handedOverArg.filter((_bookingArg, indexArg) => leftPaired[indexArg]),
|
|
507
|
+
added: currentArg.filter((_rowArg, indexArg) => !rightPaired[indexArg]),
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* The state of a period handed over, given once: the bookings stored of it (`handedOver`) or the rows of the Stapel
|
|
513
|
+
* sent (`handedOverRows`). A general reversal in it is refused: the state handed over is compared, not a correction
|
|
514
|
+
* file.
|
|
515
|
+
*/
|
|
516
|
+
export interface ICorrectionBuchungsstapelOptions {
|
|
517
|
+
/**
|
|
518
|
+
* The bookings of the period as they were handed over, as a whole: the bookings of its Buchungsstapel as stored
|
|
519
|
+
* (`IBuchungsstapelFile.bookings`), or after a correction the period's bookings as that correction left them
|
|
520
|
+
* (`bookingsAfter`). Each must state its cost centres: one stored before 4.1 does not, and is refused
|
|
521
|
+
* (`handed_over_cost_centres_unknown`); pass the rows sent for it. Bookings stored by any version, before or after
|
|
522
|
+
* writing, are compared as a file states them (`comparableOf`), so a reference the writer changed is no change.
|
|
523
|
+
*/
|
|
524
|
+
handedOver?: readonly IExportBooking[];
|
|
525
|
+
/**
|
|
526
|
+
* The rows of the Buchungsstapel as it was sent: the transmitted file read back (`@fin.cx/skr`
|
|
527
|
+
* `readExtfBuchungsstapel(bytes).rows`, skr 7.0.1 and later) or the rows given to the writer
|
|
528
|
+
* (`IBuchungsstapelFile.rows`), taken as the file states them (`bookingsAsWritten`). What DATEV received, cost
|
|
529
|
+
* centres (fields 37 and 38) included, so a reversal mirrors it exactly. For a Stapel whose bookings were stored
|
|
530
|
+
* before 4.1, which record no cost centres.
|
|
531
|
+
*/
|
|
532
|
+
handedOverRows?: readonly IDatevBuchungsstapelRow[];
|
|
533
|
+
/** the period's drafts as they stand now */
|
|
534
|
+
drafts: readonly plugins.skrCore.IJournalDraft[];
|
|
535
|
+
/** the policy the drafts are checked against */
|
|
536
|
+
policy: plugins.skrCore.IAccountPolicy;
|
|
537
|
+
/** the header of the file: the same period (`dateFrom`, `dateTo`) as the Stapel handed over, a label of its own */
|
|
538
|
+
header: IDatevExtfHeaderOptions;
|
|
539
|
+
filename: string;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export interface ICorrectionBuchungsstapelFile extends IBuchungsstapelFile {
|
|
543
|
+
/** the bookings handed over it takes back, each written as a general reversal */
|
|
544
|
+
reversed: IExportBooking[];
|
|
545
|
+
/** the rows it books anew */
|
|
546
|
+
added: IDatevBuchungsstapelRow[];
|
|
547
|
+
/**
|
|
548
|
+
* The period's bookings as DATEV holds them after this correction: each booking handed over that it keeps, as it
|
|
549
|
+
* was handed over (its text and document link too), then the rows it books anew. What the next correction is
|
|
550
|
+
* compared against.
|
|
551
|
+
*/
|
|
552
|
+
bookingsAfter: IExportBooking[];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Writes the correction Buchungsstapel of a period handed over whose bookings changed since: only what changed. Each
|
|
557
|
+
* booking handed over that the drafts no longer hold is written again with field 118 "Generalumkehr" set, so DATEV
|
|
558
|
+
* takes it back on its side and day, and each row the drafts hold that was not handed over is written as it is; rows
|
|
559
|
+
* in both are not written. Every row keeps its Belegdatum, so it counts in the period it belongs to. A booking handed
|
|
560
|
+
* over is never changed or replaced (§ 146 Abs. 4 AO): it is taken back and booked anew. Throws `nothing_to_correct`
|
|
561
|
+
* when nothing changed; `invalid_bookings` for a general reversal in the state handed over, or for a state given
|
|
562
|
+
* twice or not at all; `handed_over_cost_centres_unknown` for a booking whose cost centres are not known.
|
|
563
|
+
*/
|
|
564
|
+
export const writeCorrectionBuchungsstapel = (optionsArg: ICorrectionBuchungsstapelOptions): ICorrectionBuchungsstapelFile => {
|
|
565
|
+
// rows read back from the file sent are as written already; rows given to the writer become what it wrote
|
|
566
|
+
const handedOver = optionsArg.handedOverRows !== undefined ? bookingsAsWritten(optionsArg.handedOverRows) : optionsArg.handedOver;
|
|
567
|
+
if (handedOver === undefined || (optionsArg.handedOver !== undefined && optionsArg.handedOverRows !== undefined)) {
|
|
568
|
+
throw new CompatibilityError('invalid_bookings', 'pass the state handed over once: the bookings stored (handedOver) or the rows sent (handedOverRows)');
|
|
569
|
+
}
|
|
570
|
+
const reversal = handedOver.findIndex((bookingArg) => bookingArg.generalumkehr === true);
|
|
571
|
+
if (reversal >= 0) {
|
|
572
|
+
throw new CompatibilityError('invalid_bookings', `the state handed over holds a general reversal (booking ${reversal + 1}); pass the period's bookings as they stood, not a correction file`);
|
|
573
|
+
}
|
|
574
|
+
const current = datevRowsOf(optionsArg.drafts, optionsArg.policy);
|
|
575
|
+
const { reversed, kept, added } = correctionOf(handedOver, current);
|
|
576
|
+
if (reversed.length === 0 && added.length === 0) {
|
|
577
|
+
throw new CompatibilityError('nothing_to_correct', 'the bookings of the period are the ones handed over; there is nothing to correct');
|
|
578
|
+
}
|
|
579
|
+
const rows = [...reversed.map((bookingArg) => ({ ...rowOfBooking(bookingArg), generalumkehr: true })), ...added];
|
|
580
|
+
const file = fileOfRows(rows, optionsArg.header, optionsArg.filename);
|
|
581
|
+
// the rows booked anew as the correction file states them, after the reversals
|
|
582
|
+
return { ...file, reversed, added, bookingsAfter: [...kept, ...file.bookings.slice(reversed.length)] };
|
|
214
583
|
};
|