@opencxh/domain 1.302.0 → 1.303.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 (63) 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 +54 -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 +11 -11
  15. package/dist/entities/catalog/types.d.ts +15 -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/customers.d.ts +63 -0
  21. package/dist/entities/fulfilment/execute.d.ts +67 -0
  22. package/dist/entities/fulfilment/fulfilment.test.d.ts +1 -0
  23. package/dist/entities/fulfilment/goods.d.ts +72 -0
  24. package/dist/entities/fulfilment/index.d.ts +8 -0
  25. package/dist/entities/fulfilment/path.d.ts +8 -0
  26. package/dist/entities/fulfilment/path.test.d.ts +1 -0
  27. package/dist/entities/fulfilment/rules.d.ts +124 -0
  28. package/dist/entities/fulfilment/target.d.ts +131 -0
  29. package/dist/entities/fulfilment/types.d.ts +321 -0
  30. package/dist/entities/ledger/entry.d.ts +47 -9
  31. package/dist/entities/ledger/types.d.ts +33 -35
  32. package/dist/entities/ledger/xaf.d.ts +3 -0
  33. package/dist/entities/money/tax.d.ts +6 -0
  34. package/dist/entities/money/totals.d.ts +5 -5
  35. package/dist/entities/purchase/choose.d.ts +57 -0
  36. package/dist/entities/purchase/choose.test.d.ts +1 -0
  37. package/dist/entities/purchase/contract.d.ts +44 -25
  38. package/dist/entities/purchase/demand.d.ts +22 -40
  39. package/dist/entities/purchase/feed.d.ts +53 -5
  40. package/dist/entities/purchase/index.d.ts +1 -0
  41. package/dist/entities/purchase/kinds.d.ts +2 -1
  42. package/dist/entities/purchase/logic.d.ts +32 -7
  43. package/dist/entities/purchase/match.d.ts +48 -6
  44. package/dist/entities/purchase/packing-slip.d.ts +23 -0
  45. package/dist/entities/purchase/types.d.ts +71 -38
  46. package/dist/entities/reports/vat.d.ts +20 -5
  47. package/dist/entities/transaction/booking.d.ts +4 -4
  48. package/dist/entities/transaction/convert.d.ts +10 -10
  49. package/dist/entities/transaction/document.d.ts +8 -3
  50. package/dist/entities/transaction/dunning.d.ts +12 -1
  51. package/dist/entities/transaction/facets.test.d.ts +1 -0
  52. package/dist/entities/transaction/keys.d.ts +3 -0
  53. package/dist/entities/transaction/kinds.d.ts +11 -10
  54. package/dist/entities/transaction/receivable.d.ts +5 -2
  55. package/dist/entities/transaction/settlement.d.ts +7 -7
  56. package/dist/entities/transaction/types.d.ts +123 -68
  57. package/dist/index.cjs +25 -25
  58. package/dist/index.d.ts +2 -1
  59. package/dist/index.js +5160 -4026
  60. package/dist/platform/bookkeeping.d.ts +8 -8
  61. package/dist/platform/provisioning.d.ts +210 -13
  62. package/package.json +1 -1
  63. /package/dist/entities/{purchase/demand.test.d.ts → agreement/invoice-head.test.d.ts} +0 -0
@@ -30,6 +30,8 @@ export interface TaxRate {
30
30
  * line from this, and the bookkeeping connector maps it onto its own tax code.
31
31
  */
32
32
  export type TaxRateKind = "standard" | "reduced" | "zero" | "reverse_charge" | "exempt";
33
+ /** The rate a line falls back to when neither the article nor the caller names one. */
34
+ export declare const DEFAULT_TAX_RATE_KEY = "high";
33
35
  /** The Dutch defaults an organisation starts with. It may edit them; it may not compute them. */
34
36
  export declare const NL_TAX_RATES: readonly TaxRate[];
35
37
  /**
@@ -67,11 +69,15 @@ export interface TaxCode {
67
69
  selfAssessed?: boolean;
68
70
  /** Counts in the EC sales list (ICP). */
69
71
  icp?: boolean;
72
+ /** Which column of the EC sales list: absent on an ICP code = services. */
73
+ icpKind?: IcpKind;
70
74
  /** Deductible share of input VAT, mixed use. Absent = 100. */
71
75
  deductiblePercent?: number;
72
76
  archived?: boolean;
73
77
  order?: number;
74
78
  }
79
+ /** The EC sales list reports goods, services and triangular (ABC) supplies apart. */
80
+ export type IcpKind = "goods" | "services" | "triangular";
75
81
  export interface TaxCodeRate {
76
82
  /** `"2019-01-01"`. Days, compared as strings. */
77
83
  validFrom: string;
@@ -1,4 +1,4 @@
1
- import { TransactionLine, TransactionTotals } from '../transaction/types';
1
+ import { SalesLine, TransactionTotals } from '../transaction/types';
2
2
  import { Cents } from './money';
3
3
  import { TaxRate } from './tax';
4
4
  /**
@@ -19,10 +19,10 @@ import { TaxRate } from './tax';
19
19
  * of the line total — `priceOver`, so the line total is rounded once and then split by calendar.
20
20
  * Without a service period (a quote) it is one period's amount.
21
21
  */
22
- export declare function lineNet(line: Pick<TransactionLine, "kind" | "quantity" | "unitPriceCents" | "discountPercent" | "pricePer" | "servicePeriod">): Cents;
22
+ export declare function lineNet(line: Pick<SalesLine, "kind" | "quantity" | "unitPriceCents" | "discountPercent" | "pricePer" | "servicePeriod">): Cents;
23
23
  export interface PricedTransaction {
24
24
  /** The same lines with `netCents` and `taxCents` filled in. */
25
- lines: TransactionLine[];
25
+ lines: SalesLine[];
26
26
  totals: TransactionTotals;
27
27
  }
28
28
  /**
@@ -36,6 +36,6 @@ export interface PricedTransaction {
36
36
  * rate, because that is a property of the sale and not of the product.
37
37
  *
38
38
  * An **optional** line is priced like any other and then left out of every sum — see
39
- * `TransactionLine.optional`.
39
+ * `SalesLine.optional`.
40
40
  */
41
- export declare function transactionTotals(lines: readonly TransactionLine[], rates: readonly TaxRate[], overrideKey?: string): PricedTransaction;
41
+ export declare function transactionTotals(lines: readonly SalesLine[], rates: readonly TaxRate[], overrideKey?: string): PricedTransaction;
@@ -0,0 +1,57 @@
1
+ import { DeliverTo, SupplierOffer } from './types';
2
+ /** A hand-set stand ("called, they have 5") counts for a day (plan 045 B.6). */
3
+ export declare const MANUAL_STOCK_FRESH_MS: number;
4
+ /** What one need asks of a source. */
5
+ export interface OfferNeed {
6
+ /** Base units. */
7
+ quantity: number;
8
+ /** The sale's commitment (`Commitment.termMonths`); absent = cancel any time. */
9
+ termMonths?: number;
10
+ /** `own` through our warehouse, anything else straight to the customer. */
11
+ deliverTo?: DeliverTo["kind"];
12
+ /** The delivery check the sale was made on: only what it found available qualifies (6c). */
13
+ qualified?: readonly {
14
+ app: string;
15
+ offer: string;
16
+ }[];
17
+ /** The administration's currency; prices in another one need `rates`. */
18
+ currency: string;
19
+ /** Units of `currency` per one unit of the key (ECB, of the day). */
20
+ rates?: Readonly<Record<string, number>>;
21
+ /** A person's pick: it stands, even when it no longer fits — the reason still shows. */
22
+ pinned?: string;
23
+ }
24
+ /** Known enough, unknown (no feed, stale, or a count the supplier does not give), known short. */
25
+ export type OfferStock = "enough" | "unknown" | "short";
26
+ export type Unsuitable = "invalid" | "term" | "dropship" | "qualification";
27
+ export interface RankedOffer<T extends SupplierOffer = SupplierOffer> {
28
+ offer: T;
29
+ stock: OfferStock;
30
+ /** What the supplier said, when fresh. */
31
+ qty?: number;
32
+ /** Per product unit over a year, in the administration's currency; `null` = no price to compare. */
33
+ unitCents: number | null;
34
+ leadDays?: number;
35
+ unsuitable?: Unsuitable;
36
+ }
37
+ /**
38
+ * Why the chosen one: the preferred offer (`preferred`), the preferred one was short
39
+ * (`preferred_short`, chosen is the next best), only one qualified (`only`), the best of equals
40
+ * (`best`), nobody has enough (`short`: chosen has the most, the rest is a person's split), or a
41
+ * person picked it (`picked`).
42
+ */
43
+ export type ChoiceWhy = "preferred" | "preferred_short" | "only" | "best" | "short" | "picked";
44
+ export interface OfferChoice<T extends SupplierOffer = SupplierOffer> {
45
+ chosen: RankedOffer<T> | null;
46
+ why?: ChoiceWhy;
47
+ /** Every other offer, best first; the unsuitable last with their reason. */
48
+ alternatives: RankedOffer<T>[];
49
+ /** What `unitCents` is in: the administration's currency. */
50
+ currency: string;
51
+ }
52
+ /**
53
+ * The best source for one need (plan 045 B.2): suitability first, then stock, then price. Policy
54
+ * (Vincent 03-10): the preferred offer (`isDefault`) wins unless it is known short; the cheaper one
55
+ * is a hint in the alternatives. Nobody enough: the one with the most, never a split.
56
+ */
57
+ export declare function chooseOffer<T extends SupplierOffer>(offers: readonly T[], need: OfferNeed, now: number): OfferChoice<T>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { Period } from '../agreement/period';
2
- import { Agreement, AgreementLine } from '../agreement/types';
2
+ import { Agreement, AgreementLine, LinePrice } from '../agreement/types';
3
3
  import { Cents } from '../money/money';
4
4
  /** A sales line, followed through its changes: the agreement and the series it belongs to. */
5
5
  export interface SalesLineRef {
@@ -7,24 +7,28 @@ export interface SalesLineRef {
7
7
  seriesId: string;
8
8
  }
9
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.
10
+ * A subscription at a supplier, one row in `purchase_contract_line` (review R5): its `price` is the
11
+ * snapshot taken when it was made — an offer that gets dearer later changes nothing here; a new
12
+ * price is a successor row in the same series from a date. What was invoiced is `purchase_match`.
13
13
  */
14
- export interface PurchaseAgreementLine extends AgreementLine {
14
+ export interface SupplierContractLine extends Pick<AgreementLine, "id" | "productId" | "name" | "unitKey" | "factor" | "commitment" | "quantity" | "endsAt"> {
15
+ /** The supplier's price, a snapshot taken when the row was made. */
16
+ price?: LinePrice;
17
+ contractId: string;
18
+ /** One subscription through its changes; the first row's own id. */
19
+ seriesId: string;
20
+ startsAt: number;
15
21
  /** Provenance only: the offer it was bought on. The price is `price`, not the offer's. */
16
22
  offerId?: string;
17
- /** Their subscription id (Pax8, Microsoft). */
23
+ /** Their subscription id (Pax8, Microsoft): what a supplier invoice line is matched on. */
18
24
  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
- };
25
+ /**
26
+ * The fulfilment item this pays for (047 step 6): its sale, what it runs on and what the supplier
27
+ * handed back live there. A line without one is purchase's own (rent, a contract by hand).
28
+ */
29
+ itemId?: string;
26
30
  customerCompanyId?: string;
27
- /** The sales line that passes this on, followed by series. */
31
+ /** A line by hand: the sales line that passes it on. A line with `itemId` reads the item's. */
28
32
  salesLine?: SalesLineRef;
29
33
  /**
30
34
  * `variable`: no price, the invoice decides the amount (transit minutes). Nothing is expected;
@@ -34,18 +38,24 @@ export interface PurchaseAgreementLine extends AgreementLine {
34
38
  expected?: "variable";
35
39
  /** Days from each term's start in which the supplier still takes a change (NCE: 7). */
36
40
  changeWindowDays?: number;
37
- /** The order line it came from, so a second receipt updates instead of adding. */
41
+ /** `lineKey` of the order line it came from; a successor shares it, so it is not unique. */
38
42
  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
43
  }
45
- /** A supplier contract: `companyId` is the supplier; one contract holds many subscriptions. */
46
- export interface PurchaseAgreement extends Omit<Agreement, "lines"> {
47
- lines: PurchaseAgreementLine[];
44
+ /**
45
+ * A supplier contract's header, one per supplier × administration (044): no sales fields, no
46
+ * lines — those are rows of their own. `companyId` is the supplier.
47
+ */
48
+ export interface SupplierContract extends Pick<Agreement, "id" | "organizationId" | "businessEntityId" | "title" | "companyId" | "currency" | "status" | "startsAt" | "endsAt" | "commitment" | "createdBy" | "createdAt" | "updatedAt"> {
48
49
  }
50
+ /** A header with its rows, read together for a computation over the whole contract. */
51
+ export type SupplierContractWithLines = SupplierContract & {
52
+ lines: readonly SupplierContractLine[];
53
+ };
54
+ /** What supplier invoices said per period, per line id: derived from `purchase_match`. */
55
+ export type InvoicedHistory = ReadonlyMap<string, readonly {
56
+ from: number;
57
+ cents: Cents;
58
+ }[]>;
49
59
  export interface ExpectedLine {
50
60
  lineId: string;
51
61
  kind: "fixed" | "count" | "variable";
@@ -57,7 +67,9 @@ export interface ExpectedLine {
57
67
  * the days it ran (the same `lineNet` the sales run bills with), or "count" / "variable" where the
58
68
  * amount is not ours to know in advance.
59
69
  */
60
- export declare function expectedFor(agreement: Pick<PurchaseAgreement, "startsAt" | "endsAt" | "lines">, window: Period): ExpectedLine[];
70
+ export declare function expectedFor(agreement: Pick<SupplierContract, "startsAt" | "endsAt"> & {
71
+ lines: readonly SupplierContractLine[];
72
+ }, window: Period): ExpectedLine[];
61
73
  /**
62
74
  * A variable amount that moved more than `threshold` against the average of the (up to) three
63
75
  * periods before: that is work for a person, not a number to wave through.
@@ -73,6 +85,11 @@ export interface SalesLineState extends SalesLineRef {
73
85
  unbilled: Period[];
74
86
  /** The sales line has ended or its agreement is not active. */
75
87
  ended?: boolean;
88
+ /** The last period invoiced and how many it billed: "gefactureerd" on Producten bij klanten. */
89
+ billed?: {
90
+ quantity: number;
91
+ period: Period;
92
+ };
76
93
  }
77
94
  export type ContractSignal = {
78
95
  kind: "not_covered";
@@ -111,6 +128,8 @@ export interface SignalOptions {
111
128
  noticeDays?: number;
112
129
  /** A change window closing this soon is a reminder. */
113
130
  windowDays?: number;
131
+ /** What was invoiced per line, for the variable jump. */
132
+ invoiced?: InvoicedHistory;
114
133
  }
115
134
  /**
116
135
  * Everything about a supplier contract a person should look at, apart:
@@ -121,4 +140,4 @@ export interface SignalOptions {
121
140
  * - **notice due / change window**: reminders before the dates that decide the next term;
122
141
  * - **variable jump**: a variable amount moved more than 30%.
123
142
  */
124
- export declare function contractSignals(contracts: readonly PurchaseAgreement[], sales: readonly SalesLineState[], now: number, options?: SignalOptions): ContractSignal[];
143
+ export declare function contractSignals(contracts: readonly SupplierContractWithLines[], sales: readonly SalesLineState[], now: number, options?: SignalOptions): ContractSignal[];
@@ -1,7 +1,5 @@
1
- import { ProvisionableOffer, ProvisioningAction } from '../../platform/provisioning';
2
- import { Agreement } from '../agreement/types';
3
- import { PurchaseAgreement } from './contract';
4
- import { SupplierOffer } from './types';
1
+ import { ProvisioningAction } from '../../platform/provisioning';
2
+ import { Agreement, AgreementLine } from '../agreement/types';
5
3
  /** One thing to order, change or end, as "Te bestellen" lists it. */
6
4
  export interface DemandItem {
7
5
  /** Stable: the sales series, or the customer's account at the supplier. */
@@ -18,14 +16,6 @@ export interface DemandItem {
18
16
  targetQuantity?: number;
19
17
  from?: number;
20
18
  customerCompanyId: string;
21
- salesLine?: {
22
- agreementId: string;
23
- seriesId: string;
24
- };
25
- contractLine?: {
26
- contractId: string;
27
- lineId: string;
28
- };
29
19
  /** Epoch ms it takes effect: the term's end for an end or a reduction outside the window. */
30
20
  effectiveAt?: number;
31
21
  /** The last day to send it for `effectiveAt`; past it, `effectiveAt` is the next term's end. */
@@ -36,33 +26,25 @@ export interface DemandItem {
36
26
  nextTerm?: boolean;
37
27
  /** The purchase runs on after the sale ends: we pay until then. */
38
28
  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;
29
+ /**
30
+ * An end at a provider that prices it (045 D N-2): what ending costs, shown before "Maak
31
+ * concept" and put on the order as it was shown. `costUnknown`: the provider gave no answer.
32
+ */
33
+ cost?: {
34
+ parts: {
35
+ label: string;
36
+ cents: number;
37
+ recurring?: "month";
38
+ }[];
39
+ warnings?: string[];
45
40
  };
41
+ costUnknown?: boolean;
42
+ /** The request's answers (the site as a crm address id): what a per-order price is asked on. */
43
+ params?: Record<string, string>;
44
+ /** The request this waits for: the customer's account before its numbers. */
45
+ waitsFor?: string;
46
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[];
47
+ export declare const seriesOf: (line: AgreementLine) => string;
48
+ /** The line of a series that runs on `at`, else the one that starts next, else its last. */
49
+ export declare function currentOf<T extends AgreementLine>(parent: Pick<Agreement, "startsAt" | "endsAt">, lines: readonly T[], at: number): T;
50
+ export declare function bySeries<T extends AgreementLine>(lines: readonly T[]): Map<string, T[]>;
@@ -6,11 +6,12 @@ import { OfferAvailability, SupplierOffer } from './types';
6
6
  * turned into a product or an offer by itself.
7
7
  */
8
8
  /** The columns a feed can fill. `vendorSku` or `gtin` is the key; the rest is optional. */
9
- export type FeedColumn = "vendorSku" | "gtin" | "description" | "brand" | "group" | "cost" | "list" | "currency" | "stock" | "leadDays";
9
+ export type FeedColumn = "vendorSku" | "gtin" | "description" | "brand" | "group" | "cost" | "list" | "currency" | "stock" | "leadDays" | "lotSize" | "dropship";
10
10
  export declare const FEED_COLUMNS: readonly FeedColumn[];
11
11
  /** Per column, the header it is read from. Set once (the AI proposes, a person approves). */
12
12
  export type FeedMapping = Partial<Record<FeedColumn, string>>;
13
- export type FeedSource = "upload" | "mail" | "storage";
13
+ /** `api`: the assortment from a provisioning app (plan 045 D N-1) instead of a file. */
14
+ export type FeedSource = "upload" | "mail" | "storage" | "api";
14
15
  /** How often a new file is expected; it decides how long a stock figure counts as fresh. */
15
16
  export type FeedEvery = "day" | "week" | "month";
16
17
  export declare const FEED_EVERY_MS: Record<FeedEvery, number>;
@@ -23,6 +24,11 @@ export interface SupplierFeed {
23
24
  supplierCompanyId: string;
24
25
  name?: string;
25
26
  source: FeedSource;
27
+ /** For `api`: the app that lists its assortment, and which of its connections. */
28
+ api?: {
29
+ app: string;
30
+ accountId: string;
31
+ };
26
32
  /** For `storage`: the folder it reads the newest csv from (a dated file each time is fine). */
27
33
  storage?: {
28
34
  mountId: string;
@@ -61,6 +67,10 @@ export interface FeedRow {
61
67
  /** `null`: the file says nothing about stock, or "on request". */
62
68
  stock?: number | null;
63
69
  leadDays?: number;
70
+ lotSize?: number;
71
+ dropship?: SupplierOffer["dropship"];
72
+ /** From an `api` feed: the provider's offer key a new offer is ordered through. */
73
+ offer?: string;
64
74
  }
65
75
  export interface FeedReadResult {
66
76
  rows: FeedRow[];
@@ -96,8 +106,9 @@ export type FeedMatch = {
96
106
  export declare function matchFeed(rows: readonly FeedRow[], offers: readonly SupplierOffer[], products: readonly FeedProduct[]): FeedMatch[];
97
107
  export interface OfferChange {
98
108
  offerId: string;
99
- set: Partial<Pick<SupplierOffer, "costCents" | "listCents" | "currency" | "vendorSku">> & {
100
- availability: OfferAvailability;
109
+ set: Partial<Pick<SupplierOffer, "costCents" | "listCents" | "currency" | "vendorSku" | "lotSize" | "dropship" | "validTo">> & {
110
+ /** Absent only when an `api` feed no longer lists it: then it ends (`validTo`). */
111
+ availability?: OfferAvailability;
101
112
  };
102
113
  /** A price moved more than the feed's threshold; applied, and said. */
103
114
  jump?: {
@@ -116,6 +127,8 @@ export interface FeedUnmatched {
116
127
  costCents?: Cents;
117
128
  /** An article we have, with no offer from this supplier yet: "make an offer" is one click. */
118
129
  productId?: string;
130
+ /** From an `api` feed: the provider's offer key (045 D N-1). */
131
+ offer?: string;
119
132
  }
120
133
  export interface FeedOutcome {
121
134
  changes: OfferChange[];
@@ -129,7 +142,7 @@ export interface FeedOutcome {
129
142
  * What the run writes. Every matched offer gets a fresh stock stamp — the freshness is the
130
143
  * point of a feed; price, advice price and currency only when the file has them.
131
144
  */
132
- export declare function feedOutcome(matches: readonly FeedMatch[], offers: readonly SupplierOffer[], now: number, jumpPercent?: number): FeedOutcome;
145
+ export declare function feedOutcome(matches: readonly FeedMatch[], offers: readonly SupplierOffer[], now: number, jumpPercent?: number, every?: FeedEvery): FeedOutcome;
133
146
  /**
134
147
  * A first mapping from the header alone: every exact word first, then a header that starts with
135
148
  * one — so "Productgroep" is the group before "product" claims it as the description.
@@ -168,3 +181,38 @@ export interface FeedImportStatus {
168
181
  startedAt: number;
169
182
  finishedAt?: number;
170
183
  }
184
+ /** A run as a screen reads it: the work left is a count, not the list of writes. */
185
+ export interface FeedRunView {
186
+ id: string;
187
+ organizationId: string;
188
+ feedId: string;
189
+ state: "applying" | "done" | "failed";
190
+ /** Why it failed: `feed_no_key`, `feed_missing_headers`. */
191
+ reason?: string;
192
+ missingHeaders?: string[];
193
+ rows: number;
194
+ errors: {
195
+ line: number;
196
+ message: string;
197
+ }[];
198
+ /** Offers updated; `unchanged` of them only had their freshness renewed. */
199
+ updated: number;
200
+ unchanged: number;
201
+ gone: number;
202
+ jumps: {
203
+ offerId: string;
204
+ fromCents: number;
205
+ toCents: number;
206
+ percent: number;
207
+ }[];
208
+ unmatched: FeedUnmatched[];
209
+ /** More lines than `unmatched` holds. */
210
+ unmatchedTotal: number;
211
+ applied: number;
212
+ pendingCount: number;
213
+ importJob?: FeedImportStatus;
214
+ startedAt: number;
215
+ finishedAt?: number;
216
+ createdBy: string;
217
+ createdAt?: number;
218
+ }
@@ -1,3 +1,4 @@
1
+ export * from './choose';
1
2
  export * from './contract';
2
3
  export * from './demand';
3
4
  export * from './feed';
@@ -1,8 +1,9 @@
1
1
  import { TransactionKind } from '../transaction/kinds';
2
+ import { PurchaseStatus } from './types';
2
3
  /**
3
4
  * What `apps/purchase` issues: the same engine as a quote, its own table. `sent` is issued (a
4
5
  * number and the paper); the supplier confirms, and the goods coming in make it `received`.
5
6
  * Walking back one step stays allowed — a return reopens a received order.
6
7
  */
7
- export declare const PURCHASE_KINDS: readonly TransactionKind[];
8
+ export declare const PURCHASE_KINDS: readonly TransactionKind<PurchaseStatus>[];
8
9
  export declare const PURCHASE_ORDER_KIND = "purchase_order";
@@ -1,21 +1,39 @@
1
+ import { ProductTracking } from '../catalog/types';
1
2
  import { PricePer } from '../money/period-price';
2
- import { GoodsReceipt, GoodsReceiptLine, PurchaseLine, SupplierOffer } from './types';
3
+ import { GoodsReceipt, GoodsReceiptLine, InvoiceState, PurchaseLine, SupplierOffer } from './types';
3
4
  /** What came in for one order line: booked receipts in, returns out. */
4
5
  export declare function receivedFor(poLineId: string, receipts: readonly GoodsReceipt[]): number;
5
6
  export type ReceiptState = "none" | "partial" | "full";
6
7
  /** Over the product lines of an order. */
7
- export declare function receiptState(lines: readonly Pick<PurchaseLine, "kind" | "quantity" | "receivedQty">[]): "none" | "partial" | "full";
8
+ export declare function receiptState(lines: readonly Pick<PurchaseLine, "kind" | "quantity" | "receivedQty">[]): "none" | "full" | "partial";
9
+ type Billable = Pick<PurchaseLine, "kind" | "quantity" | "billedQty" | "act" | "unitPriceCents">;
10
+ /** A supplier invoice is expected for this line: a buy, or a change that costs something. */
11
+ export declare const expectsInvoice: (line: Billable) => boolean;
12
+ /** `full` when every line that expects an invoice is billed; derived by whoever writes the lines. */
13
+ export declare function invoiceState(lines: readonly Billable[]): InvoiceState;
8
14
  export interface GoodsReceiptProblem {
9
- code: "receipt_over_ordered" | "receipt_return_over_received" | "receipt_serials_count";
15
+ code: "receipt_over_ordered" | "receipt_return_over_received" | "receipt_serials_count" | "receipt_serials_required" | "receipt_batches_count" | "receipt_batches_required";
10
16
  poLineId: string;
11
17
  }
12
18
  /**
13
19
  * 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 in base units. No serials is fine (bulk goods); some serials
15
- * must be all.
20
+ * numbers that do not match the count in base units. No serials is fine for bulk goods; some
21
+ * serials must be all. A serial article (046 `tracking`) comes in with every serial: without them
22
+ * a drop shipment can never be delivered (Exact v2 nr. 8). A batch article likewise with batches
23
+ * that add up, in base units.
24
+ */
25
+ export declare function goodsReceiptProblems(receipt: Pick<GoodsReceipt, "movement" | "lines">, poLines: readonly Pick<PurchaseLine, "id" | "quantity" | "factor" | "productId">[], booked: readonly GoodsReceipt[], tracking?: ReadonlyMap<string, ProductTracking>): GoodsReceiptProblem[];
26
+ /** `<productId>#<serial>` for every serial on these lines: a serial is unique per article. */
27
+ export declare function serialKeysOf(lines: readonly Pick<GoodsReceiptLine, "poLineId" | "serials">[], poLines: readonly Pick<PurchaseLine, "id" | "productId">[]): string[];
28
+ /**
29
+ * Which of `keys` are in house already: booked in more often than sent back, over every receipt
30
+ * (any order). A serial that went back may come in again: a swap after a repair.
31
+ */
32
+ export declare function serialsInHouse(keys: readonly string[], receipts: readonly Pick<GoodsReceipt, "movement" | "status" | "serialKeys">[]): string[];
33
+ /**
34
+ * Lines of a receipt worth keeping: a positive quantity, serials trimmed and unique, batches with
35
+ * a code and a positive quantity (base units).
16
36
  */
17
- export declare function goodsReceiptProblems(receipt: Pick<GoodsReceipt, "movement" | "lines">, poLines: readonly Pick<PurchaseLine, "id" | "quantity" | "factor">[], booked: readonly GoodsReceipt[]): GoodsReceiptProblem[];
18
- /** Lines of a receipt worth keeping: a positive quantity, serials trimmed and unique. */
19
37
  export declare function cleanReceiptLines(lines: readonly GoodsReceiptLine[]): GoodsReceiptLine[];
20
38
  /** Valid on `at`. */
21
39
  export declare function offerValid(offer: Pick<SupplierOffer, "validFrom" | "validTo">, at: number): boolean;
@@ -28,6 +46,12 @@ export declare function defaultOffer<T extends SupplierOffer>(offers: readonly T
28
46
  export declare function toProductUnits(source: {
29
47
  factor?: number;
30
48
  }, qty: number): number;
49
+ /**
50
+ * Base units to purchase units: a box of 10 for 12 pieces is 2 boxes. Raised to the offer's
51
+ * `minQty` (Exact v2 §5), then to a multiple of its `lotSize`; what is bought beyond the need goes
52
+ * to stock.
53
+ */
54
+ export declare function purchaseUnits(base: number, factor: number | undefined, offer?: Pick<SupplierOffer, "minQty" | "lotSize">): number;
31
55
  /**
32
56
  * The offer's cost per product unit, as amount, currency and period — what is written back to the
33
57
  * product. Never a monthly equivalent: € 222 per year stays € 222 per year.
@@ -39,3 +63,4 @@ export declare function productCost(offer: SupplierOffer): {
39
63
  };
40
64
  /** Offers side by side: each over one year, per product unit. A one-off stays itself. */
41
65
  export declare function costOverYear(offer: SupplierOffer): number;
66
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { Cents } from '../money/money';
2
2
  import { PricePer } from '../money/period-price';
3
+ import { LineDimensions } from '../transaction/types';
3
4
  import { MatchCandidate, MatchedVia } from './packing-slip';
4
5
  import { SupplierOffer } from './types';
5
6
  /** What an invoice line pays for: an order line, or a contract line over a period. */
@@ -41,10 +42,26 @@ export interface InvoiceCandidate extends MatchCandidate {
41
42
  discountPercent?: number;
42
43
  /** Order line: what is still to invoice. Contract line: the agreed count, `null` when counted. */
43
44
  openQty: number | null;
45
+ /**
46
+ * Goods that come to us: received minus invoiced. Absent when no receipt is expected (a service,
47
+ * a licence, a drop shipment): then the order is the measure, as before (Exact: 3-way match).
48
+ */
49
+ receivedOpen?: number;
44
50
  /** A contract line whose invoice decides the amount: nothing expected. */
45
51
  variable?: boolean;
46
52
  use?: "own" | "resale";
47
53
  customerCompanyId?: string;
54
+ /** The order's or contract's; absent = the invoice's own. */
55
+ currency?: string;
56
+ /** A contract row: the days it runs, `to` exclusive. A line is matched to the row of its period. */
57
+ term?: {
58
+ from: number;
59
+ to?: number;
60
+ };
61
+ /** The order line's, carried to the cost it books (plan 045 F 3b). */
62
+ dimensions?: LineDimensions;
63
+ /** The article's group in this administration: its posting rule picks the cost account. */
64
+ ledgerCategoryKey?: string;
48
65
  }
49
66
  export interface MatchAllocation {
50
67
  receiptLineId: string;
@@ -56,10 +73,33 @@ export interface MatchAllocation {
56
73
  expectedCents: Cents | null;
57
74
  /** More than the order line still had open, or another count than the contract's. */
58
75
  overQuantity: boolean;
76
+ /** More than has arrived of goods that come to us: invoiced before it was received. */
77
+ notReceived?: boolean;
59
78
  businessEntityId: string;
60
79
  productId?: string;
61
80
  use?: "own" | "resale";
62
81
  customerCompanyId?: string;
82
+ dimensions?: LineDimensions;
83
+ ledgerCategoryKey?: string;
84
+ /**
85
+ * The invoice in another currency than its target: one invoice unit in the target's. The
86
+ * amount stays as printed (it is what books); the verdict compares `amountCents × rate`.
87
+ */
88
+ rate?: number;
89
+ }
90
+ /** What the invoice is in, and its rate to the euro when it is not (Receipt `fxRate`). */
91
+ export interface InvoiceMoney {
92
+ currency: string;
93
+ /** Euro per one unit of `currency`, as the receipt carries it. */
94
+ fxRate?: number;
95
+ }
96
+ export interface AllocateOptions {
97
+ /** The month a line without a printed period is for (the invoice's). */
98
+ defaultPeriod: {
99
+ from: number;
100
+ to: number;
101
+ };
102
+ invoice?: InvoiceMoney;
63
103
  }
64
104
  /**
65
105
  * Invoice lines onto order and contract lines, many to many. Each line goes to its best candidates
@@ -67,12 +107,10 @@ export interface MatchAllocation {
67
107
  * same article (10 phones over two orders of 6 and 4), the amount split by count. A contract line
68
108
  * takes the line whole, for the period printed on it or else `defaultPeriod` (the invoice's month).
69
109
  */
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
- }): {
110
+ export declare function allocateInvoice(lines: readonly InvoiceLine[], candidates: readonly InvoiceCandidate[], offers: readonly Pick<SupplierOffer, "id" | "productId" | "vendorSku">[], { defaultPeriod, invoice }: AllocateOptions): {
74
111
  allocations: MatchAllocation[];
75
112
  unmatched: string[];
113
+ needsRate: string[];
76
114
  };
77
115
  export interface MatchTolerance {
78
116
  percent: number;
@@ -80,7 +118,7 @@ export interface MatchTolerance {
80
118
  }
81
119
  /** One per cent or one euro, whichever is more: a rounding difference is not work. */
82
120
  export declare const DEFAULT_TOLERANCE: MatchTolerance;
83
- export type MatchIssue = "price" | "quantity" | "unmatched";
121
+ export type MatchIssue = "price" | "quantity" | "not_received" | "unmatched" | "fx_rate_required";
84
122
  export interface MatchVerdict {
85
123
  /** Everything matched, at the expected price and count: approve without a person. */
86
124
  ok: boolean;
@@ -95,4 +133,8 @@ export interface MatchVerdict {
95
133
  * Whether an invoice is what was ordered or agreed: every line matched, every count within what
96
134
  * was open, every amount within the tolerance (% and absolute). Anything else is work.
97
135
  */
98
- export declare function matchVerdict(allocations: readonly MatchAllocation[], unmatched: readonly string[], tolerance?: MatchTolerance): MatchVerdict;
136
+ export declare function matchVerdict(allocations: readonly MatchAllocation[], unmatched: readonly string[], { tolerance, needsRate, }?: {
137
+ tolerance?: MatchTolerance;
138
+ /** Lines that would match in another currency: the receipt needs its rate first. */
139
+ needsRate?: readonly string[];
140
+ }): MatchVerdict;
@@ -72,6 +72,29 @@ export interface SlipProposal {
72
72
  })[];
73
73
  problems: SlipProblem[];
74
74
  }
75
+ /** A packing slip as a screen reads it: an upload or a mail attachment, read by the AI. */
76
+ export interface PackingSlipView {
77
+ id: string;
78
+ source: "upload" | "mail";
79
+ filename: string;
80
+ mimeType: string;
81
+ mail?: {
82
+ interactionId: string;
83
+ activityId: string;
84
+ attachmentId: string;
85
+ from?: string;
86
+ };
87
+ /** The original in the packing-slip folder. */
88
+ file?: {
89
+ mountId: string;
90
+ fileId: string;
91
+ };
92
+ status: "extracting" | "needs_browser" | "to_check" | "booked" | "dismissed";
93
+ content?: PackingSlipContent;
94
+ purchaseOrderId?: string;
95
+ extractError?: string;
96
+ createdAt?: number;
97
+ }
75
98
  /**
76
99
  * A packing slip as a receipt to book: the matched lines with their counts and serials, and what a
77
100
  * person has to decide — no order, an article not on it, more than is open, serials that do not