@opencxh/domain 1.293.0 → 1.295.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 (58) hide show
  1. package/dist/entities/agreement/from-quote.d.ts +18 -0
  2. package/dist/entities/agreement/from-quote.test.d.ts +1 -0
  3. package/dist/entities/agreement/index.d.ts +2 -0
  4. package/dist/entities/agreement/lifecycle.d.ts +44 -0
  5. package/dist/entities/agreement/lifecycle.test.d.ts +1 -0
  6. package/dist/entities/agreement/schedule.d.ts +4 -0
  7. package/dist/entities/agreement/types.d.ts +38 -0
  8. package/dist/entities/bank/match.d.ts +1 -1
  9. package/dist/entities/bookkeeping/receipt.d.ts +95 -2
  10. package/dist/entities/bookkeeping/types.d.ts +2 -0
  11. package/dist/entities/catalog/pricing.d.ts +5 -0
  12. package/dist/entities/catalog/types.d.ts +23 -1
  13. package/dist/entities/company/types.d.ts +6 -0
  14. package/dist/entities/financing/schedule.d.ts +40 -1
  15. package/dist/entities/financing/types.d.ts +99 -3
  16. package/dist/entities/ledger/entry.d.ts +12 -6
  17. package/dist/entities/ledger/template.d.ts +5 -0
  18. package/dist/entities/ledger/types.d.ts +9 -2
  19. package/dist/entities/money/index.d.ts +2 -0
  20. package/dist/entities/money/month-parts.d.ts +19 -0
  21. package/dist/entities/money/month-parts.test.d.ts +1 -0
  22. package/dist/entities/money/period-price.d.ts +23 -0
  23. package/dist/entities/money/period-price.test.d.ts +1 -0
  24. package/dist/entities/money/totals.d.ts +5 -1
  25. package/dist/entities/purchase/contract.d.ts +124 -0
  26. package/dist/entities/purchase/contract.test.d.ts +1 -0
  27. package/dist/entities/purchase/demand.d.ts +68 -0
  28. package/dist/entities/purchase/demand.test.d.ts +1 -0
  29. package/dist/entities/purchase/index.d.ts +7 -0
  30. package/dist/entities/purchase/kinds.d.ts +8 -0
  31. package/dist/entities/purchase/logic.d.ts +35 -0
  32. package/dist/entities/purchase/match.d.ts +98 -0
  33. package/dist/entities/purchase/match.test.d.ts +1 -0
  34. package/dist/entities/purchase/packing-slip.d.ts +82 -0
  35. package/dist/entities/purchase/packing-slip.test.d.ts +1 -0
  36. package/dist/entities/purchase/purchase.test.d.ts +1 -0
  37. package/dist/entities/purchase/types.d.ts +147 -0
  38. package/dist/entities/reports/reports.d.ts +9 -0
  39. package/dist/entities/transaction/document.d.ts +3 -0
  40. package/dist/entities/transaction/keys.d.ts +1 -7
  41. package/dist/entities/transaction/kinds.d.ts +7 -0
  42. package/dist/entities/transaction/types.d.ts +11 -1
  43. package/dist/entities/work/index.d.ts +1 -1
  44. package/dist/entities/work/types.d.ts +1 -1
  45. package/dist/index.cjs +23 -23
  46. package/dist/index.d.ts +3 -1
  47. package/dist/index.js +4187 -3117
  48. package/dist/platform/bookkeeping.d.ts +29 -3
  49. package/dist/platform/manifest.d.ts +6 -27
  50. package/dist/platform/navigation.d.ts +202 -0
  51. package/dist/platform/navigation.test.d.ts +1 -0
  52. package/dist/platform/provider.d.ts +7 -0
  53. package/dist/platform/provisioning.d.ts +88 -0
  54. package/dist/platform/resource.d.ts +19 -0
  55. package/dist/platform/resource.test.d.ts +1 -0
  56. package/dist/platform/routing.d.ts +13 -18
  57. package/package.json +1 -1
  58. package/dist/platform/menu.d.ts +0 -26
@@ -33,3 +33,8 @@ export interface LedgerTemplate {
33
33
  */
34
34
  export declare const NL_BV_TEMPLATE: LedgerTemplate;
35
35
  export declare const LEDGER_TEMPLATES: readonly LedgerTemplate[];
36
+ /**
37
+ * The categories a payroll journal books on (HRM, or a package like Nmbrs or Loket), and the
38
+ * template account each lands on unless a posting rule says otherwise.
39
+ */
40
+ export declare const PAYROLL_CATEGORIES: Readonly<Record<string, string>>;
@@ -19,6 +19,11 @@ export interface LedgerAccount {
19
19
  };
20
20
  /** Accounts with detail (bank, loan, current account) require a `subAccountId` on every line. */
21
21
  requiresSubAccount?: boolean;
22
+ /**
23
+ * The VAT code a line on this account usually carries (`TaxCode.key`, e.g. `high`): proposed
24
+ * when a bank line is booked straight onto it. Empty is none.
25
+ */
26
+ defaultTaxCode?: string;
22
27
  /** Where it sits in the reports: `"revenue.net"`, `"costs.office"`. The reports are not hard-coded. */
23
28
  reportGroup?: string;
24
29
  order?: number;
@@ -29,7 +34,9 @@ export interface LedgerAccount {
29
34
  createdAt?: number;
30
35
  updatedAt?: number;
31
36
  }
32
- export type LedgerAccountRole = "debtors" | "creditors" | "vat_payable" | "vat_receivable" | "revenue_default" | "expense_default" | "suspense" | "result";
37
+ export type LedgerAccountRole = "debtors" | "creditors" | "vat_payable" | "vat_receivable" | "revenue_default" | "expense_default" | "suspense" | "result"
38
+ /** Where the difference lands when a foreign document is paid at another rate than booked. */
39
+ | "fx_difference";
33
40
  export type JournalKind = "sales" | "purchase" | "bank" | "cash" | "memorial" | "opening";
34
41
  export interface Journal {
35
42
  id: string;
@@ -155,7 +162,7 @@ export interface PostingRule {
155
162
  organizationId: string;
156
163
  businessEntityId: string;
157
164
  appliesTo: {
158
- direction: "sale" | "purchase";
165
+ direction: BookingDocument["direction"];
159
166
  kind?: BookingDocument["kind"];
160
167
  };
161
168
  match: {
@@ -1,3 +1,5 @@
1
1
  export * from './money';
2
+ export * from './month-parts';
3
+ export * from './period-price';
2
4
  export * from './tax';
3
5
  export * from './totals';
@@ -0,0 +1,19 @@
1
+ import { Cents } from './money';
2
+ /**
3
+ * `total` over the calendar months of a period (`to` inclusive, as printed): a whole month one
4
+ * share, a part of one by its days, in whole cents that add up exactly — leftover cents to the
5
+ * first months, as `allocate` does. One distribution for revenue and cost alike, so a month's
6
+ * margin is the same sum on both sides. `month` is each month's first day.
7
+ */
8
+ export declare function monthParts(total: Cents, period: {
9
+ from: string;
10
+ to: string;
11
+ }): {
12
+ month: string;
13
+ cents: Cents;
14
+ }[];
15
+ /** How many calendar months a period touches. */
16
+ export declare function monthsIn(period: {
17
+ from: string;
18
+ to: string;
19
+ }): number;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { Cents } from './money';
2
+ /** What a recurring price is for: € 25 per month, € 222 per year. */
3
+ export type PricePer = "month" | "quarter" | "year";
4
+ export declare const PRICE_PERS: readonly PricePer[];
5
+ /**
6
+ * `total` (one `per`, all units together) over `window`, in whole cents.
7
+ *
8
+ * The distribution key is the calendar: a yearly total is split over its 12 months with
9
+ * `allocate` (leftover cents to the first months), a quarterly one over its 3. A month is billed
10
+ * as its own share, a part of a month by its days. So month 7 on its own is exactly month 7 of a
11
+ * yearly run, and twelve months of € 100/year add up to € 100,00. Rounded once, at the end.
12
+ */
13
+ export declare function priceOver(total: Cents, per: PricePer, window: {
14
+ from: number;
15
+ to: number;
16
+ }): Cents;
17
+ /**
18
+ * An amount per `from` restated per `to` (€ 222 per year → € 18,50 per month), for comparing two
19
+ * prices side by side — a margin, a cost next to a list price. **Never stored**: billing goes
20
+ * through {@link priceOver}, which keeps the calendar split exact.
21
+ */
22
+ export declare function restate(cents: Cents, from: PricePer | undefined, to: PricePer | undefined): Cents;
23
+ export declare function isPricePer(value: unknown): value is PricePer;
@@ -0,0 +1 @@
1
+ export {};
@@ -14,8 +14,12 @@ import { TaxRate } from './tax';
14
14
  * and the tax office do.
15
15
  *
16
16
  * `quantity` may be fractional (3.5 hours); a *price* never is.
17
+ *
18
+ * A price with a period (`pricePer`) on a line with a service period bills that period's share
19
+ * of the line total — `priceOver`, so the line total is rounded once and then split by calendar.
20
+ * Without a service period (a quote) it is one period's amount.
17
21
  */
18
- export declare function lineNet(line: TransactionLine): Cents;
22
+ export declare function lineNet(line: Pick<TransactionLine, "kind" | "quantity" | "unitPriceCents" | "discountPercent" | "pricePer" | "servicePeriod">): Cents;
19
23
  export interface PricedTransaction {
20
24
  /** The same lines with `netCents` and `taxCents` filled in. */
21
25
  lines: TransactionLine[];
@@ -0,0 +1,124 @@
1
+ import { Period } from '../agreement/period';
2
+ import { Agreement, AgreementLine } from '../agreement/types';
3
+ import { Cents } from '../money/money';
4
+ /** A sales line, followed through its changes: the agreement and the series it belongs to. */
5
+ export interface SalesLineRef {
6
+ agreementId: string;
7
+ seriesId: string;
8
+ }
9
+ /**
10
+ * A subscription at a supplier: the shape of a sales agreement line, plus where it came from and
11
+ * whom it is for. Its `price` is the snapshot taken when it was made — an offer that gets dearer
12
+ * later changes nothing here; a new price is a successor line from a date.
13
+ */
14
+ export interface PurchaseAgreementLine extends AgreementLine {
15
+ /** Provenance only: the offer it was bought on. The price is `price`, not the offer's. */
16
+ offerId?: string;
17
+ /** Their subscription id (Pax8, Microsoft). */
18
+ externalRef?: string;
19
+ /** What the supplier handed back when it was made (a tenant id, an account id). */
20
+ fulfillment?: Record<string, string>;
21
+ /** Where it landed when that is not the supplier, so an end takes it off there first. */
22
+ deliveredTo?: {
23
+ app: string;
24
+ externalRef?: string;
25
+ };
26
+ customerCompanyId?: string;
27
+ /** The sales line that passes this on, followed by series. */
28
+ salesLine?: SalesLineRef;
29
+ /**
30
+ * `variable`: no price, the invoice decides the amount (transit minutes). Nothing is expected;
31
+ * only a jump against the last three months is work. A varying *count* (seats) is `quantity:
32
+ * manual|meter` with a fixed price instead — never this.
33
+ */
34
+ expected?: "variable";
35
+ /** Days from each term's start in which the supplier still takes a change (NCE: 7). */
36
+ changeWindowDays?: number;
37
+ /** The order line it came from, so a second receipt updates instead of adding. */
38
+ sourceKey?: string;
39
+ /** What supplier invoices said per period (fase 7): the history a variable line is judged on. */
40
+ invoiced?: {
41
+ from: number;
42
+ cents: Cents;
43
+ }[];
44
+ }
45
+ /** A supplier contract: `companyId` is the supplier; one contract holds many subscriptions. */
46
+ export interface PurchaseAgreement extends Omit<Agreement, "lines"> {
47
+ lines: PurchaseAgreementLine[];
48
+ }
49
+ export interface ExpectedLine {
50
+ lineId: string;
51
+ kind: "fixed" | "count" | "variable";
52
+ /** Fixed lines only; a count waits for the count, a variable line expects nothing. */
53
+ cents?: Cents;
54
+ }
55
+ /**
56
+ * What the supplier should invoice over `window`, per line: a fixed count at the agreed price over
57
+ * the days it ran (the same `lineNet` the sales run bills with), or "count" / "variable" where the
58
+ * amount is not ours to know in advance.
59
+ */
60
+ export declare function expectedFor(agreement: Pick<PurchaseAgreement, "startsAt" | "endsAt" | "lines">, window: Period): ExpectedLine[];
61
+ /**
62
+ * A variable amount that moved more than `threshold` against the average of the (up to) three
63
+ * periods before: that is work for a person, not a number to wave through.
64
+ */
65
+ export declare function variableJump(previous: readonly Cents[], current: Cents, threshold?: number): boolean;
66
+ /** A sales line as sales reports it now, by series. */
67
+ export interface SalesLineState extends SalesLineRef {
68
+ /** Fixed count now; `null` for a count from a meter or entered per period. */
69
+ quantity: number | null;
70
+ /** When it really ends if the customer gives notice today. */
71
+ effectiveEndAt: number;
72
+ /** Windows due now without an invoice line (`agreementLineId` + `servicePeriod`). */
73
+ unbilled: Period[];
74
+ /** The sales line has ended or its agreement is not active. */
75
+ ended?: boolean;
76
+ }
77
+ export type ContractSignal = {
78
+ kind: "not_covered";
79
+ contractId: string;
80
+ lineId: string;
81
+ reason: "no_sales_line" | "quantity" | "sales_ended";
82
+ } | {
83
+ kind: "not_billed";
84
+ contractId: string;
85
+ lineId: string;
86
+ windows: Period[];
87
+ } | {
88
+ kind: "term_mismatch";
89
+ contractId: string;
90
+ lineId: string;
91
+ purchaseEnd: number;
92
+ salesEnd: number;
93
+ } | {
94
+ kind: "notice_due";
95
+ contractId: string;
96
+ lineId: string;
97
+ deadline: number;
98
+ } | {
99
+ kind: "change_window";
100
+ contractId: string;
101
+ lineId: string;
102
+ closesAt: number;
103
+ } | {
104
+ kind: "variable_jump";
105
+ contractId: string;
106
+ lineId: string;
107
+ cents: Cents;
108
+ };
109
+ export interface SignalOptions {
110
+ /** A notice deadline this close is a reminder. */
111
+ noticeDays?: number;
112
+ /** A change window closing this soon is a reminder. */
113
+ windowDays?: number;
114
+ }
115
+ /**
116
+ * Everything about a supplier contract a person should look at, apart:
117
+ * - **not covered**: bought for a customer, but no sales line passes it on, or another count;
118
+ * - **not billed**: the sales line exists but a period that is due has no invoice line — a sales
119
+ * line that exists proves nothing, so this reads what sales actually invoiced;
120
+ * - **term mismatch**: we are bound longer than the customer is;
121
+ * - **notice due / change window**: reminders before the dates that decide the next term;
122
+ * - **variable jump**: a variable amount moved more than 30%.
123
+ */
124
+ export declare function contractSignals(contracts: readonly PurchaseAgreement[], sales: readonly SalesLineState[], now: number, options?: SignalOptions): ContractSignal[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ import { ProvisionableOffer, ProvisioningAction } from '../../platform/provisioning';
2
+ import { Agreement } from '../agreement/types';
3
+ import { PurchaseAgreement } from './contract';
4
+ import { SupplierOffer } from './types';
5
+ /** One thing to order, change or end, as "Te bestellen" lists it. */
6
+ export interface DemandItem {
7
+ /** Stable: the sales series, or the customer's account at the supplier. */
8
+ key: string;
9
+ action: ProvisioningAction;
10
+ level: "account" | "item";
11
+ supplierCompanyId: string;
12
+ offerId: string;
13
+ productId: string;
14
+ name: string;
15
+ /** Purchase: how many. Change: by how many. End: the current count. */
16
+ quantity: number;
17
+ /** A change: the count it should be after, and the count it is now. */
18
+ targetQuantity?: number;
19
+ from?: number;
20
+ customerCompanyId: string;
21
+ salesLine?: {
22
+ agreementId: string;
23
+ seriesId: string;
24
+ };
25
+ contractLine?: {
26
+ contractId: string;
27
+ lineId: string;
28
+ };
29
+ /** Epoch ms it takes effect: the term's end for an end or a reduction outside the window. */
30
+ effectiveAt?: number;
31
+ /** The last day to send it for `effectiveAt`; past it, `effectiveAt` is the next term's end. */
32
+ deadline?: number;
33
+ /** A reduction outside the change window: not renewing the difference, not lowering now. */
34
+ atRenewal?: boolean;
35
+ /** The notice was too late for this term: it ends at the next one. */
36
+ nextTerm?: boolean;
37
+ /** The purchase runs on after the sale ends: we pay until then. */
38
+ runsOnUntil?: number;
39
+ /** The account item this waits for. */
40
+ waitsFor?: string;
41
+ /** It lands on another platform than the supplier's (a Telnyx number on Eylo). */
42
+ deliverTo?: {
43
+ app: string;
44
+ offer: string;
45
+ };
46
+ }
47
+ export interface DemandInput {
48
+ agreements: readonly Agreement[];
49
+ contracts: readonly PurchaseAgreement[];
50
+ offers: readonly SupplierOffer[];
51
+ /** Provider describes, by `app:offer`. */
52
+ provisionable: ReadonlyMap<string, ProvisionableOffer>;
53
+ /** Keys already on an order that has not settled: never proposed twice. */
54
+ open: ReadonlySet<string>;
55
+ now: number;
56
+ }
57
+ /**
58
+ * What to order, change or end so purchase follows sales (fase 9). Per sales series with a fixed
59
+ * count and a default offer:
60
+ * - no contract line passes it on: buy the count;
61
+ * - the contract has fewer: one change to the new count;
62
+ * - the contract has more: lower it now inside the supplier's change window, else do not renew
63
+ * the difference at the term's end;
64
+ * - the sale has an end: end the purchase at the first term end the notice still reaches, with
65
+ * the deadline to send it by, and a signal when we pay on after the customer stopped.
66
+ * An item that needs the customer's account at the supplier gets that account first.
67
+ */
68
+ export declare function demandFor(input: DemandInput): DemandItem[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export * from './contract';
2
+ export * from './demand';
3
+ export * from './kinds';
4
+ export * from './logic';
5
+ export * from './match';
6
+ export * from './packing-slip';
7
+ export * from './types';
@@ -0,0 +1,8 @@
1
+ import { TransactionKind } from '../transaction/kinds';
2
+ /**
3
+ * What `apps/purchase` issues: the same engine as a quote, its own table. `sent` is issued (a
4
+ * number and the paper); the supplier confirms, and the goods coming in make it `received`.
5
+ * Walking back one step stays allowed — a return reopens a received order.
6
+ */
7
+ export declare const PURCHASE_KINDS: readonly TransactionKind[];
8
+ export declare const PURCHASE_ORDER_KIND = "purchase_order";
@@ -0,0 +1,35 @@
1
+ import { PricePer } from '../money/period-price';
2
+ import { GoodsReceipt, GoodsReceiptLine, PurchaseLine, SupplierOffer } from './types';
3
+ /** What came in for one order line: booked receipts in, returns out. */
4
+ export declare function receivedFor(poLineId: string, receipts: readonly GoodsReceipt[]): number;
5
+ export type ReceiptState = "none" | "partial" | "full";
6
+ /** Over the product lines of an order. */
7
+ export declare function receiptState(lines: readonly Pick<PurchaseLine, "kind" | "quantity" | "receivedQty">[]): "none" | "partial" | "full";
8
+ export interface GoodsReceiptProblem {
9
+ code: "receipt_over_ordered" | "receipt_return_over_received" | "receipt_serials_count";
10
+ poLineId: string;
11
+ }
12
+ /**
13
+ * Why a receipt cannot be booked: more in than still open, more back than came in, or serial
14
+ * numbers that do not match the count. No serials is fine (bulk goods); some serials must be all.
15
+ */
16
+ export declare function goodsReceiptProblems(receipt: Pick<GoodsReceipt, "movement" | "lines">, poLines: readonly Pick<PurchaseLine, "id" | "quantity">[], booked: readonly GoodsReceipt[]): GoodsReceiptProblem[];
17
+ /** Lines of a receipt worth keeping: a positive quantity, serials trimmed and unique. */
18
+ export declare function cleanReceiptLines(lines: readonly GoodsReceiptLine[]): GoodsReceiptLine[];
19
+ /** Valid on `at`. */
20
+ export declare function offerValid(offer: Pick<SupplierOffer, "validFrom" | "validTo">, at: number): boolean;
21
+ /** The offer that prices the product: the marked default if valid, else the first valid one. */
22
+ export declare function defaultOffer<T extends SupplierOffer>(offers: readonly T[], at: number): T | null;
23
+ /** Purchase units → product units: two rolls of 500 m are 1,000 m. */
24
+ export declare function toProductUnits(offer: Pick<SupplierOffer, "unitsPerPurchaseUnit">, qty: number): number;
25
+ /**
26
+ * The offer's cost per product unit, as amount, currency and period — what is written back to the
27
+ * product. Never a monthly equivalent: € 222 per year stays € 222 per year.
28
+ */
29
+ export declare function productCost(offer: SupplierOffer): {
30
+ cents: number;
31
+ currency: string;
32
+ per?: PricePer;
33
+ };
34
+ /** Offers side by side: each over one year, per product unit. A one-off stays itself. */
35
+ export declare function costOverYear(offer: SupplierOffer): number;
@@ -0,0 +1,98 @@
1
+ import { Cents } from '../money/money';
2
+ import { PricePer } from '../money/period-price';
3
+ import { MatchCandidate, MatchedVia } from './packing-slip';
4
+ import { SupplierOffer } from './types';
5
+ /** What an invoice line pays for: an order line, or a contract line over a period. */
6
+ export type InvoiceTarget = {
7
+ kind: "po_line";
8
+ orderId: string;
9
+ lineId: string;
10
+ } | {
11
+ kind: "contract_line";
12
+ contractId: string;
13
+ lineId: string;
14
+ period: {
15
+ from: number;
16
+ to: number;
17
+ };
18
+ };
19
+ /** A supplier invoice line as the match reads it — `ReceiptLine`, narrowed. */
20
+ export interface InvoiceLine {
21
+ id: string;
22
+ description?: string;
23
+ sku?: string;
24
+ externalRef?: string;
25
+ quantity?: number;
26
+ netCents: Cents;
27
+ /** As printed: `to` inclusive. */
28
+ period?: {
29
+ from: string;
30
+ to: string;
31
+ };
32
+ }
33
+ /** An open order line or a running contract line of this supplier, with what it expects. */
34
+ export interface InvoiceCandidate extends MatchCandidate {
35
+ kind: "po_line" | "contract_line";
36
+ /** The order or contract. */
37
+ parentId: string;
38
+ businessEntityId: string;
39
+ unitPriceCents: Cents;
40
+ pricePer?: PricePer;
41
+ discountPercent?: number;
42
+ /** Order line: what is still to invoice. Contract line: the agreed count, `null` when counted. */
43
+ openQty: number | null;
44
+ /** A contract line whose invoice decides the amount: nothing expected. */
45
+ variable?: boolean;
46
+ use?: "own" | "resale";
47
+ customerCompanyId?: string;
48
+ }
49
+ export interface MatchAllocation {
50
+ receiptLineId: string;
51
+ target: InvoiceTarget;
52
+ via: MatchedVia;
53
+ quantity: number;
54
+ amountCents: Cents;
55
+ /** What this part should have cost; `null` for a variable line. */
56
+ expectedCents: Cents | null;
57
+ /** More than the order line still had open, or another count than the contract's. */
58
+ overQuantity: boolean;
59
+ businessEntityId: string;
60
+ productId?: string;
61
+ use?: "own" | "resale";
62
+ customerCompanyId?: string;
63
+ }
64
+ /**
65
+ * Invoice lines onto order and contract lines, many to many. Each line goes to its best candidates
66
+ * ({@link lineAffinity}); a count larger than one order line has open runs on to the next of the
67
+ * same article (10 phones over two orders of 6 and 4), the amount split by count. A contract line
68
+ * takes the line whole, for the period printed on it or else `defaultPeriod` (the invoice's month).
69
+ */
70
+ export declare function allocateInvoice(lines: readonly InvoiceLine[], candidates: readonly InvoiceCandidate[], offers: readonly Pick<SupplierOffer, "id" | "productId" | "vendorSku">[], defaultPeriod: {
71
+ from: number;
72
+ to: number;
73
+ }): {
74
+ allocations: MatchAllocation[];
75
+ unmatched: string[];
76
+ };
77
+ export interface MatchTolerance {
78
+ percent: number;
79
+ cents: Cents;
80
+ }
81
+ /** One per cent or one euro, whichever is more: a rounding difference is not work. */
82
+ export declare const DEFAULT_TOLERANCE: MatchTolerance;
83
+ export type MatchIssue = "price" | "quantity" | "unmatched";
84
+ export interface MatchVerdict {
85
+ /** Everything matched, at the expected price and count: approve without a person. */
86
+ ok: boolean;
87
+ issues: {
88
+ receiptLineId: string;
89
+ issue: MatchIssue;
90
+ expectedCents?: Cents;
91
+ amountCents?: Cents;
92
+ }[];
93
+ }
94
+ /**
95
+ * Whether an invoice is what was ordered or agreed: every line matched, every count within what
96
+ * was open, every amount within the tolerance (% and absolute). Anything else is work.
97
+ */
98
+ export declare function matchVerdict(allocations: readonly MatchAllocation[], unmatched: readonly string[], tolerance?: MatchTolerance): MatchVerdict;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import { GoodsReceipt, GoodsReceiptLine, PurchaseLine, PurchaseOrder, SupplierOffer } from './types';
2
+ /** One line as a supplier's document states it: their code, their words, a count, serials. */
3
+ export interface DocumentLine {
4
+ sku?: string;
5
+ description: string;
6
+ quantity: number;
7
+ serials: string[];
8
+ }
9
+ /** What a packing slip says, read from a pdf, a picture or a sheet. */
10
+ export interface PackingSlipContent {
11
+ /** Their packing-slip number. */
12
+ supplierRef?: string;
13
+ /** Our order number as they printed it ("Uw referentie IO2026-0013"). */
14
+ orderRef?: string;
15
+ /** `YYYY-MM-DD`. */
16
+ deliveredOn?: string;
17
+ lines: DocumentLine[];
18
+ }
19
+ /**
20
+ * One line per article: a sheet lists devices one per row (1 × toestel, serial S1), and a person
21
+ * reads that as 4 × toestel with four serials.
22
+ */
23
+ export declare function groupDocumentLines(lines: readonly DocumentLine[]): DocumentLine[];
24
+ /** The order a document is about: our number first, then their reference on it. `null` = none. */
25
+ export declare function orderForDocument<T extends Pick<PurchaseOrder, "number" | "reference">>(doc: Pick<PackingSlipContent, "orderRef" | "supplierRef">, orders: readonly T[]): T | null;
26
+ export type MatchedVia = "reference" | "sku" | "description";
27
+ /** Something a document line can be matched to: an order line, a contract line. */
28
+ export interface MatchCandidate {
29
+ id: string;
30
+ productId?: string;
31
+ offerId?: string;
32
+ name: string;
33
+ /** Their subscription id, when the candidate knows it (a contract line). */
34
+ externalRef?: string;
35
+ }
36
+ export interface LineMatch {
37
+ docIndex: number;
38
+ poLineId: string;
39
+ via: MatchedVia;
40
+ }
41
+ /**
42
+ * How well a document line is this candidate, or not at all: their subscription id, then their
43
+ * article code (on the offer it was bought on, or any offer of that product), then the words.
44
+ * One ranking for packing slips and invoices alike.
45
+ */
46
+ export declare function lineAffinity(doc: {
47
+ sku?: string;
48
+ description?: string;
49
+ externalRef?: string;
50
+ }, candidate: MatchCandidate, offers: readonly Pick<SupplierOffer, "id" | "productId" | "vendorSku">[]): {
51
+ via: MatchedVia;
52
+ rank: number;
53
+ } | null;
54
+ /**
55
+ * Which order line each document line is, by {@link lineAffinity}; each order line is taken once,
56
+ * the best free one wins. What stays unmatched is an exception for a person.
57
+ */
58
+ export declare function matchDocumentLines(docLines: readonly DocumentLine[], poLines: readonly Pick<PurchaseLine, "id" | "kind" | "productId" | "offerId" | "name">[], offers: readonly Pick<SupplierOffer, "id" | "productId" | "vendorSku">[]): {
59
+ matches: LineMatch[];
60
+ unmatched: number[];
61
+ };
62
+ export type SlipProblemCode = "slip_no_order" | "slip_unknown_item" | "receipt_over_ordered" | "receipt_serials_count";
63
+ export interface SlipProblem {
64
+ code: SlipProblemCode;
65
+ docIndex?: number;
66
+ poLineId?: string;
67
+ }
68
+ export interface SlipProposal {
69
+ lines: (GoodsReceiptLine & {
70
+ docIndex: number;
71
+ via: MatchedVia;
72
+ })[];
73
+ problems: SlipProblem[];
74
+ }
75
+ /**
76
+ * A packing slip as a receipt to book: the matched lines with their counts and serials, and what a
77
+ * person has to decide — no order, an article not on it, more than is open, serials that do not
78
+ * match the count. Recomputed against the receipts booked so far, never stored stale.
79
+ */
80
+ export declare function slipProposal(slip: PackingSlipContent, order: Pick<PurchaseOrder, "lines"> | null, offers: readonly Pick<SupplierOffer, "id" | "productId" | "vendorSku">[], booked: readonly GoodsReceipt[]): SlipProposal;
81
+ /** A model's answer as slip content, as far as it holds; `null` when it is no JSON at all. */
82
+ export declare function parsePackingSlip(answer: string): PackingSlipContent | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};