@wirecat/cli-core 0.556.0 → 0.557.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/CHANGELOG.md +21 -0
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/domain/finance.d.ts +40 -0
- package/dist/domain/finance.js +39 -0
- package/dist/domain/money.d.ts +29 -0
- package/dist/domain/money.js +98 -0
- package/dist/domain/ref-summaries.js +2 -0
- package/dist/domain/references.d.ts +1 -1
- package/dist/domain/references.js +2 -0
- package/dist/errors/reasons.js +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mail/cli/review.js +0 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/services/proposals/kinds/finance.d.ts +4 -0
- package/dist/services/proposals/kinds/finance.js +40 -0
- package/dist/services/proposals/registry.js +3 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/sqlite/entity-types.d.ts +2 -0
- package/dist/store/sqlite/entity-types.js +2 -0
- package/dist/store/sqlite/finance.d.ts +199 -0
- package/dist/store/sqlite/finance.js +514 -0
- package/dist/store/sqlite/knowledge.js +3 -1
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/migrations.generated.js +35 -0
- package/dist/store/sqlite/ref-summaries.js +33 -0
- package/dist/store/sqlite/schema.d.ts +1043 -0
- package/dist/store/sqlite/schema.js +98 -0
- package/dist/store/sqlite/things.js +2 -0
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.557.0 — 02.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Invoices and payments in the store (store version 45).** `store.finance` adds, lists and shows invoices with
|
|
15
|
+
their lines and payments, allocates a payment to invoices and sums what open invoices still owe per currency
|
|
16
|
+
(`openTotals`). Money is an integer in the currency's minor units (`CURRENCY_SCALES`, ISO 4217), passed and read as
|
|
17
|
+
decimal strings; an unknown currency is refused and nothing is converted. A total must equal its lines, and an
|
|
18
|
+
allocation never exceeds the payment or the invoice or crosses currencies; triggers back the code. The status
|
|
19
|
+
follows confirmed allocations; only the owner issues a draft or voids an invoice.
|
|
20
|
+
- **An agent's or an import's invoice or payment is a proposal.** The owner's entry is confirmed at once; a bot's
|
|
21
|
+
waits as `invoice` or `payment` in `proposalRegistry`, and only the owner decides it. A pending payment's
|
|
22
|
+
allocations never count against an invoice, so they never block the owner's own. Confirming one counts them in the
|
|
23
|
+
same write, or is refused while other confirmed payments already cover the invoice; rejecting drops them.
|
|
24
|
+
- **`invoice:<id>` and `payment:<id>` references,** with `refs.summaries` titles such as
|
|
25
|
+
`42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)`.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **Mail `review` gives three examples**, as the command lint asks; `--waiting` stays in its help.
|
|
30
|
+
|
|
10
31
|
## 0.556.0 — 02.11.2026
|
|
11
32
|
|
|
12
33
|
### Added
|
|
@@ -10,13 +10,13 @@ export declare const calendarTools: ({
|
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: string;
|
|
12
12
|
properties: {
|
|
13
|
-
limit?: undefined;
|
|
14
13
|
account_id: {
|
|
15
14
|
type: string;
|
|
16
15
|
minimum: number;
|
|
17
16
|
};
|
|
18
17
|
query?: undefined;
|
|
19
18
|
timezone?: undefined;
|
|
19
|
+
limit?: undefined;
|
|
20
20
|
page?: undefined;
|
|
21
21
|
event?: undefined;
|
|
22
22
|
} | {
|
|
@@ -45,9 +45,9 @@ export declare const calendarTools: ({
|
|
|
45
45
|
};
|
|
46
46
|
event?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
limit?: undefined;
|
|
49
48
|
query?: undefined;
|
|
50
49
|
timezone?: undefined;
|
|
50
|
+
limit?: undefined;
|
|
51
51
|
page?: undefined;
|
|
52
52
|
event: {
|
|
53
53
|
type: string;
|
|
@@ -58,9 +58,9 @@ export declare const calendarTools: ({
|
|
|
58
58
|
minimum: number;
|
|
59
59
|
};
|
|
60
60
|
} | {
|
|
61
|
-
limit?: undefined;
|
|
62
61
|
query?: undefined;
|
|
63
62
|
timezone?: undefined;
|
|
63
|
+
limit?: undefined;
|
|
64
64
|
page?: undefined;
|
|
65
65
|
event?: undefined;
|
|
66
66
|
account_id?: undefined;
|
|
@@ -80,13 +80,13 @@ export declare const calendarTools: ({
|
|
|
80
80
|
inputSchema: {
|
|
81
81
|
type: string;
|
|
82
82
|
properties: {
|
|
83
|
-
limit?: undefined;
|
|
84
83
|
account_id: {
|
|
85
84
|
type: string;
|
|
86
85
|
minimum: number;
|
|
87
86
|
};
|
|
88
87
|
query?: undefined;
|
|
89
88
|
timezone?: undefined;
|
|
89
|
+
limit?: undefined;
|
|
90
90
|
page?: undefined;
|
|
91
91
|
event?: undefined;
|
|
92
92
|
} | {
|
|
@@ -115,9 +115,9 @@ export declare const calendarTools: ({
|
|
|
115
115
|
};
|
|
116
116
|
event?: undefined;
|
|
117
117
|
} | {
|
|
118
|
-
limit?: undefined;
|
|
119
118
|
query?: undefined;
|
|
120
119
|
timezone?: undefined;
|
|
120
|
+
limit?: undefined;
|
|
121
121
|
page?: undefined;
|
|
122
122
|
event: {
|
|
123
123
|
type: string;
|
|
@@ -128,9 +128,9 @@ export declare const calendarTools: ({
|
|
|
128
128
|
minimum: number;
|
|
129
129
|
};
|
|
130
130
|
} | {
|
|
131
|
-
limit?: undefined;
|
|
132
131
|
query?: undefined;
|
|
133
132
|
timezone?: undefined;
|
|
133
|
+
limit?: undefined;
|
|
134
134
|
page?: undefined;
|
|
135
135
|
event?: undefined;
|
|
136
136
|
account_id?: undefined;
|
|
@@ -150,13 +150,13 @@ export declare const calendarTools: ({
|
|
|
150
150
|
inputSchema: {
|
|
151
151
|
type: string;
|
|
152
152
|
properties: {
|
|
153
|
-
limit?: undefined;
|
|
154
153
|
account_id: {
|
|
155
154
|
type: string;
|
|
156
155
|
minimum: number;
|
|
157
156
|
};
|
|
158
157
|
query?: undefined;
|
|
159
158
|
timezone?: undefined;
|
|
159
|
+
limit?: undefined;
|
|
160
160
|
page?: undefined;
|
|
161
161
|
event?: undefined;
|
|
162
162
|
} | {
|
|
@@ -185,9 +185,9 @@ export declare const calendarTools: ({
|
|
|
185
185
|
};
|
|
186
186
|
event?: undefined;
|
|
187
187
|
} | {
|
|
188
|
-
limit?: undefined;
|
|
189
188
|
query?: undefined;
|
|
190
189
|
timezone?: undefined;
|
|
190
|
+
limit?: undefined;
|
|
191
191
|
page?: undefined;
|
|
192
192
|
event: {
|
|
193
193
|
type: string;
|
|
@@ -198,9 +198,9 @@ export declare const calendarTools: ({
|
|
|
198
198
|
minimum: number;
|
|
199
199
|
};
|
|
200
200
|
} | {
|
|
201
|
-
limit?: undefined;
|
|
202
201
|
query?: undefined;
|
|
203
202
|
timezone?: undefined;
|
|
203
|
+
limit?: undefined;
|
|
204
204
|
page?: undefined;
|
|
205
205
|
event?: undefined;
|
|
206
206
|
account_id?: undefined;
|
|
@@ -220,13 +220,13 @@ export declare const calendarTools: ({
|
|
|
220
220
|
inputSchema: {
|
|
221
221
|
type: string;
|
|
222
222
|
properties: {
|
|
223
|
-
limit?: undefined;
|
|
224
223
|
account_id: {
|
|
225
224
|
type: string;
|
|
226
225
|
minimum: number;
|
|
227
226
|
};
|
|
228
227
|
query?: undefined;
|
|
229
228
|
timezone?: undefined;
|
|
229
|
+
limit?: undefined;
|
|
230
230
|
page?: undefined;
|
|
231
231
|
event?: undefined;
|
|
232
232
|
} | {
|
|
@@ -255,9 +255,9 @@ export declare const calendarTools: ({
|
|
|
255
255
|
};
|
|
256
256
|
event?: undefined;
|
|
257
257
|
} | {
|
|
258
|
-
limit?: undefined;
|
|
259
258
|
query?: undefined;
|
|
260
259
|
timezone?: undefined;
|
|
260
|
+
limit?: undefined;
|
|
261
261
|
page?: undefined;
|
|
262
262
|
event: {
|
|
263
263
|
type: string;
|
|
@@ -268,9 +268,9 @@ export declare const calendarTools: ({
|
|
|
268
268
|
minimum: number;
|
|
269
269
|
};
|
|
270
270
|
} | {
|
|
271
|
-
limit?: undefined;
|
|
272
271
|
query?: undefined;
|
|
273
272
|
timezone?: undefined;
|
|
273
|
+
limit?: undefined;
|
|
274
274
|
page?: undefined;
|
|
275
275
|
event?: undefined;
|
|
276
276
|
account_id?: undefined;
|
|
@@ -3,9 +3,9 @@ import type { CacheDatabase } from "../../store/driver.js";
|
|
|
3
3
|
import type { Messenger } from "./context.js";
|
|
4
4
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
5
5
|
export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
6
|
-
error?: undefined;
|
|
7
6
|
path: string;
|
|
8
7
|
exists: boolean;
|
|
8
|
+
error?: undefined;
|
|
9
9
|
} | {
|
|
10
10
|
path: string;
|
|
11
11
|
exists: boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const INVOICE_DIRECTIONS: readonly ["receivable", "payable"];
|
|
2
|
+
export declare const INVOICE_STATUSES: readonly ["draft", "issued", "partly_paid", "paid", "void"];
|
|
3
|
+
/** `in` is money to the owner, `out` money from the owner. */
|
|
4
|
+
export declare const MONEY_DIRECTIONS: readonly ["in", "out"];
|
|
5
|
+
/** The owner's entry is confirmed at once; an agent's or an import's waits as proposed. */
|
|
6
|
+
export declare const FINANCE_REVIEWS: readonly ["proposed", "confirmed", "rejected"];
|
|
7
|
+
export type InvoiceDirection = (typeof INVOICE_DIRECTIONS)[number];
|
|
8
|
+
export type InvoiceStatus = (typeof INVOICE_STATUSES)[number];
|
|
9
|
+
export type MoneyDirection = (typeof MONEY_DIRECTIONS)[number];
|
|
10
|
+
export type FinanceReview = (typeof FINANCE_REVIEWS)[number];
|
|
11
|
+
/** An invoice's status from what confirmed payments settle; `draft` and `void` are the owner's and stay. */
|
|
12
|
+
export declare const settledStatus: (current: InvoiceStatus, total: number, paid: number) => InvoiceStatus;
|
|
13
|
+
export interface InvoiceTitleFields {
|
|
14
|
+
id: number;
|
|
15
|
+
number: string | null;
|
|
16
|
+
direction: InvoiceDirection;
|
|
17
|
+
total: number;
|
|
18
|
+
currency: string;
|
|
19
|
+
counterpartyName: string | null;
|
|
20
|
+
dueDate: string | null;
|
|
21
|
+
status: InvoiceStatus;
|
|
22
|
+
review: FinanceReview;
|
|
23
|
+
}
|
|
24
|
+
/** "42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)": a ref summary's title, which `refLabel` prefixes. */
|
|
25
|
+
export declare const invoiceLine: (invoice: InvoiceTitleFields) => string;
|
|
26
|
+
export interface PaymentTitleFields {
|
|
27
|
+
direction: MoneyDirection;
|
|
28
|
+
amount: number;
|
|
29
|
+
currency: string;
|
|
30
|
+
/** Who paid the owner, or whom the owner paid. */
|
|
31
|
+
partyName: string | null;
|
|
32
|
+
date: string;
|
|
33
|
+
review: FinanceReview;
|
|
34
|
+
}
|
|
35
|
+
/** "50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
|
|
36
|
+
export declare const paymentLine: (payment: PaymentTitleFields) => string;
|
|
37
|
+
/** "Invoice 42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)". */
|
|
38
|
+
export declare const invoiceTitle: (invoice: InvoiceTitleFields) => string;
|
|
39
|
+
/** "Payment · 50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
|
|
40
|
+
export declare const paymentEntryTitle: (payment: PaymentTitleFields) => string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { moneyLabel } from "./money.js";
|
|
2
|
+
export const INVOICE_DIRECTIONS = ["receivable", "payable"];
|
|
3
|
+
export const INVOICE_STATUSES = ["draft", "issued", "partly_paid", "paid", "void"];
|
|
4
|
+
/** `in` is money to the owner, `out` money from the owner. */
|
|
5
|
+
export const MONEY_DIRECTIONS = ["in", "out"];
|
|
6
|
+
/** The owner's entry is confirmed at once; an agent's or an import's waits as proposed. */
|
|
7
|
+
export const FINANCE_REVIEWS = ["proposed", "confirmed", "rejected"];
|
|
8
|
+
/** An invoice's status from what confirmed payments settle; `draft` and `void` are the owner's and stay. */
|
|
9
|
+
export const settledStatus = (current, total, paid) => {
|
|
10
|
+
if (current === "draft" || current === "void")
|
|
11
|
+
return current;
|
|
12
|
+
if (paid <= 0)
|
|
13
|
+
return "issued";
|
|
14
|
+
return paid < total ? "partly_paid" : "paid";
|
|
15
|
+
};
|
|
16
|
+
const dayMonth = (date) => (date === null ? null : `${date.slice(8, 10)}.${date.slice(5, 7)}`);
|
|
17
|
+
const notes = (...words) => {
|
|
18
|
+
const kept = words.filter((word) => word !== null);
|
|
19
|
+
return kept.length === 0 ? "" : ` (${kept.join(", ")})`;
|
|
20
|
+
};
|
|
21
|
+
/** "42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)": a ref summary's title, which `refLabel` prefixes. */
|
|
22
|
+
export const invoiceLine = (invoice) => {
|
|
23
|
+
const party = invoice.counterpartyName === null
|
|
24
|
+
? ""
|
|
25
|
+
: `${invoice.direction === "receivable" ? " to" : " from"} ${invoice.counterpartyName}`;
|
|
26
|
+
const due = invoice.dueDate === null ? "" : `, due ${dayMonth(invoice.dueDate)}`;
|
|
27
|
+
return (`${invoice.number ?? `#${invoice.id}`} · ${moneyLabel(invoice.total, invoice.currency)}${party}${due}` +
|
|
28
|
+
notes(invoice.status.replace("_", " "), invoice.review === "confirmed" ? null : invoice.review));
|
|
29
|
+
};
|
|
30
|
+
/** "50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
|
|
31
|
+
export const paymentLine = (payment) => {
|
|
32
|
+
const party = payment.partyName === null ? "" : `${payment.direction === "in" ? " from" : " to"} ${payment.partyName}`;
|
|
33
|
+
return (`${moneyLabel(payment.amount, payment.currency)}${party}, ${dayMonth(payment.date)}` +
|
|
34
|
+
notes(payment.review === "confirmed" ? null : payment.review));
|
|
35
|
+
};
|
|
36
|
+
/** "Invoice 42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)". */
|
|
37
|
+
export const invoiceTitle = (invoice) => `Invoice ${invoiceLine(invoice)}`;
|
|
38
|
+
/** "Payment · 50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
|
|
39
|
+
export const paymentEntryTitle = (payment) => `Payment · ${paymentLine(payment)}`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minor-unit digits per ISO 4217 for the currencies the store keeps money in. A currency outside the list is refused
|
|
3
|
+
* rather than guessed: a wrong scale would store every amount 100 times off.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CURRENCY_SCALES: Readonly<Record<string, number>>;
|
|
6
|
+
/** An exact amount as callers read and write it: a decimal string with the currency's scale, never a float. */
|
|
7
|
+
export interface Money {
|
|
8
|
+
amount: string;
|
|
9
|
+
currency: string;
|
|
10
|
+
}
|
|
11
|
+
/** A non-negative decimal as an integer of `scale` decimal places; `undefined` when it is not one. */
|
|
12
|
+
export declare const parseDecimal: (text: string, scale: number) => bigint | undefined;
|
|
13
|
+
export declare const formatDecimal: (units: bigint | number, scale: number) => string;
|
|
14
|
+
/** `50000.00` as `50 000.00`: groups of three in the whole part, as a list prints an amount. */
|
|
15
|
+
export declare const groupDigits: (decimal: string) => string;
|
|
16
|
+
export declare const currencyOf: (text: unknown) => string;
|
|
17
|
+
export declare const scaleOf: (currency: string) => number;
|
|
18
|
+
/** Minor units of `currency` for a decimal string such as "50000.00"; refuses more decimals than the currency has. */
|
|
19
|
+
export declare const minorUnits: (amount: unknown, currency: string, what?: string) => number;
|
|
20
|
+
export declare const moneyOf: (units: number, currency: string) => Money;
|
|
21
|
+
/** "50 000.00 RUB". */
|
|
22
|
+
export declare const moneyLabel: (units: number, currency: string) => string;
|
|
23
|
+
/** A quantity such as "1.5" (hours, days), kept as text: at most 3 decimals, more than 0. */
|
|
24
|
+
export declare const quantityOf: (text: unknown) => {
|
|
25
|
+
text: string;
|
|
26
|
+
thousandths: bigint;
|
|
27
|
+
};
|
|
28
|
+
/** Quantity times unit price in minor units, half rounded up. */
|
|
29
|
+
export declare const lineUnits: (thousandths: bigint, unitPrice: number) => number;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
/**
|
|
3
|
+
* Minor-unit digits per ISO 4217 for the currencies the store keeps money in. A currency outside the list is refused
|
|
4
|
+
* rather than guessed: a wrong scale would store every amount 100 times off.
|
|
5
|
+
*/
|
|
6
|
+
export const CURRENCY_SCALES = {
|
|
7
|
+
AED: 2,
|
|
8
|
+
AMD: 2,
|
|
9
|
+
AUD: 2,
|
|
10
|
+
BHD: 3,
|
|
11
|
+
BYN: 2,
|
|
12
|
+
CAD: 2,
|
|
13
|
+
CHF: 2,
|
|
14
|
+
CLP: 0,
|
|
15
|
+
CNY: 2,
|
|
16
|
+
CZK: 2,
|
|
17
|
+
DKK: 2,
|
|
18
|
+
EUR: 2,
|
|
19
|
+
GBP: 2,
|
|
20
|
+
GEL: 2,
|
|
21
|
+
HUF: 2,
|
|
22
|
+
ILS: 2,
|
|
23
|
+
INR: 2,
|
|
24
|
+
ISK: 0,
|
|
25
|
+
JPY: 0,
|
|
26
|
+
KGS: 2,
|
|
27
|
+
KRW: 0,
|
|
28
|
+
KWD: 3,
|
|
29
|
+
KZT: 2,
|
|
30
|
+
NOK: 2,
|
|
31
|
+
PLN: 2,
|
|
32
|
+
RSD: 2,
|
|
33
|
+
RUB: 2,
|
|
34
|
+
SEK: 2,
|
|
35
|
+
THB: 2,
|
|
36
|
+
TRY: 2,
|
|
37
|
+
UAH: 2,
|
|
38
|
+
USD: 2,
|
|
39
|
+
UZS: 2,
|
|
40
|
+
VND: 0,
|
|
41
|
+
};
|
|
42
|
+
const refuse = (message, reason) => {
|
|
43
|
+
throw new CliError("validation_error", message, { reason });
|
|
44
|
+
};
|
|
45
|
+
const DECIMAL = /^(\d{1,15})(?:\.(\d+))?$/;
|
|
46
|
+
/** A non-negative decimal as an integer of `scale` decimal places; `undefined` when it is not one. */
|
|
47
|
+
export const parseDecimal = (text, scale) => {
|
|
48
|
+
const match = DECIMAL.exec(text.trim());
|
|
49
|
+
if (!match || (match[2] ?? "").length > scale)
|
|
50
|
+
return undefined;
|
|
51
|
+
return BigInt(`${match[1]}${(match[2] ?? "").padEnd(scale, "0")}`);
|
|
52
|
+
};
|
|
53
|
+
export const formatDecimal = (units, scale) => {
|
|
54
|
+
const digits = BigInt(units)
|
|
55
|
+
.toString()
|
|
56
|
+
.padStart(scale + 1, "0");
|
|
57
|
+
return scale === 0 ? digits : `${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
|
|
58
|
+
};
|
|
59
|
+
/** `50000.00` as `50 000.00`: groups of three in the whole part, as a list prints an amount. */
|
|
60
|
+
export const groupDigits = (decimal) => {
|
|
61
|
+
const [whole = "0", fraction] = decimal.split(".");
|
|
62
|
+
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
|
63
|
+
return fraction === undefined ? grouped : `${grouped}.${fraction}`;
|
|
64
|
+
};
|
|
65
|
+
export const currencyOf = (text) => {
|
|
66
|
+
const code = typeof text === "string" ? text.trim().toUpperCase() : "";
|
|
67
|
+
if (CURRENCY_SCALES[code] === undefined)
|
|
68
|
+
return refuse(`${typeof text === "string" && text.trim() ? text.trim() : "an empty currency"} is not a currency the store keeps; ` +
|
|
69
|
+
`use one of ${Object.keys(CURRENCY_SCALES).join(", ")}`, "money.currency");
|
|
70
|
+
return code;
|
|
71
|
+
};
|
|
72
|
+
export const scaleOf = (currency) => CURRENCY_SCALES[currencyOf(currency)];
|
|
73
|
+
/** Minor units of `currency` for a decimal string such as "50000.00"; refuses more decimals than the currency has. */
|
|
74
|
+
export const minorUnits = (amount, currency, what = "an amount") => {
|
|
75
|
+
const scale = scaleOf(currency);
|
|
76
|
+
const units = typeof amount === "string" ? parseDecimal(amount, scale) : undefined;
|
|
77
|
+
if (units === undefined || units > BigInt(Number.MAX_SAFE_INTEGER))
|
|
78
|
+
return refuse(`${what} is a decimal string with at most ${scale} decimal${scale === 1 ? "" : "s"} for ${currency}, ` +
|
|
79
|
+
`such as "${formatDecimal(5_000 * 10 ** scale, scale)}"`, "money.amount");
|
|
80
|
+
return Number(units);
|
|
81
|
+
};
|
|
82
|
+
export const moneyOf = (units, currency) => ({
|
|
83
|
+
amount: formatDecimal(units, scaleOf(currency)),
|
|
84
|
+
currency,
|
|
85
|
+
});
|
|
86
|
+
/** "50 000.00 RUB". */
|
|
87
|
+
export const moneyLabel = (units, currency) => `${groupDigits(formatDecimal(units, scaleOf(currency)))} ${currency}`;
|
|
88
|
+
const QUANTITY_SCALE = 3;
|
|
89
|
+
/** A quantity such as "1.5" (hours, days), kept as text: at most 3 decimals, more than 0. */
|
|
90
|
+
export const quantityOf = (text) => {
|
|
91
|
+
const thousandths = typeof text === "string" ? parseDecimal(text, QUANTITY_SCALE) : undefined;
|
|
92
|
+
if (thousandths === undefined || thousandths === 0n)
|
|
93
|
+
return refuse('a quantity is a decimal string above 0 with at most 3 decimals, such as "1.5"', "money.quantity");
|
|
94
|
+
const plain = formatDecimal(thousandths, QUANTITY_SCALE).replace(/\.?0+$/, "");
|
|
95
|
+
return { text: plain, thousandths };
|
|
96
|
+
};
|
|
97
|
+
/** Quantity times unit price in minor units, half rounded up. */
|
|
98
|
+
export const lineUnits = (thousandths, unitPrice) => Number((thousandths * BigInt(unitPrice) + 500n) / 1000n);
|
|
@@ -33,7 +33,7 @@ export type Reference = {
|
|
|
33
33
|
* Things named by their store id (a task also by its key or the task package's id). `entity` is the name
|
|
34
34
|
* before organizations and projects: it still parses, so an old file's reference resolves as not found.
|
|
35
35
|
*/
|
|
36
|
-
export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "entity"];
|
|
36
|
+
export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "invoice", "payment", "entity"];
|
|
37
37
|
export type SimpleReference = (typeof SIMPLE_REFERENCES)[number];
|
|
38
38
|
export type RefKindName = Exclude<Reference["type"], "entity">;
|
|
39
39
|
export interface RefKindSpec<R extends Reference = Reference> {
|
package/dist/errors/reasons.js
CHANGED
|
@@ -138,8 +138,23 @@ const OTHERS = [
|
|
|
138
138
|
validation("input.required", "the command needs an answer and cannot ask for one"),
|
|
139
139
|
validation("input.too-large", "the input is over its size limit"),
|
|
140
140
|
["meeting-project.stale", "configuration_error", "the meeting's project changed meanwhile"],
|
|
141
|
+
validation("finance.allocate", "the invoice or payment cannot take an allocation in its state"),
|
|
142
|
+
validation("finance.currency", "the payment and the invoice are in different currencies; nothing is converted"),
|
|
143
|
+
validation("finance.field", "a field of the invoice or payment is not valid"),
|
|
144
|
+
validation("finance.line-amount", "the line's amount is not its quantity times its unit price"),
|
|
145
|
+
validation("finance.lines", "the invoice's lines are not valid"),
|
|
146
|
+
validation("finance.over-invoice", "the allocation would settle more than the invoice's total"),
|
|
147
|
+
validation("finance.over-payment", "the allocation would use more than the payment's amount"),
|
|
148
|
+
["finance.owner-only", "permission_error", "only the owner does this with money"],
|
|
149
|
+
validation("finance.party", "a party is a person: or organization: reference"),
|
|
150
|
+
validation("finance.ref", "the reference names no invoice or payment"),
|
|
151
|
+
validation("finance.status", "the invoice's status does not allow this"),
|
|
152
|
+
validation("finance.total", "the invoice's total is not the sum of its lines"),
|
|
141
153
|
validation("memory.evidence-required", "a memory needs its evidence"),
|
|
142
154
|
validation("memory.scope-required", "a memory needs its scope"),
|
|
155
|
+
validation("money.amount", "the amount is not a decimal string in the currency's scale"),
|
|
156
|
+
validation("money.currency", "the currency is not one the store keeps money in"),
|
|
157
|
+
validation("money.quantity", "the quantity is not a decimal above 0"),
|
|
143
158
|
["model.not-downloaded", "not_found", "the model is not downloaded on this machine"],
|
|
144
159
|
["person.no-owner", "not_found", "no person is marked as the owner in the store"],
|
|
145
160
|
["transcription.no-key", "authentication_error", "no OpenAI key is set for transcription"],
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { bareCheck, type CheckWords, checkWords } from "./domain/checks.js";
|
|
2
2
|
export type { CounterField, CounterObservation, CounterObservations, CounterState } from "./domain/counters.js";
|
|
3
3
|
export { COUNTER_FIELDS, counterValue, observedCounters } from "./domain/counters.js";
|
|
4
|
+
export { FINANCE_REVIEWS, type FinanceReview, INVOICE_DIRECTIONS, INVOICE_STATUSES, type InvoiceDirection, type InvoiceStatus, MONEY_DIRECTIONS, type MoneyDirection, } from "./domain/finance.js";
|
|
4
5
|
export { type FormattedText, type HtmlFormatting, type MarkdownFormatting, type TextSpan, validateFormattedText, } from "./domain/formatting.js";
|
|
5
6
|
export { formatLocator, isLocator, type MessageLocator, parseLocator } from "./domain/locator.js";
|
|
6
7
|
export { type Markup, parseMarkdown } from "./domain/markdown.js";
|
|
7
8
|
export { parseMarkdownSpans } from "./domain/markdown-spans.js";
|
|
8
9
|
export type { MessageLink, MessagePermalink } from "./domain/message-link.js";
|
|
9
10
|
export type * from "./domain/models.js";
|
|
11
|
+
export { CURRENCY_SCALES, currencyOf, type Money, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
|
|
10
12
|
export { countedPoll } from "./domain/polls.js";
|
|
11
13
|
export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, type Kind, kindIn, kindOf, PROVIDERS, type ProviderEntry, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
|
|
12
14
|
export type { RankingGraphEvidence, RankingGraphLink } from "./domain/rankings-graph.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { bareCheck, checkWords } from "./domain/checks.js";
|
|
2
2
|
export { COUNTER_FIELDS, counterValue, observedCounters } from "./domain/counters.js";
|
|
3
|
+
export { FINANCE_REVIEWS, INVOICE_DIRECTIONS, INVOICE_STATUSES, MONEY_DIRECTIONS, } from "./domain/finance.js";
|
|
3
4
|
export { validateFormattedText, } from "./domain/formatting.js";
|
|
4
5
|
export { formatLocator, isLocator, parseLocator } from "./domain/locator.js";
|
|
5
6
|
export { parseMarkdown } from "./domain/markdown.js";
|
|
6
7
|
export { parseMarkdownSpans } from "./domain/markdown-spans.js";
|
|
8
|
+
export { CURRENCY_SCALES, currencyOf, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
|
|
7
9
|
export { countedPoll } from "./domain/polls.js";
|
|
8
10
|
export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
|
|
9
11
|
export { rankingOptions } from "./domain/rankings-options.js";
|
package/dist/mail/cli/review.js
CHANGED
|
@@ -188,7 +188,6 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
|
|
|
188
188
|
`${mail.app.command} personal review "date>2h"`,
|
|
189
189
|
`${mail.app.command} personal review --new`,
|
|
190
190
|
`${mail.app.command} personal review --unanswered 1d`,
|
|
191
|
-
`${mail.app.command} personal review --waiting 7d`,
|
|
192
191
|
],
|
|
193
192
|
effects: LOCAL_READ,
|
|
194
193
|
partial: true,
|