@substrat-run/connector-fortnox 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mock.js ADDED
@@ -0,0 +1,111 @@
1
+ /** Encode to ISO-8859-1. Any code point above 0xff becomes `?`, exactly as Fortnox would. */
2
+ export function latin1Bytes(text) {
3
+ const out = new Uint8Array(text.length);
4
+ for (let i = 0; i < text.length; i += 1) {
5
+ const code = text.charCodeAt(i);
6
+ out[i] = code <= 0xff ? code : 0x3f;
7
+ }
8
+ return out;
9
+ }
10
+ export class FortnoxMock {
11
+ apiBase = 'https://api.fortnox.test/3';
12
+ oauthBase = 'https://apps.fortnox.test/oauth-v1';
13
+ clientId;
14
+ clientSecret;
15
+ tenantId;
16
+ company;
17
+ years;
18
+ sie;
19
+ expiresIn;
20
+ /** Every token this mock has minted — a test asserts the mint happened exactly once. */
21
+ mints = [];
22
+ /** Every REST path fetched, in order. */
23
+ calls = [];
24
+ constructor(options = {}) {
25
+ this.clientId = options.clientId ?? 'client-id';
26
+ this.clientSecret = options.clientSecret ?? 'client-secret';
27
+ this.tenantId = options.tenantId ?? '123456';
28
+ this.company = options.company ?? {
29
+ CompanyName: 'Testbolaget AB',
30
+ OrganizationNumber: '556677-8899',
31
+ DatabaseNumber: 123456,
32
+ };
33
+ this.years = options.financialYears ?? [
34
+ { Id: 1, FromDate: '2025-01-01', ToDate: '2025-12-31' },
35
+ { Id: 2, FromDate: '2026-01-01', ToDate: '2026-12-31' },
36
+ ];
37
+ this.sie = options.sie ?? {};
38
+ this.expiresIn = options.expiresIn ?? 3600;
39
+ }
40
+ /** Replace one year's SIE payload — how a test simulates "the books changed". */
41
+ setSie(financialYearId, text) {
42
+ this.sie[financialYearId] = text;
43
+ }
44
+ json(body, status = 200) {
45
+ const text = JSON.stringify(body);
46
+ return {
47
+ ok: status >= 200 && status < 300,
48
+ status,
49
+ text: async () => text,
50
+ arrayBuffer: async () => latin1Bytes(text).buffer,
51
+ };
52
+ }
53
+ /** The `fetch` a connector is handed. */
54
+ fetch = async (input, init) => {
55
+ const url = new URL(input);
56
+ const path = url.pathname + url.search;
57
+ if (path.endsWith('/oauth-v1/token')) {
58
+ const headers = (init?.headers ?? {});
59
+ const auth = headers['Authorization'] ?? '';
60
+ const expected = `Basic ${btoa(`${this.clientId}:${this.clientSecret}`)}`;
61
+ if (auth !== expected) {
62
+ return this.json({ error: 'invalid_client', error_description: 'bad client credentials' }, 401);
63
+ }
64
+ if ((headers['TenantId'] ?? '') !== this.tenantId) {
65
+ // Fortnox answers 400 here, not 404 — a wrong TenantId reads like a bad client
66
+ // pair unless the message is surfaced, which is why the connector surfaces it.
67
+ return this.json({ error: 'invalid_request', error_description: 'unknown tenant' }, 400);
68
+ }
69
+ if (!String(init?.body ?? '').includes('grant_type=client_credentials')) {
70
+ return this.json({ error: 'unsupported_grant_type' }, 400);
71
+ }
72
+ const token = `token-${this.mints.length + 1}`;
73
+ this.mints.push(token);
74
+ return this.json({
75
+ access_token: token,
76
+ token_type: 'bearer',
77
+ expires_in: this.expiresIn,
78
+ scope: 'bookkeeping companyinformation',
79
+ });
80
+ }
81
+ const headers = (init?.headers ?? {});
82
+ const bearer = (headers['Authorization'] ?? '').replace(/^Bearer /, '');
83
+ if (!this.mints.includes(bearer)) {
84
+ return this.json({ ErrorInformation: { message: 'invalid token' } }, 401);
85
+ }
86
+ this.calls.push(path);
87
+ if (path.endsWith('/companyinformation')) {
88
+ return this.json({ CompanyInformation: this.company });
89
+ }
90
+ if (path.endsWith('/financialyears')) {
91
+ return this.json({ FinancialYears: this.years });
92
+ }
93
+ const sieMatch = /\/sie\/4\?financialyear=(\d+)$/.exec(path);
94
+ if (sieMatch) {
95
+ const id = Number(sieMatch[1]);
96
+ const text = this.sie[id];
97
+ if (text === undefined) {
98
+ return this.json({ ErrorInformation: { message: 'no such financial year' } }, 404);
99
+ }
100
+ const bytes = latin1Bytes(text);
101
+ return {
102
+ ok: true,
103
+ status: 200,
104
+ text: async () => text,
105
+ arrayBuffer: async () => bytes.buffer,
106
+ };
107
+ }
108
+ return this.json({ ErrorInformation: { message: `unmocked path ${path}` } }, 404);
109
+ };
110
+ }
111
+ //# sourceMappingURL=mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.js","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"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"}
package/dist/sie4.d.ts ADDED
@@ -0,0 +1,132 @@
1
+ /**
2
+ * A SIE4 reader — the Swedish standard accounting interchange format, which is how
3
+ * this connector reads a whole year's bookkeeping in one request instead of walking
4
+ * per-voucher REST endpoints.
5
+ *
6
+ * Deliberately a parser over a *text* input, with no I/O of its own: the decoding
7
+ * from ISO-8859-1 happens at the seam that owns the bytes ({@link FortnoxApi.sieFile}),
8
+ * so this file is pure and testable against a fixture, and a caller reading SIE from
9
+ * somewhere else (an upload, an archive) can use it unchanged.
10
+ *
11
+ * ## What it reads, and what it deliberately ignores
12
+ *
13
+ * Posts read: `#FNAMN`, `#ORGNR`, `#VALUTA`, `#RAR`, `#KONTO`, `#DIM`, `#OBJEKT`,
14
+ * `#VER` and its `#TRANS` rows. Everything else — `#IB`/`#UB`/`#RES` balances,
15
+ * `#KTYP`, `#SRU`, the file metadata — is skipped, and skipped ON PURPOSE rather
16
+ * than by omission:
17
+ *
18
+ * - **`#IB`/`#UB` are derived, and including them double-counts.** They are the
19
+ * opening and closing balances the vouchers already sum to. A reader that adds
20
+ * them to the `#TRANS` rows reports every balance twice over.
21
+ * - Everything else is metadata this connector has no use for. An unknown post is
22
+ * ignored rather than fatal: SIE files carry vendor extensions, and refusing a
23
+ * file over a post we do not read would break on a Fortnox release that adds one.
24
+ */
25
+ /** A parsed `#KONTO` — the chart of accounts. */
26
+ export interface SieAccount {
27
+ number: string;
28
+ name: string;
29
+ }
30
+ /** A parsed `#DIM` — a dimension. Dimension `1` is kostnadsställe (cost centre). */
31
+ export interface SieDimension {
32
+ number: string;
33
+ name: string;
34
+ }
35
+ /** A parsed `#OBJEKT` — one value within a dimension, e.g. a specific property. */
36
+ export interface SieObject {
37
+ dimension: string;
38
+ code: string;
39
+ name: string;
40
+ }
41
+ /** One `#TRANS` row inside a voucher. */
42
+ export interface SieTransaction {
43
+ account: string;
44
+ /**
45
+ * The object list, as `dimension → object code`.
46
+ *
47
+ * `{"1" "2001"}` becomes `{ '1': '2001' }`. Dimension 1 is the cost centre, which
48
+ * is what lets a caller break a ledger down per property/unit with no extra API
49
+ * call and no per-object filtering in the request.
50
+ */
51
+ objects: Record<string, string>;
52
+ /** Debit-positive, as SIE defines it. See {@link SieLedger} on the sign convention. */
53
+ amount: string;
54
+ /** `YYYY-MM-DD` when the row carries its own date; otherwise the voucher's. */
55
+ date: string;
56
+ text: string;
57
+ quantity: string | null;
58
+ }
59
+ /** One `#VER` — a voucher and its rows. */
60
+ export interface SieVoucher {
61
+ series: string;
62
+ number: string;
63
+ /** `YYYY-MM-DD`, normalized from SIE's `YYYYMMDD`. */
64
+ date: string;
65
+ text: string;
66
+ transactions: SieTransaction[];
67
+ }
68
+ /**
69
+ * A whole SIE4 file, parsed.
70
+ *
71
+ * ## The sign convention, stated once
72
+ *
73
+ * SIE amounts are **debit-positive**. A cost account therefore carries a positive
74
+ * amount for a cost, and a revenue account (a credit) carries a *negative* one. That
75
+ * is correct double-entry and it is not what a report wants to show, so a consumer
76
+ * normalizes with a per-account sign when it maps to its own vocabulary. This parser
77
+ * does not normalize: inventing a sign here would put business meaning in a format
78
+ * reader, and the mapping is the vertical's to own.
79
+ */
80
+ export interface SieLedger {
81
+ company: string;
82
+ organizationNumber: string;
83
+ /** From `#VALUTA`; `'SEK'` when the file names none, which is the Fortnox default. */
84
+ currency: string;
85
+ /** From `#RAR 0 …` — the year this file covers, when it says. */
86
+ range: {
87
+ from: string;
88
+ to: string;
89
+ } | null;
90
+ accounts: SieAccount[];
91
+ dimensions: SieDimension[];
92
+ objects: SieObject[];
93
+ vouchers: SieVoucher[];
94
+ }
95
+ /**
96
+ * Split one SIE line into fields, honouring quoting and object lists.
97
+ *
98
+ * Three things make this more than `line.split(' ')`, and all three appear in real
99
+ * Fortnox output:
100
+ *
101
+ * 1. **Quoted fields** hold spaces — `"Hyra juni"` is one field, and an account name
102
+ * is almost always quoted.
103
+ * 2. **Backslash escapes** inside quotes — `\"` and `\\` — so a name containing a
104
+ * quote does not end the field early.
105
+ * 3. **Object lists** are brace-delimited and hold their own quoted fields, so
106
+ * `{"1" "2001"}` must survive as ONE field for the caller to unpack.
107
+ */
108
+ export declare function splitSieLine(line: string): string[];
109
+ /** `YYYYMMDD` → `YYYY-MM-DD`. Anything else is passed through unchanged. */
110
+ export declare function sieDate(raw: string | undefined): string;
111
+ /**
112
+ * Normalize a SIE amount to the exact decimal string the platform's money helpers
113
+ * take.
114
+ *
115
+ * SIE writes amounts with a `.` decimal separator and no thousands grouping, but
116
+ * real files carry `-50000`, `-50000.00` and `1 234.50` alike, and a stray
117
+ * non-breaking space has been seen in exported names. Everything is normalized to a
118
+ * plain signed decimal string — never a float, per the platform's money rule, because
119
+ * `parseFloat` on a ledger is how a rounding error becomes an accounting discrepancy.
120
+ */
121
+ export declare function sieAmount(raw: string | undefined): string;
122
+ /**
123
+ * Parse a SIE4 document.
124
+ *
125
+ * Tolerant by design: an unknown post is skipped, a malformed `#TRANS` outside a
126
+ * voucher is ignored rather than fatal, and a voucher block that never closes still
127
+ * yields the rows it had. A ledger reader that refuses a whole year over one odd line
128
+ * is worse than one that reports what it could read — the caller can see the totals
129
+ * and the account coverage, which is where a real problem shows up anyway.
130
+ */
131
+ export declare function parseSie4(text: string): SieLedger;
132
+ //# sourceMappingURL=sie4.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sie4.d.ts","sourceRoot":"","sources":["../src/sie4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,iDAAiD;AACjD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,cAAc,EAAE,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3C,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0DnD;AAgBD,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAiBzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAkGjD"}
package/dist/sie4.js ADDED
@@ -0,0 +1,265 @@
1
+ /**
2
+ * A SIE4 reader — the Swedish standard accounting interchange format, which is how
3
+ * this connector reads a whole year's bookkeeping in one request instead of walking
4
+ * per-voucher REST endpoints.
5
+ *
6
+ * Deliberately a parser over a *text* input, with no I/O of its own: the decoding
7
+ * from ISO-8859-1 happens at the seam that owns the bytes ({@link FortnoxApi.sieFile}),
8
+ * so this file is pure and testable against a fixture, and a caller reading SIE from
9
+ * somewhere else (an upload, an archive) can use it unchanged.
10
+ *
11
+ * ## What it reads, and what it deliberately ignores
12
+ *
13
+ * Posts read: `#FNAMN`, `#ORGNR`, `#VALUTA`, `#RAR`, `#KONTO`, `#DIM`, `#OBJEKT`,
14
+ * `#VER` and its `#TRANS` rows. Everything else — `#IB`/`#UB`/`#RES` balances,
15
+ * `#KTYP`, `#SRU`, the file metadata — is skipped, and skipped ON PURPOSE rather
16
+ * than by omission:
17
+ *
18
+ * - **`#IB`/`#UB` are derived, and including them double-counts.** They are the
19
+ * opening and closing balances the vouchers already sum to. A reader that adds
20
+ * them to the `#TRANS` rows reports every balance twice over.
21
+ * - Everything else is metadata this connector has no use for. An unknown post is
22
+ * ignored rather than fatal: SIE files carry vendor extensions, and refusing a
23
+ * file over a post we do not read would break on a Fortnox release that adds one.
24
+ */
25
+ /**
26
+ * Split one SIE line into fields, honouring quoting and object lists.
27
+ *
28
+ * Three things make this more than `line.split(' ')`, and all three appear in real
29
+ * Fortnox output:
30
+ *
31
+ * 1. **Quoted fields** hold spaces — `"Hyra juni"` is one field, and an account name
32
+ * is almost always quoted.
33
+ * 2. **Backslash escapes** inside quotes — `\"` and `\\` — so a name containing a
34
+ * quote does not end the field early.
35
+ * 3. **Object lists** are brace-delimited and hold their own quoted fields, so
36
+ * `{"1" "2001"}` must survive as ONE field for the caller to unpack.
37
+ */
38
+ export function splitSieLine(line) {
39
+ const fields = [];
40
+ let i = 0;
41
+ while (i < line.length) {
42
+ const ch = line[i];
43
+ if (ch === ' ' || ch === '\t') {
44
+ i += 1;
45
+ continue;
46
+ }
47
+ if (ch === '{') {
48
+ // Brace-delimited object list, kept whole (quotes inside are not terminators).
49
+ let depth = 0;
50
+ let start = i;
51
+ let inQuote = false;
52
+ while (i < line.length) {
53
+ const c = line[i];
54
+ if (inQuote) {
55
+ if (c === '\\')
56
+ i += 1;
57
+ else if (c === '"')
58
+ inQuote = false;
59
+ }
60
+ else if (c === '"')
61
+ inQuote = true;
62
+ else if (c === '{')
63
+ depth += 1;
64
+ else if (c === '}') {
65
+ depth -= 1;
66
+ if (depth === 0) {
67
+ i += 1;
68
+ break;
69
+ }
70
+ }
71
+ i += 1;
72
+ }
73
+ fields.push(line.slice(start, i));
74
+ continue;
75
+ }
76
+ if (ch === '"') {
77
+ let out = '';
78
+ i += 1;
79
+ while (i < line.length) {
80
+ const c = line[i];
81
+ if (c === '\\' && i + 1 < line.length) {
82
+ out += line[i + 1];
83
+ i += 2;
84
+ continue;
85
+ }
86
+ if (c === '"') {
87
+ i += 1;
88
+ break;
89
+ }
90
+ out += c;
91
+ i += 1;
92
+ }
93
+ fields.push(out);
94
+ continue;
95
+ }
96
+ let start = i;
97
+ while (i < line.length && line[i] !== ' ' && line[i] !== '\t')
98
+ i += 1;
99
+ fields.push(line.slice(start, i));
100
+ }
101
+ return fields;
102
+ }
103
+ /** Unpack `{"1" "2001" "6" "A"}` into `{ '1': '2001', '6': 'A' }`. Empty `{}` → `{}`. */
104
+ function parseObjectList(field) {
105
+ if (field === undefined || !field.startsWith('{'))
106
+ return {};
107
+ const inner = field.slice(1, field.endsWith('}') ? -1 : undefined);
108
+ const parts = splitSieLine(inner);
109
+ const objects = {};
110
+ for (let i = 0; i + 1 < parts.length; i += 2) {
111
+ const dim = parts[i];
112
+ const code = parts[i + 1];
113
+ if (dim !== '' && code !== '')
114
+ objects[dim] = code;
115
+ }
116
+ return objects;
117
+ }
118
+ /** `YYYYMMDD` → `YYYY-MM-DD`. Anything else is passed through unchanged. */
119
+ export function sieDate(raw) {
120
+ if (raw === undefined)
121
+ return '';
122
+ const digits = raw.trim();
123
+ if (!/^\d{8}$/.test(digits))
124
+ return digits;
125
+ return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`;
126
+ }
127
+ /**
128
+ * Normalize a SIE amount to the exact decimal string the platform's money helpers
129
+ * take.
130
+ *
131
+ * SIE writes amounts with a `.` decimal separator and no thousands grouping, but
132
+ * real files carry `-50000`, `-50000.00` and `1 234.50` alike, and a stray
133
+ * non-breaking space has been seen in exported names. Everything is normalized to a
134
+ * plain signed decimal string — never a float, per the platform's money rule, because
135
+ * `parseFloat` on a ledger is how a rounding error becomes an accounting discrepancy.
136
+ */
137
+ export function sieAmount(raw) {
138
+ if (raw === undefined)
139
+ return '0';
140
+ const cleaned = raw.replace(/[\s\u00a0]/g, '').replace(',', '.');
141
+ const m = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(cleaned);
142
+ // At least one digit SOMEWHERE, or this is not a number. `.`, `+.` and `-` all
143
+ // matched the old pattern and were returned verbatim; `.` then reached `moneyOf`
144
+ // downstream, whose `moneyAmount` regex rejects it \u2014 so one malformed field killed
145
+ // the whole year's sync rather than costing a single row.
146
+ if (!m || `${m[2] ?? ''}${m[3] ?? ''}` === '')
147
+ return '0';
148
+ const sign = m[1] === '-' ? '-' : '';
149
+ // A LEADING DIGIT, always. `moneyAmount` is `/^-?\d+(\.\d{1,6})?$/`, so `.5` is
150
+ // invalid there however reasonable it looks here.
151
+ const int = m[2] === '' || m[2] === undefined ? '0' : m[2];
152
+ const frac = m[3] ?? '';
153
+ // A trailing `.` (`12.`) is dropped rather than kept: it is also invalid downstream,
154
+ // and it carries no information.
155
+ return frac === '' ? `${sign}${int}` : `${sign}${int}.${frac}`;
156
+ }
157
+ /**
158
+ * Parse a SIE4 document.
159
+ *
160
+ * Tolerant by design: an unknown post is skipped, a malformed `#TRANS` outside a
161
+ * voucher is ignored rather than fatal, and a voucher block that never closes still
162
+ * yields the rows it had. A ledger reader that refuses a whole year over one odd line
163
+ * is worse than one that reports what it could read — the caller can see the totals
164
+ * and the account coverage, which is where a real problem shows up anyway.
165
+ */
166
+ export function parseSie4(text) {
167
+ const ledger = {
168
+ company: '',
169
+ organizationNumber: '',
170
+ currency: 'SEK',
171
+ range: null,
172
+ accounts: [],
173
+ dimensions: [],
174
+ objects: [],
175
+ vouchers: [],
176
+ };
177
+ let current = null;
178
+ let inBlock = false;
179
+ for (const rawLine of text.split(/\r?\n/)) {
180
+ const line = rawLine.trim();
181
+ if (line === '')
182
+ continue;
183
+ if (line === '{') {
184
+ inBlock = true;
185
+ continue;
186
+ }
187
+ if (line === '}') {
188
+ inBlock = false;
189
+ current = null;
190
+ continue;
191
+ }
192
+ if (!line.startsWith('#'))
193
+ continue;
194
+ const fields = splitSieLine(line);
195
+ const post = fields[0];
196
+ switch (post) {
197
+ case '#FNAMN':
198
+ ledger.company = fields[1] ?? '';
199
+ break;
200
+ case '#ORGNR':
201
+ ledger.organizationNumber = fields[1] ?? '';
202
+ break;
203
+ case '#VALUTA':
204
+ if (fields[1])
205
+ ledger.currency = fields[1];
206
+ break;
207
+ case '#RAR':
208
+ // `#RAR 0 <from> <to>` — year 0 is the file's own year; -1 is the previous
209
+ // one, which Fortnox includes for comparison and which we do not want.
210
+ if (fields[1] === '0') {
211
+ ledger.range = { from: sieDate(fields[2]), to: sieDate(fields[3]) };
212
+ }
213
+ break;
214
+ case '#KONTO':
215
+ if (fields[1])
216
+ ledger.accounts.push({ number: fields[1], name: fields[2] ?? '' });
217
+ break;
218
+ case '#DIM':
219
+ if (fields[1])
220
+ ledger.dimensions.push({ number: fields[1], name: fields[2] ?? '' });
221
+ break;
222
+ case '#OBJEKT':
223
+ if (fields[1] && fields[2]) {
224
+ ledger.objects.push({ dimension: fields[1], code: fields[2], name: fields[3] ?? '' });
225
+ }
226
+ break;
227
+ case '#VER': {
228
+ // `#VER <serie> <nr> <datum> [text] [regdatum] [sign]`
229
+ current = {
230
+ series: fields[1] ?? '',
231
+ number: fields[2] ?? '',
232
+ date: sieDate(fields[3]),
233
+ text: fields[4] ?? '',
234
+ transactions: [],
235
+ };
236
+ ledger.vouchers.push(current);
237
+ break;
238
+ }
239
+ case '#TRANS': {
240
+ // `#TRANS <konto> <objektlista> <belopp> [transdat] [transtext] [kvantitet] [sign]`
241
+ // Only inside a voucher block: a `#TRANS` with no `#VER` above it has nothing
242
+ // to attach to, and silently inventing a voucher for it would fabricate data.
243
+ if (current === null)
244
+ break;
245
+ current.transactions.push({
246
+ account: fields[1] ?? '',
247
+ objects: parseObjectList(fields[2]),
248
+ amount: sieAmount(fields[3]),
249
+ date: fields[4] ? sieDate(fields[4]) : current.date,
250
+ text: fields[5] ?? '',
251
+ quantity: fields[6] ? sieAmount(fields[6]) : null,
252
+ });
253
+ break;
254
+ }
255
+ // `#BTRANS` (removed) and `#RTRANS` (corrected) describe edits to a row that
256
+ // `#TRANS` already states in its final form. Counting them double-counts the
257
+ // correction, so both are skipped exactly like the balance posts.
258
+ default:
259
+ break;
260
+ }
261
+ void inBlock;
262
+ }
263
+ return ledger;
264
+ }
265
+ //# sourceMappingURL=sie4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sie4.js","sourceRoot":"","sources":["../src/sie4.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@substrat-run/connector-fortnox",
3
+ "version": "0.2.0",
4
+ "description": "Substrat connector: Fortnox accounting (Swedish). Reads a company's bookkeeping as SIE4 on a poll and lands it into a scope through the vertical's own operation. Auth is OAuth2 client-credentials against a service consent, so there is no refresh token to rotate. Host code — swept on a ScopeHost, never module code.",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/substrat-run/substrat.git",
9
+ "directory": "connectors/fortnox"
10
+ },
11
+ "homepage": "https://substrat.net/connectors/fortnox",
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
30
+ "test": "vitest run"
31
+ },
32
+ "dependencies": {
33
+ "@substrat-run/contracts": "workspace:^",
34
+ "@substrat-run/kernel": "workspace:^"
35
+ },
36
+ "devDependencies": {
37
+ "@substrat-run/adapter-sqlite": "workspace:^",
38
+ "typescript": "catalog:",
39
+ "vitest": "catalog:",
40
+ "zod": "catalog:"
41
+ },
42
+ "peerDependencies": {
43
+ "zod": "^4.4.0"
44
+ }
45
+ }