@opencxh/domain 1.288.0 → 1.290.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/entities/bank/bank.test.d.ts +1 -0
- package/dist/entities/bank/index.d.ts +3 -0
- package/dist/entities/bank/match.d.ts +52 -0
- package/dist/entities/bank/parse.d.ts +13 -0
- package/dist/entities/bank/types.d.ts +197 -0
- package/dist/entities/bookkeeping/index.d.ts +2 -0
- package/dist/entities/bookkeeping/receipt.d.ts +104 -0
- package/dist/entities/bookkeeping/receipt.test.d.ts +1 -0
- package/dist/entities/bookkeeping/types.d.ts +114 -0
- package/dist/entities/bookkeeping/types.test.d.ts +1 -0
- package/dist/entities/business-entity/types.d.ts +21 -1
- package/dist/entities/company/identifiers.d.ts +9 -0
- package/dist/entities/company/identifiers.test.d.ts +1 -0
- package/dist/entities/company/index.d.ts +1 -0
- package/dist/entities/company/types.d.ts +9 -0
- package/dist/entities/financing/closing-schedules.test.d.ts +1 -0
- package/dist/entities/financing/index.d.ts +2 -0
- package/dist/entities/financing/schedule.d.ts +71 -0
- package/dist/entities/financing/schedule.test.d.ts +1 -0
- package/dist/entities/financing/types.d.ts +135 -0
- package/dist/entities/ledger/closing.d.ts +18 -0
- package/dist/entities/ledger/closing.test.d.ts +1 -0
- package/dist/entities/ledger/entry.d.ts +83 -0
- package/dist/entities/ledger/index.d.ts +6 -0
- package/dist/entities/ledger/ledger.test.d.ts +1 -0
- package/dist/entities/ledger/periods.d.ts +9 -0
- package/dist/entities/ledger/template.d.ts +35 -0
- package/dist/entities/ledger/types.d.ts +178 -0
- package/dist/entities/ledger/xaf.d.ts +82 -0
- package/dist/entities/money/tax.d.ts +62 -0
- package/dist/entities/money/tax.test.d.ts +1 -0
- package/dist/entities/reports/index.d.ts +2 -0
- package/dist/entities/reports/reports.d.ts +90 -0
- package/dist/entities/reports/reports.test.d.ts +1 -0
- package/dist/entities/reports/vat.d.ts +59 -0
- package/dist/entities/transaction/booking.d.ts +14 -0
- package/dist/entities/transaction/booking.test.d.ts +1 -0
- package/dist/entities/transaction/convert.d.ts +5 -0
- package/dist/entities/transaction/dunning.d.ts +37 -0
- package/dist/entities/transaction/dunning.test.d.ts +1 -0
- package/dist/entities/transaction/index.d.ts +4 -0
- package/dist/entities/transaction/receivable.d.ts +47 -0
- package/dist/entities/transaction/receivable.test.d.ts +1 -0
- package/dist/entities/transaction/settlement.d.ts +20 -0
- package/dist/entities/transaction/settlement.test.d.ts +1 -0
- package/dist/entities/transaction/types.d.ts +20 -2
- package/dist/index.cjs +24 -21
- package/dist/index.d.ts +7 -1
- package/dist/index.js +4098 -2589
- package/dist/platform/bookkeeping.d.ts +276 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Cents } from '../money/money';
|
|
2
|
+
import { BankLine, BankRule, MatchProposal, Reconciliation } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A proposal for one bank line, in the order of the datamodel (§6.1): a reference with the
|
|
5
|
+
* amount, then an IBAN with the amount, then a transfer between own accounts, then a rule.
|
|
6
|
+
* Nothing guessed: no proposal is better than a wrong one in "Klaar om te bevestigen".
|
|
7
|
+
*/
|
|
8
|
+
/** An open sales invoice (money in) or receipt (money out), with what is still open. */
|
|
9
|
+
export interface OpenDocument {
|
|
10
|
+
kind: "sales_invoice" | "receipt";
|
|
11
|
+
id: string;
|
|
12
|
+
sourceApp?: string;
|
|
13
|
+
number?: string;
|
|
14
|
+
/** IBANs this party pays from or to (crm `Company.ibans`, or the receipt's). */
|
|
15
|
+
ibans: string[];
|
|
16
|
+
/** Positive: what is still to receive (an invoice) or to pay (a receipt). */
|
|
17
|
+
openCents: Cents;
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MatchContext {
|
|
21
|
+
documents: readonly OpenDocument[];
|
|
22
|
+
rules: readonly BankRule[];
|
|
23
|
+
/** Our own accounts, to recognise a transfer. */
|
|
24
|
+
ownIbans: readonly {
|
|
25
|
+
iban: string;
|
|
26
|
+
bankAccountId: string;
|
|
27
|
+
}[];
|
|
28
|
+
/** Instalments waiting for their bank line: the amount to expect, when, and the party's IBANs. */
|
|
29
|
+
scheduled?: readonly {
|
|
30
|
+
id: string;
|
|
31
|
+
expectedBankCents: Cents;
|
|
32
|
+
dueOn: string;
|
|
33
|
+
ibans: string[];
|
|
34
|
+
label: string;
|
|
35
|
+
}[];
|
|
36
|
+
/** Unbooked lines on other own accounts, for the other side of a transfer. */
|
|
37
|
+
otherLines: readonly Pick<BankLine, "id" | "bankAccountId" | "date" | "amountCents" | "state">[];
|
|
38
|
+
}
|
|
39
|
+
export declare function proposeMatch(line: Pick<BankLine, "bankAccountId" | "date" | "amountCents" | "counterpartyIban" | "description" | "endToEndId">, context: MatchContext): MatchProposal | undefined;
|
|
40
|
+
/** What is left of an item: its amount minus every active allocation on it. Computed, never stored. */
|
|
41
|
+
export declare function openCentsOf(itemKey: string, amountCents: Cents, reconciliations: readonly Pick<Reconciliation, "status" | "allocations">[]): Cents;
|
|
42
|
+
/** The invariant of a reconciliation: its allocations cancel out (a difference is in the entry). */
|
|
43
|
+
export declare function reconciliationBalances(rec: Pick<Reconciliation, "allocations">): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The whole payment state of an item from its amount and what is still open, both signed like
|
|
46
|
+
* the item (an invoice +, a receipt −). What sales hears, and what the books show.
|
|
47
|
+
*/
|
|
48
|
+
export declare function paymentOfItem(grossCents: Cents, openCents: Cents): {
|
|
49
|
+
state: "paid" | "overpaid" | "unpaid" | "partially_paid";
|
|
50
|
+
paidCents: number;
|
|
51
|
+
openCents: number;
|
|
52
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ParsedStatement } from './types';
|
|
2
|
+
/** Every `<tag>…</tag>` body at any depth, in order. Tags are matched by local name. */
|
|
3
|
+
declare function blocks(xml: string, tag: string): string[];
|
|
4
|
+
/** The text at a path of tags: `first(x, "BookgDt", "Dt")`. */
|
|
5
|
+
declare function first(xml: string, ...path: string[]): string | undefined;
|
|
6
|
+
/** ISO 20022 camt.053: one statement per file is the common case; every `Stmt` is read. */
|
|
7
|
+
export declare function parseCamt053(input: string): ParsedStatement;
|
|
8
|
+
/** SWIFT MT940, as Dutch banks export it. Several statements in one file are read in order. */
|
|
9
|
+
export declare function parseMt940(input: string): ParsedStatement;
|
|
10
|
+
/** Which format a file is in, by its content, not its name. */
|
|
11
|
+
export declare function parseStatement(input: string): ParsedStatement;
|
|
12
|
+
/** The same scanner for other flat XML the browser reads (the XAF import). */
|
|
13
|
+
export { blocks as xmlBlocks, first as xmlText };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Cents } from '../money/money';
|
|
2
|
+
/**
|
|
3
|
+
* Bank accounts, statements and their lines, and settling one with the other (plan 039
|
|
4
|
+
* datamodel §5.1, §6.1–6.3). Only the bank kinds of `SubAccount` are built here; loans, the
|
|
5
|
+
* director's current account and participations follow in step 6b on the same row.
|
|
6
|
+
*/
|
|
7
|
+
export type BankSubAccountKind = "bank" | "savings" | "deposit" | "credit_card" | "psp" | "cash";
|
|
8
|
+
/** A bank account (or cash, a card, a payment provider balance) under a ledger account. */
|
|
9
|
+
export interface BankAccount {
|
|
10
|
+
id: string;
|
|
11
|
+
organizationId: string;
|
|
12
|
+
businessEntityId: string;
|
|
13
|
+
kind: BankSubAccountKind;
|
|
14
|
+
name: string;
|
|
15
|
+
/** The ledger account it sits under (`1100 Bank`), and the bank journal its lines post in. */
|
|
16
|
+
ledgerAccountId: string;
|
|
17
|
+
journalId: string;
|
|
18
|
+
iban?: string;
|
|
19
|
+
currency: string;
|
|
20
|
+
archived?: boolean;
|
|
21
|
+
createdAt?: number;
|
|
22
|
+
updatedAt?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface BankImport {
|
|
25
|
+
id: string;
|
|
26
|
+
organizationId: string;
|
|
27
|
+
businessEntityId: string;
|
|
28
|
+
bankAccountId: string;
|
|
29
|
+
format: "camt053" | "mt940";
|
|
30
|
+
from: string;
|
|
31
|
+
to: string;
|
|
32
|
+
/** What the statement says. A difference with our balance is a signal, never a silent fix. */
|
|
33
|
+
statementOpeningCents?: Cents;
|
|
34
|
+
statementClosingCents?: Cents;
|
|
35
|
+
lineCount: number;
|
|
36
|
+
importedBy: string;
|
|
37
|
+
createdAt?: number;
|
|
38
|
+
}
|
|
39
|
+
/** A line as a parser reads it from a statement, before it is stored. */
|
|
40
|
+
export interface ParsedBankLine {
|
|
41
|
+
date: string;
|
|
42
|
+
valueDate?: string;
|
|
43
|
+
/** Positive = money in. */
|
|
44
|
+
amountCents: Cents;
|
|
45
|
+
currency: string;
|
|
46
|
+
counterpartyName?: string;
|
|
47
|
+
counterpartyIban?: string;
|
|
48
|
+
description: string;
|
|
49
|
+
endToEndId?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Unique per account: the bank's own reference (CAMT `AcctSvcrRef`, MT940 `:61:` reference), or
|
|
52
|
+
* else statement number + line number. Never date + amount + text alone: two coffees of € 3,20
|
|
53
|
+
* on one day are two lines.
|
|
54
|
+
*/
|
|
55
|
+
dedupeKey: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ParsedStatement {
|
|
58
|
+
format: BankImport["format"];
|
|
59
|
+
iban?: string;
|
|
60
|
+
currency?: string;
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
openingCents?: Cents;
|
|
64
|
+
closingCents?: Cents;
|
|
65
|
+
lines: ParsedBankLine[];
|
|
66
|
+
}
|
|
67
|
+
/** What a bank line is paired with. The pair lives on the line only; nothing points back. */
|
|
68
|
+
export type MatchTarget = {
|
|
69
|
+
kind: "sales_invoice";
|
|
70
|
+
transactionId: string;
|
|
71
|
+
sourceApp: string;
|
|
72
|
+
} | {
|
|
73
|
+
kind: "receipt";
|
|
74
|
+
receiptId: string;
|
|
75
|
+
}
|
|
76
|
+
/** A prepared posting from a balance item's schedule: a loan instalment. */
|
|
77
|
+
| {
|
|
78
|
+
kind: "scheduled_posting";
|
|
79
|
+
scheduledPostingId: string;
|
|
80
|
+
}
|
|
81
|
+
/** The other side of a transfer between two own accounts. */
|
|
82
|
+
| {
|
|
83
|
+
kind: "transfer";
|
|
84
|
+
bankLineId: string;
|
|
85
|
+
}
|
|
86
|
+
/** Straight to an account (bank costs, a rule). */
|
|
87
|
+
| {
|
|
88
|
+
kind: "account";
|
|
89
|
+
accountId: string;
|
|
90
|
+
taxCode?: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
};
|
|
93
|
+
export type MatchReason = "amount" | "iban" | "reference" | "invoice_number" | "date_window" | "rule" | "own_account";
|
|
94
|
+
export interface MatchProposal {
|
|
95
|
+
target: MatchTarget;
|
|
96
|
+
via: "exact" | "rule" | "manual";
|
|
97
|
+
confidence: "high" | "medium" | "low";
|
|
98
|
+
ruleId?: string;
|
|
99
|
+
/** What agreed, for the explanation on screen: "same amount, same IBAN". */
|
|
100
|
+
reasons: MatchReason[];
|
|
101
|
+
/** What the target is called, so the queue need not look it up: "F2026-0190 · Studio Noord". */
|
|
102
|
+
label?: string;
|
|
103
|
+
}
|
|
104
|
+
export interface BankLine extends ParsedBankLine {
|
|
105
|
+
id: string;
|
|
106
|
+
organizationId: string;
|
|
107
|
+
businessEntityId: string;
|
|
108
|
+
bankAccountId: string;
|
|
109
|
+
importId: string;
|
|
110
|
+
/** Computed at import and after, stored so the queue loads fast. */
|
|
111
|
+
proposal?: MatchProposal;
|
|
112
|
+
/** unmatched → booked (entry and reconciliation written) · ignored. */
|
|
113
|
+
state: "unmatched" | "booked" | "ignored";
|
|
114
|
+
/** What it was booked against, and the entry that did it. */
|
|
115
|
+
pairedWith?: MatchTarget;
|
|
116
|
+
entryId?: string;
|
|
117
|
+
/**
|
|
118
|
+
* How often it was booked and undone: the entry's source key is `bank:<id>:<attempt>`, so booking
|
|
119
|
+
* again after an undo makes a new entry instead of finding the reversed one.
|
|
120
|
+
*/
|
|
121
|
+
attempt?: number;
|
|
122
|
+
createdAt?: number;
|
|
123
|
+
updatedAt?: number;
|
|
124
|
+
}
|
|
125
|
+
/** Made by a person, or offered after a manual pairing ("Voortaan automatisch?"). */
|
|
126
|
+
export interface BankRule {
|
|
127
|
+
id: string;
|
|
128
|
+
organizationId: string;
|
|
129
|
+
businessEntityId: string;
|
|
130
|
+
name: string;
|
|
131
|
+
/** Every condition given must hold. */
|
|
132
|
+
when: {
|
|
133
|
+
bankAccountId?: string;
|
|
134
|
+
counterpartyIban?: string;
|
|
135
|
+
/** Case-insensitive, in memory (the store has no `$regex`). */
|
|
136
|
+
descriptionContains?: string;
|
|
137
|
+
direction?: "in" | "out";
|
|
138
|
+
};
|
|
139
|
+
action: {
|
|
140
|
+
accountId: string;
|
|
141
|
+
taxCode?: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
};
|
|
144
|
+
/** High lands in "Klaar om te bevestigen", medium in "Controleren". */
|
|
145
|
+
confidence: "high" | "medium";
|
|
146
|
+
order: number;
|
|
147
|
+
}
|
|
148
|
+
/** An item that can be open: a posted line (debtor, creditor), or a bank line. */
|
|
149
|
+
export type OpenItemRef = {
|
|
150
|
+
kind: "journal_line";
|
|
151
|
+
id: string;
|
|
152
|
+
} | {
|
|
153
|
+
kind: "bank_line";
|
|
154
|
+
id: string;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* One reconciliation: amounts that cancel out. Immutable once active; undoing sets `reversed`
|
|
158
|
+
* and deletes nothing, so the history stays (datamodel §6.3).
|
|
159
|
+
*/
|
|
160
|
+
export interface Reconciliation {
|
|
161
|
+
id: string;
|
|
162
|
+
organizationId: string;
|
|
163
|
+
businessEntityId: string;
|
|
164
|
+
/** `bank:<bankLineId>` while active; a reversed one gets `:reversed:<at>` so the line can pair again. Unique. */
|
|
165
|
+
reconKey: string;
|
|
166
|
+
kind: "payment" | "credit_offset" | "transfer" | "write_off" | "manual";
|
|
167
|
+
/**
|
|
168
|
+
* The sign follows the item: a debtor line is +, its receipt −. The allocations cancel out; a
|
|
169
|
+
* difference is booked in the payment's entry and listed below for the audit.
|
|
170
|
+
*/
|
|
171
|
+
allocations: {
|
|
172
|
+
item: OpenItemRef;
|
|
173
|
+
amountCents: Cents;
|
|
174
|
+
}[];
|
|
175
|
+
differences?: {
|
|
176
|
+
amountCents: Cents;
|
|
177
|
+
reason: "payment_difference" | "bank_costs" | "fx" | "write_off";
|
|
178
|
+
accountId: string;
|
|
179
|
+
entryId: string;
|
|
180
|
+
}[];
|
|
181
|
+
/** Flat and indexed: which reconciliations touch this item is an `$in` on this field. */
|
|
182
|
+
itemKeys: string[];
|
|
183
|
+
status: "active" | "reversed";
|
|
184
|
+
origin: {
|
|
185
|
+
via: "exact" | "rule" | "manual";
|
|
186
|
+
ruleId?: string;
|
|
187
|
+
proposalReasons?: MatchReason[];
|
|
188
|
+
};
|
|
189
|
+
createdBy: string;
|
|
190
|
+
createdAt?: number;
|
|
191
|
+
reversal?: {
|
|
192
|
+
at: number;
|
|
193
|
+
by: string;
|
|
194
|
+
reason: string;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export declare const itemKeyOf: (item: OpenItemRef) => string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { BookingDocument } from '../../platform/bookkeeping';
|
|
2
|
+
import { Cents } from '../money/money';
|
|
3
|
+
import { TaxRateKind } from '../money/tax';
|
|
4
|
+
import { WorkQueueItem } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* A receipt or purchase invoice, as finance holds it until it is booked (plan 039 datamodel §6.4).
|
|
7
|
+
* Finance is its own `bookable-source` for these, so a receipt reaches Jortt the way an invoice
|
|
8
|
+
* does, through the same `BookingSync`.
|
|
9
|
+
*/
|
|
10
|
+
export interface Receipt {
|
|
11
|
+
id: string;
|
|
12
|
+
organizationId: string;
|
|
13
|
+
businessEntityId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Where it came from, flat and unique: `mail:<activityId>:<attachmentId>` or
|
|
16
|
+
* `upload:<fileId>`. The same attachment sent to the macro twice is one receipt.
|
|
17
|
+
*/
|
|
18
|
+
sourceKey: string;
|
|
19
|
+
/** The copy in storage: the books keep it seven years, a mailbox does not. */
|
|
20
|
+
file: {
|
|
21
|
+
mountId: string;
|
|
22
|
+
fileId: string;
|
|
23
|
+
filename: string;
|
|
24
|
+
mimeType: string;
|
|
25
|
+
};
|
|
26
|
+
receivedVia: "mail" | "upload";
|
|
27
|
+
receivedAt: number;
|
|
28
|
+
mail?: {
|
|
29
|
+
interactionId: string;
|
|
30
|
+
activityId: string;
|
|
31
|
+
from?: string;
|
|
32
|
+
subject?: string;
|
|
33
|
+
};
|
|
34
|
+
supplierCompanyId?: string;
|
|
35
|
+
fields: ReceiptFields;
|
|
36
|
+
lines: ReceiptLine[];
|
|
37
|
+
totals: {
|
|
38
|
+
netCents: Cents;
|
|
39
|
+
taxCents: Cents;
|
|
40
|
+
grossCents: Cents;
|
|
41
|
+
};
|
|
42
|
+
status: ReceiptStatus;
|
|
43
|
+
/** Why extraction gave nothing, when it did not: shown on the item, a person fills it in. */
|
|
44
|
+
extractError?: string;
|
|
45
|
+
/** Only the reference; the state lives on the approval request (one approval system, plan 023). */
|
|
46
|
+
approvalRequestId?: string;
|
|
47
|
+
/** When it was handed to the books: the "issue" moment the sweeper and `activeFrom` go by. */
|
|
48
|
+
bookedAt?: number;
|
|
49
|
+
postedAt?: number;
|
|
50
|
+
externalIds?: string[];
|
|
51
|
+
createdBy?: string;
|
|
52
|
+
createdAt?: number;
|
|
53
|
+
updatedAt?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* extracting: the AI is reading it.
|
|
57
|
+
* to_check: something is filled in and not confirmed yet.
|
|
58
|
+
* awaiting_approval: confirmed, above the administration's threshold, waiting on a sign-off.
|
|
59
|
+
* booked: handed to the books (the `BookingSync` says whether it arrived).
|
|
60
|
+
* rejected: not a receipt, or a duplicate.
|
|
61
|
+
*/
|
|
62
|
+
export type ReceiptStatus = "extracting" | "to_check" | "awaiting_approval" | "booked" | "rejected";
|
|
63
|
+
/** A recognised value: whether the AI proposed it, and whether a person has looked. */
|
|
64
|
+
export interface Field<T> {
|
|
65
|
+
value: T;
|
|
66
|
+
ai?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface ReceiptFields {
|
|
69
|
+
supplierName?: Field<string>;
|
|
70
|
+
supplierVatNumber?: Field<string>;
|
|
71
|
+
supplierIban?: Field<string>;
|
|
72
|
+
invoiceNumber?: Field<string>;
|
|
73
|
+
/** `"2026-09-24"`. */
|
|
74
|
+
issueDate?: Field<string>;
|
|
75
|
+
dueDate?: Field<string>;
|
|
76
|
+
currency?: Field<string>;
|
|
77
|
+
}
|
|
78
|
+
export interface ReceiptLine {
|
|
79
|
+
description?: string;
|
|
80
|
+
netCents: Cents;
|
|
81
|
+
taxCents: Cents;
|
|
82
|
+
/** `TaxCode.key`, e.g. `high`. */
|
|
83
|
+
taxRateKey: string;
|
|
84
|
+
taxPercent?: number;
|
|
85
|
+
taxKind?: TaxRateKind;
|
|
86
|
+
/** The account in the target books (`LedgerOption.key`). */
|
|
87
|
+
accountKey?: string;
|
|
88
|
+
aiSuggested?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/** Totals from the lines: the lines are the truth, the totals follow. */
|
|
91
|
+
export declare function receiptTotals(lines: readonly ReceiptLine[]): Receipt["totals"];
|
|
92
|
+
export type ReceiptProblem = "no_supplier" | "no_date" | "no_lines" | "no_account";
|
|
93
|
+
export declare const RECEIPT_SCOPE_KIND = "receipt";
|
|
94
|
+
/** The act a sign-off on a receipt is about. */
|
|
95
|
+
export declare const RECEIPT_BOOK_ACT = "book";
|
|
96
|
+
/** What still stands between this receipt and the books. Empty = it can be confirmed. */
|
|
97
|
+
export declare function receiptProblems(receipt: Pick<Receipt, "fields" | "lines">): ReceiptProblem[];
|
|
98
|
+
/**
|
|
99
|
+
* Where it sits in "Te verwerken": ready to confirm when nothing is missing, to check when only
|
|
100
|
+
* the reading needs a look, to choose when an account is missing (no proposal to confirm).
|
|
101
|
+
*/
|
|
102
|
+
export declare function receiptGroupOf(receipt: Pick<Receipt, "fields" | "lines" | "status">): WorkQueueItem["group"] | null;
|
|
103
|
+
/** The receipt as the books need it: a purchase, with the account per line a person chose. */
|
|
104
|
+
export declare function bookingDocumentOfReceipt(receipt: Receipt, sourceApp?: string): BookingDocument;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { BookingDocument, BookingLine } from '../../platform/bookkeeping';
|
|
2
|
+
import { Cents } from '../money/money';
|
|
3
|
+
/**
|
|
4
|
+
* Who keeps the books of one administration. One row per administration, stored by finance; no
|
|
5
|
+
* row means nothing is booked (fail-closed, no fan-out). Switching books is one write on this row.
|
|
6
|
+
*/
|
|
7
|
+
export interface BookkeepingBinding {
|
|
8
|
+
id: string;
|
|
9
|
+
organizationId: string;
|
|
10
|
+
businessEntityId: string;
|
|
11
|
+
/** The `bookkeeping` provider that receives documents: `finance` (own books) or `jortt`. */
|
|
12
|
+
app: string;
|
|
13
|
+
/** `ManagedAccount.id` of the connection; empty for our own books. */
|
|
14
|
+
accountId?: string;
|
|
15
|
+
/** Mapping onto an external package's accounts. Our own books use `PostingRule` instead. */
|
|
16
|
+
ledgerMapping?: LedgerMappingRule[];
|
|
17
|
+
/** Documents issued before this day stay with the previous books. */
|
|
18
|
+
activeFrom: string;
|
|
19
|
+
createdBy: string;
|
|
20
|
+
createdAt?: number;
|
|
21
|
+
updatedAt?: number;
|
|
22
|
+
}
|
|
23
|
+
/** First match wins: product, then category, then VAT code, then the rule without a match. */
|
|
24
|
+
export interface LedgerMappingRule {
|
|
25
|
+
match: {
|
|
26
|
+
productId?: string;
|
|
27
|
+
ledgerCategoryKey?: string;
|
|
28
|
+
taxRateKey?: string;
|
|
29
|
+
};
|
|
30
|
+
/** `BookkeepingDescribe.ledgerOptions[].key`. */
|
|
31
|
+
targetKey: string;
|
|
32
|
+
externalTaxCode?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* pending: to do, or to do again.
|
|
36
|
+
* sending: an attempt runs, or ran and its outcome is unknown.
|
|
37
|
+
* posted: arrived; `externalId` is known.
|
|
38
|
+
* failed: worth retrying; `nextAttemptAt` is set.
|
|
39
|
+
* attention: a lasting refusal (validation, a closed year). Waits for a person.
|
|
40
|
+
*/
|
|
41
|
+
export type BookingSyncState = "pending" | "sending" | "posted" | "failed" | "attention";
|
|
42
|
+
/**
|
|
43
|
+
* The durable processing state of one document on its way to the books (plan 039 datamodel §3.5):
|
|
44
|
+
* where "did this arrive, and if not, what was the last attempt?" is written down.
|
|
45
|
+
*/
|
|
46
|
+
export interface BookingSync {
|
|
47
|
+
id: string;
|
|
48
|
+
organizationId: string;
|
|
49
|
+
businessEntityId: string;
|
|
50
|
+
/** `<bindingId>:<sourceApp>:<documentId>`. Flat and never empty: the unique index. */
|
|
51
|
+
syncKey: string;
|
|
52
|
+
bindingId: string;
|
|
53
|
+
targetApp: string;
|
|
54
|
+
sourceApp: string;
|
|
55
|
+
documentId: string;
|
|
56
|
+
documentKind: BookingDocument["kind"];
|
|
57
|
+
documentNumber?: string;
|
|
58
|
+
/** `oc:<documentId>`: sent along, and searched on after a lost response. */
|
|
59
|
+
correlationKey: string;
|
|
60
|
+
state: BookingSyncState;
|
|
61
|
+
attempts: number;
|
|
62
|
+
/** When the running attempt began. A `sending` older than ten minutes has an unknown outcome. */
|
|
63
|
+
sendingSince?: number;
|
|
64
|
+
nextAttemptAt?: number;
|
|
65
|
+
lastError?: {
|
|
66
|
+
code: string;
|
|
67
|
+
message: string;
|
|
68
|
+
at: number;
|
|
69
|
+
};
|
|
70
|
+
externalId?: string;
|
|
71
|
+
/** The number the books gave the document, when they number it themselves. */
|
|
72
|
+
externalNumber?: string;
|
|
73
|
+
postedAt?: number;
|
|
74
|
+
resolvedVia?: "response" | "lookup";
|
|
75
|
+
/** The source was told it arrived. False until then, so a retry can find what is still owed. */
|
|
76
|
+
sourceNotified: boolean;
|
|
77
|
+
/** Jortt only: the open amount last seen, compared on every poll. */
|
|
78
|
+
lastSeenDueCents?: Cents;
|
|
79
|
+
/** A credit note: the invoice it credits, and whether the books settled one with the other. */
|
|
80
|
+
creditsDocumentId?: string;
|
|
81
|
+
/** `false` until settled; written only on a credit note, so a query can find what is owed. */
|
|
82
|
+
creditSettled?: boolean;
|
|
83
|
+
createdAt?: number;
|
|
84
|
+
updatedAt?: number;
|
|
85
|
+
}
|
|
86
|
+
export declare function bookingSyncKey(bindingId: string, sourceApp: string, documentId: string): string;
|
|
87
|
+
/** Minutes to wait after the nth failed attempt (1-based): 1, 5, 15, 60, then every hour. */
|
|
88
|
+
export declare function retryDelayMinutes(attempts: number): number;
|
|
89
|
+
/** How long a `sending` row may run before its outcome counts as unknown. */
|
|
90
|
+
export declare const SENDING_STALE_MS: number;
|
|
91
|
+
/**
|
|
92
|
+
* One item in "Te verwerken", derived at read time from the state of its source row — never
|
|
93
|
+
* stored, so it cannot drift (plan 039 datamodel §6.5). Receipts, bank lines and schedule entries
|
|
94
|
+
* join as they are built.
|
|
95
|
+
*/
|
|
96
|
+
export interface WorkQueueItem {
|
|
97
|
+
kind: "booking_sync" | "receipt" | "bank_line";
|
|
98
|
+
ref: string;
|
|
99
|
+
group: "failed" | "confirm" | "check" | "choose";
|
|
100
|
+
title: string;
|
|
101
|
+
subtitle: string;
|
|
102
|
+
date: number;
|
|
103
|
+
amountCents?: Cents;
|
|
104
|
+
/** Why it is here, as an error code the app translates (`error_<code>`), plus the raw detail. */
|
|
105
|
+
reason?: {
|
|
106
|
+
code: string;
|
|
107
|
+
message: string;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The rule a line books under: product, then category, then VAT code, then the rule without a
|
|
112
|
+
* match. `undefined` when nothing applies — the caller refuses rather than guessing an account.
|
|
113
|
+
*/
|
|
114
|
+
export declare function ledgerRuleFor(line: Pick<BookingLine, "productId" | "ledgerCategoryKey" | "taxRateKey">, rules: readonly LedgerMappingRule[]): LedgerMappingRule | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -47,8 +47,26 @@ export interface BusinessEntity {
|
|
|
47
47
|
* BV and its Belgian sister write different default languages under one workspace.
|
|
48
48
|
*/
|
|
49
49
|
locale?: string;
|
|
50
|
-
/** One per entity: an administration is set up per country and per currency. */
|
|
50
|
+
/** One per entity: an administration is set up per country and per currency. The books' functional currency. */
|
|
51
51
|
currency?: string;
|
|
52
|
+
/** ISO 3166-1 alpha-2. Picks the country pack: chart template, tax taxonomy, statutory export. */
|
|
53
|
+
country?: string;
|
|
54
|
+
/** Picks the chart template and how equity is laid out. */
|
|
55
|
+
legalForm?: LegalForm;
|
|
56
|
+
/** A VAT table (NL/EU) or an external tax engine (US, later). Absent = the table for `country`. */
|
|
57
|
+
taxProfile?: {
|
|
58
|
+
strategy: "table";
|
|
59
|
+
taxonomy: string;
|
|
60
|
+
} | {
|
|
61
|
+
strategy: "provider";
|
|
62
|
+
providerId: string;
|
|
63
|
+
};
|
|
64
|
+
/** The month the fiscal year starts, 1 = January. Periods are rows derived from it (plan 039 §4.3). */
|
|
65
|
+
fiscalYearStartMonth?: number;
|
|
66
|
+
/** How often VAT is filed. Absent = per quarter, the Dutch default. */
|
|
67
|
+
vatCadence?: "quarter" | "month" | "year";
|
|
68
|
+
/** Kleineondernemersregeling: no VAT charged or deducted, so no return. */
|
|
69
|
+
vatKor?: boolean;
|
|
52
70
|
/** The one a new document lands on. Exactly one row carries it. */
|
|
53
71
|
isDefault?: boolean;
|
|
54
72
|
/** Archived, never deleted: an issued quote names the entity it went out as. */
|
|
@@ -58,6 +76,8 @@ export interface BusinessEntity {
|
|
|
58
76
|
createdAt?: number;
|
|
59
77
|
updatedAt?: number;
|
|
60
78
|
}
|
|
79
|
+
export type LegalForm = "bv" | "nv" | "eenmanszaak" | "vof" | "maatschap" | "stichting" | "vereniging" | "other";
|
|
80
|
+
export declare const LEGAL_FORMS: readonly LegalForm[];
|
|
61
81
|
/** What {@link businessEntitySender} reads. Structural, so the document layer keeps pointing here. */
|
|
62
82
|
export type BusinessEntityLike = Pick<BusinessEntity, "name" | "address" | "vatNumber" | "kvkNumber" | "iban" | "logo" | "footerText">;
|
|
63
83
|
/**
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two identifiers a bank line and an invoice are matched on, in one canonical form. Stored
|
|
3
|
+
* normalised on write, compared normalised everywhere: `NL91 abna 0417 1643 00` and
|
|
4
|
+
* `NL91ABNA0417164300` are one account.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeIban(value: string): string;
|
|
7
|
+
export declare function normalizeVatNumber(value: string): string;
|
|
8
|
+
/** Normalised, blanks dropped, duplicates removed, order kept. */
|
|
9
|
+
export declare function normalizeIbans(values: readonly unknown[]): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -61,6 +61,15 @@ export interface Company {
|
|
|
61
61
|
* is created and keeps it from then on, so changing this never rewrites what already went out.
|
|
62
62
|
*/
|
|
63
63
|
paymentTermCode?: string;
|
|
64
|
+
/** Normalised: upper case, no spaces (`NL123456789B01`). */
|
|
65
|
+
vatNumber?: string;
|
|
66
|
+
kvkNumber?: string;
|
|
67
|
+
/** This company's bank accounts, normalised. Flat and indexed: matching a payment looks here. */
|
|
68
|
+
ibans?: string[];
|
|
69
|
+
/** `<scheme>:<id>` (`0106:12345678`), for Peppol e-invoicing. */
|
|
70
|
+
peppolId?: string;
|
|
71
|
+
debtorNumber?: string;
|
|
72
|
+
creditorNumber?: string;
|
|
64
73
|
/** Raw, human-readable phone/website as entered, for display. */
|
|
65
74
|
phone?: string;
|
|
66
75
|
website?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Cents } from '../money/money';
|
|
2
|
+
import { AccrualTerms, CurrentAccountTerms, FinancingItem, FixedAssetTerms, LoanTerms, ScheduledPosting } from './types';
|
|
3
|
+
/** `day` plus `months`, on the same day of the month or the last one when it is shorter. */
|
|
4
|
+
export declare function addMonths(day: string, months: number): string;
|
|
5
|
+
export interface Installment {
|
|
6
|
+
sequence: number;
|
|
7
|
+
dueOn: string;
|
|
8
|
+
interestCents: Cents;
|
|
9
|
+
principalCents: Cents;
|
|
10
|
+
/** What is still owed after this instalment. */
|
|
11
|
+
remainingCents: Cents;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A loan's monthly schedule: annuity (equal payments), linear (equal principal) or bullet
|
|
15
|
+
* (interest only, principal at the end). Cents are rounded per instalment and the last one takes
|
|
16
|
+
* what is left, so the principal adds up exactly.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loanSchedule(terms: Pick<LoanTerms, "principalCents" | "ratePercent" | "method" | "termMonths" | "firstDueOn">): Installment[];
|
|
19
|
+
/**
|
|
20
|
+
* Interest over the daily balance (actual/365): every day's closing balance times the day rate,
|
|
21
|
+
* rounded once at the end. `movements` are the item's booked lines, signed; the balance before
|
|
22
|
+
* `from` is what they add up to by then.
|
|
23
|
+
*/
|
|
24
|
+
export declare function interestOnDailyBalance(movements: readonly {
|
|
25
|
+
date: string;
|
|
26
|
+
cents: Cents;
|
|
27
|
+
}[], from: string, to: string, ratePercent: string): Cents;
|
|
28
|
+
/** The periods interest is settled over, from `openedOn` up to and including the one holding `through`. */
|
|
29
|
+
export declare function interestPeriods(openedOn: string, through: string, cadence: CurrentAccountTerms["cadence"]): {
|
|
30
|
+
sequence: number;
|
|
31
|
+
from: string;
|
|
32
|
+
to: string;
|
|
33
|
+
}[];
|
|
34
|
+
export declare const EXCESSIVE_LENDING_THRESHOLD_CENTS = 50000000;
|
|
35
|
+
/** Wet excessief lenen: how far the director's debt to the BV is toward the threshold. */
|
|
36
|
+
export declare function excessiveLending(directorOwesCents: Cents, thresholdCents?: number): {
|
|
37
|
+
over: boolean;
|
|
38
|
+
ratio: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The postings a loan's schedule asks for, up to `through`: one per instalment, its lines the
|
|
42
|
+
* interest and the principal (on the loan, with the item as sub-account). A received loan is paid
|
|
43
|
+
* (the bank line is −), a given loan is received (+).
|
|
44
|
+
*/
|
|
45
|
+
export declare function loanPostings(item: Pick<FinancingItem, "id" | "ledgerAccountId" | "name"> & {
|
|
46
|
+
terms: LoanTerms;
|
|
47
|
+
}, through: string): Omit<ScheduledPosting, "id" | "organizationId" | "businessEntityId" | "status">[];
|
|
48
|
+
type PlannedPosting = Omit<ScheduledPosting, "id" | "organizationId" | "businessEntityId" | "status">;
|
|
49
|
+
/** The last day of the month `day` falls in. */
|
|
50
|
+
export declare function monthEnd(day: string): string;
|
|
51
|
+
/** `totalCents` over `months` equal parts; the last takes the rounding, so the parts add up exactly. */
|
|
52
|
+
export declare function spread(totalCents: Cents, months: number): Cents[];
|
|
53
|
+
/** Straight-line depreciation: (cost − residual) over the life, from the month of acquisition. */
|
|
54
|
+
export declare function depreciationPostings(item: Pick<FinancingItem, "id" | "name"> & {
|
|
55
|
+
terms: FixedAssetTerms;
|
|
56
|
+
}, through: string): PlannedPosting[];
|
|
57
|
+
/** A transitorium released month by month onto its result account. */
|
|
58
|
+
export declare function accrualPostings(item: Pick<FinancingItem, "id" | "name" | "ledgerAccountId"> & {
|
|
59
|
+
terms: AccrualTerms;
|
|
60
|
+
}, through: string): PlannedPosting[];
|
|
61
|
+
/**
|
|
62
|
+
* Where a fixed asset or transitorium stands without its own opening lines: the booked movements
|
|
63
|
+
* on the item plus what its terms say was there to begin with (cost, or an amount paid upfront).
|
|
64
|
+
*/
|
|
65
|
+
export declare function startingCents(terms: FixedAssetTerms | AccrualTerms): Cents;
|
|
66
|
+
/**
|
|
67
|
+
* The part of a loan's principal due within twelve months after `yearEnd`: the annual accounts
|
|
68
|
+
* show it as a short-term liability (aflossingsverplichting), the rest as long-term.
|
|
69
|
+
*/
|
|
70
|
+
export declare function shortTermPrincipal(terms: Pick<LoanTerms, "principalCents" | "ratePercent" | "method" | "termMonths" | "firstDueOn">, yearEnd: string): Cents;
|
|
71
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|