@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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/compatibility',
6
- version: '4.0.0',
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=
@@ -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; the message names it;
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9lcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFRQSxNQUFNLE9BQU8sa0JBQW1CLFNBQVEsS0FBSztJQUczQyxZQUFZLE9BQWdDLEVBQUUsVUFBa0I7UUFDOUQsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ2xCLElBQUksQ0FBQyxJQUFJLEdBQUcsb0JBQW9CLENBQUM7UUFDakMsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUM7SUFDdEIsQ0FBQztDQUNGIn0=
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9lcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFnQkEsTUFBTSxPQUFPLGtCQUFtQixTQUFRLEtBQUs7SUFHM0MsWUFBWSxPQUFnQyxFQUFFLFVBQWtCO1FBQzlELEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNsQixJQUFJLENBQUMsSUFBSSxHQUFHLG9CQUFvQixDQUFDO1FBQ2pDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDO0lBQ3RCLENBQUM7Q0FDRiJ9
@@ -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 and Belegfeld
30
- * 1) and not on its wording or its document link. The rows are compared as a
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;
@@ -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
- * How many rows two Buchungsstapel of one period do not share, compared on
26
- * what a booking says (its day, amount, side, accounts, BU key and Belegfeld
27
- * 1) and not on its wording or its document link. The rows are compared as a
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
- export const differingBookingCount = (leftArg, rightArg) => {
31
- const keyOf = (bookingArg) => [bookingArg.date, bookingArg.umsatzCents, bookingArg.sollHaben, bookingArg.konto, bookingArg.gegenkonto, bookingArg.buKey, bookingArg.belegfeld1 ?? ''].join('|');
32
- const unmatched = new Map();
33
- for (const booking of leftArg) {
34
- unmatched.set(keyOf(booking), (unmatched.get(keyOf(booking)) ?? 0) + 1);
35
- }
36
- let differing = 0;
37
- for (const booking of rightArg) {
38
- const left = unmatched.get(keyOf(booking)) ?? 0;
39
- if (left > 0) {
40
- unmatched.set(keyOf(booking), left - 1);
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
- else {
43
- differing += 1;
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
- for (const left of unmatched.values()) {
47
- differing += left;
48
- }
49
- return differing;
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
- /** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
113
- export const writeBuchungsstapel = (optionsArg) => {
114
- const rows = datevRowsOf(optionsArg.drafts, optionsArg.policy);
115
- const { csv } = plugins.skrCore.writeExtfBuchungsstapel(rows, optionsArg.header);
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
- return { filename: optionsArg.filename, csv, bytes, rows, bookings: bookingsOfRows(rows), replacedCharacters };
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": "4.0.0",
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": "^6.0.0",
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": "^6.0.0"
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` (^6.0.0) 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.
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 and Belegfeld 1, as a multiset, and ignores wording and document links.
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';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/compatibility',
6
- version: '4.0.0',
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; the message names it;
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 and Belegfeld
53
- * 1) and not on its wording or its document link. The rows are compared as a
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 keyOf = (bookingArg: IExportBooking): string =>
58
- [bookingArg.date, bookingArg.umsatzCents, bookingArg.sollHaben, bookingArg.konto, bookingArg.gegenkonto, bookingArg.buKey, bookingArg.belegfeld1 ?? ''].join('|');
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
- /** Writes a Buchungsstapel: the rows of the drafts, the header and the caption row, encoded in Windows-1252. */
186
- export const writeBuchungsstapel = (optionsArg: IBuchungsstapelOptions): IBuchungsstapelFile => {
187
- const rows = datevRowsOf(optionsArg.drafts, optionsArg.policy);
188
- const { csv } = plugins.skrCore.writeExtfBuchungsstapel(rows, optionsArg.header);
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
- return { filename: optionsArg.filename, csv, bytes, rows, bookings: bookingsOfRows(rows), replacedCharacters };
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
  };