@fin.cx/compatibility 1.1.0 → 2.0.0

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