@opencxh/domain 1.296.1 → 1.300.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.
@@ -63,11 +63,10 @@ export interface Commitment {
63
63
  }
64
64
  /** A price fixed on the line when it was agreed, instead of the ladder at billing time. */
65
65
  export interface LinePrice {
66
+ /** Per the line's `unitKey`. */
66
67
  cents: Cents;
67
68
  currency: string;
68
69
  per?: PricePer;
69
- /** The unit the price is for, when it is not the product's (a supplier's box of 10). */
70
- unit?: string;
71
70
  }
72
71
  export type AgreementInterval = "month" | "quarter" | "year";
73
72
  export interface AgreementLine {
@@ -75,6 +74,10 @@ export interface AgreementLine {
75
74
  productId: string;
76
75
  /** Copied from the product, like a transaction line: the agreement must stay readable. */
77
76
  name: string;
77
+ /** The unit it is counted in (a supplier's box of 10); absent = the article's base unit. */
78
+ unitKey?: string;
79
+ /** Base units in one `unitKey`, fixed when the line was made (plan 046 #14). Absent = 1. */
80
+ factor?: number;
78
81
  /**
79
82
  * One line through its changes: 10 seats ending on 12 May and 12 seats from then share it. A
80
83
  * billed line never changes; a change ends it and starts a successor in the same series.
@@ -161,6 +164,8 @@ export interface RunLine {
161
164
  name: string;
162
165
  productId: string;
163
166
  unitKey: string;
167
+ /** Base units in one `unitKey`, from the agreement line (a box of 10). Absent = 1. */
168
+ factor?: number;
164
169
  taxRateKey: string;
165
170
  quantity: number;
166
171
  unitPriceCents: Cents;
@@ -112,5 +112,6 @@ export interface WorkQueueItem {
112
112
  /**
113
113
  * The rule a line books under: product, then category, then VAT code, then the rule without a
114
114
  * match. `undefined` when nothing applies — the caller refuses rather than guessing an account.
115
+ * With a `direction`, only rules for that direction count: a sale rule never books a purchase.
115
116
  */
116
- export declare function ledgerRuleFor(line: Pick<BookingLine, "productId" | "ledgerCategoryKey" | "taxRateKey">, rules: readonly LedgerMappingRule[]): LedgerMappingRule | undefined;
117
+ export declare function ledgerRuleFor(line: Pick<BookingLine, "productId" | "ledgerCategoryKey" | "taxRateKey">, rules: readonly LedgerMappingRule[], direction?: "sale" | "purchase"): LedgerMappingRule | undefined;
@@ -0,0 +1,28 @@
1
+ import { Cents } from '../money/money';
2
+ import { PricePer } from '../money/period-price';
3
+ import { Product, ProductRelease, ReferenceCost } from './types';
4
+ /**
5
+ * `qty` of `unitKey` in the article's base unit: 3 rolls of 500 m are 1,500 m. Throws on a unit
6
+ * the article does not know — a guessed factor of 1 is how a box of ten gets billed as one.
7
+ */
8
+ export declare function toBaseUnits(product: Pick<Product, "unitKey" | "units">, unitKey: string, qty: number): number;
9
+ /** Base units in one `unitKey`; 1 for the base unit itself. */
10
+ export declare function unitFactor(product: Pick<Product, "unitKey" | "units">, unitKey: string): number;
11
+ /** What is wrong with a set of units, as an error code; `null` when nothing is. */
12
+ export declare function unitsProblem(product: Pick<Product, "unitKey" | "units">): string | null;
13
+ /** A parent with variants is a container, never on a line: its variants are what is traded. */
14
+ export declare function isTradeable(product: Pick<Product, "id">, all: readonly Pick<Product, "parentId">[]): boolean;
15
+ /**
16
+ * Why a variant does not fit its parent, as an error code; `null` when it does. Every variant
17
+ * carries exactly the parent's option keys, and no two share a combination — what a shop sync
18
+ * (Shopify's `selectedOptions`) requires.
19
+ */
20
+ export declare function variantOptionsProblem(parent: Pick<Product, "optionKeys">, variant: Pick<Product, "id" | "options">, siblings: readonly Pick<Product, "id" | "options">[]): string | null;
21
+ /**
22
+ * Whether a **new** commitment may name this article in this administration (a line added, a
23
+ * quote sent, an order confirmed, a PO sent) — as an error code, `null` when it may. Carrying out
24
+ * a commitment that already exists (an agreement run, receiving a sent PO) never asks this.
25
+ */
26
+ export declare function commitProblem(product: Pick<Product, "archived">, release: Pick<ProductRelease, "blocked"> | undefined): string | null;
27
+ /** The reference cost restated to another period, in its own currency (€ 240/year → € 20/month). */
28
+ export declare function referenceCostIn(cost: ReferenceCost, per: PricePer | undefined): Cents;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,4 @@
1
+ export * from './article';
1
2
  export * from './codes';
2
3
  export * from './pricing';
3
4
  export * from './rate-csv';
@@ -1,7 +1,9 @@
1
1
  import { Cents } from '../money/money';
2
2
  import { PricePer } from '../money/period-price';
3
3
  import { TransactionLine } from '../transaction/types';
4
- import { PriceRule, Product } from './types';
4
+ import { PriceRule, SellableProduct } from './types';
5
+ /** What the ladder reads of an article: its identity and sales' price row. */
6
+ export type PricedProduct = Pick<SellableProduct, "id" | "categoryKey" | "listPriceCents" | "currency" | "pricePer" | "rateCardId">;
5
7
  /**
6
8
  * The ladder: the most specific rule wins, whole. No sum, no stacking.
7
9
  *
@@ -24,21 +26,23 @@ export interface PriceContext {
24
26
  companyId?: string;
25
27
  segmentKey?: string;
26
28
  contractId?: string;
29
+ /** In the article's **base** unit: 2 boxes of 10 reach a tier "from 20" (plan 046 #10). */
27
30
  quantity: number;
28
31
  /** Epoch ms; the validity window is judged against this, not against "now". */
29
32
  at: number;
30
33
  }
31
34
  /** Amount, currency and period travel together: € 222 means nothing without "per year". */
32
35
  export interface ResolvedPrice {
33
- unitPriceCents: Cents;
36
+ /** Absent with `sourceKey: "missing"`: no list price and no rule. Never a silent € 0. */
37
+ unitPriceCents?: Cents;
34
38
  currency: string;
35
39
  /** Absent = once, or per unit. */
36
40
  per?: PricePer;
37
- /** Where this amount came from. Almost free, and the only question a salesperson asks. */
41
+ /** Where this amount came from. Almost free, and the only question a salesperson asks. `missing` = no price. */
38
42
  sourceKey: string;
39
43
  ruleId?: string;
40
44
  }
41
- export declare function resolvePrice(product: Product, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedPrice;
45
+ export declare function resolvePrice(product: PricedProduct, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedPrice;
42
46
  export interface ResolvedRateCard {
43
47
  rateCardId: string;
44
48
  sourceKey: string;
@@ -48,12 +52,14 @@ export interface ResolvedRateCard {
48
52
  * The rate card a `rated` product is billed on for this customer: the same ladder as a price,
49
53
  * over the rules whose outcome is a card, then the product's own card. `null` = it has none.
50
54
  */
51
- export declare function resolveRateCard(product: Product, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedRateCard | null;
55
+ export declare function resolveRateCard(product: PricedProduct, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedRateCard | null;
52
56
  /** The `priceSourceKey` of a price somebody typed themselves. The ladder never overwrites it. */
53
57
  export declare const MANUAL_PRICE_SOURCE = "manual";
58
+ /** Nothing priced it: sending or confirming refuses (`price_missing`) until a person types one. */
59
+ export declare const MISSING_PRICE_SOURCE = "missing";
54
60
  /**
55
61
  * The ladder again, for lines already on a document: after the quantity or the customer changed,
56
62
  * a tier or an agreement may now apply. A typed price stays, and so does a line whose product
57
63
  * is no longer in `products`.
58
64
  */
59
- export declare function repriceLines(lines: readonly TransactionLine[], products: readonly Product[], rules: readonly PriceRule[], ctx: Omit<PriceContext, "quantity">): TransactionLine[];
65
+ export declare function repriceLines(lines: readonly TransactionLine[], products: readonly PricedProduct[], rules: readonly PriceRule[], ctx: Omit<PriceContext, "quantity">): TransactionLine[];
@@ -120,3 +120,15 @@ export interface RatingResult {
120
120
  }
121
121
  /** A period's records, rated and grouped per rate. Cents are rounded once per group. */
122
122
  export declare function rateRecords(records: readonly UsageRecord[], rates: readonly Rate[], aggregation?: UsageAggregation): RatingResult;
123
+ /**
124
+ * A rated group as an invoice line: hours × the hourly price when a flat rate says so exactly,
125
+ * else 1 piece × the group's total (tiers, a markup, a setup fee, a price below a cent).
126
+ */
127
+ export declare function ratedLineAmount(group: RatedGroup, rate: Rate | undefined, display: {
128
+ unit: string;
129
+ per: number;
130
+ }): {
131
+ quantity: number;
132
+ unitKey: string;
133
+ unitPriceCents: Cents;
134
+ };
@@ -1,18 +1,21 @@
1
1
  import { Cents } from '../money/money';
2
2
  import { PricePer } from '../money/period-price';
3
3
  /**
4
- * What we sell, variants included.
4
+ * An article: what we sell, buy, stock or make, variants included (plan 046). Catalog's, shared by
5
+ * every administration; what differs per administration is its {@link ProductRelease}. Prices are
6
+ * sales' ({@link SalesProduct}), offers purchase's.
5
7
  *
6
8
  * One self-referencing table instead of a product/variant pair: a variant has exactly the same
7
- * fields as its parent and misses none of them. What it adds is {@link Product.options} — and that
8
- * is a column, not an entity.
9
+ * fields as its parent and misses none of them. What it adds is {@link Product.options}.
9
10
  */
10
11
  export interface Product {
11
12
  id: string;
12
13
  organizationId: string;
13
- /** The parent this is a variant of. Absent = a product in its own right. */
14
+ /** The parent this is a variant of. Absent = a product in its own right. A parent is never traded. */
14
15
  parentId?: string;
15
- /** What makes this variant different: `[{ key: "colour", value: "black" }]`. A label, nothing more. */
16
+ /** On a parent: the option keys every variant carries, in this order (`["colour", "size"]`). */
17
+ optionKeys?: string[];
18
+ /** What makes this variant different: `[{ key: "colour", value: "black" }]`. */
16
19
  options?: {
17
20
  key: string;
18
21
  value: string;
@@ -23,28 +26,115 @@ export interface Product {
23
26
  sku?: string;
24
27
  /** The global code: EAN-13, UPC-12, GTIN-14. Stored normalised — see `normalizeGtin`. */
25
28
  gtin?: string;
26
- /** Whatever else is scannable (outer case, our own Code128), as scanned. */
29
+ /** Whatever else is scannable without a quantity of its own, as scanned. */
27
30
  barcodes?: string[];
28
- /**
29
- * The category this falls under — a key from a small table of its own, not free text.
30
- *
31
- * Having to be stable is the whole requirement: together with {@link Product.taxRateKey} this is
32
- * the key the bookkeeping bridge picks a ledger account on. A free text field where one person
33
- * types "Services" and another "services " cannot carry that.
34
- */
31
+ /** References to this product elsewhere (`shopify:<shop>:<gid>`). A reference, never a copy. */
32
+ externalIds?: string[];
33
+ /** A {@link ProductCategory} key: groups the offering, and price agreements name it. */
35
34
  categoryKey?: string;
35
+ /** Is anything delivered? Fulfilment reads it; a service is done, goods are shipped. */
36
+ kind: ProductKind;
37
+ /** Serial or batch numbers on receipt and delivery. Absent = none; goods only. */
38
+ tracking?: ProductTracking;
39
+ /** The base unit everything counts in: stock, deliveries, allocations. Locked once used. */
40
+ unitKey: string;
41
+ /** Other units it comes in: a roll of 500 m, a box of 10. See {@link toBaseUnits}. */
42
+ units?: ProductUnit[];
43
+ /** On quotes, invoices and agreements. Absent = yes; steel plate you only buy is `false`. */
44
+ sellable?: boolean;
45
+ /** On purchase orders. Absent = no. */
46
+ purchasable?: boolean;
47
+ /** When it was first put on a document: locks {@link Product.unitKey} and forbids a delete. */
48
+ usedAt?: number;
49
+ archived?: boolean;
50
+ order?: number;
51
+ createdBy: string;
52
+ createdAt?: number;
53
+ updatedAt?: number;
54
+ }
55
+ export type ProductKind = "goods" | "service";
56
+ export type ProductTracking = "none" | "serial" | "batch";
57
+ /** Another unit an article comes in. `factor` = base units in one: a roll of 500 m is 500. */
58
+ export interface ProductUnit {
59
+ key: string;
60
+ /** A pack of this article's own ("tray van 24"); absent = the fixed unit's name for `key`. */
61
+ label?: string;
62
+ factor: number;
63
+ /** One GTIN per packaging level (GS1): scanning the box counts ten at once. */
64
+ gtin?: string;
65
+ }
66
+ /**
67
+ * An article in use in one administration. No release = not offered there. A variant has its own
68
+ * (its own cost); a parent with variants has none.
69
+ */
70
+ export interface ProductRelease {
71
+ id: string;
72
+ organizationId: string;
73
+ productId: string;
74
+ businessEntityId: string;
75
+ /** Key into that administration's VAT codes, never the percentage itself. */
76
+ taxRateKey: string;
77
+ /** A {@link ProductLedgerCategory} key: which revenue or cost account a line books to. */
78
+ ledgerCategoryKey?: string;
36
79
  /**
37
- * Which revenue account a line books to, copied onto the line. Not `categoryKey`: that one
38
- * groups the offering for price agreements, and "discount on services" names no account.
80
+ * What we normally pay for this. **Not** a cost price out of an inventory valuation. Shielded by
81
+ * `catalog.product.cost`, and never in a template context.
39
82
  */
40
- ledgerCategoryKey?: string;
41
- /** References to this product elsewhere (`moneybird:412`). A reference, never a copy. */
42
- externalIds?: string[];
83
+ referenceCost?: ReferenceCost;
84
+ /** Stopped in this administration only: no new commitments, running ones carry on. */
85
+ blocked?: boolean;
86
+ createdBy: string;
87
+ createdAt?: number;
88
+ updatedAt?: number;
89
+ }
90
+ /** A cost with its own currency, period and date: $ 240 per year, as of 28-09. */
91
+ export interface ReferenceCost {
92
+ cents: Cents;
93
+ currency: string;
94
+ per?: PricePer;
95
+ asOf: number;
96
+ /** The purchase offer it comes from; absent = typed by hand. Read-only in the form then. */
97
+ offerId?: string;
98
+ supplierCompanyId?: string;
99
+ }
100
+ /** A small table of its own, never free text: price agreements and reports name it. */
101
+ export interface ProductCategory {
102
+ id: string;
103
+ organizationId: string;
104
+ key: string;
105
+ name: string;
106
+ order?: number;
107
+ archived?: boolean;
108
+ }
109
+ /**
110
+ * Which account a line books to, in either direction: "hardware" is revenue 8000 on a sale and
111
+ * cost 7000 on a purchase, through finance's mapping. Catalog's list; finance reads it as the
112
+ * contract's `LedgerCategory { key, label }` and maps it.
113
+ */
114
+ export interface ProductLedgerCategory {
115
+ id: string;
116
+ organizationId: string;
117
+ key: string;
118
+ name: string;
119
+ order?: number;
120
+ archived?: boolean;
121
+ }
122
+ /**
123
+ * Sales' own row per article and administration: the price and how it bills. Absent row, or no
124
+ * `listPriceCents` = no price — which is not € 0 (plan 046 #12).
125
+ */
126
+ export interface SalesProduct {
127
+ id: string;
128
+ organizationId: string;
129
+ productId: string;
130
+ businessEntityId: string;
131
+ listPriceCents?: Cents;
132
+ currency: string;
43
133
  /**
44
- * Hours booked on this `WorkType.key` are invoiced as this product. Here and not on the work
45
- * type: time must not know sales, and several products may share one type (junior/senior).
134
+ * The period the list price is for: € 222 per year. Absent = once, or per unit. A recurring
135
+ * agreement line needs one; the run bills its share of each window.
46
136
  */
47
- workTypeKey?: string;
137
+ pricePer?: PricePer;
48
138
  /**
49
139
  * `unit`: a price per unit through the ladder (list price, tiers, customer agreements).
50
140
  * `rated`: every record priced on a rate card by its dimensions (calls, traffic).
@@ -52,61 +142,34 @@ export interface Product {
52
142
  pricing?: ProductPricing;
53
143
  /** The default card of a `rated` product. A price agreement may name another. */
54
144
  rateCardId?: string;
55
- /** See {@link FIXED_UNITS}. Drives the form and the word after the quantity. */
56
- unitKey: string;
57
- /** Key into the organisation's VAT table, never the percentage itself. */
58
- taxRateKey: string;
59
- listPriceCents: Cents;
60
- currency: string;
61
- /**
62
- * The period the list and cost price are for: € 222 per year. Absent = once, or per unit.
63
- * A recurring agreement line needs one; the run bills its share of each window.
64
- */
65
- pricePer?: PricePer;
66
- /**
67
- * What we normally pay for this. **Not** a cost price out of an inventory valuation.
68
- *
69
- * Copied onto the line when it is added, and shielded: margin is not for everyone holding
70
- * `sales.product.read`. It never reaches a template context either — `{line.cost}` on a quote
71
- * would be one typo away.
72
- */
73
- costPriceCents?: Cents;
74
- /** The period the cost price is for, when it differs from {@link Product.pricePer}. */
75
- costPricePer?: PricePer;
76
- /** The purchase offer the cost comes from; absent = typed by hand. Read-only in the form then. */
77
- costOfferId?: string;
78
- /** Who we buy it from. A `Company` in crm, so free — and all a later purchase order needs. */
79
- supplierCompanyId?: string;
80
- /**
81
- * The administration this belongs to. Unset = the default one — see `belongsToEntity`.
82
- *
83
- * Exactly one, not a list: two administrations that both sell this keep two rows, with their
84
- * own sku, their own price and their own ledger category. That is the price of a hard split and
85
- * it is what was chosen.
86
- */
87
- businessEntityId?: string;
88
- /** On quotes, invoices and agreements. Absent = yes; steel plate you only buy is `false`. */
89
- sellable?: boolean;
90
- /** On purchase orders. Absent = no. */
91
- purchasable?: boolean;
92
- archived?: boolean;
93
- order?: number;
94
- createdBy: string;
95
- createdAt?: number;
145
+ /** The unit a new line starts in (a box); absent = the base unit. */
146
+ unitKey?: string;
96
147
  updatedAt?: number;
97
148
  }
98
- /** The cost price in the period of the list price, for a margin. Absent without a cost price. */
99
- export declare function costInPricePeriod(product: Pick<Product, "costPriceCents" | "costPricePer" | "pricePer">): Cents | undefined;
149
+ /**
150
+ * An article as sales sees it in one administration: catalog's article, its release, and sales'
151
+ * own row, flat. A read model — nothing stores it. See {@link sellableProduct}.
152
+ */
153
+ export interface SellableProduct extends Product, Partial<Pick<ProductRelease, "taxRateKey" | "ledgerCategoryKey" | "referenceCost" | "blocked">>, Omit<Partial<SalesProduct>, "id" | "organizationId" | "productId" | "unitKey" | "updatedAt"> {
154
+ /** The price list's currency, else the administration's: what price rules are matched on. */
155
+ currency: string;
156
+ /** Sales' default unit for a new line. */
157
+ salesUnitKey?: string;
158
+ /** Whether this administration has released it at all. */
159
+ released: boolean;
160
+ }
161
+ /** Puts the three rows together. `currency` is the administration's, for an article without a price. */
162
+ export declare function sellableProduct(product: Product, release: ProductRelease | undefined, sales: SalesProduct | undefined, currency: string): SellableProduct;
100
163
  /** Whether a sales picker offers it: absent counts as sellable, so older products stay. */
101
164
  export declare function isSellable(product: Pick<Product, "sellable" | "archived">): boolean;
102
165
  /** Whether a purchase picker offers it. */
103
166
  export declare function isPurchasable(product: Pick<Product, "purchasable" | "archived">): boolean;
167
+ export type ProductPricing = "unit" | "rated";
104
168
  /**
105
169
  * Units follow work's `FIXED_TYPES` pattern: a fixed list plus a namespaced fallback, so an
106
170
  * unknown key reads as itself instead of breaking.
107
171
  */
108
- export type ProductPricing = "unit" | "rated";
109
- export declare const FIXED_UNITS: readonly ["piece", "hour", "day", "month", "km", "license", "minute", "kg", "ton", "m", "m2", "m3", "liter"];
172
+ export declare const FIXED_UNITS: readonly ["piece", "hour", "day", "month", "km", "license", "minute", "kg", "ton", "m", "m2", "m3", "liter", "box", "roll", "pallet", "pack"];
110
173
  export type FixedUnit = (typeof FIXED_UNITS)[number];
111
174
  /**
112
175
  * A price for a customer, a segment or a contract.
@@ -179,6 +242,10 @@ export interface SaveProductRequest extends Partial<Omit<Product, "id" | "organi
179
242
  id?: string;
180
243
  name: string;
181
244
  }
245
+ export interface SaveReleaseRequest extends Partial<Omit<ProductRelease, "id" | "organizationId" | "createdBy" | "createdAt" | "updatedAt">> {
246
+ productId: string;
247
+ businessEntityId: string;
248
+ }
182
249
  export interface SavePriceRuleRequest extends Partial<Omit<PriceRule, "id" | "organizationId" | "createdBy" | "createdAt">> {
183
250
  id?: string;
184
251
  }
@@ -34,6 +34,17 @@ export interface UsageMeter {
34
34
  * is nothing to pick, and the customer's id is the account. Absent = `source`.
35
35
  */
36
36
  account?: "source" | "customer";
37
+ /**
38
+ * Billing increments worth offering beyond the generic ones (as used, first unit whole, per
39
+ * started unit): hours per started quarter. In meter units; `label` is a `locales` key.
40
+ */
41
+ increments?: UsageIncrement[];
42
+ }
43
+ /** `{ first: 900, next: 900 }` = per started quarter of an hour, in seconds. */
44
+ export interface UsageIncrement {
45
+ first: number;
46
+ next: number;
47
+ label: string;
37
48
  }
38
49
  export interface UsageDimension {
39
50
  key: string;
@@ -34,6 +34,8 @@ export interface Organization {
34
34
  scopes: string[];
35
35
  domain: string;
36
36
  defaultApp?: string;
37
+ /** Where a member lands with no deeplink or last tab (plan 048); absent = the first visible. */
38
+ defaultWorkspace?: string;
37
39
  permissions?: string[];
38
40
  /** Tenant slug, mirrored from system.tenants. */
39
41
  slug?: string;
@@ -11,17 +11,23 @@ export interface GoodsReceiptProblem {
11
11
  }
12
12
  /**
13
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.
14
+ * numbers that do not match the count in base units. No serials is fine (bulk goods); some serials
15
+ * must be all.
15
16
  */
16
- export declare function goodsReceiptProblems(receipt: Pick<GoodsReceipt, "movement" | "lines">, poLines: readonly Pick<PurchaseLine, "id" | "quantity">[], booked: readonly GoodsReceipt[]): GoodsReceiptProblem[];
17
+ export declare function goodsReceiptProblems(receipt: Pick<GoodsReceipt, "movement" | "lines">, poLines: readonly Pick<PurchaseLine, "id" | "quantity" | "factor">[], booked: readonly GoodsReceipt[]): GoodsReceiptProblem[];
17
18
  /** Lines of a receipt worth keeping: a positive quantity, serials trimmed and unique. */
18
19
  export declare function cleanReceiptLines(lines: readonly GoodsReceiptLine[]): GoodsReceiptLine[];
19
20
  /** Valid on `at`. */
20
21
  export declare function offerValid(offer: Pick<SupplierOffer, "validFrom" | "validTo">, at: number): boolean;
21
22
  /** The offer that prices the product: the marked default if valid, else the first valid one. */
22
23
  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;
24
+ /**
25
+ * Purchase units → base units: two rolls of 500 m are 1,000 m. Takes an offer or an order line —
26
+ * a receipt counts with the factor the line was ordered at, never today's offer (plan 046 #14).
27
+ */
28
+ export declare function toProductUnits(source: {
29
+ factor?: number;
30
+ }, qty: number): number;
25
31
  /**
26
32
  * The offer's cost per product unit, as amount, currency and period — what is written back to the
27
33
  * product. Never a monthly equivalent: € 222 per year stays € 222 per year.
@@ -1,4 +1,5 @@
1
1
  import { ProvisioningAction, ProvisioningState } from '../../platform/provisioning';
2
+ import { LineRhythm } from '../agreement/types';
2
3
  import { Cents } from '../money/money';
3
4
  import { PricePer } from '../money/period-price';
4
5
  import { PostalAddress } from '../organization/types';
@@ -24,14 +25,17 @@ export interface SupplierOffer {
24
25
  costCents: Cents;
25
26
  currency: string;
26
27
  per?: PricePer;
27
- /** The supplier's term: an NCE yearly licence is `year` even when billed monthly. */
28
- term?: "month" | "year" | "3year";
28
+ /**
29
+ * The supplier's commitment in months (1, 12, 24, 36), in sales' words (`Commitment.termMonths`):
30
+ * an NCE yearly licence is 12 even when billed monthly. Absent = cancel any time.
31
+ */
32
+ termMonths?: number;
29
33
  /** How often they invoice; `costCents` is per `per`, not per invoice. */
30
- billedEvery?: "month" | "year" | "once";
31
- /** What you order in: a roll, a pallet, a box of 50. Absent = the product's own unit. */
32
- purchaseUnit?: string;
33
- /** Product units in one purchase unit: a roll of 500 m is 500. */
34
- unitsPerPurchaseUnit?: number;
34
+ billedEvery?: LineRhythm;
35
+ /** What you order in: a roll, a pallet, a box of 50. Absent = the article's base unit. */
36
+ unitKey?: string;
37
+ /** Base units in one `unitKey` (a roll of 500 m is 500). Absent = the article's own factor, else 1. */
38
+ factor?: number;
35
39
  validFrom?: number;
36
40
  validTo?: number;
37
41
  minQty?: number;
@@ -181,7 +181,13 @@ export interface TransactionLine {
181
181
  name: string;
182
182
  description?: string;
183
183
  unitKey: string;
184
+ /**
185
+ * Base units in one `unitKey`, fixed when the line was added (a box of 10 is 10). Absent = 1.
186
+ * Receipt, delivery and invoice count with this, never with today's article (plan 046 #14).
187
+ */
188
+ factor?: number;
184
189
  quantity: number;
190
+ /** Per `unitKey`: a box of 10 at € 3 a piece is € 30. */
185
191
  unitPriceCents: Cents;
186
192
  /** The period the unit price is for ("€ 25 per month"). Absent = once, per unit. */
187
193
  pricePer?: PricePer;
@@ -193,7 +199,10 @@ export interface TransactionLine {
193
199
  netCents: Cents;
194
200
  /** This line's share of its rate group's VAT. Shielded, like the cost price. */
195
201
  taxCents: Cents;
196
- /** The cost price as it stood when the line was added. Shielded by `sales.product.cost`. */
202
+ /**
203
+ * The cost price as it stood when the line was added, per `unitKey`, **in the document's
204
+ * currency** (converted on that day, plan 046 #16). Shielded by `catalog.product.cost`.
205
+ */
197
206
  costPriceCents?: Cents;
198
207
  /** The mapping axis onto a chart of accounts, copied from the product's category. */
199
208
  ledgerCategoryKey?: string;