@opencxh/domain 1.302.0 → 1.304.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.
Files changed (67) hide show
  1. package/dist/entities/agreement/from-quote.d.ts +8 -8
  2. package/dist/entities/agreement/index.d.ts +2 -0
  3. package/dist/entities/agreement/invoice-head.d.ts +21 -0
  4. package/dist/entities/agreement/lifecycle.d.ts +10 -0
  5. package/dist/entities/agreement/period.d.ts +8 -0
  6. package/dist/entities/agreement/prices.d.ts +43 -0
  7. package/dist/entities/agreement/prices.test.d.ts +1 -0
  8. package/dist/entities/agreement/types.d.ts +55 -10
  9. package/dist/entities/bank/match.d.ts +16 -2
  10. package/dist/entities/bank/types.d.ts +12 -0
  11. package/dist/entities/bookkeeping/receipt.d.ts +45 -6
  12. package/dist/entities/bookkeeping/types.d.ts +1 -2
  13. package/dist/entities/business-entity/types.d.ts +11 -15
  14. package/dist/entities/catalog/pricing.d.ts +14 -12
  15. package/dist/entities/catalog/types.d.ts +20 -9
  16. package/dist/entities/company/role.d.ts +14 -0
  17. package/dist/entities/financing/schedule.d.ts +9 -3
  18. package/dist/entities/financing/types.d.ts +17 -6
  19. package/dist/entities/fulfilment/adopt.d.ts +149 -0
  20. package/dist/entities/fulfilment/configuration.d.ts +99 -0
  21. package/dist/entities/fulfilment/configuration.test.d.ts +1 -0
  22. package/dist/entities/fulfilment/customers.d.ts +63 -0
  23. package/dist/entities/fulfilment/execute.d.ts +67 -0
  24. package/dist/entities/fulfilment/fulfilment.test.d.ts +1 -0
  25. package/dist/entities/fulfilment/goods.d.ts +72 -0
  26. package/dist/entities/fulfilment/index.d.ts +9 -0
  27. package/dist/entities/fulfilment/path.d.ts +8 -0
  28. package/dist/entities/fulfilment/path.test.d.ts +1 -0
  29. package/dist/entities/fulfilment/rules.d.ts +124 -0
  30. package/dist/entities/fulfilment/target.d.ts +133 -0
  31. package/dist/entities/fulfilment/types.d.ts +293 -0
  32. package/dist/entities/ledger/entry.d.ts +47 -9
  33. package/dist/entities/ledger/types.d.ts +33 -35
  34. package/dist/entities/ledger/xaf.d.ts +3 -0
  35. package/dist/entities/money/tax.d.ts +6 -0
  36. package/dist/entities/money/totals.d.ts +5 -5
  37. package/dist/entities/purchase/choose.d.ts +62 -0
  38. package/dist/entities/purchase/choose.test.d.ts +1 -0
  39. package/dist/entities/purchase/contract.d.ts +44 -25
  40. package/dist/entities/purchase/demand.d.ts +24 -40
  41. package/dist/entities/purchase/feed.d.ts +53 -5
  42. package/dist/entities/purchase/index.d.ts +2 -0
  43. package/dist/entities/purchase/kinds.d.ts +2 -1
  44. package/dist/entities/purchase/logic.d.ts +32 -7
  45. package/dist/entities/purchase/match.d.ts +48 -6
  46. package/dist/entities/purchase/packing-slip.d.ts +23 -0
  47. package/dist/entities/purchase/types.d.ts +90 -42
  48. package/dist/entities/purchase/upsell.d.ts +20 -0
  49. package/dist/entities/purchase/upsell.test.d.ts +1 -0
  50. package/dist/entities/reports/vat.d.ts +20 -5
  51. package/dist/entities/transaction/booking.d.ts +4 -4
  52. package/dist/entities/transaction/convert.d.ts +10 -10
  53. package/dist/entities/transaction/document.d.ts +8 -3
  54. package/dist/entities/transaction/dunning.d.ts +12 -1
  55. package/dist/entities/transaction/facets.test.d.ts +1 -0
  56. package/dist/entities/transaction/keys.d.ts +3 -0
  57. package/dist/entities/transaction/kinds.d.ts +11 -10
  58. package/dist/entities/transaction/receivable.d.ts +5 -2
  59. package/dist/entities/transaction/settlement.d.ts +7 -7
  60. package/dist/entities/transaction/types.d.ts +128 -68
  61. package/dist/index.cjs +25 -25
  62. package/dist/index.d.ts +2 -1
  63. package/dist/index.js +5300 -4033
  64. package/dist/platform/bookkeeping.d.ts +8 -8
  65. package/dist/platform/provisioning.d.ts +292 -17
  66. package/package.json +1 -1
  67. /package/dist/entities/{purchase/demand.test.d.ts → agreement/invoice-head.test.d.ts} +0 -0
@@ -1,18 +1,18 @@
1
- import { Transaction, TransactionLine } from '../transaction/types';
1
+ import { SalesLine, SalesTransaction } from '../transaction/types';
2
2
  import { SaveAgreementRequest } from './types';
3
3
  /** A line that runs every period: a product line priced per month, quarter or year. */
4
- export declare function isRecurringLine(line: TransactionLine): boolean;
4
+ export declare function isRecurringLine(line: SalesLine): boolean;
5
5
  /**
6
- * An accepted quote as an agreement: its recurring lines, each with its price as a snapshot —
7
- * what the customer accepted, not what the ladder says on some later day. One-off lines stay on
8
- * the quote (they are invoiced from it).
6
+ * An accepted quote as an agreement: its recurring lines, each with the accepted price as its
7
+ * first price from the agreement's start — what the customer accepted, not what the ladder says
8
+ * on some later day — and the quote's invoice head. One-off lines stay on the quote.
9
9
  *
10
10
  * ponytail: a line discount is folded into the unit price, rounded per unit; a discount field on
11
- * `LinePrice` if a cent per unit ever matters.
11
+ * `AgreedPrice` if a cent per unit ever matters.
12
12
  */
13
- export declare function agreementFromQuote(quote: Transaction, startsAt: number): SaveAgreementRequest;
13
+ export declare function agreementFromQuote(quote: SalesTransaction, startsAt: number): SaveAgreementRequest;
14
14
  /**
15
15
  * What a quote is worth to the deal, excluding VAT: the one-off lines once, the recurring ones
16
16
  * over a year (the term a quote does not state). Optional lines count for nothing.
17
17
  */
18
- export declare function dealValueCents(quote: Pick<Transaction, "lines">): number;
18
+ export declare function dealValueCents(quote: Pick<SalesTransaction, "lines">): number;
@@ -1,5 +1,7 @@
1
1
  export * from './from-quote';
2
+ export * from './invoice-head';
2
3
  export * from './lifecycle';
3
4
  export * from './period';
5
+ export * from './prices';
4
6
  export * from './schedule';
5
7
  export * from './types';
@@ -0,0 +1,21 @@
1
+ import { CompanyRole } from '../company/role';
2
+ import { SalesTransaction } from '../transaction/types';
3
+ import { AgreementInvoice } from './types';
4
+ /** The part of an invoice that says who pays, where, on what condition and under what reference. */
5
+ export type InvoiceHead = Partial<Pick<SalesTransaction, "contactId" | "invoiceTo" | "billingAddressId" | "paymentTerm" | "reference">>;
6
+ /**
7
+ * The invoice head, field by field: what the document asks, else its agreement's, else the
8
+ * customer role's. Who pays (recipient, contact, address) is one choice — a contact or address
9
+ * of the agreement's payer never lands on a document that names another. The role has only a
10
+ * payment term; `saveDraft` applies it when `paymentTermCode` comes from there.
11
+ */
12
+ export declare function partyTerms(layers: {
13
+ role?: Pick<CompanyRole, "paymentTermKey">;
14
+ agreement?: AgreementInvoice;
15
+ asked?: AgreementInvoice;
16
+ }): {
17
+ head: InvoiceHead;
18
+ paymentTermCode?: string;
19
+ };
20
+ /** A quote's head as the agreement keeps it: the run puts it on every invoice it writes. */
21
+ export declare function invoiceFromQuote(quote: SalesTransaction): AgreementInvoice;
@@ -33,6 +33,16 @@ export declare function noticeDeadline(start: number, commitment: Commitment | u
33
33
  * Not the same as stopping the service today — that is a different decision.
34
34
  */
35
35
  export declare function effectiveEndAt(start: number, commitment: Commitment | undefined, noticeAt: number): number;
36
+ /**
37
+ * Where a purchase that must cover a sale until `saleEnd` can end, with notice given now: the
38
+ * first term end on or after the sale's end — or, when its notice deadline has passed, the one
39
+ * after (`nextTerm`). Never before the sale ends: the customer still uses it (plan 047 §3).
40
+ */
41
+ export declare function coverEndAt(start: number, commitment: Commitment | undefined, saleEnd: number, now: number): {
42
+ endsAt: number;
43
+ deadline?: number;
44
+ nextTerm?: boolean;
45
+ };
36
46
  /** The whole agreement: the latest effective end over the lines still running. */
37
47
  export declare function agreementEffectiveEnd(agreement: Pick<Agreement, "startsAt" | "endsAt" | "commitment" | "lines">, noticeAt: number): number;
38
48
  /** A billed line may only get (or move) its end; anything else must be a successor. */
@@ -18,6 +18,14 @@ export declare function lineTerm(agreement: Pick<Agreement, "startsAt" | "endsAt
18
18
  from: number;
19
19
  to?: number;
20
20
  };
21
+ /**
22
+ * When a line bills: its term, from the day it first ran when billing waits for that (plan 047
23
+ * §10.2). `null` = not yet: nothing to bill. `delivery` follows with goods (047 step 4).
24
+ */
25
+ export declare function billingTerm(agreement: Pick<Agreement, "startsAt" | "endsAt">, line: Pick<AgreementLine, "startsAt" | "endsAt" | "billingStart" | "activatedAt">): {
26
+ from: number;
27
+ to?: number;
28
+ } | null;
21
29
  /** The part of `period` a term covers, or `null` when they do not meet. */
22
30
  export declare function overlap(term: {
23
31
  from: number;
@@ -0,0 +1,43 @@
1
+ import { Cents } from '../money/money';
2
+ import { BilledLine } from './schedule';
3
+ import { AgreedPrice, AgreementLine } from './types';
4
+ /**
5
+ * The price that holds on `at`: the latest `from` at or before it. Before the first one the first
6
+ * holds — it starts with the line. `undefined` = no agreed price, the ladder decides.
7
+ */
8
+ export declare function priceAt(prices: readonly AgreedPrice[] | undefined, at: number): AgreedPrice | undefined;
9
+ /** The first price that starts after `at`: "€ 20,30 from 1 Jan 2027". */
10
+ export declare function nextPrice(prices: readonly AgreedPrice[] | undefined, at: number): AgreedPrice | undefined;
11
+ /** `entry` in the list: replaces one on the same day, kept ascending. */
12
+ export declare function withPrice(prices: readonly AgreedPrice[] | undefined, entry: AgreedPrice): AgreedPrice[];
13
+ /** Per agreement line, the exclusive end of the last window an invoice billed. */
14
+ export declare function billedThroughOf(billed: readonly BilledLine[]): Record<string, number>;
15
+ /**
16
+ * Whether `after` changes a price that billed windows were priced at: anything starting before
17
+ * `billedThrough`. A new price starts after the last billed day (review 5a-6, besluit 2).
18
+ */
19
+ export declare function changesBilledPrice(before: readonly AgreedPrice[] | undefined, after: readonly AgreedPrice[] | undefined, billedThrough: number | undefined): boolean;
20
+ /** What indexing did to one line, for the modal's preview. */
21
+ export interface IndexedLine {
22
+ lineId: string;
23
+ /** Not indexed: it follows the ladder (`ladder`), or is priced per record (`rated`). */
24
+ skipped?: "ladder" | "rated";
25
+ /** The price the day before `from`. */
26
+ base?: Cents;
27
+ next?: Cents;
28
+ /** A price already planned on `from`, replaced. */
29
+ replaces?: Cents;
30
+ }
31
+ /**
32
+ * Index every agreed price by `percent` from `from`: one new date per line, never a split. The
33
+ * base is the price that holds the day before, rounded half up to a cent. Lines on the ladder or
34
+ * priced per record are skipped — those prices are indexed in the price list or the rate card.
35
+ */
36
+ export declare function indexPrices(lines: readonly AgreementLine[], change: {
37
+ from: number;
38
+ percent: number;
39
+ ratedProductIds?: readonly string[];
40
+ }): {
41
+ lines: AgreementLine[];
42
+ results: IndexedLine[];
43
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import { PriceBand } from '../catalog/rating';
2
2
  import { Cents } from '../money/money';
3
3
  import { PricePer } from '../money/period-price';
4
+ import { TransactionPaymentTerm } from '../transaction/payment-term';
4
5
  /**
5
6
  * A standing arrangement with a customer that bills every period: number rental, licences, and
6
7
  * the usage of an account at a source (calls on a PBX tenant).
@@ -13,7 +14,11 @@ export interface Agreement {
13
14
  organizationId: string;
14
15
  title: string;
15
16
  companyId: string;
16
- businessEntityId?: string;
17
+ /** What every invoice of this agreement carries in its head; filled from the quote. */
18
+ invoice?: AgreementInvoice;
19
+ /** From the administration's `agreement` series, drawn when it first becomes active. */
20
+ number?: string;
21
+ businessEntityId: string;
17
22
  currency: string;
18
23
  status: AgreementStatus;
19
24
  /** Epoch ms. The first period starts on the interval boundary at or before this. */
@@ -32,7 +37,6 @@ export interface Agreement {
32
37
  * records still arrive, and both land on the same run.
33
38
  */
34
39
  graceDays: number;
35
- paymentTermKey?: string;
36
40
  /** "Reverse charge / exempt" for this customer: every invoice the run writes carries it. */
37
41
  taxOverrideKey?: string;
38
42
  /** The default commitment of its lines; a line may carry its own. Absent = cancel any time. */
@@ -61,13 +65,38 @@ export interface Commitment {
61
65
  noticeDays: number;
62
66
  renew: "term" | "month" | "none";
63
67
  }
64
- /** A price fixed on the line when it was agreed, instead of the ladder at billing time. */
68
+ /**
69
+ * The head the run puts on each invoice (review 5a-5). `contactId` is a contact of whoever pays:
70
+ * the `invoiceTo` company when there is one, else the customer. A change applies from the next
71
+ * invoice; an issued one froze its own.
72
+ */
73
+ export interface AgreementInvoice {
74
+ contactId?: string;
75
+ /** The holding that pays for its daughter. Its contact is `contactId`. */
76
+ invoiceTo?: {
77
+ companyId: string;
78
+ };
79
+ /** A crm address of whoever pays; absent = their standard billing address. */
80
+ billingAddressId?: string;
81
+ paymentTerm?: TransactionPaymentTerm;
82
+ /** The customer's PO number: on every invoice of this agreement. */
83
+ reference?: string;
84
+ }
85
+ /** A price fixed on a supplier contract line when it was made (purchase). Per its `unitKey`. */
65
86
  export interface LinePrice {
66
- /** Per the line's `unitKey`. */
67
87
  cents: Cents;
68
88
  currency: string;
69
89
  per?: PricePer;
70
90
  }
91
+ /**
92
+ * The price agreed on a line from a day on (D1): each holds until the next one starts. In the
93
+ * agreement's currency, per the line's `unitKey`; `per` absent = the product's price period.
94
+ */
95
+ export interface AgreedPrice {
96
+ from: number;
97
+ cents: Cents;
98
+ per?: PricePer;
99
+ }
71
100
  export type AgreementInterval = "month" | "quarter" | "year";
72
101
  export interface AgreementLine {
73
102
  id: string;
@@ -84,14 +113,16 @@ export interface AgreementLine {
84
113
  * Absent = the line's own id.
85
114
  */
86
115
  seriesId?: string;
87
- /** Agreed price, snapshot (from the quote). Absent = the price ladder on the window's day. */
88
- price?: LinePrice;
89
- /** Overrides the agreement's commitment for this line. */
90
- commitment?: Commitment;
91
116
  /**
92
- * Where the quantity comes from. How it is priced is the product's business (`pricing`), and a
93
- * price for this agreement is a price agreement with `contractId` = the agreement.
117
+ * What this customer pays for this line, ascending on `from` (D1). A price change is a new
118
+ * date, never a split. Empty = the price ladder on the window's day.
94
119
  */
120
+ prices?: AgreedPrice[];
121
+ /** A line priced per record: its rate card. Absent = the ladder's (customer, else product). */
122
+ rateCardId?: string;
123
+ /** Overrides the agreement's commitment for this line. */
124
+ commitment?: Commitment;
125
+ /** Where the quantity comes from. Per unit or per record is the product's `pricing`. */
95
126
  quantity: LineQuantity;
96
127
  /** Taken off the quantity before the price: "the first 5 are included". */
97
128
  included?: number;
@@ -106,8 +137,22 @@ export interface AgreementLine {
106
137
  billing?: LineBilling;
107
138
  /** A fixed line's own rhythm; never shorter than the agreement's. Absent = the agreement's. */
108
139
  every?: LineRhythm;
140
+ /**
141
+ * When billing starts (plan 047 §10.2): on its dates (`date`, or absent), or only once what it
142
+ * sells runs (`activation`: the first time fulfilment sees it active). A new line takes its
143
+ * product's {@link Product.billingStart}. `delivery` follows with goods sold on an agreement.
144
+ */
145
+ billingStart?: BillingStart;
146
+ /** Set by fulfilment, never by a person: the first day it ran. A resume does not move it. */
147
+ activatedAt?: number;
148
+ /** From the quote line (plan 053): see {@link SalesLine.configurationId}. */
149
+ configurationId?: string;
150
+ /** A sub-line of that configuration: the answer it comes from (see `SalesLine.optionKey`). */
151
+ optionKey?: string;
109
152
  }
110
153
  export type LineBilling = "advance" | "arrears";
154
+ /** When a line starts billing: on its dates, or once what it sells runs (047 §10.2). */
155
+ export type BillingStart = "date" | "activation";
111
156
  export type LineRhythm = AgreementInterval | "once";
112
157
  export type LineQuantity = {
113
158
  from: "fixed";
@@ -37,8 +37,22 @@ export interface MatchContext {
37
37
  otherLines: readonly Pick<BankLine, "id" | "bankAccountId" | "date" | "amountCents" | "state">[];
38
38
  }
39
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;
40
+ /**
41
+ * `Reconciliation.on`: the latest day of every line it matches (Exact `EndDate`, "the highest
42
+ * invoice date of all matched"). A prepayment on 15-12 for an invoice of 05-01 settles on 05-01,
43
+ * so on 31-12 it is still open as a prepayment.
44
+ */
45
+ export declare const reconciledOn: (days: readonly string[]) => string;
46
+ /**
47
+ * Whether a reconciliation settles on `asOf` (`YYYY-MM-DD`): in effect by then and not yet undone.
48
+ * Without a day: whether it is active now.
49
+ */
50
+ export declare function settlesOn(rec: Pick<Reconciliation, "status" | "on" | "reversal">, asOf?: string): boolean;
51
+ /**
52
+ * What is left of an item: its amount minus every allocation on it that settles on `asOf` (now,
53
+ * without one). Computed, never stored.
54
+ */
55
+ export declare function openCentsOf(itemKey: string, amountCents: Cents, reconciliations: readonly Pick<Reconciliation, "status" | "allocations" | "on" | "reversal">[], asOf?: string): Cents;
42
56
  /** The invariant of a reconciliation: its allocations cancel out (a difference is in the entry). */
43
57
  export declare function reconciliationBalances(rec: Pick<Reconciliation, "allocations">): boolean;
44
58
  /**
@@ -32,6 +32,11 @@ export interface BankImport {
32
32
  /** What the statement says. A difference with our balance is a signal, never a silent fix. */
33
33
  statementOpeningCents?: Cents;
34
34
  statementClosingCents?: Cents;
35
+ /**
36
+ * The balances do not add up: `sum` = opening + lines ≠ closing, `gap` = the opening is not the
37
+ * previous statement's closing (a statement missed or imported twice).
38
+ */
39
+ balanceIssue?: "sum" | "gap";
35
40
  lineCount: number;
36
41
  importedBy: string;
37
42
  createdAt?: number;
@@ -62,6 +67,8 @@ export interface ParsedStatement {
62
67
  to: string;
63
68
  openingCents?: Cents;
64
69
  closingCents?: Cents;
70
+ /** Σ of the lines: sent with the header, because the lines themselves arrive in chunks. */
71
+ movementCents?: Cents;
65
72
  lines: ParsedBankLine[];
66
73
  }
67
74
  /** What a bank line is paired with. The pair lives on the line only; nothing points back. */
@@ -188,6 +195,11 @@ export interface Reconciliation {
188
195
  /** Flat and indexed: which reconciliations touch this item is an `$in` on this field. */
189
196
  itemKeys: string[];
190
197
  status: "active" | "reversed";
198
+ /**
199
+ * The day it takes effect (`YYYY-MM-DD`): the bank or payment day, not the click. "What was open
200
+ * on 31-12" is rebuilt from this (Exact: `EndPeriod` on the open item).
201
+ */
202
+ on: string;
191
203
  origin: {
192
204
  via: "exact" | "rule" | "manual";
193
205
  ruleId?: string;
@@ -2,6 +2,7 @@ import { BookingDocument } from '../../platform/bookkeeping';
2
2
  import { Cents } from '../money/money';
3
3
  import { TaxRateKind } from '../money/tax';
4
4
  import { MatchAllocation, MatchVerdict } from '../purchase/match';
5
+ import { LineDimensions } from '../transaction/types';
5
6
  import { WorkQueueItem } from './types';
6
7
  /**
7
8
  * A receipt or purchase invoice, as finance holds it until it is booked (plan 039 datamodel §6.4).
@@ -60,6 +61,11 @@ export interface Receipt {
60
61
  grossCents: Cents;
61
62
  };
62
63
  status: ReceiptStatus;
64
+ /**
65
+ * Another receipt of this supplier with the same invoice number, not rejected: the same invoice
66
+ * by mail and by upload. Set by the server; `""` once it is not any more.
67
+ */
68
+ duplicateOf?: string;
63
69
  /** Why extraction gave nothing, when it did not: shown on the item, a person fills it in. */
64
70
  extractError?: string;
65
71
  /** Only the reference; the state lives on the approval request (one approval system, plan 023). */
@@ -90,6 +96,8 @@ export interface ReceiptFields {
90
96
  supplierVatNumber?: Field<string>;
91
97
  supplierIban?: Field<string>;
92
98
  invoiceNumber?: Field<string>;
99
+ /** The reference the supplier wants with the payment (betalingskenmerk), when printed. */
100
+ paymentReference?: Field<string>;
93
101
  /** `"2026-09-24"`. */
94
102
  issueDate?: Field<string>;
95
103
  dueDate?: Field<string>;
@@ -103,13 +111,16 @@ export interface ReceiptFields {
103
111
  export interface ReceiptMatch {
104
112
  allocations: MatchAllocation[];
105
113
  unmatched: string[];
114
+ /** Lines that match in another currency: the receipt's rate first (045 F). */
115
+ needsRate?: string[];
106
116
  verdict: MatchVerdict;
107
117
  /** The one administration every target sits in; absent when mixed or nothing matched. */
108
118
  businessEntityId?: string;
109
119
  mixed: boolean;
110
120
  }
111
121
  /** One printed line of a supplier invoice, with how it books. */
112
- export interface ReceiptLine {
122
+ /** The B1 dimensions sit flat on the line: `customerCompanyId` is whose cost this is. */
123
+ export interface ReceiptLine extends LineDimensions {
113
124
  /** Stable for the line's life; what a treatment or a match points at (`receipt:<id>#<lineId>`). */
114
125
  id?: string;
115
126
  description?: string;
@@ -133,6 +144,8 @@ export interface ReceiptLine {
133
144
  /** The account in the target books (`LedgerOption.key`). */
134
145
  accountKey?: string;
135
146
  aiSuggested?: boolean;
147
+ /** Where a proposed account came from: the article's own rule or its ledger group's (045 F-3). */
148
+ accountFrom?: "product" | "group";
136
149
  /** Absent = a cost. Own books only: the line becomes a fixed asset or is spread over months. */
137
150
  treatment?: ReceiptLineTreatment;
138
151
  /**
@@ -140,11 +153,33 @@ export interface ReceiptLine {
140
153
  * customer and never a fixed asset, whatever the amount. Set by the order match or a person.
141
154
  */
142
155
  use?: "own" | "resale";
143
- /** Whose cost this is: reaches the journal line as a dimension, for margin per customer. */
144
- customerCompanyId?: string;
145
156
  /** The product it was ordered as (from the match): posting rules on product apply. */
146
157
  productId?: string;
158
+ /** The article's ledger group (from the match): the rule on the group picks the account. */
159
+ ledgerCategoryKey?: string;
160
+ /**
161
+ * One printed line for more than one destination — a collective invoice for A and B (plan 045
162
+ * F). The line stays as printed; the books take one line per share. The shares add up to the
163
+ * line exactly. Absent = one destination, on the line itself.
164
+ */
165
+ allocations?: ReceiptLineShare[];
166
+ }
167
+ /** Whose part of a printed line, and what it costs: the dimensions of what it was ordered for. */
168
+ export interface ReceiptLineShare extends LineDimensions {
169
+ netCents: Cents;
170
+ taxCents: Cents;
171
+ /** A contract period the invoice did not print, as the line's `period`. */
172
+ period?: {
173
+ from: string;
174
+ to: string;
175
+ };
147
176
  }
177
+ /**
178
+ * A printed line with what the match says it paid for (045 F-2): the product and its ledger
179
+ * group, and where the cost goes. One destination sits on the line; two or more become shares,
180
+ * split by what the match allotted each and with the VAT in the same proportion.
181
+ */
182
+ export declare function matchedLine(line: ReceiptLine, parts: readonly MatchAllocation[]): ReceiptLine;
148
183
  /**
149
184
  * What a line becomes besides its booking (plan 041 §4): a fixed asset in a group (its account
150
185
  * is then the group's cost account), or a prepaid cost released over months (its account is
@@ -176,17 +211,21 @@ export declare function suggestTreatments(lines: readonly ReceiptLine[], groups:
176
211
  }[], thresholdCents: number | undefined): ReceiptLine[];
177
212
  /** Totals from the lines: the lines are the truth, the totals follow. */
178
213
  export declare function receiptTotals(lines: readonly ReceiptLine[]): Receipt["totals"];
179
- export type ReceiptProblem = "no_supplier" | "no_date" | "no_lines" | "no_account" | "asset_without_group" | "spread_without_months";
214
+ export type ReceiptProblem = "no_supplier" | "no_date" | "no_lines" | "no_account" | "asset_without_group" | "spread_without_months" | "duplicate";
180
215
  export declare const RECEIPT_SCOPE_KIND = "receipt";
181
216
  /** The act a sign-off on a receipt is about. */
182
217
  export declare const RECEIPT_BOOK_ACT = "book";
183
- /** What still stands between this receipt and the books. Empty = it can be confirmed. */
218
+ /**
219
+ * What still stands between this receipt and the books. Empty = it can be confirmed. A duplicate
220
+ * is no problem here but a warning (plan 052 point 4): it keeps the receipt from being booked
221
+ * without a person, and a person may still book it.
222
+ */
184
223
  export declare function receiptProblems(receipt: Pick<Receipt, "fields" | "lines">): ReceiptProblem[];
185
224
  /**
186
225
  * Where it sits in "Te verwerken": ready to confirm when nothing is missing, to check when only
187
226
  * the reading needs a look, to choose when an account is missing (no proposal to confirm).
188
227
  */
189
- export declare function receiptGroupOf(receipt: Pick<Receipt, "fields" | "lines" | "status" | "match">): WorkQueueItem["group"] | null;
228
+ export declare function receiptGroupOf(receipt: Pick<Receipt, "fields" | "lines" | "status" | "match" | "duplicateOf">): WorkQueueItem["group"] | null;
190
229
  /**
191
230
  * The printed lines grouped as the books take them: one per account, VAT rate and customer, the
192
231
  * descriptions joined. Grouped here, at booking, and never stored — what is booked is always
@@ -12,7 +12,7 @@ export interface BookkeepingBinding {
12
12
  app: string;
13
13
  /** `ManagedAccount.id` of the connection; empty for our own books. */
14
14
  accountId?: string;
15
- /** Mapping onto an external package's accounts. Our own books use `PostingRule` instead. */
15
+ /** Rules onto the target books' accounts: a package's keys, or our own account ids. */
16
16
  ledgerMapping?: LedgerMappingRule[];
17
17
  /** Documents issued before this day stay with the previous books. */
18
18
  activeFrom: string;
@@ -31,7 +31,6 @@ export interface LedgerMappingRule {
31
31
  direction?: "sale" | "purchase";
32
32
  /** `BookkeepingDescribe.ledgerOptions[].key`. */
33
33
  targetKey: string;
34
- externalTaxCode?: string;
35
34
  }
36
35
  /**
37
36
  * pending: to do, or to do again.
@@ -73,6 +73,14 @@ export interface BusinessEntity {
73
73
  */
74
74
  firstDebtorNumber?: number;
75
75
  firstCreditorNumber?: number;
76
+ /**
77
+ * How far a supplier invoice may differ from its order or contract and still book without a
78
+ * person (plan 045 F-3): the larger of the two counts. Absent or a part absent = 1 % / € 1,00.
79
+ */
80
+ matchTolerance?: {
81
+ percent?: number;
82
+ cents?: number;
83
+ };
76
84
  /** The one a new document lands on. Exactly one row carries it. */
77
85
  isDefault?: boolean;
78
86
  /** Archived, never deleted: an issued quote names the entity it went out as. */
@@ -86,23 +94,11 @@ export type LegalForm = "bv" | "nv" | "eenmanszaak" | "vof" | "maatschap" | "sti
86
94
  export declare const LEGAL_FORMS: readonly LegalForm[];
87
95
  /** What {@link businessEntitySender} reads. Structural, so the document layer keeps pointing here. */
88
96
  export type BusinessEntityLike = Pick<BusinessEntity, "name" | "address" | "vatNumber" | "kvkNumber" | "iban" | "logo" | "footerText">;
89
- /**
90
- * Does this row belong to this entity?
91
- *
92
- * Master data is owned by **exactly one** entity. An empty owner means the **default** entity, so
93
- * rows written before entities existed stay where they were without a backfill.
94
- */
95
- export declare function belongsToEntity(rowEntityId: string | undefined, entityId: string, isDefaultEntity: boolean): boolean;
96
- /**
97
- * The `businessEntityId` values that belong to this entity, as a query filter:
98
- * `{ businessEntityId: { $in: entityOwnerIds(entity) } }`. Same rule as {@link belongsToEntity};
99
- * it works as a filter because every sales schema defaults the column to `""`, never unset.
100
- */
101
- export declare function entityOwnerIds(entity: Pick<BusinessEntity, "id" | "isDefault">): string[];
102
97
  /**
103
98
  * A shared table with per-entity overrides: rows with an empty `businessEntityId` first, then the
104
- * entity's own over them by `key`. How VAT rates and payment terms both work — a Belgian sister
105
- * needs `low` at 6, not `low` hidden.
99
+ * entity's own over them by `key` — a Belgian sister needs `low` at 6, not `low` hidden. The only
100
+ * place `""` means anything: "shared", on master data (tax codes, payment terms, dunning). A document,
101
+ * booking or item always carries a real id (review 5a-4).
106
102
  */
107
103
  export declare function shadowByKey<T extends {
108
104
  businessEntityId?: string;
@@ -1,22 +1,23 @@
1
1
  import { Cents } from '../money/money';
2
2
  import { PricePer } from '../money/period-price';
3
- import { TransactionLine } from '../transaction/types';
3
+ import { SalesLine } from '../transaction/types';
4
4
  import { PriceRule, SellableProduct } from './types';
5
5
  /** What the ladder reads of an article: its identity and sales' price row. */
6
- export type PricedProduct = Pick<SellableProduct, "id" | "categoryKey" | "listPriceCents" | "currency" | "pricePer" | "rateCardId">;
6
+ export type PricedProduct = Pick<SellableProduct, "id" | "categoryKey" | "listPriceCents" | "currency" | "pricePer" | "rateCardId" | "markupPercent">;
7
7
  /**
8
8
  * The ladder: the most specific rule wins, whole. No sum, no stacking.
9
9
  *
10
10
  * | Rank | Rule | Source |
11
11
  * |---|---|---|
12
- * | 1 | contract + product | `contract` |
13
- * | 2 | customer + product | `customer` |
14
- * | 3 | customer + category | `customer_category` |
15
- * | 4 | segment + product | `segment` |
16
- * | 5 | segment + category | `segment_category` |
17
- * | 6 | everyone + product | `everyone` |
18
- * | 7 | everyone + category | `everyone_category` |
19
- * | 8 | the product's list price | `list` |
12
+ * | 1 | customer + product | `customer` |
13
+ * | 2 | customer + category | `customer_category` |
14
+ * | 3 | segment + product | `segment` |
15
+ * | 4 | segment + category | `segment_category` |
16
+ * | 5 | everyone + product | `everyone` |
17
+ * | 6 | everyone + category | `everyone_category` |
18
+ * | 7 | the product's list price | `list` |
19
+ *
20
+ * No contract rung (D1): a price agreed in an agreement lives on its line (`prices[]`).
20
21
  *
21
22
  * A tier (`minQuantity`) is not a rung of its own but a tie-break within one: of the rules that
22
23
  * pass, the highest threshold that the quantity reaches wins, and the source then reads `tier`
@@ -25,11 +26,12 @@ export type PricedProduct = Pick<SellableProduct, "id" | "categoryKey" | "listPr
25
26
  export interface PriceContext {
26
27
  companyId?: string;
27
28
  segmentKey?: string;
28
- contractId?: string;
29
29
  /** In the article's **base** unit: 2 boxes of 10 reach a tier "from 20" (plan 046 #10). */
30
30
  quantity: number;
31
31
  /** Epoch ms; the validity window is judged against this, not against "now". */
32
32
  at: number;
33
+ /** What one unit costs us, for a `markupPercent` article (from the line's configuration). */
34
+ costCents?: Cents;
33
35
  }
34
36
  /** Amount, currency and period travel together: € 222 means nothing without "per year". */
35
37
  export interface ResolvedPrice {
@@ -62,4 +64,4 @@ export declare const MISSING_PRICE_SOURCE = "missing";
62
64
  * a tier or an agreement may now apply. A typed price stays, and so does a line whose product
63
65
  * is no longer in `products`.
64
66
  */
65
- export declare function repriceLines(lines: readonly TransactionLine[], products: readonly PricedProduct[], rules: readonly PriceRule[], ctx: Omit<PriceContext, "quantity">): TransactionLine[];
67
+ export declare function repriceLines(lines: readonly SalesLine[], products: readonly PricedProduct[], rules: readonly PriceRule[], ctx: Omit<PriceContext, "quantity">): SalesLine[];
@@ -36,6 +36,11 @@ export interface Product {
36
36
  kind: ProductKind;
37
37
  /** Serial or batch numbers on receipt and delivery. Absent = none; goods only. */
38
38
  tracking?: ProductTracking;
39
+ /**
40
+ * When an agreement line for it starts billing, as a default the line takes (047 vraag 2): on
41
+ * its dates, or once it runs — a number or a licence that counts only when it works.
42
+ */
43
+ billingStart?: "date" | "activation";
39
44
  /** The base unit everything counts in: stock, deliveries, allocations. Locked once used. */
40
45
  unitKey: string;
41
46
  /** Other units it comes in: a roll of 500 m, a box of 10. See {@link toBaseUnits}. */
@@ -142,6 +147,11 @@ export interface SalesProduct {
142
147
  pricing?: ProductPricing;
143
148
  /** The default card of a `rated` product. A price agreement may name another. */
144
149
  rateCardId?: string;
150
+ /**
151
+ * Priced as cost plus this percentage instead of a list price (plan 053: an extra EVC costs us
152
+ * anything from € 4 to € 397). The line's cost comes from its configuration; no cost = no price.
153
+ */
154
+ markupPercent?: number;
145
155
  /** The unit a new line starts in (a box); absent = the base unit. */
146
156
  unitKey?: string;
147
157
  updatedAt?: number;
@@ -172,7 +182,13 @@ export type ProductPricing = "unit" | "rated";
172
182
  export declare const FIXED_UNITS: readonly ["piece", "hour", "day", "month", "km", "license", "minute", "kg", "ton", "m", "m2", "m3", "liter", "box", "roll", "pallet", "pack"];
173
183
  export type FixedUnit = (typeof FIXED_UNITS)[number];
174
184
  /**
175
- * A price for a customer, a segment or a contract.
185
+ * Whether a quantity in this unit may have a fraction. Counted units (piece, licence, box, an
186
+ * article's own pack) take whole numbers (Exact: `IsFractionAllowedItem`, here per unit).
187
+ */
188
+ export declare const allowsFraction: (unitKey: string) => boolean;
189
+ /**
190
+ * A price for a customer, a segment or everyone. A price agreed in an agreement is not one: it
191
+ * lives on the agreement line (D1).
176
192
  *
177
193
  * Deliberately no price expression language: no formulas, no conditions, no ordering field. The
178
194
  * moment a price cannot be explained to the customer it is the wrong price.
@@ -201,13 +217,8 @@ export interface PriceRule {
201
217
  currency: string;
202
218
  validFrom?: number;
203
219
  validTo?: number;
204
- /**
205
- * The contract that placed this rule. Declared now because it is the top rung of the ladder: a
206
- * contract price is not a second pricing mechanism, it is a price rule with an owner and a term.
207
- */
208
- contractId?: string;
209
- /** The administration this rule belongs to. Unset = the default one. */
210
- businessEntityId?: string;
220
+ /** The administration this rule belongs to. */
221
+ businessEntityId: string;
211
222
  createdBy: string;
212
223
  createdAt?: number;
213
224
  }
@@ -228,7 +239,7 @@ export interface RateCard {
228
239
  currency: string;
229
240
  validFrom?: number;
230
241
  validTo?: number;
231
- businessEntityId?: string;
242
+ businessEntityId: string;
232
243
  archived?: boolean;
233
244
  createdBy: string;
234
245
  createdAt?: number;
@@ -1,3 +1,4 @@
1
+ import { PostalAddress } from '../organization/types';
1
2
  /** Which side of the table a company sits on, for one administration. */
2
3
  export type CompanyRoleKind = "customer" | "supplier";
3
4
  export declare const COMPANY_ROLE_KINDS: readonly CompanyRoleKind[];
@@ -31,3 +32,16 @@ export interface EnsureCompanyRoleRequest {
31
32
  }
32
33
  /** The payment-term direction a role pays in: a customer pays us, we pay a supplier. */
33
34
  export declare const roleDirection: (role: CompanyRoleKind) => "sales" | "purchase";
35
+ /**
36
+ * A party as a document went out to it (review R2): frozen at issue, so the books, the EC sales
37
+ * list and the audit file show who it was then, never crm today. `number` is its role number at
38
+ * the issuing administration (the debtor number).
39
+ */
40
+ export interface PartySnapshot {
41
+ companyId: string;
42
+ number?: string;
43
+ name: string;
44
+ vatNumber?: string;
45
+ kvkNumber?: string;
46
+ address?: PostalAddress;
47
+ }