@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.
- package/dist/entities/agreement/from-quote.d.ts +18 -0
- package/dist/entities/agreement/from-quote.test.d.ts +1 -0
- package/dist/entities/agreement/index.d.ts +2 -0
- package/dist/entities/agreement/lifecycle.d.ts +44 -0
- package/dist/entities/agreement/lifecycle.test.d.ts +1 -0
- package/dist/entities/agreement/schedule.d.ts +4 -0
- package/dist/entities/agreement/types.d.ts +38 -0
- package/dist/entities/bank/match.d.ts +1 -1
- package/dist/entities/bookkeeping/receipt.d.ts +95 -2
- package/dist/entities/bookkeeping/types.d.ts +2 -0
- package/dist/entities/catalog/pricing.d.ts +5 -0
- package/dist/entities/catalog/types.d.ts +23 -1
- package/dist/entities/company/types.d.ts +6 -0
- package/dist/entities/financing/schedule.d.ts +40 -1
- package/dist/entities/financing/types.d.ts +99 -3
- package/dist/entities/ledger/entry.d.ts +12 -6
- package/dist/entities/ledger/template.d.ts +5 -0
- package/dist/entities/ledger/types.d.ts +9 -2
- package/dist/entities/money/index.d.ts +2 -0
- package/dist/entities/money/month-parts.d.ts +19 -0
- package/dist/entities/money/month-parts.test.d.ts +1 -0
- package/dist/entities/money/period-price.d.ts +23 -0
- package/dist/entities/money/period-price.test.d.ts +1 -0
- package/dist/entities/money/totals.d.ts +5 -1
- package/dist/entities/purchase/contract.d.ts +124 -0
- package/dist/entities/purchase/contract.test.d.ts +1 -0
- package/dist/entities/purchase/demand.d.ts +68 -0
- package/dist/entities/purchase/demand.test.d.ts +1 -0
- package/dist/entities/purchase/index.d.ts +7 -0
- package/dist/entities/purchase/kinds.d.ts +8 -0
- package/dist/entities/purchase/logic.d.ts +35 -0
- package/dist/entities/purchase/match.d.ts +98 -0
- package/dist/entities/purchase/match.test.d.ts +1 -0
- package/dist/entities/purchase/packing-slip.d.ts +82 -0
- package/dist/entities/purchase/packing-slip.test.d.ts +1 -0
- package/dist/entities/purchase/purchase.test.d.ts +1 -0
- package/dist/entities/purchase/types.d.ts +147 -0
- package/dist/entities/reports/reports.d.ts +9 -0
- package/dist/entities/transaction/document.d.ts +3 -0
- package/dist/entities/transaction/keys.d.ts +1 -7
- package/dist/entities/transaction/kinds.d.ts +7 -0
- package/dist/entities/transaction/types.d.ts +11 -1
- package/dist/entities/work/index.d.ts +1 -1
- package/dist/entities/work/types.d.ts +1 -1
- package/dist/index.cjs +23 -23
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4187 -3117
- package/dist/platform/bookkeeping.d.ts +29 -3
- package/dist/platform/manifest.d.ts +6 -27
- package/dist/platform/navigation.d.ts +202 -0
- package/dist/platform/navigation.test.d.ts +1 -0
- package/dist/platform/provider.d.ts +7 -0
- package/dist/platform/provisioning.d.ts +88 -0
- package/dist/platform/resource.d.ts +19 -0
- package/dist/platform/resource.test.d.ts +1 -0
- package/dist/platform/routing.d.ts +13 -18
- package/package.json +1 -1
- 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: "
|
|
165
|
+
direction: BookingDocument["direction"];
|
|
159
166
|
kind?: BookingDocument["kind"];
|
|
160
167
|
};
|
|
161
168
|
match: {
|
|
@@ -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,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 {};
|