@opencxh/domain 1.287.0 → 1.290.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 (66) hide show
  1. package/dist/entities/agreement/index.d.ts +3 -0
  2. package/dist/entities/agreement/period.d.ts +37 -0
  3. package/dist/entities/agreement/period.test.d.ts +1 -0
  4. package/dist/entities/agreement/schedule.d.ts +80 -0
  5. package/dist/entities/agreement/schedule.test.d.ts +1 -0
  6. package/dist/entities/agreement/types.d.ts +167 -0
  7. package/dist/entities/bank/bank.test.d.ts +1 -0
  8. package/dist/entities/bank/index.d.ts +3 -0
  9. package/dist/entities/bank/match.d.ts +52 -0
  10. package/dist/entities/bank/parse.d.ts +13 -0
  11. package/dist/entities/bank/types.d.ts +197 -0
  12. package/dist/entities/bookkeeping/index.d.ts +2 -0
  13. package/dist/entities/bookkeeping/receipt.d.ts +104 -0
  14. package/dist/entities/bookkeeping/receipt.test.d.ts +1 -0
  15. package/dist/entities/bookkeeping/types.d.ts +114 -0
  16. package/dist/entities/bookkeeping/types.test.d.ts +1 -0
  17. package/dist/entities/business-entity/types.d.ts +21 -1
  18. package/dist/entities/catalog/index.d.ts +3 -0
  19. package/dist/entities/catalog/pricing.d.ts +9 -6
  20. package/dist/entities/catalog/rate-csv.d.ts +29 -0
  21. package/dist/entities/catalog/rate-csv.test.d.ts +1 -0
  22. package/dist/entities/catalog/rating.d.ts +122 -0
  23. package/dist/entities/catalog/rating.test.d.ts +1 -0
  24. package/dist/entities/catalog/types.d.ts +41 -2
  25. package/dist/entities/catalog/usage-source.d.ts +83 -0
  26. package/dist/entities/company/identifiers.d.ts +9 -0
  27. package/dist/entities/company/identifiers.test.d.ts +1 -0
  28. package/dist/entities/company/index.d.ts +1 -0
  29. package/dist/entities/company/types.d.ts +9 -0
  30. package/dist/entities/financing/closing-schedules.test.d.ts +1 -0
  31. package/dist/entities/financing/index.d.ts +2 -0
  32. package/dist/entities/financing/schedule.d.ts +71 -0
  33. package/dist/entities/financing/schedule.test.d.ts +1 -0
  34. package/dist/entities/financing/types.d.ts +135 -0
  35. package/dist/entities/ledger/closing.d.ts +18 -0
  36. package/dist/entities/ledger/closing.test.d.ts +1 -0
  37. package/dist/entities/ledger/entry.d.ts +83 -0
  38. package/dist/entities/ledger/index.d.ts +6 -0
  39. package/dist/entities/ledger/ledger.test.d.ts +1 -0
  40. package/dist/entities/ledger/periods.d.ts +9 -0
  41. package/dist/entities/ledger/template.d.ts +35 -0
  42. package/dist/entities/ledger/types.d.ts +178 -0
  43. package/dist/entities/ledger/xaf.d.ts +82 -0
  44. package/dist/entities/money/tax.d.ts +62 -0
  45. package/dist/entities/money/tax.test.d.ts +1 -0
  46. package/dist/entities/reports/index.d.ts +2 -0
  47. package/dist/entities/reports/reports.d.ts +90 -0
  48. package/dist/entities/reports/reports.test.d.ts +1 -0
  49. package/dist/entities/reports/vat.d.ts +59 -0
  50. package/dist/entities/transaction/booking.d.ts +14 -0
  51. package/dist/entities/transaction/booking.test.d.ts +1 -0
  52. package/dist/entities/transaction/convert.d.ts +5 -0
  53. package/dist/entities/transaction/dunning.d.ts +37 -0
  54. package/dist/entities/transaction/dunning.test.d.ts +1 -0
  55. package/dist/entities/transaction/index.d.ts +4 -0
  56. package/dist/entities/transaction/keys.d.ts +7 -0
  57. package/dist/entities/transaction/receivable.d.ts +47 -0
  58. package/dist/entities/transaction/receivable.test.d.ts +1 -0
  59. package/dist/entities/transaction/settlement.d.ts +20 -0
  60. package/dist/entities/transaction/settlement.test.d.ts +1 -0
  61. package/dist/entities/transaction/types.d.ts +34 -2
  62. package/dist/index.cjs +24 -21
  63. package/dist/index.d.ts +7 -0
  64. package/dist/index.js +4240 -2387
  65. package/dist/platform/bookkeeping.d.ts +276 -0
  66. package/package.json +1 -1
@@ -0,0 +1,114 @@
1
+ import { BookingDocument, BookingLine } from '../../platform/bookkeeping';
2
+ import { Cents } from '../money/money';
3
+ /**
4
+ * Who keeps the books of one administration. One row per administration, stored by finance; no
5
+ * row means nothing is booked (fail-closed, no fan-out). Switching books is one write on this row.
6
+ */
7
+ export interface BookkeepingBinding {
8
+ id: string;
9
+ organizationId: string;
10
+ businessEntityId: string;
11
+ /** The `bookkeeping` provider that receives documents: `finance` (own books) or `jortt`. */
12
+ app: string;
13
+ /** `ManagedAccount.id` of the connection; empty for our own books. */
14
+ accountId?: string;
15
+ /** Mapping onto an external package's accounts. Our own books use `PostingRule` instead. */
16
+ ledgerMapping?: LedgerMappingRule[];
17
+ /** Documents issued before this day stay with the previous books. */
18
+ activeFrom: string;
19
+ createdBy: string;
20
+ createdAt?: number;
21
+ updatedAt?: number;
22
+ }
23
+ /** First match wins: product, then category, then VAT code, then the rule without a match. */
24
+ export interface LedgerMappingRule {
25
+ match: {
26
+ productId?: string;
27
+ ledgerCategoryKey?: string;
28
+ taxRateKey?: string;
29
+ };
30
+ /** `BookkeepingDescribe.ledgerOptions[].key`. */
31
+ targetKey: string;
32
+ externalTaxCode?: string;
33
+ }
34
+ /**
35
+ * pending: to do, or to do again.
36
+ * sending: an attempt runs, or ran and its outcome is unknown.
37
+ * posted: arrived; `externalId` is known.
38
+ * failed: worth retrying; `nextAttemptAt` is set.
39
+ * attention: a lasting refusal (validation, a closed year). Waits for a person.
40
+ */
41
+ export type BookingSyncState = "pending" | "sending" | "posted" | "failed" | "attention";
42
+ /**
43
+ * The durable processing state of one document on its way to the books (plan 039 datamodel §3.5):
44
+ * where "did this arrive, and if not, what was the last attempt?" is written down.
45
+ */
46
+ export interface BookingSync {
47
+ id: string;
48
+ organizationId: string;
49
+ businessEntityId: string;
50
+ /** `<bindingId>:<sourceApp>:<documentId>`. Flat and never empty: the unique index. */
51
+ syncKey: string;
52
+ bindingId: string;
53
+ targetApp: string;
54
+ sourceApp: string;
55
+ documentId: string;
56
+ documentKind: BookingDocument["kind"];
57
+ documentNumber?: string;
58
+ /** `oc:<documentId>`: sent along, and searched on after a lost response. */
59
+ correlationKey: string;
60
+ state: BookingSyncState;
61
+ attempts: number;
62
+ /** When the running attempt began. A `sending` older than ten minutes has an unknown outcome. */
63
+ sendingSince?: number;
64
+ nextAttemptAt?: number;
65
+ lastError?: {
66
+ code: string;
67
+ message: string;
68
+ at: number;
69
+ };
70
+ externalId?: string;
71
+ /** The number the books gave the document, when they number it themselves. */
72
+ externalNumber?: string;
73
+ postedAt?: number;
74
+ resolvedVia?: "response" | "lookup";
75
+ /** The source was told it arrived. False until then, so a retry can find what is still owed. */
76
+ sourceNotified: boolean;
77
+ /** Jortt only: the open amount last seen, compared on every poll. */
78
+ lastSeenDueCents?: Cents;
79
+ /** A credit note: the invoice it credits, and whether the books settled one with the other. */
80
+ creditsDocumentId?: string;
81
+ /** `false` until settled; written only on a credit note, so a query can find what is owed. */
82
+ creditSettled?: boolean;
83
+ createdAt?: number;
84
+ updatedAt?: number;
85
+ }
86
+ export declare function bookingSyncKey(bindingId: string, sourceApp: string, documentId: string): string;
87
+ /** Minutes to wait after the nth failed attempt (1-based): 1, 5, 15, 60, then every hour. */
88
+ export declare function retryDelayMinutes(attempts: number): number;
89
+ /** How long a `sending` row may run before its outcome counts as unknown. */
90
+ export declare const SENDING_STALE_MS: number;
91
+ /**
92
+ * One item in "Te verwerken", derived at read time from the state of its source row — never
93
+ * stored, so it cannot drift (plan 039 datamodel §6.5). Receipts, bank lines and schedule entries
94
+ * join as they are built.
95
+ */
96
+ export interface WorkQueueItem {
97
+ kind: "booking_sync" | "receipt" | "bank_line";
98
+ ref: string;
99
+ group: "failed" | "confirm" | "check" | "choose";
100
+ title: string;
101
+ subtitle: string;
102
+ date: number;
103
+ amountCents?: Cents;
104
+ /** Why it is here, as an error code the app translates (`error_<code>`), plus the raw detail. */
105
+ reason?: {
106
+ code: string;
107
+ message: string;
108
+ };
109
+ }
110
+ /**
111
+ * The rule a line books under: product, then category, then VAT code, then the rule without a
112
+ * match. `undefined` when nothing applies — the caller refuses rather than guessing an account.
113
+ */
114
+ export declare function ledgerRuleFor(line: Pick<BookingLine, "productId" | "ledgerCategoryKey" | "taxRateKey">, rules: readonly LedgerMappingRule[]): LedgerMappingRule | undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -47,8 +47,26 @@ export interface BusinessEntity {
47
47
  * BV and its Belgian sister write different default languages under one workspace.
48
48
  */
49
49
  locale?: string;
50
- /** One per entity: an administration is set up per country and per currency. */
50
+ /** One per entity: an administration is set up per country and per currency. The books' functional currency. */
51
51
  currency?: string;
52
+ /** ISO 3166-1 alpha-2. Picks the country pack: chart template, tax taxonomy, statutory export. */
53
+ country?: string;
54
+ /** Picks the chart template and how equity is laid out. */
55
+ legalForm?: LegalForm;
56
+ /** A VAT table (NL/EU) or an external tax engine (US, later). Absent = the table for `country`. */
57
+ taxProfile?: {
58
+ strategy: "table";
59
+ taxonomy: string;
60
+ } | {
61
+ strategy: "provider";
62
+ providerId: string;
63
+ };
64
+ /** The month the fiscal year starts, 1 = January. Periods are rows derived from it (plan 039 §4.3). */
65
+ fiscalYearStartMonth?: number;
66
+ /** How often VAT is filed. Absent = per quarter, the Dutch default. */
67
+ vatCadence?: "quarter" | "month" | "year";
68
+ /** Kleineondernemersregeling: no VAT charged or deducted, so no return. */
69
+ vatKor?: boolean;
52
70
  /** The one a new document lands on. Exactly one row carries it. */
53
71
  isDefault?: boolean;
54
72
  /** Archived, never deleted: an issued quote names the entity it went out as. */
@@ -58,6 +76,8 @@ export interface BusinessEntity {
58
76
  createdAt?: number;
59
77
  updatedAt?: number;
60
78
  }
79
+ export type LegalForm = "bv" | "nv" | "eenmanszaak" | "vof" | "maatschap" | "stichting" | "vereniging" | "other";
80
+ export declare const LEGAL_FORMS: readonly LegalForm[];
61
81
  /** What {@link businessEntitySender} reads. Structural, so the document layer keeps pointing here. */
62
82
  export type BusinessEntityLike = Pick<BusinessEntity, "name" | "address" | "vatNumber" | "kvkNumber" | "iban" | "logo" | "footerText">;
63
83
  /**
@@ -1,3 +1,6 @@
1
1
  export * from './codes';
2
2
  export * from './pricing';
3
+ export * from './rate-csv';
4
+ export * from './rating';
3
5
  export * from './types';
6
+ export * from './usage-source';
@@ -33,14 +33,17 @@ export interface ResolvedPrice {
33
33
  sourceKey: string;
34
34
  ruleId?: string;
35
35
  }
36
+ export declare function resolvePrice(product: Product, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedPrice;
37
+ export interface ResolvedRateCard {
38
+ rateCardId: string;
39
+ sourceKey: string;
40
+ ruleId?: string;
41
+ }
36
42
  /**
37
- * The price for this product, for this customer, at this quantity, on this date.
38
- *
39
- * Pure, so the screen can compute along while you type and land on the same cent the server
40
- * stores. A rule that names neither the product nor its category does not apply, however
41
- * specific its customer is: a discount on "everything" is a decision, not a price rule.
43
+ * The rate card a `rated` product is billed on for this customer: the same ladder as a price,
44
+ * over the rules whose outcome is a card, then the product's own card. `null` = it has none.
42
45
  */
43
- export declare function resolvePrice(product: Product, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedPrice;
46
+ export declare function resolveRateCard(product: Product, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedRateCard | null;
44
47
  /** The `priceSourceKey` of a price somebody typed themselves. The ladder never overwrites it. */
45
48
  export declare const MANUAL_PRICE_SOURCE = "manual";
46
49
  /**
@@ -0,0 +1,29 @@
1
+ import { Micros, Rate, RatePrice } from './rating';
2
+ import { UsageMeter } from './usage-source';
3
+ /**
4
+ * A rate deck as CSV, one rate per line, with columns that follow the meter:
5
+ *
6
+ * label;<one column per dimension>;setup;price[;increment;minimum]
7
+ * Nederland mobiel;316;outbound;0,0450;0,0650;60/1;60 (calls: destination, direction)
8
+ * Internet uit;eu-west-1;;+12% (traffic: region, direction)
9
+ *
10
+ * `;` or `,` separated (a Dutch Excel writes `;` because `,` is its decimal mark); a first line
11
+ * whose price column is not a price is a header. The price is per `meter.display` unit, written
12
+ * with either decimal mark, or `+12%` for a markup on cost. An empty dimension matches anything.
13
+ * Increment and minimum only exist when the meter bills per record (`display.per > 1`). Tiers
14
+ * are edited on screen, not in a file.
15
+ */
16
+ export interface RateCsvResult {
17
+ rates: Omit<Rate, "id">[];
18
+ errors: {
19
+ line: number;
20
+ message: string;
21
+ }[];
22
+ }
23
+ /** `"0,0450"` → 45 000 micros, without a float in between. */
24
+ export declare function parseMicros(value: string): Micros | undefined;
25
+ /** 45 000 micros → `"0,0450"`: at least four decimals, more only when the rate has them. */
26
+ export declare function formatMicros(micros: Micros): string;
27
+ /** `"+12%"` → a markup; a plain amount → a flat price; anything else → `undefined`. */
28
+ export declare function parseRatePrice(value: string): RatePrice | undefined;
29
+ export declare function parseRateCsv(text: string, meter: UsageMeter): RateCsvResult;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,122 @@
1
+ import { Cents } from '../money/money';
2
+ /**
3
+ * Pricing records on a rate card: a call, a text, a gigabyte of traffic, an hour of compute.
4
+ *
5
+ * Each record is matched to one rate on its dimensions; the records of a rate are then summed
6
+ * (or averaged, per the meter) and priced as a group — flat, tiered, or as a markup on cost.
7
+ * Carriers and clouds quote in four or five decimals, so rates are in **micros** (millionths of
8
+ * the currency unit); only a group's total becomes cents.
9
+ */
10
+ export type Micros = number;
11
+ export declare const MICROS_PER_CENT = 10000;
12
+ /**
13
+ * One usage record as a source reports it: a real event (a call) or a summary (GB transferred
14
+ * in a region on one day) — a source with millions of events sends summaries.
15
+ */
16
+ export interface UsageRecord {
17
+ id: string;
18
+ meter: string;
19
+ /** In the meter's unit: seconds for a call, bytes or GB for traffic, seats for licences. */
20
+ quantity: number;
21
+ /** Whatever the meter declares: `direction`, `destination`, `region`, … */
22
+ dimensions: Record<string, string>;
23
+ occurredAt: number;
24
+ /** What the source paid for it. Needed by a markup rate; kept for a later margin view. */
25
+ costMicros?: Micros;
26
+ }
27
+ /** How a period's records become one number: calls add up, licences take their peak. */
28
+ export type UsageAggregation = "sum" | "max" | "last" | "average";
29
+ export declare function aggregateUsage(records: readonly {
30
+ quantity: number;
31
+ occurredAt?: number;
32
+ }[], aggregation: UsageAggregation): number;
33
+ /** `equals` pins a value; `prefix` takes the longest match (a number range, a region family). */
34
+ export type RateMatcher = {
35
+ equals: string;
36
+ } | {
37
+ prefix: string;
38
+ };
39
+ export type RatePrice =
40
+ /** The price of `unitSize` units. */
41
+ {
42
+ kind: "flat";
43
+ micros: Micros;
44
+ }
45
+ /**
46
+ * Bands over the group's quantity. `graduated`: every band at its own price. `volume`: the whole
47
+ * quantity at the price of the band it lands in. The last band has no `upTo`.
48
+ */
49
+ | {
50
+ kind: "tiered";
51
+ mode: "graduated" | "volume";
52
+ tiers: {
53
+ upTo?: number;
54
+ micros: Micros;
55
+ }[];
56
+ }
57
+ /** The source's cost plus a percentage. A record without a cost has no price. */
58
+ | {
59
+ kind: "markup";
60
+ percent: number;
61
+ };
62
+ export interface Rate {
63
+ /** Unique within its card. */
64
+ id: string;
65
+ /** What the invoice line says: "Nederland mobiel", "Internet uit · eu-west-1". */
66
+ label: string;
67
+ /** Per dimension. A dimension not named matches anything. */
68
+ match: Record<string, RateMatcher>;
69
+ /** Once per record with a quantity above zero. */
70
+ setupMicros: Micros;
71
+ price: RatePrice;
72
+ /** How many meter units a price is for: 60 seconds, 3600 seconds, a million requests. */
73
+ unitSize: number;
74
+ /** Rounding per record: 60 / 1 bills the first 60 units whole, then each unit. 1/1 = none. */
75
+ firstIncrement: number;
76
+ nextIncrement: number;
77
+ /** At least this many units per record. */
78
+ minimum: number;
79
+ /** Free units per period, taken off the group before its price. */
80
+ included?: number;
81
+ }
82
+ /** `+31 6-1234`, `0031 61234` and `31612 34` are all `3161234`. */
83
+ export declare function digitsOf(value: string): string;
84
+ export declare function matchRate(record: UsageRecord, rates: readonly Rate[]): Rate | undefined;
85
+ export declare function billedQuantity(quantity: number, rate: Rate): number;
86
+ export interface PriceBand {
87
+ quantity: number;
88
+ micros: Micros;
89
+ }
90
+ /** A tiered price over `quantity` meter units, with the bands it used. */
91
+ export declare function tieredMicros(quantity: number, price: Extract<RatePrice, {
92
+ kind: "tiered";
93
+ }>, unitSize: number): {
94
+ micros: Micros;
95
+ bands: PriceBand[];
96
+ };
97
+ export interface RatedGroup {
98
+ rateId: string;
99
+ label: string;
100
+ records: number;
101
+ /** The records it holds, so an invoice can claim them (time entries). */
102
+ recordIds: string[];
103
+ /** After per-record rounding and the meter's aggregation, before `included`. */
104
+ measured: number;
105
+ included: number;
106
+ /** What is priced: `measured - included`, never below zero. */
107
+ billed: number;
108
+ micros: Micros;
109
+ cents: Cents;
110
+ /** The bands of a tiered price, for the invoice. */
111
+ bands?: PriceBand[];
112
+ }
113
+ export interface RatingResult {
114
+ groups: RatedGroup[];
115
+ /**
116
+ * Records no rate matched, or matched a markup without a cost. The caller decides, out loud,
117
+ * whether to bill without them — usage is never silently free.
118
+ */
119
+ unrated: UsageRecord[];
120
+ }
121
+ /** A period's records, rated and grouped per rate. Cents are rounded once per group. */
122
+ export declare function rateRecords(records: readonly UsageRecord[], rates: readonly Rate[], aggregation?: UsageAggregation): RatingResult;
@@ -0,0 +1 @@
1
+ export {};
@@ -44,6 +44,13 @@ export interface Product {
44
44
  * type: time must not know sales, and several products may share one type (junior/senior).
45
45
  */
46
46
  workTypeKey?: string;
47
+ /**
48
+ * `unit`: a price per unit through the ladder (list price, tiers, customer agreements).
49
+ * `rated`: every record priced on a rate card by its dimensions (calls, traffic).
50
+ */
51
+ pricing?: ProductPricing;
52
+ /** The default card of a `rated` product. A price agreement may name another. */
53
+ rateCardId?: string;
47
54
  /** See {@link FIXED_UNITS}. Drives the form and the word after the quantity. */
48
55
  unitKey: string;
49
56
  /** Key into the organisation's VAT table, never the percentage itself. */
@@ -78,7 +85,8 @@ export interface Product {
78
85
  * Units follow work's `FIXED_TYPES` pattern: a fixed list plus a namespaced fallback, so an
79
86
  * unknown key reads as itself instead of breaking.
80
87
  */
81
- export declare const FIXED_UNITS: readonly ["piece", "hour", "day", "month", "km", "license"];
88
+ export type ProductPricing = "unit" | "rated";
89
+ export declare const FIXED_UNITS: readonly ["piece", "hour", "day", "month", "km", "license", "minute"];
82
90
  export type FixedUnit = (typeof FIXED_UNITS)[number];
83
91
  /**
84
92
  * A price for a customer, a segment or a contract.
@@ -98,9 +106,13 @@ export interface PriceRule {
98
106
  segmentKey?: string;
99
107
  /** From which quantity. Absent = from one. */
100
108
  minQuantity?: number;
101
- /** Exactly one of the two. A fixed price beats a discount when both would apply. */
109
+ /**
110
+ * Exactly one outcome. A fixed price beats a discount; a rate card is the outcome for a `rated`
111
+ * product ("this customer calls on Enterprise") and is ignored by the unit ladder.
112
+ */
102
113
  unitPriceCents?: Cents;
103
114
  discountPercent?: number;
115
+ rateCardId?: string;
104
116
  currency: string;
105
117
  validFrom?: number;
106
118
  validTo?: number;
@@ -114,6 +126,29 @@ export interface PriceRule {
114
126
  createdBy: string;
115
127
  createdAt?: number;
116
128
  }
129
+ /**
130
+ * A price list for usage: which meter it prices, in which currency, from when. The rates travel
131
+ * separately (`rate-card/:id/rates`) — a carrier's deck has thousands of rows and a list of cards
132
+ * must not carry them.
133
+ */
134
+ export interface RateCard {
135
+ id: string;
136
+ organizationId: string;
137
+ name: string;
138
+ /** The app that reports the usage (`eylo-voip`) and its meter key (`eylo.call`). */
139
+ sourceApp: string;
140
+ meter: string;
141
+ /** Kept with the rates, so the list can show it without loading them. */
142
+ rateCount?: number;
143
+ currency: string;
144
+ validFrom?: number;
145
+ validTo?: number;
146
+ businessEntityId?: string;
147
+ archived?: boolean;
148
+ createdBy: string;
149
+ createdAt?: number;
150
+ updatedAt?: number;
151
+ }
117
152
  /**
118
153
  * What the product form sends. Everything optional but the name: the server fills defaults, and
119
154
  * a form that has to send a complete product cannot add a variant with two fields.
@@ -125,3 +160,7 @@ export interface SaveProductRequest extends Partial<Omit<Product, "id" | "organi
125
160
  export interface SavePriceRuleRequest extends Partial<Omit<PriceRule, "id" | "organizationId" | "createdBy" | "createdAt">> {
126
161
  id?: string;
127
162
  }
163
+ export interface SaveRateCardRequest extends Partial<Omit<RateCard, "id" | "organizationId" | "createdBy" | "createdAt" | "updatedAt">> {
164
+ id?: string;
165
+ name: string;
166
+ }
@@ -0,0 +1,83 @@
1
+ import { LocaleBundle } from '../../platform/localization';
2
+ import { UsageAggregation, UsageRecord } from './rating';
3
+ /**
4
+ * Role group of an app whose usage can be billed: calls, texts, gigabytes.
5
+ *
6
+ * A constant for the same reason as `ONBOARDING_SOURCE_PROVIDER_GROUP`: an unknown group answers
7
+ * with an empty list, so a typo reads as "nothing to bill".
8
+ */
9
+ export declare const USAGE_SOURCE_PROVIDER_GROUP = "usage-source";
10
+ /** Route under the provider scope that returns a period's records. App-only. */
11
+ export declare const USAGE_RECORDS_ROUTE = "/usage/records";
12
+ export interface UsageMeter {
13
+ /** Namespaced by the owner, like analytics metrics: `eylo.call`, never `call`. */
14
+ key: string;
15
+ /** An i18n key from the source's `locales`. */
16
+ label: string;
17
+ /** What `UsageRecord.quantity` counts: `second`, `message`, `gigabyte`, `seat`. */
18
+ unit: string;
19
+ /**
20
+ * How a person reads it: `{ unit: "minute", per: 60 }` prices seconds per minute. `per > 1` is
21
+ * also what makes per-record rounding (60/1) and a minimum worth showing.
22
+ */
23
+ display: {
24
+ unit: string;
25
+ per: number;
26
+ };
27
+ /** How a period's records become one number: calls `sum`, licences `max`, storage `average`. */
28
+ aggregation: UsageAggregation;
29
+ /** What a rate may match on; `prefix` for number ranges and families, `exact` otherwise. */
30
+ dimensions: UsageDimension[];
31
+ /**
32
+ * Whose usage it is. `source`: one of the source's own accounts (a PBX tenant), picked on the
33
+ * agreement. `customer`: the agreement's customer itself — hours booked on a company — so there
34
+ * is nothing to pick, and the customer's id is the account. Absent = `source`.
35
+ */
36
+ account?: "source" | "customer";
37
+ }
38
+ export interface UsageDimension {
39
+ key: string;
40
+ label: string;
41
+ match: "exact" | "prefix";
42
+ /**
43
+ * The values it takes, when that is a known list: a rate then picks one instead of typing it,
44
+ * and a typo cannot silently match nothing. `label` is a key in the source's `locales`, or
45
+ * plain text for values the organisation named itself (a work type).
46
+ */
47
+ options?: {
48
+ value: string;
49
+ label: string;
50
+ }[];
51
+ }
52
+ export interface UsageSourceDescription {
53
+ meters: UsageMeter[];
54
+ /**
55
+ * The accounts usage can be read for: a PBX tenant, a sms sender. The agreement binds one to a
56
+ * customer; the source never learns about customers.
57
+ */
58
+ accounts: {
59
+ ref: string;
60
+ name: string;
61
+ }[];
62
+ locales?: LocaleBundle;
63
+ }
64
+ export interface UsageRecordsRequest {
65
+ meter: string;
66
+ accountRef: string;
67
+ /** Epoch ms, `to` exclusive. */
68
+ from: number;
69
+ to: number;
70
+ }
71
+ export interface UsageRecordsResponse {
72
+ records: UsageRecord[];
73
+ }
74
+ /**
75
+ * A meter as another app described it, made whole. A describe crosses an app boundary and may be
76
+ * older than this contract (a source not redeployed yet, a cached describe): missing fields get
77
+ * the neutral answer — its own unit per 1, summed, matched exactly — instead of crashing a screen.
78
+ */
79
+ export declare function normalizeMeter(raw: Partial<UsageMeter> & {
80
+ key: string;
81
+ }): UsageMeter;
82
+ /** Every meter of a describe made whole; see {@link normalizeMeter}. */
83
+ export declare function normalizeUsageSource<T extends UsageSourceDescription>(source: T): T;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The two identifiers a bank line and an invoice are matched on, in one canonical form. Stored
3
+ * normalised on write, compared normalised everywhere: `NL91 abna 0417 1643 00` and
4
+ * `NL91ABNA0417164300` are one account.
5
+ */
6
+ export declare function normalizeIban(value: string): string;
7
+ export declare function normalizeVatNumber(value: string): string;
8
+ /** Normalised, blanks dropped, duplicates removed, order kept. */
9
+ export declare function normalizeIbans(values: readonly unknown[]): string[];
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,2 @@
1
1
  export * from './types';
2
+ export * from './identifiers';
@@ -61,6 +61,15 @@ export interface Company {
61
61
  * is created and keeps it from then on, so changing this never rewrites what already went out.
62
62
  */
63
63
  paymentTermCode?: string;
64
+ /** Normalised: upper case, no spaces (`NL123456789B01`). */
65
+ vatNumber?: string;
66
+ kvkNumber?: string;
67
+ /** This company's bank accounts, normalised. Flat and indexed: matching a payment looks here. */
68
+ ibans?: string[];
69
+ /** `<scheme>:<id>` (`0106:12345678`), for Peppol e-invoicing. */
70
+ peppolId?: string;
71
+ debtorNumber?: string;
72
+ creditorNumber?: string;
64
73
  /** Raw, human-readable phone/website as entered, for display. */
65
74
  phone?: string;
66
75
  website?: string;
@@ -0,0 +1,2 @@
1
+ export * from './schedule';
2
+ export * from './types';
@@ -0,0 +1,71 @@
1
+ import { Cents } from '../money/money';
2
+ import { AccrualTerms, CurrentAccountTerms, FinancingItem, FixedAssetTerms, LoanTerms, ScheduledPosting } from './types';
3
+ /** `day` plus `months`, on the same day of the month or the last one when it is shorter. */
4
+ export declare function addMonths(day: string, months: number): string;
5
+ export interface Installment {
6
+ sequence: number;
7
+ dueOn: string;
8
+ interestCents: Cents;
9
+ principalCents: Cents;
10
+ /** What is still owed after this instalment. */
11
+ remainingCents: Cents;
12
+ }
13
+ /**
14
+ * A loan's monthly schedule: annuity (equal payments), linear (equal principal) or bullet
15
+ * (interest only, principal at the end). Cents are rounded per instalment and the last one takes
16
+ * what is left, so the principal adds up exactly.
17
+ */
18
+ export declare function loanSchedule(terms: Pick<LoanTerms, "principalCents" | "ratePercent" | "method" | "termMonths" | "firstDueOn">): Installment[];
19
+ /**
20
+ * Interest over the daily balance (actual/365): every day's closing balance times the day rate,
21
+ * rounded once at the end. `movements` are the item's booked lines, signed; the balance before
22
+ * `from` is what they add up to by then.
23
+ */
24
+ export declare function interestOnDailyBalance(movements: readonly {
25
+ date: string;
26
+ cents: Cents;
27
+ }[], from: string, to: string, ratePercent: string): Cents;
28
+ /** The periods interest is settled over, from `openedOn` up to and including the one holding `through`. */
29
+ export declare function interestPeriods(openedOn: string, through: string, cadence: CurrentAccountTerms["cadence"]): {
30
+ sequence: number;
31
+ from: string;
32
+ to: string;
33
+ }[];
34
+ export declare const EXCESSIVE_LENDING_THRESHOLD_CENTS = 50000000;
35
+ /** Wet excessief lenen: how far the director's debt to the BV is toward the threshold. */
36
+ export declare function excessiveLending(directorOwesCents: Cents, thresholdCents?: number): {
37
+ over: boolean;
38
+ ratio: number;
39
+ };
40
+ /**
41
+ * The postings a loan's schedule asks for, up to `through`: one per instalment, its lines the
42
+ * interest and the principal (on the loan, with the item as sub-account). A received loan is paid
43
+ * (the bank line is −), a given loan is received (+).
44
+ */
45
+ export declare function loanPostings(item: Pick<FinancingItem, "id" | "ledgerAccountId" | "name"> & {
46
+ terms: LoanTerms;
47
+ }, through: string): Omit<ScheduledPosting, "id" | "organizationId" | "businessEntityId" | "status">[];
48
+ type PlannedPosting = Omit<ScheduledPosting, "id" | "organizationId" | "businessEntityId" | "status">;
49
+ /** The last day of the month `day` falls in. */
50
+ export declare function monthEnd(day: string): string;
51
+ /** `totalCents` over `months` equal parts; the last takes the rounding, so the parts add up exactly. */
52
+ export declare function spread(totalCents: Cents, months: number): Cents[];
53
+ /** Straight-line depreciation: (cost − residual) over the life, from the month of acquisition. */
54
+ export declare function depreciationPostings(item: Pick<FinancingItem, "id" | "name"> & {
55
+ terms: FixedAssetTerms;
56
+ }, through: string): PlannedPosting[];
57
+ /** A transitorium released month by month onto its result account. */
58
+ export declare function accrualPostings(item: Pick<FinancingItem, "id" | "name" | "ledgerAccountId"> & {
59
+ terms: AccrualTerms;
60
+ }, through: string): PlannedPosting[];
61
+ /**
62
+ * Where a fixed asset or transitorium stands without its own opening lines: the booked movements
63
+ * on the item plus what its terms say was there to begin with (cost, or an amount paid upfront).
64
+ */
65
+ export declare function startingCents(terms: FixedAssetTerms | AccrualTerms): Cents;
66
+ /**
67
+ * The part of a loan's principal due within twelve months after `yearEnd`: the annual accounts
68
+ * show it as a short-term liability (aflossingsverplichting), the rest as long-term.
69
+ */
70
+ export declare function shortTermPrincipal(terms: Pick<LoanTerms, "principalCents" | "ratePercent" | "method" | "termMonths" | "firstDueOn">, yearEnd: string): Cents;
71
+ export {};
@@ -0,0 +1 @@
1
+ export {};