@opencxh/domain 1.286.3 → 1.288.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/activity/types.d.ts +4 -0
- package/dist/entities/agreement/index.d.ts +3 -0
- package/dist/entities/agreement/period.d.ts +37 -0
- package/dist/entities/agreement/period.test.d.ts +1 -0
- package/dist/entities/agreement/schedule.d.ts +80 -0
- package/dist/entities/agreement/schedule.test.d.ts +1 -0
- package/dist/entities/agreement/types.d.ts +167 -0
- package/dist/entities/catalog/index.d.ts +3 -0
- package/dist/entities/catalog/pricing.d.ts +9 -6
- package/dist/entities/catalog/rate-csv.d.ts +29 -0
- package/dist/entities/catalog/rate-csv.test.d.ts +1 -0
- package/dist/entities/catalog/rating.d.ts +122 -0
- package/dist/entities/catalog/rating.test.d.ts +1 -0
- package/dist/entities/catalog/types.d.ts +41 -2
- package/dist/entities/catalog/usage-source.d.ts +83 -0
- package/dist/entities/inbox/types.d.ts +3 -0
- package/dist/entities/interaction/index.d.ts +1 -0
- package/dist/entities/interaction/resolution.d.ts +29 -0
- package/dist/entities/interaction/resolution.test.d.ts +1 -0
- package/dist/entities/interaction/types.d.ts +7 -0
- package/dist/entities/transaction/keys.d.ts +7 -0
- package/dist/entities/transaction/types.d.ts +14 -0
- package/dist/index.cjs +20 -20
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2539 -2162
- package/dist/platform/ai-tools.d.ts +10 -0
- package/dist/platform/coded-message.d.ts +15 -0
- package/dist/platform/coded-message.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -239,6 +239,10 @@ export type InteractionStatus = "open" | "closed" | "snoozed";
|
|
|
239
239
|
export type InteractionStatusChangedPayload = {
|
|
240
240
|
fromStatus: InteractionStatus;
|
|
241
241
|
toStatus: InteractionStatus;
|
|
242
|
+
/** Set on a close with a reason: the key, and its label and outcome as they were at that moment. */
|
|
243
|
+
resolution?: string;
|
|
244
|
+
resolutionLabel?: string;
|
|
245
|
+
resolutionPositive?: boolean;
|
|
242
246
|
};
|
|
243
247
|
/**
|
|
244
248
|
* A service target that ran out. Written by the app that owns the clocks, not by comms.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Agreement, AgreementInterval, AgreementLine } from './types';
|
|
2
|
+
/** `a` bills more often than `b`: a monthly line in a yearly agreement would bill 12 at once. */
|
|
3
|
+
export declare function isShorter(a: AgreementInterval, b: AgreementInterval): boolean;
|
|
4
|
+
export interface Period {
|
|
5
|
+
from: number;
|
|
6
|
+
/** Exclusive. */
|
|
7
|
+
to: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The period of `interval` that contains `at`, on calendar boundaries.
|
|
11
|
+
*
|
|
12
|
+
* ponytail: UTC boundaries — a call at 00:30 Amsterdam time on the 1st lands in the previous
|
|
13
|
+
* month. Pass the organisation's zone offset here when that difference starts to matter.
|
|
14
|
+
*/
|
|
15
|
+
export declare function periodAt(at: number, interval: AgreementInterval): Period;
|
|
16
|
+
/** When a line runs: its own dates inside the agreement's term. `to` absent = open-ended. */
|
|
17
|
+
export declare function lineTerm(agreement: Pick<Agreement, "startsAt" | "endsAt">, line: Pick<AgreementLine, "startsAt" | "endsAt">): {
|
|
18
|
+
from: number;
|
|
19
|
+
to?: number;
|
|
20
|
+
};
|
|
21
|
+
/** The part of `period` a term covers, or `null` when they do not meet. */
|
|
22
|
+
export declare function overlap(term: {
|
|
23
|
+
from: number;
|
|
24
|
+
to?: number;
|
|
25
|
+
}, period: Period): Period | null;
|
|
26
|
+
/**
|
|
27
|
+
* How much of a period a fixed line bills: 1 for a whole period, 0 when it ran only part of it
|
|
28
|
+
* and `proration` is off, the share of days when it is on (12 of 31 days = 12/31).
|
|
29
|
+
*/
|
|
30
|
+
export declare function periodShare(term: {
|
|
31
|
+
from: number;
|
|
32
|
+
to?: number;
|
|
33
|
+
}, period: Period, proration: boolean): number;
|
|
34
|
+
export type LineStatus = "planned" | "active" | "ended";
|
|
35
|
+
export declare function lineStatus(agreement: Pick<Agreement, "startsAt" | "endsAt">, line: Pick<AgreementLine, "startsAt" | "endsAt">, now: number): LineStatus;
|
|
36
|
+
/** Lines that run at `at` or will later — the ones whose accounts are taken. */
|
|
37
|
+
export declare function currentLines<T extends Pick<AgreementLine, "startsAt" | "endsAt">>(agreement: Pick<Agreement, "startsAt" | "endsAt">, lines: readonly T[], at: number): T[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Cents } from '../money/money';
|
|
2
|
+
import { Period } from './period';
|
|
3
|
+
import { Agreement, AgreementLine, LineBilling, LineRhythm } from './types';
|
|
4
|
+
/** A fixed line keeps its own rhythm; usage follows the agreement, it is read per period. */
|
|
5
|
+
export declare function rhythmOf(agreement: Pick<Agreement, "interval">, line: AgreementLine): LineRhythm;
|
|
6
|
+
/** Fixed lines bill in advance unless told otherwise; usage is only known afterwards. */
|
|
7
|
+
export declare function timingOf(line: AgreementLine): LineBilling;
|
|
8
|
+
/** An invoice line an agreement wrote earlier, as far as scheduling cares. */
|
|
9
|
+
export interface BilledLine {
|
|
10
|
+
agreementLineId: string;
|
|
11
|
+
servicePeriod: Period;
|
|
12
|
+
quantity: number;
|
|
13
|
+
unitPriceCents: Cents;
|
|
14
|
+
correction?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** One window of one line that is due and not yet on an invoice. */
|
|
17
|
+
export interface Charge {
|
|
18
|
+
lineId: string;
|
|
19
|
+
/** The line's whole window in its rhythm: what "billed" is matched on. */
|
|
20
|
+
window: Period;
|
|
21
|
+
/** The part of it the line ran, which the invoice line says it covers. */
|
|
22
|
+
part: Period;
|
|
23
|
+
/** Fixed lines: the share of the window billed, 1 unless prorated. Usage: 1. */
|
|
24
|
+
share: number;
|
|
25
|
+
timing: LineBilling;
|
|
26
|
+
}
|
|
27
|
+
/** A window billed before the line's term changed, settled at the price it was billed at. */
|
|
28
|
+
export interface Correction {
|
|
29
|
+
lineId: string;
|
|
30
|
+
window: Period;
|
|
31
|
+
/** The days added (positive quantity) or taken off (negative). */
|
|
32
|
+
part: Period;
|
|
33
|
+
quantity: number;
|
|
34
|
+
unitPriceCents: Cents;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* What an agreement owes on `now`, per line: every window that is due minus the windows an
|
|
38
|
+
* invoice already covers, plus corrections for billed windows whose days changed since.
|
|
39
|
+
*
|
|
40
|
+
* Due: a window in advance on its first day, in arrears after its last, plus grace either way,
|
|
41
|
+
* so "fixed October" and "usage September" land on the same run. A window a line only partly
|
|
42
|
+
* runs, and a correction, wait for the window's end — they ride along with the next regular
|
|
43
|
+
* invoice instead of producing one of their own.
|
|
44
|
+
*/
|
|
45
|
+
export declare function dueCharges(agreement: Pick<Agreement, "startsAt" | "endsAt" | "interval" | "graceDays" | "proration">, lines: readonly AgreementLine[], billed: readonly BilledLine[], now: number): {
|
|
46
|
+
charges: Charge[];
|
|
47
|
+
corrections: Correction[];
|
|
48
|
+
};
|
|
49
|
+
export interface RunSpan {
|
|
50
|
+
timing: LineBilling;
|
|
51
|
+
from: number;
|
|
52
|
+
/** Exclusive. */
|
|
53
|
+
to: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* What a run or an invoice covers, for a list: per timing the first and last service day, and how
|
|
57
|
+
* many corrections — "Vooraf okt · Achteraf sep · 1 correctie".
|
|
58
|
+
*/
|
|
59
|
+
export declare function runSpans(lines: readonly {
|
|
60
|
+
timing: LineBilling;
|
|
61
|
+
servicePeriod: Period;
|
|
62
|
+
correction?: boolean;
|
|
63
|
+
}[]): {
|
|
64
|
+
spans: RunSpan[];
|
|
65
|
+
corrections: number;
|
|
66
|
+
};
|
|
67
|
+
export type DueStatus = "ready" | "needs_quantity" | "unrated" | "error";
|
|
68
|
+
/** One row of "to bill": what a run on this agreement would do right now. */
|
|
69
|
+
export interface DueRun {
|
|
70
|
+
agreementId: string;
|
|
71
|
+
title: string;
|
|
72
|
+
companyId: string;
|
|
73
|
+
status: DueStatus;
|
|
74
|
+
netCents: number;
|
|
75
|
+
spans: RunSpan[];
|
|
76
|
+
corrections: number;
|
|
77
|
+
needsQuantity: number;
|
|
78
|
+
unrated: number;
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { PriceBand } from '../catalog/rating';
|
|
2
|
+
import { Cents } from '../money/money';
|
|
3
|
+
/**
|
|
4
|
+
* A standing arrangement with a customer that bills every period: number rental, licences, and
|
|
5
|
+
* the usage of an account at a source (calls on a PBX tenant).
|
|
6
|
+
*
|
|
7
|
+
* It does not hold the invoices it produced. Each invoice line it wrote carries the agreement line
|
|
8
|
+
* and the service period it covers, and those lines are the whole "what was billed" state.
|
|
9
|
+
*/
|
|
10
|
+
export interface Agreement {
|
|
11
|
+
id: string;
|
|
12
|
+
organizationId: string;
|
|
13
|
+
title: string;
|
|
14
|
+
companyId: string;
|
|
15
|
+
businessEntityId?: string;
|
|
16
|
+
currency: string;
|
|
17
|
+
status: AgreementStatus;
|
|
18
|
+
/** Epoch ms. The first period starts on the interval boundary at or before this. */
|
|
19
|
+
startsAt: number;
|
|
20
|
+
/** Exclusive: the first moment it no longer runs. A screen shows the day before as "last day". */
|
|
21
|
+
endsAt?: number;
|
|
22
|
+
/**
|
|
23
|
+
* A fixed line that starts or stops inside a period: `false` bills whole periods only (from the
|
|
24
|
+
* first full one, up to the last full one); `true` bills the days it ran, pro rata, and settles
|
|
25
|
+
* a window billed in advance when the line's term changes afterwards.
|
|
26
|
+
*/
|
|
27
|
+
proration?: boolean;
|
|
28
|
+
interval: AgreementInterval;
|
|
29
|
+
/**
|
|
30
|
+
* Days after a window opens (in advance) or closes (in arrears) before it is due: late call
|
|
31
|
+
* records still arrive, and both land on the same run.
|
|
32
|
+
*/
|
|
33
|
+
graceDays: number;
|
|
34
|
+
paymentTermKey?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The account usage is read from (a PBX tenant). Usage lines without an account of their own
|
|
37
|
+
* take this one; a line may still name another, say texts from a second source.
|
|
38
|
+
*/
|
|
39
|
+
sourceAccount?: UsageAccountRef;
|
|
40
|
+
lines: AgreementLine[];
|
|
41
|
+
createdBy: string;
|
|
42
|
+
createdAt?: number;
|
|
43
|
+
updatedAt?: number;
|
|
44
|
+
}
|
|
45
|
+
export type AgreementStatus = "draft" | "active" | "ended";
|
|
46
|
+
export type AgreementInterval = "month" | "quarter" | "year";
|
|
47
|
+
export interface AgreementLine {
|
|
48
|
+
id: string;
|
|
49
|
+
productId: string;
|
|
50
|
+
/** Copied from the product, like a transaction line: the agreement must stay readable. */
|
|
51
|
+
name: string;
|
|
52
|
+
/**
|
|
53
|
+
* Where the quantity comes from. How it is priced is the product's business (`pricing`), and a
|
|
54
|
+
* price for this agreement is a price agreement with `contractId` = the agreement.
|
|
55
|
+
*/
|
|
56
|
+
quantity: LineQuantity;
|
|
57
|
+
/** Taken off the quantity before the price: "the first 5 are included". */
|
|
58
|
+
included?: number;
|
|
59
|
+
/**
|
|
60
|
+
* When this line runs, inside the agreement's own term. Absent = with the agreement. A change
|
|
61
|
+
* "from 1 May" ends one line and starts another, so billed periods keep saying what they said.
|
|
62
|
+
*/
|
|
63
|
+
startsAt?: number;
|
|
64
|
+
/** Exclusive, like `Agreement.endsAt`. */
|
|
65
|
+
endsAt?: number;
|
|
66
|
+
/** A fixed line only: usage is known after the period, so it always bills in arrears. */
|
|
67
|
+
billing?: LineBilling;
|
|
68
|
+
/** A fixed line's own rhythm; never shorter than the agreement's. Absent = the agreement's. */
|
|
69
|
+
every?: LineRhythm;
|
|
70
|
+
}
|
|
71
|
+
export type LineBilling = "advance" | "arrears";
|
|
72
|
+
export type LineRhythm = AgreementInterval | "once";
|
|
73
|
+
export type LineQuantity = {
|
|
74
|
+
from: "fixed";
|
|
75
|
+
value: number;
|
|
76
|
+
}
|
|
77
|
+
/** Entered per period, when billing. */
|
|
78
|
+
| {
|
|
79
|
+
from: "manual";
|
|
80
|
+
}
|
|
81
|
+
/** Read from a usage source. No `accountRef` = the agreement's `sourceAccount`. */
|
|
82
|
+
| {
|
|
83
|
+
from: "meter";
|
|
84
|
+
app: string;
|
|
85
|
+
meter: string;
|
|
86
|
+
accountRef?: string;
|
|
87
|
+
};
|
|
88
|
+
/** An account at a usage source: which app reports it, and its id there. */
|
|
89
|
+
export interface UsageAccountRef {
|
|
90
|
+
app: string;
|
|
91
|
+
accountRef: string;
|
|
92
|
+
}
|
|
93
|
+
/** The account a meter line reads, own or inherited. `null` when neither names one. */
|
|
94
|
+
export declare function usageAccountOf(agreement: Pick<Agreement, "sourceAccount">, line: AgreementLine): UsageAccountRef | null;
|
|
95
|
+
/** Every account an agreement reads usage from, deduplicated. */
|
|
96
|
+
export declare function usageAccountsOf(agreement: Pick<Agreement, "sourceAccount" | "lines">): UsageAccountRef[];
|
|
97
|
+
export interface SaveAgreementRequest extends Partial<Omit<Agreement, "id" | "organizationId" | "createdBy" | "createdAt" | "updatedAt">> {
|
|
98
|
+
id?: string;
|
|
99
|
+
}
|
|
100
|
+
/** What a billing run would put on the invoice, before anything is written. */
|
|
101
|
+
export interface AgreementRunPreview {
|
|
102
|
+
/** The moment it was computed for: everything due by then and not yet billed. */
|
|
103
|
+
at: number;
|
|
104
|
+
/** The earliest and latest service day on it. Absent when nothing is due. */
|
|
105
|
+
covers?: {
|
|
106
|
+
from: number;
|
|
107
|
+
to: number;
|
|
108
|
+
};
|
|
109
|
+
lines: RunLine[];
|
|
110
|
+
netCents: Cents;
|
|
111
|
+
/** Records without a rate, per dimension combination. Billing needs `skipUnrated` then. */
|
|
112
|
+
unrated: {
|
|
113
|
+
meter: string;
|
|
114
|
+
dimensions: Record<string, string>;
|
|
115
|
+
count: number;
|
|
116
|
+
}[];
|
|
117
|
+
/** Quantity keys of manual lines still waiting for a number. Billing refuses until given. */
|
|
118
|
+
needsQuantity: string[];
|
|
119
|
+
/** Records seen across all meter lines, rated or not. */
|
|
120
|
+
records: number;
|
|
121
|
+
}
|
|
122
|
+
export interface RunLine {
|
|
123
|
+
/** The agreement line it came from; a rated line yields one invoice line per rate. */
|
|
124
|
+
lineId: string;
|
|
125
|
+
name: string;
|
|
126
|
+
productId: string;
|
|
127
|
+
unitKey: string;
|
|
128
|
+
taxRateKey: string;
|
|
129
|
+
quantity: number;
|
|
130
|
+
unitPriceCents: Cents;
|
|
131
|
+
priceSourceKey: string;
|
|
132
|
+
/** What it covers: the part of the line's window it ran. */
|
|
133
|
+
servicePeriod: {
|
|
134
|
+
from: number;
|
|
135
|
+
to: number;
|
|
136
|
+
};
|
|
137
|
+
timing: LineBilling;
|
|
138
|
+
/** Settles a window that was billed before the line's term changed. */
|
|
139
|
+
correction?: boolean;
|
|
140
|
+
/** Manual lines: the key its entered quantity travels under — see `quantityKey`. */
|
|
141
|
+
quantityKey?: string;
|
|
142
|
+
/** A fixed line that ran part of its window: the share billed. */
|
|
143
|
+
share?: number;
|
|
144
|
+
/** Hours read from the time app: the invoice stamps them, so nothing bills them again. */
|
|
145
|
+
timeEntryIds?: string[];
|
|
146
|
+
usage?: LineUsage;
|
|
147
|
+
}
|
|
148
|
+
/** Manual quantities are entered per line and window: a catch-up run may ask for two months. */
|
|
149
|
+
export declare function quantityKey(lineId: string, windowFrom: number): string;
|
|
150
|
+
/**
|
|
151
|
+
* How a usage line was counted, kept on the invoice line. A rated group bills 1 × its total (it
|
|
152
|
+
* has no whole-cent unit price), so the real quantity and the tier bands live here.
|
|
153
|
+
*/
|
|
154
|
+
export interface LineUsage {
|
|
155
|
+
from: "manual" | "meter";
|
|
156
|
+
/** Rated lines: the rate this group is. */
|
|
157
|
+
rateId?: string;
|
|
158
|
+
records?: number;
|
|
159
|
+
/** Aggregated, before `included`. */
|
|
160
|
+
measured: number;
|
|
161
|
+
included?: number;
|
|
162
|
+
/** For display: the meter's display unit (`minute`) or the product's unit. */
|
|
163
|
+
unit: string;
|
|
164
|
+
/** Meter units per display unit (60 seconds per minute). */
|
|
165
|
+
per: number;
|
|
166
|
+
bands?: PriceBand[];
|
|
167
|
+
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { InteractionResolution } from '../interaction/resolution';
|
|
1
2
|
export interface Inbox {
|
|
2
3
|
id: string;
|
|
3
4
|
organizationId: string;
|
|
@@ -23,4 +24,6 @@ export interface Inbox {
|
|
|
23
24
|
* priority itself is rarely set.
|
|
24
25
|
*/
|
|
25
26
|
slaHours?: number;
|
|
27
|
+
/** This inbox's own resolutions, next to `SYSTEM_RESOLUTIONS`. Read through `resolutionsFor`. */
|
|
28
|
+
resolutions?: InteractionResolution[];
|
|
26
29
|
}
|