@wirecat/cli-core 0.556.0 → 0.558.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 +37 -0
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -1
- 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/entities/fact.d.ts +1 -12
- package/dist/entities/fact.js +0 -13
- package/dist/entities/index.d.ts +2 -12
- package/dist/entities/meeting-findings.d.ts +1 -1
- package/dist/errors/reasons.js +16 -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/findings-rules.d.ts +7 -4
- package/dist/meetings/findings-rules.js +11 -4
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/payment.d.ts +10 -6
- package/dist/meetings/payment.js +21 -6
- package/dist/services/project-status/index.d.ts +1 -1
- package/dist/services/project-status/index.js +7 -3
- package/dist/services/project-status/signals/payments.d.ts +3 -3
- package/dist/services/project-status/signals/payments.js +43 -17
- 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/evidence-findings.js +16 -8
- package/dist/store/index.d.ts +2 -1
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/entity-types.d.ts +2 -0
- package/dist/store/sqlite/entity-types.js +2 -0
- package/dist/store/sqlite/facts.d.ts +0 -13
- package/dist/store/sqlite/facts.js +5 -58
- package/dist/store/sqlite/finance.d.ts +203 -0
- package/dist/store/sqlite/finance.js +529 -0
- package/dist/store/sqlite/findings-core.js +5 -14
- package/dist/store/sqlite/knowledge.js +3 -1
- package/dist/store/sqlite/manifest.js +3 -0
- package/dist/store/sqlite/migrations.generated.js +43 -0
- package/dist/store/sqlite/open-store.js +2 -0
- package/dist/store/sqlite/payment-entries.d.ts +26 -0
- package/dist/store/sqlite/payment-entries.js +91 -0
- package/dist/store/sqlite/payment-facts.d.ts +12 -0
- package/dist/store/sqlite/payment-facts.js +92 -0
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- 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 -1
- package/dist/store/store.js +5 -0
- package/package.json +1 -1
- package/dist/cli/payment-query.d.ts +0 -8
- package/dist/cli/payment-query.js +0 -27
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,43 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.558.0 — 02.11.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **A payment is no longer a fact (store version 46).** Money lives only in `store.finance`. `facts.add` refuses type
|
|
15
|
+
`payment` with reason `fact.payment-retired`, naming `store.finance.addPayment`, and a trigger refuses one from an
|
|
16
|
+
older build. `FACT_TYPES`, the `fact` entity and `Fact` lose `payment`; `PaymentFilter`, `facts.list({ payment })`,
|
|
17
|
+
`PAYMENT_QUERY_FIELDS` and `paymentFilterOf` are gone. The first writable open moves stored payment facts into rows.
|
|
18
|
+
- **An accepted payment finding writes a finance row, not a fact.** A paid one is a `payment:` row, an expected one
|
|
19
|
+
an `invoice:` of one line, proposed by the agent and confirmed by the owner's acceptance. `supersedes` now names
|
|
20
|
+
the `invoice:` a paid one settles or a cancelled one voids, not a `fact:`. An expected one needs a `person:` or
|
|
21
|
+
`organization:` counterparty, and the amount must fit the currency's decimals. New optional field `paid_at`.
|
|
22
|
+
- **`projects status` reads money from `store.finance`.** The `payments` signal (version 3) lists payments and open
|
|
23
|
+
invoices by their `payment:` and `invoice:` refs, `status` being `proposed` or `confirmed`; the open total counts
|
|
24
|
+
confirmed invoices only. Finance rows gain `evidence`, and `addInvoice` and `addPayment` take it.
|
|
25
|
+
|
|
26
|
+
## 0.557.0 — 02.11.2026
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Invoices and payments in the store (store version 45).** `store.finance` adds, lists and shows invoices with
|
|
31
|
+
their lines and payments, allocates a payment to invoices and sums what open invoices still owe per currency
|
|
32
|
+
(`openTotals`). Money is an integer in the currency's minor units (`CURRENCY_SCALES`, ISO 4217), passed and read as
|
|
33
|
+
decimal strings; an unknown currency is refused and nothing is converted. A total must equal its lines, and an
|
|
34
|
+
allocation never exceeds the payment or the invoice or crosses currencies; triggers back the code. The status
|
|
35
|
+
follows confirmed allocations; only the owner issues a draft or voids an invoice.
|
|
36
|
+
- **An agent's or an import's invoice or payment is a proposal.** The owner's entry is confirmed at once; a bot's
|
|
37
|
+
waits as `invoice` or `payment` in `proposalRegistry`, and only the owner decides it. A pending payment's
|
|
38
|
+
allocations never count against an invoice, so they never block the owner's own. Confirming one counts them in the
|
|
39
|
+
same write, or is refused while other confirmed payments already cover the invoice; rejecting drops them.
|
|
40
|
+
- **`invoice:<id>` and `payment:<id>` references,** with `refs.summaries` titles such as
|
|
41
|
+
`42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)`.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Mail `review` gives three examples**, as the command lint asks; `--waiting` stays in its help.
|
|
46
|
+
|
|
10
47
|
## 0.556.0 — 02.11.2026
|
|
11
48
|
|
|
12
49
|
### 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;
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -111,7 +111,6 @@ export { type Saving, stored } from "./messenger/stored.js";
|
|
|
111
111
|
export { dateOrNone } from "./messenger/tasks-command.js";
|
|
112
112
|
export { MODEL_CATALOGUE_QUERY, type ModelCatalogueOptions } from "./model-catalogue-command.js";
|
|
113
113
|
export { positiveCount, window, withPaging } from "./paging.js";
|
|
114
|
-
export { PAYMENT_QUERY_FIELDS, paymentFilterOf } from "./payment-query.js";
|
|
115
114
|
export { type CommandPermissions, commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, type PermissionKeyOf, type PermissionKeySource, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
116
115
|
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
117
116
|
export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -101,7 +101,6 @@ export { stored } from "./messenger/stored.js";
|
|
|
101
101
|
export { dateOrNone } from "./messenger/tasks-command.js";
|
|
102
102
|
export { MODEL_CATALOGUE_QUERY } from "./model-catalogue-command.js";
|
|
103
103
|
export { positiveCount, window, withPaging } from "./paging.js";
|
|
104
|
-
export { PAYMENT_QUERY_FIELDS, paymentFilterOf } from "./payment-query.js";
|
|
105
104
|
export { commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
106
105
|
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
107
106
|
export { createProgram, run } from "./program.js";
|
|
@@ -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/entities/fact.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as v from "valibot";
|
|
|
2
2
|
export declare const fact: v.StrictObjectSchema<{
|
|
3
3
|
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
4
4
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
5
|
-
readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"
|
|
5
|
+
readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
6
6
|
readonly text: v.StringSchema<undefined>;
|
|
7
7
|
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
8
8
|
readonly project: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -29,17 +29,6 @@ export declare const fact: v.StrictObjectSchema<{
|
|
|
29
29
|
/** Who recorded it: the owner, or an agent whose fact waits as proposed. */
|
|
30
30
|
readonly origin: v.PicklistSchema<["owner", "agent"], undefined>;
|
|
31
31
|
readonly evidence: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
32
|
-
/** A `payment` fact's fields. */
|
|
33
|
-
readonly payment: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
|
|
34
|
-
readonly amount: v.StringSchema<undefined>;
|
|
35
|
-
readonly currency: v.StringSchema<undefined>;
|
|
36
|
-
readonly direction: v.PicklistSchema<readonly ["to_us", "from_us"], undefined>;
|
|
37
|
-
readonly counterparty: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
38
|
-
readonly counterparty_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
39
|
-
readonly due_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
40
|
-
readonly state: v.PicklistSchema<readonly ["expected", "paid", "cancelled"], undefined>;
|
|
41
|
-
readonly item: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
42
|
-
}, undefined>, undefined>, undefined>;
|
|
43
32
|
/** With `--sources`: each piece of evidence read back. */
|
|
44
33
|
readonly sources: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
45
34
|
readonly ref: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
package/dist/entities/fact.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import { FACT_STATUSES, FACT_TYPES, MEMORY_SCOPES } from "../domain/knowledge.js";
|
|
3
|
-
import { PAYMENT_DIRECTIONS, PAYMENT_STATES } from "../meetings/index.js";
|
|
4
3
|
import { author, evidenceSource, id, reference, text, time } from "./fields.js";
|
|
5
|
-
const payment = v.strictObject({
|
|
6
|
-
amount: text,
|
|
7
|
-
currency: text,
|
|
8
|
-
direction: v.picklist(PAYMENT_DIRECTIONS),
|
|
9
|
-
counterparty: v.nullable(reference),
|
|
10
|
-
counterparty_name: v.nullable(text),
|
|
11
|
-
due_at: v.nullable(text),
|
|
12
|
-
state: v.picklist(PAYMENT_STATES),
|
|
13
|
-
item: v.nullable(text),
|
|
14
|
-
});
|
|
15
4
|
export const fact = v.strictObject({
|
|
16
5
|
ref: reference,
|
|
17
6
|
id,
|
|
@@ -35,8 +24,6 @@ export const fact = v.strictObject({
|
|
|
35
24
|
/** Who recorded it: the owner, or an agent whose fact waits as proposed. */
|
|
36
25
|
origin: v.picklist(["owner", "agent"]),
|
|
37
26
|
evidence: v.array(reference),
|
|
38
|
-
/** A `payment` fact's fields. */
|
|
39
|
-
payment: v.optional(v.nullable(payment)),
|
|
40
27
|
/** With `--sources`: each piece of evidence read back. */
|
|
41
28
|
sources: v.optional(v.array(evidenceSource)),
|
|
42
29
|
created_at: time,
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -5761,7 +5761,7 @@ export declare const ENTITIES: {
|
|
|
5761
5761
|
readonly fact: v.StrictObjectSchema<{
|
|
5762
5762
|
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
5763
5763
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
5764
|
-
readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"
|
|
5764
|
+
readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
5765
5765
|
readonly text: v.StringSchema<undefined>;
|
|
5766
5766
|
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
5767
5767
|
readonly project: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -5784,16 +5784,6 @@ export declare const ENTITIES: {
|
|
|
5784
5784
|
readonly scope: v.NullableSchema<v.PicklistSchema<readonly ["personal", "work"], undefined>, undefined>;
|
|
5785
5785
|
readonly origin: v.PicklistSchema<["owner", "agent"], undefined>;
|
|
5786
5786
|
readonly evidence: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
5787
|
-
readonly payment: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
|
|
5788
|
-
readonly amount: v.StringSchema<undefined>;
|
|
5789
|
-
readonly currency: v.StringSchema<undefined>;
|
|
5790
|
-
readonly direction: v.PicklistSchema<readonly ["to_us", "from_us"], undefined>;
|
|
5791
|
-
readonly counterparty: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
5792
|
-
readonly counterparty_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5793
|
-
readonly due_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5794
|
-
readonly state: v.PicklistSchema<readonly ["expected", "paid", "cancelled"], undefined>;
|
|
5795
|
-
readonly item: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5796
|
-
}, undefined>, undefined>, undefined>;
|
|
5797
5787
|
readonly sources: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
5798
5788
|
readonly ref: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
5799
5789
|
readonly kind: v.StringSchema<undefined>;
|
|
@@ -6589,7 +6579,7 @@ export declare const ENTITIES: {
|
|
|
6589
6579
|
readonly fingerprint: v.StringSchema<undefined>;
|
|
6590
6580
|
}, undefined>, undefined>;
|
|
6591
6581
|
readonly kind: v.LiteralSchema<"fact", undefined>;
|
|
6592
|
-
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"
|
|
6582
|
+
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
6593
6583
|
readonly statement: v.StringSchema<undefined>;
|
|
6594
6584
|
}, undefined>, v.StrictObjectSchema<{
|
|
6595
6585
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
@@ -17,7 +17,7 @@ export declare const meetingFindings: v.StrictObjectSchema<{
|
|
|
17
17
|
readonly fingerprint: v.StringSchema<undefined>;
|
|
18
18
|
}, undefined>, undefined>;
|
|
19
19
|
readonly kind: v.LiteralSchema<"fact", undefined>;
|
|
20
|
-
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"
|
|
20
|
+
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
21
21
|
readonly statement: v.StringSchema<undefined>;
|
|
22
22
|
}, undefined>, v.StrictObjectSchema<{
|
|
23
23
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
package/dist/errors/reasons.js
CHANGED
|
@@ -138,8 +138,24 @@ 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("fact.payment-retired", "a payment is a payments row in store.finance, not a fact"),
|
|
142
|
+
validation("finance.allocate", "the invoice or payment cannot take an allocation in its state"),
|
|
143
|
+
validation("finance.currency", "the payment and the invoice are in different currencies; nothing is converted"),
|
|
144
|
+
validation("finance.field", "a field of the invoice or payment is not valid"),
|
|
145
|
+
validation("finance.line-amount", "the line's amount is not its quantity times its unit price"),
|
|
146
|
+
validation("finance.lines", "the invoice's lines are not valid"),
|
|
147
|
+
validation("finance.over-invoice", "the allocation would settle more than the invoice's total"),
|
|
148
|
+
validation("finance.over-payment", "the allocation would use more than the payment's amount"),
|
|
149
|
+
["finance.owner-only", "permission_error", "only the owner does this with money"],
|
|
150
|
+
validation("finance.party", "a party is a person: or organization: reference"),
|
|
151
|
+
validation("finance.ref", "the reference names no invoice or payment"),
|
|
152
|
+
validation("finance.status", "the invoice's status does not allow this"),
|
|
153
|
+
validation("finance.total", "the invoice's total is not the sum of its lines"),
|
|
141
154
|
validation("memory.evidence-required", "a memory needs its evidence"),
|
|
142
155
|
validation("memory.scope-required", "a memory needs its scope"),
|
|
156
|
+
validation("money.amount", "the amount is not a decimal string in the currency's scale"),
|
|
157
|
+
validation("money.currency", "the currency is not one the store keeps money in"),
|
|
158
|
+
validation("money.quantity", "the quantity is not a decimal above 0"),
|
|
143
159
|
["model.not-downloaded", "not_found", "the model is not downloaded on this machine"],
|
|
144
160
|
["person.no-owner", "not_found", "no person is marked as the owner in the store"],
|
|
145
161
|
["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,
|