@superheld/summae-knex 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/README.md +25 -0
- package/dist/index.cjs +688 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +161 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import knexFactory from 'knex';
|
|
3
|
+
import { Account, Uuid, AccountNumber, Period, FiscalYear, JournalEntry, PeriodRef, Voucher, Settlement, OpenItem, Partner, Asset, AuditRecord, Tenant, CalendarDate, DimensionValue, EntryLine, Money } from '@superheld/summae-core';
|
|
4
|
+
|
|
5
|
+
// src/sync-db.ts
|
|
6
|
+
var SyncDb = class {
|
|
7
|
+
db;
|
|
8
|
+
/** Reiner Query-/Schema-Builder (keine Verbindung). */
|
|
9
|
+
knex;
|
|
10
|
+
constructor(filename = ":memory:") {
|
|
11
|
+
this.db = new Database(filename);
|
|
12
|
+
this.db.pragma("foreign_keys = ON");
|
|
13
|
+
this.knex = knexFactory({ client: "better-sqlite3", useNullAsDefault: true });
|
|
14
|
+
}
|
|
15
|
+
/** Tabelle als Knex-QueryBuilder (nur zum Bauen, Ausführung via run/all/first). */
|
|
16
|
+
table(name) {
|
|
17
|
+
return this.knex(name);
|
|
18
|
+
}
|
|
19
|
+
/** DDL (Schema kann mehrere Statements erzeugen) synchron ausführen. */
|
|
20
|
+
schema(build) {
|
|
21
|
+
for (const stmt of build(this.knex.schema).toSQL()) {
|
|
22
|
+
this.db.prepare(stmt.sql).run(...normalize(stmt.bindings));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Schreibende Anweisung (insert/update/delete). */
|
|
26
|
+
run(builder) {
|
|
27
|
+
const native = builder.toSQL().toNative();
|
|
28
|
+
this.db.prepare(native.sql).run(...normalize(native.bindings));
|
|
29
|
+
}
|
|
30
|
+
/** Alle Treffer als rohe Zeilen. */
|
|
31
|
+
all(builder) {
|
|
32
|
+
const native = builder.toSQL().toNative();
|
|
33
|
+
return this.db.prepare(native.sql).all(...normalize(native.bindings));
|
|
34
|
+
}
|
|
35
|
+
/** Erster Treffer oder null. */
|
|
36
|
+
first(builder) {
|
|
37
|
+
return this.all(builder.limit(1))[0] ?? null;
|
|
38
|
+
}
|
|
39
|
+
close() {
|
|
40
|
+
this.db.close();
|
|
41
|
+
void this.knex.destroy();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function normalize(bindings) {
|
|
45
|
+
return bindings.map((b) => {
|
|
46
|
+
if (b === void 0 || b === null) return null;
|
|
47
|
+
if (typeof b === "boolean") return b ? 1 : 0;
|
|
48
|
+
return b;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/schema-installer.ts
|
|
53
|
+
var TABLE_PREFIX = "summae_";
|
|
54
|
+
function installSchema(db) {
|
|
55
|
+
db.schema(
|
|
56
|
+
(schema) => schema.createTable(`${TABLE_PREFIX}accounts`, (t) => {
|
|
57
|
+
t.uuid("id").primary();
|
|
58
|
+
t.uuid("tenant_id").index();
|
|
59
|
+
t.string("number", 64);
|
|
60
|
+
t.string("name");
|
|
61
|
+
t.string("type", 16);
|
|
62
|
+
t.string("subtype", 32).nullable();
|
|
63
|
+
t.string("status", 16).defaultTo("active");
|
|
64
|
+
t.unique(["tenant_id", "number"]);
|
|
65
|
+
}).createTable(`${TABLE_PREFIX}fiscal_years`, (t) => {
|
|
66
|
+
t.uuid("id").primary();
|
|
67
|
+
t.uuid("tenant_id").index();
|
|
68
|
+
t.integer("year");
|
|
69
|
+
t.date("start");
|
|
70
|
+
t.date("end");
|
|
71
|
+
t.string("status", 16).defaultTo("open");
|
|
72
|
+
t.json("periods");
|
|
73
|
+
t.unique(["tenant_id", "year"]);
|
|
74
|
+
}).createTable(`${TABLE_PREFIX}vouchers`, (t) => {
|
|
75
|
+
t.uuid("id").primary();
|
|
76
|
+
t.uuid("tenant_id").index();
|
|
77
|
+
t.json("payload");
|
|
78
|
+
}).createTable(`${TABLE_PREFIX}journal_entries`, (t) => {
|
|
79
|
+
t.uuid("id").primary();
|
|
80
|
+
t.uuid("tenant_id").index();
|
|
81
|
+
t.integer("fiscal_year");
|
|
82
|
+
t.integer("sequence_number");
|
|
83
|
+
t.integer("period");
|
|
84
|
+
t.string("status", 16);
|
|
85
|
+
t.date("entry_date");
|
|
86
|
+
t.date("voucher_date").nullable();
|
|
87
|
+
t.string("recorded_at", 40);
|
|
88
|
+
t.uuid("voucher_id");
|
|
89
|
+
t.text("text");
|
|
90
|
+
t.json("lines");
|
|
91
|
+
t.uuid("reverses").nullable();
|
|
92
|
+
t.uuid("reversed_by").nullable();
|
|
93
|
+
t.unique(["tenant_id", "fiscal_year", "sequence_number"]);
|
|
94
|
+
}).createTable(`${TABLE_PREFIX}open_items`, (t) => {
|
|
95
|
+
t.uuid("id").primary();
|
|
96
|
+
t.uuid("tenant_id").index();
|
|
97
|
+
t.string("kind", 16);
|
|
98
|
+
t.uuid("origin_entry_id").index();
|
|
99
|
+
t.integer("origin_line_index");
|
|
100
|
+
t.string("amount", 32);
|
|
101
|
+
t.string("currency", 3);
|
|
102
|
+
t.uuid("voucher_id");
|
|
103
|
+
t.date("opened_at");
|
|
104
|
+
t.uuid("partner_id").nullable();
|
|
105
|
+
t.json("settlements");
|
|
106
|
+
}).createTable(`${TABLE_PREFIX}partners`, (t) => {
|
|
107
|
+
t.uuid("id").primary();
|
|
108
|
+
t.uuid("tenant_id").index();
|
|
109
|
+
t.json("payload");
|
|
110
|
+
}).createTable(`${TABLE_PREFIX}assets`, (t) => {
|
|
111
|
+
t.uuid("id").primary();
|
|
112
|
+
t.uuid("tenant_id").index();
|
|
113
|
+
t.json("payload");
|
|
114
|
+
t.json("state");
|
|
115
|
+
}).createTable(`${TABLE_PREFIX}audit_log`, (t) => {
|
|
116
|
+
t.bigIncrements("seq");
|
|
117
|
+
t.uuid("id").unique();
|
|
118
|
+
t.uuid("tenant_id").index();
|
|
119
|
+
t.json("payload");
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
function isRecord(value) {
|
|
124
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
125
|
+
}
|
|
126
|
+
function encode(data) {
|
|
127
|
+
return JSON.stringify(data);
|
|
128
|
+
}
|
|
129
|
+
function decode(json) {
|
|
130
|
+
if (typeof json !== "string" || json === "") return {};
|
|
131
|
+
const parsed = JSON.parse(json);
|
|
132
|
+
return isRecord(parsed) ? parsed : {};
|
|
133
|
+
}
|
|
134
|
+
function decodeList(json) {
|
|
135
|
+
if (typeof json !== "string" || json === "") return [];
|
|
136
|
+
const parsed = JSON.parse(json);
|
|
137
|
+
return Array.isArray(parsed) ? parsed.filter(isRecord) : [];
|
|
138
|
+
}
|
|
139
|
+
function money(data) {
|
|
140
|
+
const amount = typeof data.amount === "string" ? data.amount : "0";
|
|
141
|
+
const currency = typeof data.currency === "string" ? data.currency : "EUR";
|
|
142
|
+
return Money.of(amount, currency);
|
|
143
|
+
}
|
|
144
|
+
function date(value) {
|
|
145
|
+
return typeof value === "string" && value !== "" ? CalendarDate.of(value.slice(0, 10)) : null;
|
|
146
|
+
}
|
|
147
|
+
function entryLines(lines) {
|
|
148
|
+
return lines.map((line) => {
|
|
149
|
+
const dimensions = (Array.isArray(line.dimensions) ? line.dimensions : []).filter(isRecord).filter((d) => typeof d.type === "string" && typeof d.code === "string").map((d) => DimensionValue.of(String(d.type), String(d.code)));
|
|
150
|
+
const taxTag = isRecord(line.taxTag) ? line.taxTag : null;
|
|
151
|
+
const moneyData = isRecord(line.money) ? line.money : {};
|
|
152
|
+
return new EntryLine(
|
|
153
|
+
Uuid.fromString(typeof line.accountId === "string" ? line.accountId : ""),
|
|
154
|
+
AccountNumber.of(typeof line.account === "string" ? line.account : "0"),
|
|
155
|
+
typeof line.side === "string" ? line.side : "debit",
|
|
156
|
+
money(moneyData),
|
|
157
|
+
dimensions,
|
|
158
|
+
taxTag
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function requireDate(value, field) {
|
|
163
|
+
const result = date(value);
|
|
164
|
+
if (result === null) throw new Error(`${field} fehlt im persistierten Datensatz`);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/repositories.ts
|
|
169
|
+
function str(row, key) {
|
|
170
|
+
const v = row[key];
|
|
171
|
+
return typeof v === "string" ? v : "";
|
|
172
|
+
}
|
|
173
|
+
function strOrNull(row, key) {
|
|
174
|
+
const v = row[key];
|
|
175
|
+
return typeof v === "string" ? v : null;
|
|
176
|
+
}
|
|
177
|
+
function int(row, key) {
|
|
178
|
+
const v = row[key];
|
|
179
|
+
if (typeof v === "number") return v;
|
|
180
|
+
if (typeof v === "bigint") return Number(v);
|
|
181
|
+
if (typeof v === "string") return Number.parseInt(v, 10);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
var DatabaseAccountRepository = class {
|
|
185
|
+
constructor(db, tenantId) {
|
|
186
|
+
this.db = db;
|
|
187
|
+
this.tenantId = tenantId;
|
|
188
|
+
}
|
|
189
|
+
db;
|
|
190
|
+
tenantId;
|
|
191
|
+
add(account) {
|
|
192
|
+
this.db.run(
|
|
193
|
+
this.table().insert({
|
|
194
|
+
id: account.id.value,
|
|
195
|
+
tenant_id: this.tenantId.value,
|
|
196
|
+
number: account.number.value,
|
|
197
|
+
name: account.name,
|
|
198
|
+
type: account.type,
|
|
199
|
+
subtype: account.subtype,
|
|
200
|
+
status: account.status()
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
save(account) {
|
|
205
|
+
this.db.run(this.table().where("id", account.id.value).update({ name: account.name, status: account.status() }));
|
|
206
|
+
}
|
|
207
|
+
byNumber(number) {
|
|
208
|
+
const row = this.db.first(this.table().where("tenant_id", this.tenantId.value).where("number", number.value));
|
|
209
|
+
return row === null ? null : this.hydrate(row);
|
|
210
|
+
}
|
|
211
|
+
byId(id) {
|
|
212
|
+
const row = this.db.first(this.table().where("id", id.value));
|
|
213
|
+
return row === null ? null : this.hydrate(row);
|
|
214
|
+
}
|
|
215
|
+
all() {
|
|
216
|
+
return this.rows().map((row) => this.hydrate(row)).sort((a, b) => a.number.compareTo(b.number));
|
|
217
|
+
}
|
|
218
|
+
hydrate(row) {
|
|
219
|
+
return new Account(
|
|
220
|
+
Uuid.fromString(str(row, "id")),
|
|
221
|
+
AccountNumber.of(str(row, "number")),
|
|
222
|
+
str(row, "name"),
|
|
223
|
+
str(row, "type"),
|
|
224
|
+
strOrNull(row, "subtype"),
|
|
225
|
+
str(row, "status")
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
rows() {
|
|
229
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid"));
|
|
230
|
+
}
|
|
231
|
+
table() {
|
|
232
|
+
return this.db.table(`${TABLE_PREFIX}accounts`);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var DatabaseFiscalYearRepository = class {
|
|
236
|
+
constructor(db, tenantId) {
|
|
237
|
+
this.db = db;
|
|
238
|
+
this.tenantId = tenantId;
|
|
239
|
+
}
|
|
240
|
+
db;
|
|
241
|
+
tenantId;
|
|
242
|
+
add(fiscalYear) {
|
|
243
|
+
this.db.run(
|
|
244
|
+
this.table().insert({
|
|
245
|
+
id: fiscalYear.id.value,
|
|
246
|
+
tenant_id: this.tenantId.value,
|
|
247
|
+
year: fiscalYear.year,
|
|
248
|
+
start: fiscalYear.start.iso,
|
|
249
|
+
end: fiscalYear.end.iso,
|
|
250
|
+
status: fiscalYear.status(),
|
|
251
|
+
periods: this.encodePeriods(fiscalYear)
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
save(fiscalYear) {
|
|
256
|
+
this.db.run(
|
|
257
|
+
this.table().where("id", fiscalYear.id.value).update({ status: fiscalYear.status(), periods: this.encodePeriods(fiscalYear) })
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
byYear(year) {
|
|
261
|
+
const row = this.db.first(this.table().where("tenant_id", this.tenantId.value).where("year", year));
|
|
262
|
+
return row === null ? null : this.hydrate(row);
|
|
263
|
+
}
|
|
264
|
+
forDate(date2) {
|
|
265
|
+
for (const fiscalYear of this.all()) {
|
|
266
|
+
if (fiscalYear.contains(date2)) return fiscalYear;
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
all() {
|
|
271
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid")).map((row) => this.hydrate(row)).sort((a, b) => a.year - b.year);
|
|
272
|
+
}
|
|
273
|
+
encodePeriods(fiscalYear) {
|
|
274
|
+
return encode(
|
|
275
|
+
fiscalYear.periods().map((period) => ({
|
|
276
|
+
period: period.number,
|
|
277
|
+
start: period.start.iso,
|
|
278
|
+
end: period.end.iso,
|
|
279
|
+
status: period.status()
|
|
280
|
+
}))
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
hydrate(row) {
|
|
284
|
+
const periods = decodeList(row.periods).map(
|
|
285
|
+
(p) => new Period(
|
|
286
|
+
int(p, "period"),
|
|
287
|
+
requireDate(p.start, "Periodenstart"),
|
|
288
|
+
requireDate(p.end, "Periodenende"),
|
|
289
|
+
str(p, "status")
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
return FiscalYear.restore(
|
|
293
|
+
Uuid.fromString(str(row, "id")),
|
|
294
|
+
int(row, "year"),
|
|
295
|
+
requireDate(row.start, "start"),
|
|
296
|
+
requireDate(row.end, "end"),
|
|
297
|
+
str(row, "status"),
|
|
298
|
+
periods
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
table() {
|
|
302
|
+
return this.db.table(`${TABLE_PREFIX}fiscal_years`);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var DatabaseJournalRepository = class {
|
|
306
|
+
constructor(db, tenantId) {
|
|
307
|
+
this.db = db;
|
|
308
|
+
this.tenantId = tenantId;
|
|
309
|
+
}
|
|
310
|
+
db;
|
|
311
|
+
tenantId;
|
|
312
|
+
append(entry) {
|
|
313
|
+
this.db.run(
|
|
314
|
+
this.table().insert({
|
|
315
|
+
id: entry.id.value,
|
|
316
|
+
tenant_id: this.tenantId.value,
|
|
317
|
+
fiscal_year: entry.periodRef.fiscalYear,
|
|
318
|
+
sequence_number: entry.sequenceNumber,
|
|
319
|
+
period: entry.periodRef.period,
|
|
320
|
+
status: entry.status(),
|
|
321
|
+
entry_date: entry.entryDate.iso,
|
|
322
|
+
voucher_date: entry.voucherDate?.iso ?? null,
|
|
323
|
+
recorded_at: entry.recordedAt,
|
|
324
|
+
voucher_id: entry.voucherId.value,
|
|
325
|
+
text: entry.text(),
|
|
326
|
+
lines: this.encodeLines(entry),
|
|
327
|
+
reverses: entry.reverses?.value ?? null,
|
|
328
|
+
reversed_by: entry.reversedBy()?.value ?? null
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
save(entry) {
|
|
333
|
+
this.db.run(
|
|
334
|
+
this.table().where("id", entry.id.value).update({
|
|
335
|
+
status: entry.status(),
|
|
336
|
+
text: entry.text(),
|
|
337
|
+
lines: this.encodeLines(entry),
|
|
338
|
+
reversed_by: entry.reversedBy()?.value ?? null
|
|
339
|
+
})
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
byId(id) {
|
|
343
|
+
const row = this.db.first(this.table().where("id", id.value));
|
|
344
|
+
return row === null ? null : this.hydrate(row);
|
|
345
|
+
}
|
|
346
|
+
nextSequenceNumber(fiscalYear) {
|
|
347
|
+
const rows = this.db.all(
|
|
348
|
+
this.table().where("tenant_id", this.tenantId.value).where("fiscal_year", fiscalYear).max("sequence_number as max")
|
|
349
|
+
);
|
|
350
|
+
const max = rows[0]?.max;
|
|
351
|
+
return (typeof max === "number" ? max : typeof max === "bigint" ? Number(max) : 0) + 1;
|
|
352
|
+
}
|
|
353
|
+
all() {
|
|
354
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid")).map((row) => this.hydrate(row)).sort(
|
|
355
|
+
(a, b) => a.periodRef.fiscalYear !== b.periodRef.fiscalYear ? a.periodRef.fiscalYear - b.periodRef.fiscalYear : a.sequenceNumber - b.sequenceNumber
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
forFiscalYear(fiscalYear) {
|
|
359
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).where("fiscal_year", fiscalYear).orderBy("rowid")).map((row) => this.hydrate(row)).sort((a, b) => a.sequenceNumber - b.sequenceNumber);
|
|
360
|
+
}
|
|
361
|
+
encodeLines(entry) {
|
|
362
|
+
return encode(entry.lines().map((line) => line.toJSON()));
|
|
363
|
+
}
|
|
364
|
+
hydrate(row) {
|
|
365
|
+
return new JournalEntry(
|
|
366
|
+
Uuid.fromString(str(row, "id")),
|
|
367
|
+
int(row, "sequence_number"),
|
|
368
|
+
requireDate(row.entry_date, "entry_date"),
|
|
369
|
+
date(row.voucher_date),
|
|
370
|
+
str(row, "recorded_at"),
|
|
371
|
+
new PeriodRef(int(row, "fiscal_year"), int(row, "period")),
|
|
372
|
+
Uuid.fromString(str(row, "voucher_id")),
|
|
373
|
+
str(row, "text"),
|
|
374
|
+
entryLines(decodeList(row.lines)),
|
|
375
|
+
strOrNull(row, "reverses") === null ? null : Uuid.fromString(str(row, "reverses")),
|
|
376
|
+
strOrNull(row, "reversed_by") === null ? null : Uuid.fromString(str(row, "reversed_by")),
|
|
377
|
+
str(row, "status")
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
table() {
|
|
381
|
+
return this.db.table(`${TABLE_PREFIX}journal_entries`);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
var DatabaseVoucherRepository = class {
|
|
385
|
+
constructor(db, tenantId) {
|
|
386
|
+
this.db = db;
|
|
387
|
+
this.tenantId = tenantId;
|
|
388
|
+
}
|
|
389
|
+
db;
|
|
390
|
+
tenantId;
|
|
391
|
+
add(voucher) {
|
|
392
|
+
this.db.run(
|
|
393
|
+
this.table().insert({ id: voucher.id.value, tenant_id: this.tenantId.value, payload: encode(voucher.toJSON()) })
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
byId(id) {
|
|
397
|
+
const row = this.db.first(this.table().where("id", id.value));
|
|
398
|
+
return row === null ? null : this.hydrate(row);
|
|
399
|
+
}
|
|
400
|
+
all() {
|
|
401
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid")).map((row) => this.hydrate(row)).sort((a, b) => a.id.value < b.id.value ? -1 : a.id.value > b.id.value ? 1 : 0);
|
|
402
|
+
}
|
|
403
|
+
hydrate(row) {
|
|
404
|
+
const data = decode(row.payload);
|
|
405
|
+
const servicePeriod = isRecord(data.servicePeriod) ? data.servicePeriod : {};
|
|
406
|
+
return new Voucher({
|
|
407
|
+
id: Uuid.fromString(str(row, "id")),
|
|
408
|
+
voucherNumber: str(data, "voucherNumber"),
|
|
409
|
+
voucherDate: requireDate(data.voucherDate, "voucherDate"),
|
|
410
|
+
due: date(data.due),
|
|
411
|
+
recurring: data.recurring === true,
|
|
412
|
+
economicYear: typeof data.economicYear === "number" ? data.economicYear : null,
|
|
413
|
+
supplierTaxationMethod: strOrNull(data, "supplierTaxationMethod"),
|
|
414
|
+
serviceDate: date(data.serviceDate),
|
|
415
|
+
servicePeriodFrom: date(servicePeriod.from),
|
|
416
|
+
servicePeriodTo: date(servicePeriod.to),
|
|
417
|
+
kind: strOrNull(data, "kind"),
|
|
418
|
+
partnerId: typeof data.partnerId === "string" ? Uuid.fromString(data.partnerId) : null,
|
|
419
|
+
issuer: strOrNull(data, "issuer")
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
table() {
|
|
423
|
+
return this.db.table(`${TABLE_PREFIX}vouchers`);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
var DatabaseOpenItemRepository = class {
|
|
427
|
+
constructor(db, tenantId) {
|
|
428
|
+
this.db = db;
|
|
429
|
+
this.tenantId = tenantId;
|
|
430
|
+
}
|
|
431
|
+
db;
|
|
432
|
+
tenantId;
|
|
433
|
+
add(item) {
|
|
434
|
+
this.db.run(
|
|
435
|
+
this.table().insert({
|
|
436
|
+
id: item.id.value,
|
|
437
|
+
tenant_id: this.tenantId.value,
|
|
438
|
+
kind: item.kind,
|
|
439
|
+
origin_entry_id: item.originEntryId.value,
|
|
440
|
+
origin_line_index: item.originLineIndex,
|
|
441
|
+
amount: item.money.amountAsString(),
|
|
442
|
+
currency: item.money.currency.code,
|
|
443
|
+
voucher_id: item.voucherId.value,
|
|
444
|
+
opened_at: item.openedAt.iso,
|
|
445
|
+
partner_id: item.partnerId?.value ?? null,
|
|
446
|
+
settlements: this.encodeSettlements(item)
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
save(item) {
|
|
451
|
+
this.db.run(this.table().where("id", item.id.value).update({ settlements: this.encodeSettlements(item) }));
|
|
452
|
+
}
|
|
453
|
+
byId(id) {
|
|
454
|
+
const row = this.db.first(this.table().where("id", id.value));
|
|
455
|
+
return row === null ? null : this.hydrate(row);
|
|
456
|
+
}
|
|
457
|
+
byOriginEntry(entryId) {
|
|
458
|
+
return this.db.all(this.table().where("origin_entry_id", entryId.value).orderBy("rowid")).map((row) => this.hydrate(row));
|
|
459
|
+
}
|
|
460
|
+
all() {
|
|
461
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid")).map((row) => this.hydrate(row));
|
|
462
|
+
}
|
|
463
|
+
encodeSettlements(item) {
|
|
464
|
+
return encode(item.settlements().map((settlement) => settlement.toJSON()));
|
|
465
|
+
}
|
|
466
|
+
hydrate(row) {
|
|
467
|
+
const settlements = decodeList(row.settlements).map((data) => {
|
|
468
|
+
const difference = isRecord(data.difference) ? data.difference : null;
|
|
469
|
+
const differenceMoney = difference !== null && isRecord(difference.money) ? money(difference.money) : null;
|
|
470
|
+
const differenceKind = difference !== null && typeof difference.kind === "string" ? difference.kind : null;
|
|
471
|
+
return new Settlement(
|
|
472
|
+
Uuid.fromString(str(data, "entryId")),
|
|
473
|
+
money(isRecord(data.money) ? data.money : {}),
|
|
474
|
+
requireDate(data.settledAt, "settledAt"),
|
|
475
|
+
differenceMoney,
|
|
476
|
+
differenceKind
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
return OpenItem.restore(
|
|
480
|
+
Uuid.fromString(str(row, "id")),
|
|
481
|
+
str(row, "kind"),
|
|
482
|
+
Uuid.fromString(str(row, "origin_entry_id")),
|
|
483
|
+
int(row, "origin_line_index"),
|
|
484
|
+
money({ amount: str(row, "amount"), currency: str(row, "currency") }),
|
|
485
|
+
Uuid.fromString(str(row, "voucher_id")),
|
|
486
|
+
requireDate(row.opened_at, "opened_at"),
|
|
487
|
+
strOrNull(row, "partner_id") === null ? null : Uuid.fromString(str(row, "partner_id")),
|
|
488
|
+
settlements
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
table() {
|
|
492
|
+
return this.db.table(`${TABLE_PREFIX}open_items`);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
var DatabasePartnerRepository = class {
|
|
496
|
+
constructor(db, tenantId) {
|
|
497
|
+
this.db = db;
|
|
498
|
+
this.tenantId = tenantId;
|
|
499
|
+
}
|
|
500
|
+
db;
|
|
501
|
+
tenantId;
|
|
502
|
+
add(partner) {
|
|
503
|
+
this.db.run(
|
|
504
|
+
this.table().insert({ id: partner.id.value, tenant_id: this.tenantId.value, payload: encode(partner.toJSON()) })
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
save(partner) {
|
|
508
|
+
this.db.run(this.table().where("id", partner.id.value).update({ payload: encode(partner.toJSON()) }));
|
|
509
|
+
}
|
|
510
|
+
byId(id) {
|
|
511
|
+
const row = this.db.first(this.table().where("id", id.value));
|
|
512
|
+
return row === null ? null : this.hydrate(row);
|
|
513
|
+
}
|
|
514
|
+
all() {
|
|
515
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid")).map((row) => this.hydrate(row)).sort((a, b) => {
|
|
516
|
+
const byName = a.name() < b.name() ? -1 : a.name() > b.name() ? 1 : 0;
|
|
517
|
+
return byName !== 0 ? byName : a.id.value < b.id.value ? -1 : a.id.value > b.id.value ? 1 : 0;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
hydrate(row) {
|
|
521
|
+
const data = decode(row.payload);
|
|
522
|
+
const accountNumbers = (Array.isArray(data.accountNumbers) ? data.accountNumbers : []).filter(
|
|
523
|
+
(n) => typeof n === "string"
|
|
524
|
+
);
|
|
525
|
+
const address = isRecord(data.address) ? data.address : {};
|
|
526
|
+
return new Partner(
|
|
527
|
+
Uuid.fromString(str(row, "id")),
|
|
528
|
+
str(data, "name"),
|
|
529
|
+
typeof data.kind === "string" ? data.kind : "both",
|
|
530
|
+
strOrNull(data, "vatId"),
|
|
531
|
+
typeof data.paymentTermsDays === "number" ? data.paymentTermsDays : null,
|
|
532
|
+
accountNumbers,
|
|
533
|
+
address
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
table() {
|
|
537
|
+
return this.db.table(`${TABLE_PREFIX}partners`);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
var DatabaseAssetRepository = class {
|
|
541
|
+
constructor(db, tenantId) {
|
|
542
|
+
this.db = db;
|
|
543
|
+
this.tenantId = tenantId;
|
|
544
|
+
}
|
|
545
|
+
db;
|
|
546
|
+
tenantId;
|
|
547
|
+
add(asset) {
|
|
548
|
+
this.db.run(
|
|
549
|
+
this.table().insert({
|
|
550
|
+
id: asset.id.value,
|
|
551
|
+
tenant_id: this.tenantId.value,
|
|
552
|
+
payload: encode(this.payload(asset)),
|
|
553
|
+
state: encode(this.state(asset))
|
|
554
|
+
})
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
save(asset) {
|
|
558
|
+
this.db.run(this.table().where("id", asset.id.value).update({ state: encode(this.state(asset)) }));
|
|
559
|
+
}
|
|
560
|
+
byId(id) {
|
|
561
|
+
const row = this.db.first(this.table().where("id", id.value));
|
|
562
|
+
return row === null ? null : this.hydrate(row);
|
|
563
|
+
}
|
|
564
|
+
all() {
|
|
565
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("rowid")).map((row) => this.hydrate(row));
|
|
566
|
+
}
|
|
567
|
+
payload(asset) {
|
|
568
|
+
return { ...asset.toJSON(), monthlySchedule: asset.monthlySchedule.map((amount) => amount.toJSON()) };
|
|
569
|
+
}
|
|
570
|
+
state(asset) {
|
|
571
|
+
return {
|
|
572
|
+
disposed: asset.isDisposed(),
|
|
573
|
+
disposedOn: asset.toJSON().disposedOn,
|
|
574
|
+
accumulated: asset.accumulatedDepreciationAt(null).toJSON(),
|
|
575
|
+
depreciations: asset.depreciationsForPersistence()
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
hydrate(row) {
|
|
579
|
+
const data = decode(row.payload);
|
|
580
|
+
const state = decode(row.state);
|
|
581
|
+
const schedule = (Array.isArray(data.monthlySchedule) ? data.monthlySchedule : []).filter(isRecord).map((amount) => money(amount));
|
|
582
|
+
const depreciations = (Array.isArray(state.depreciations) ? state.depreciations : []).filter(isRecord).map((booking) => ({
|
|
583
|
+
planMonth: int(booking, "planMonth"),
|
|
584
|
+
date: requireDate(booking.date, "AfA-Datum"),
|
|
585
|
+
amount: money(isRecord(booking.amount) ? booking.amount : {}),
|
|
586
|
+
entryId: Uuid.fromString(str(booking, "entryId"))
|
|
587
|
+
}));
|
|
588
|
+
return Asset.restore(
|
|
589
|
+
Uuid.fromString(str(row, "id")),
|
|
590
|
+
str(data, "name"),
|
|
591
|
+
str(data, "assetClass"),
|
|
592
|
+
AccountNumber.of(str(data, "assetAccount")),
|
|
593
|
+
money(isRecord(data.acquisitionCost) ? data.acquisitionCost : {}),
|
|
594
|
+
requireDate(data.acquiredOn, "acquiredOn"),
|
|
595
|
+
str(data, "route"),
|
|
596
|
+
typeof data.usefulLifeMonths === "number" ? data.usefulLifeMonths : null,
|
|
597
|
+
schedule,
|
|
598
|
+
Uuid.fromString(str(data, "voucherId")),
|
|
599
|
+
depreciations,
|
|
600
|
+
state.disposed === true,
|
|
601
|
+
date(state.disposedOn)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
table() {
|
|
605
|
+
return this.db.table(`${TABLE_PREFIX}assets`);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
var DatabaseAuditTrail = class {
|
|
609
|
+
constructor(db, tenantId) {
|
|
610
|
+
this.db = db;
|
|
611
|
+
this.tenantId = tenantId;
|
|
612
|
+
}
|
|
613
|
+
db;
|
|
614
|
+
tenantId;
|
|
615
|
+
append(record) {
|
|
616
|
+
this.db.run(
|
|
617
|
+
this.table().insert({ id: record.id.value, tenant_id: this.tenantId.value, payload: encode(record.toJSON()) })
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
all() {
|
|
621
|
+
return this.db.all(this.table().where("tenant_id", this.tenantId.value).orderBy("seq")).map((row) => {
|
|
622
|
+
const data = decode(row.payload);
|
|
623
|
+
const changes = isRecord(data.changes) ? data.changes : {};
|
|
624
|
+
return new AuditRecord(
|
|
625
|
+
Uuid.fromString(str(data, "id")),
|
|
626
|
+
str(data, "at"),
|
|
627
|
+
typeof data.actor === "string" ? data.actor : "system",
|
|
628
|
+
str(data, "objectType"),
|
|
629
|
+
Uuid.fromString(str(data, "objectId")),
|
|
630
|
+
str(data, "action"),
|
|
631
|
+
changes
|
|
632
|
+
);
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
table() {
|
|
636
|
+
return this.db.table(`${TABLE_PREFIX}audit_log`);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
// src/database-tenant-factory.ts
|
|
641
|
+
var DatabaseTenantFactory = class {
|
|
642
|
+
static build(db, name, baseCurrency, clock, ids, options = {}) {
|
|
643
|
+
const tenantId = options.tenantId ?? ids.next();
|
|
644
|
+
return Tenant.fromPorts(
|
|
645
|
+
tenantId,
|
|
646
|
+
name,
|
|
647
|
+
baseCurrency,
|
|
648
|
+
{
|
|
649
|
+
accounts: new DatabaseAccountRepository(db, tenantId),
|
|
650
|
+
fiscalYears: new DatabaseFiscalYearRepository(db, tenantId),
|
|
651
|
+
vouchers: new DatabaseVoucherRepository(db, tenantId),
|
|
652
|
+
journal: new DatabaseJournalRepository(db, tenantId),
|
|
653
|
+
openItems: new DatabaseOpenItemRepository(db, tenantId),
|
|
654
|
+
assets: new DatabaseAssetRepository(db, tenantId),
|
|
655
|
+
partners: new DatabasePartnerRepository(db, tenantId),
|
|
656
|
+
audit: new DatabaseAuditTrail(db, tenantId)
|
|
657
|
+
},
|
|
658
|
+
clock,
|
|
659
|
+
ids,
|
|
660
|
+
options.dimensions,
|
|
661
|
+
options.taxCodes,
|
|
662
|
+
options.taxProfile,
|
|
663
|
+
options.mappings
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
export { DatabaseAccountRepository, DatabaseAssetRepository, DatabaseAuditTrail, DatabaseFiscalYearRepository, DatabaseJournalRepository, DatabaseOpenItemRepository, DatabasePartnerRepository, DatabaseTenantFactory, DatabaseVoucherRepository, SyncDb, TABLE_PREFIX, installSchema };
|
|
669
|
+
//# sourceMappingURL=index.js.map
|
|
670
|
+
//# sourceMappingURL=index.js.map
|