@wirecat/cli-core 0.574.0 → 0.575.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 +16 -0
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/findings-input.js +6 -0
- package/dist/domain/orders.d.ts +19 -0
- package/dist/domain/orders.js +12 -0
- package/dist/domain/ref-summaries.js +1 -0
- package/dist/domain/references.d.ts +1 -1
- package/dist/domain/references.js +1 -0
- package/dist/entities/index.d.ts +75 -0
- package/dist/entities/mail-findings.d.ts +75 -0
- package/dist/entities/mail-findings.js +62 -0
- package/dist/errors/reasons.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mail/findings.d.ts +73 -1
- package/dist/mail/findings.js +164 -1
- package/dist/services/proposals/kinds/mail-findings.js +9 -0
- package/dist/services/proposals/kinds/orders.d.ts +4 -0
- package/dist/services/proposals/kinds/orders.js +32 -0
- package/dist/services/proposals/registry.js +2 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/sqlite/entity-types.d.ts +1 -0
- package/dist/store/sqlite/entity-types.js +1 -0
- package/dist/store/sqlite/finance.d.ts +40 -2
- package/dist/store/sqlite/finance.js +48 -42
- package/dist/store/sqlite/knowledge.js +2 -1
- package/dist/store/sqlite/mail-findings.js +81 -1
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/migrations.generated.js +20 -0
- package/dist/store/sqlite/orders.d.ts +84 -0
- package/dist/store/sqlite/orders.js +186 -0
- package/dist/store/sqlite/ref-summaries.js +10 -0
- package/dist/store/sqlite/schema.d.ts +440 -0
- package/dist/store/sqlite/schema.js +38 -0
- package/dist/store/sqlite/things.js +1 -0
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +5 -0
- package/package.json +1 -1
|
@@ -954,5 +954,25 @@ export const GENERATED = [
|
|
|
954
954
|
"statements": [
|
|
955
955
|
"ALTER TABLE `record_relations` ADD `amount` text;"
|
|
956
956
|
]
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
"name": "20261105125314_version-50-orders",
|
|
960
|
+
"statements": [
|
|
961
|
+
"CREATE TABLE `orders` (\n\t`id` integer PRIMARY KEY,\n\t`customer_type` text NOT NULL,\n\t`customer_id` integer NOT NULL,\n\t`purpose` text NOT NULL,\n\t`supplier_type` text,\n\t`supplier_id` integer,\n\t`supplier_name` text NOT NULL,\n\t`number` text,\n\t`currency` text,\n\t`total` integer,\n\t`status` text NOT NULL,\n\t`order_date` text,\n\t`fulfilled_date` text,\n\t`invoice_id` integer,\n\t`payment_id` integer,\n\t`project_id` integer,\n\t`review` text NOT NULL,\n\t`author_type` text NOT NULL,\n\t`author_id` integer NOT NULL,\n\t`model` text,\n\t`confirmed_by_type` text,\n\t`confirmed_by_id` integer,\n\t`source` text NOT NULL,\n\t`metadata` text,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\t`deleted_at` integer,\n\tCONSTRAINT `fk_orders_invoice_id_invoices_id_fk` FOREIGN KEY (`invoice_id`) REFERENCES `invoices`(`id`),\n\tCONSTRAINT `fk_orders_payment_id_payments_id_fk` FOREIGN KEY (`payment_id`) REFERENCES `payments`(`id`),\n\tCONSTRAINT `fk_orders_project_id_projects_id_fk` FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`)\n);",
|
|
962
|
+
"CREATE INDEX `orders_by_customer_type_customer_id` ON `orders` (`customer_type`,`customer_id`);",
|
|
963
|
+
"CREATE INDEX `orders_by_supplier_type_supplier_id` ON `orders` (`supplier_type`,`supplier_id`);",
|
|
964
|
+
"CREATE INDEX `orders_by_invoice_id` ON `orders` (`invoice_id`);",
|
|
965
|
+
"CREATE INDEX `orders_by_payment_id` ON `orders` (`payment_id`);",
|
|
966
|
+
"CREATE INDEX `orders_by_project_id` ON `orders` (`project_id`);",
|
|
967
|
+
"CREATE INDEX `orders_by_author_type_author_id` ON `orders` (`author_type`,`author_id`);",
|
|
968
|
+
"CREATE INDEX `orders_by_confirmed_by_type_confirmed_by_id` ON `orders` (`confirmed_by_type`,`confirmed_by_id`);"
|
|
969
|
+
]
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
"name": "20261105125315_version-50-orders-rules",
|
|
973
|
+
"statements": [
|
|
974
|
+
"-- The store's code checks these first and says why; the triggers keep a stray write from another build or a hand\n-- edit from storing an order with an unknown status or purpose, or an amount without its currency.\nCREATE TRIGGER orders_valid_bi BEFORE INSERT ON orders\n WHEN new.customer_type NOT IN ('person', 'organization')\n OR new.purpose NOT IN ('personal', 'business', 'client', 'other')\n OR new.status NOT IN ('placed', 'shipped', 'delivered', 'cancelled', 'returned')\n OR new.review NOT IN ('proposed', 'confirmed', 'rejected')\n OR (new.total IS NULL) <> (new.currency IS NULL) OR new.total < 0 BEGIN\n SELECT RAISE(ABORT, 'orders: a person or organization customer, a known purpose, status and review, an amount with its currency');\nEND;",
|
|
975
|
+
"CREATE TRIGGER orders_valid_bu BEFORE UPDATE ON orders\n WHEN new.customer_type NOT IN ('person', 'organization')\n OR new.purpose NOT IN ('personal', 'business', 'client', 'other')\n OR new.status NOT IN ('placed', 'shipped', 'delivered', 'cancelled', 'returned')\n OR new.review NOT IN ('proposed', 'confirmed', 'rejected')\n OR (new.total IS NULL) <> (new.currency IS NULL) OR new.total < 0 BEGIN\n SELECT RAISE(ABORT, 'orders: a person or organization customer, a known purpose, status and review, an amount with its currency');\nEND;"
|
|
976
|
+
]
|
|
957
977
|
}
|
|
958
978
|
];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type FinanceReview } from "../../domain/finance.js";
|
|
2
|
+
import { type OrderPurpose, type OrderStatus } from "../../domain/orders.js";
|
|
3
|
+
import { type Decider } from "../decisions.js";
|
|
4
|
+
import { type ProposedByFilter } from "./actors.js";
|
|
5
|
+
import { type Party, type Reviewed, type Written } from "./finance.js";
|
|
6
|
+
import { type Author } from "./memories.js";
|
|
7
|
+
import type { StoreContext } from "./open.js";
|
|
8
|
+
export interface Order extends Reviewed {
|
|
9
|
+
id: string;
|
|
10
|
+
ref: string;
|
|
11
|
+
/** One line, as a list or the review inbox prints it. */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Whom it is for: the owner's own person for his own purchase. */
|
|
14
|
+
customer: Party;
|
|
15
|
+
purpose: OrderPurpose;
|
|
16
|
+
supplier: Party | null;
|
|
17
|
+
/** The shop or supplier as its mail names it. */
|
|
18
|
+
supplierName: string;
|
|
19
|
+
number: string | null;
|
|
20
|
+
/** Both `null` when the mail gave no amount. */
|
|
21
|
+
currency: string | null;
|
|
22
|
+
total: string | null;
|
|
23
|
+
status: OrderStatus;
|
|
24
|
+
/** `YYYY-MM-DD`. */
|
|
25
|
+
orderDate: string | null;
|
|
26
|
+
fulfilledDate: string | null;
|
|
27
|
+
invoice: string | null;
|
|
28
|
+
payment: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface OrderInput extends Written {
|
|
31
|
+
/** `person:` or `organization:`; the owner's own person when left out. */
|
|
32
|
+
customer?: string;
|
|
33
|
+
purpose: OrderPurpose;
|
|
34
|
+
supplier?: string;
|
|
35
|
+
supplierName: string;
|
|
36
|
+
number?: string;
|
|
37
|
+
/** A decimal string in `currency`; give both or neither. */
|
|
38
|
+
total?: string;
|
|
39
|
+
currency?: string;
|
|
40
|
+
/** `placed` when left out. */
|
|
41
|
+
status?: OrderStatus;
|
|
42
|
+
orderDate?: string;
|
|
43
|
+
fulfilledDate?: string;
|
|
44
|
+
invoice?: string;
|
|
45
|
+
payment?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface OrderFilter {
|
|
48
|
+
status?: OrderStatus;
|
|
49
|
+
purpose?: OrderPurpose;
|
|
50
|
+
customer?: string;
|
|
51
|
+
/** The shop's name in any letter case; with `number`, how a later mail finds its order. */
|
|
52
|
+
supplierName?: string;
|
|
53
|
+
number?: string;
|
|
54
|
+
project?: string;
|
|
55
|
+
review?: FinanceReview;
|
|
56
|
+
proposedBy?: ProposedByFilter;
|
|
57
|
+
/** 1–500, 100 by default; newest written first. */
|
|
58
|
+
limit?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What the owner, his business or a client ordered, from whom, and how far delivery got. The owner's entry is
|
|
62
|
+
* confirmed at once; an agent's waits as proposed for him, and only he decides it.
|
|
63
|
+
*/
|
|
64
|
+
export interface OrdersStore {
|
|
65
|
+
add(input: OrderInput): Promise<Order>;
|
|
66
|
+
get(reference: string): Promise<Order>;
|
|
67
|
+
list(filter?: OrderFilter): Promise<Order[]>;
|
|
68
|
+
/** The owner moves an order on, such as a later mail saying it shipped; `date` is the delivery day. */
|
|
69
|
+
setStatus(reference: string, input: {
|
|
70
|
+
status: OrderStatus;
|
|
71
|
+
date?: string;
|
|
72
|
+
by: Author;
|
|
73
|
+
}): Promise<Order>;
|
|
74
|
+
confirm(reference: string, by: Decider, reason?: string): Promise<Order>;
|
|
75
|
+
reject(reference: string, by: Decider, reason?: string): Promise<Order>;
|
|
76
|
+
}
|
|
77
|
+
type OrderOperations = {
|
|
78
|
+
[K in keyof OrdersStore]: OrdersStore[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R : never;
|
|
79
|
+
};
|
|
80
|
+
export declare const orderOperationsOver: (context: StoreContext) => OrderOperations;
|
|
81
|
+
/** The one stored order of a shop with a number, as a later mail names it; refuses none and more than one. */
|
|
82
|
+
export declare const orderOfShop: (context: StoreContext, supplierName: string, number: string) => string;
|
|
83
|
+
export declare const ordersStoreOver: (context: StoreContext) => OrdersStore;
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { FINANCE_REVIEWS } from "../../domain/finance.js";
|
|
3
|
+
import { currencyOf, formatDecimal, minorUnits, scaleOf } from "../../domain/money.js";
|
|
4
|
+
import { ORDER_PURPOSES, ORDER_STATUSES, orderTitle } from "../../domain/orders.js";
|
|
5
|
+
import { assertMayDecide, deciderAuthor } from "../decisions.js";
|
|
6
|
+
import { fold } from "../normalize.js";
|
|
7
|
+
import { ownerPerson, proposedBySql, proposerOf } from "./actors.js";
|
|
8
|
+
import { isoDate, oneOf, refuse, reviewedRowsOver, text } from "./finance.js";
|
|
9
|
+
import { authorActor, projectId } from "./memories.js";
|
|
10
|
+
import { requiredThing } from "./notes.js";
|
|
11
|
+
import { write } from "./transaction.js";
|
|
12
|
+
const ORDERS = "SELECT o.*, b.name AS author_name, b.kind AS author_kind FROM orders o " +
|
|
13
|
+
"LEFT JOIN bots b ON o.author_type = 'bot' AND b.id = o.author_id";
|
|
14
|
+
export const orderOperationsOver = (context) => {
|
|
15
|
+
const { database, now } = context;
|
|
16
|
+
const { partyOf, partyThing, reviewedOf, written, keepEvidence, ownerOnly, keepDecision } = reviewedRowsOver(context);
|
|
17
|
+
const orderOf = (row) => {
|
|
18
|
+
const currency = row.currency == null ? null : String(row.currency);
|
|
19
|
+
const total = row.total == null || currency === null ? null : Number(row.total);
|
|
20
|
+
const fields = {
|
|
21
|
+
id: Number(row.id),
|
|
22
|
+
number: row.number == null ? null : String(row.number),
|
|
23
|
+
supplierName: String(row.supplier_name),
|
|
24
|
+
total,
|
|
25
|
+
currency,
|
|
26
|
+
status: row.status,
|
|
27
|
+
review: row.review,
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
id: String(row.id),
|
|
31
|
+
ref: `order:${row.id}`,
|
|
32
|
+
title: orderTitle(fields),
|
|
33
|
+
customer: partyOf(row.customer_type, row.customer_id),
|
|
34
|
+
purpose: row.purpose,
|
|
35
|
+
supplier: partyOf(row.supplier_type, row.supplier_id),
|
|
36
|
+
supplierName: fields.supplierName,
|
|
37
|
+
number: fields.number,
|
|
38
|
+
currency,
|
|
39
|
+
total: total === null || currency === null ? null : formatDecimal(total, scaleOf(currency)),
|
|
40
|
+
status: fields.status,
|
|
41
|
+
orderDate: row.order_date == null ? null : String(row.order_date),
|
|
42
|
+
fulfilledDate: row.fulfilled_date == null ? null : String(row.fulfilled_date),
|
|
43
|
+
invoice: row.invoice_id == null ? null : `invoice:${row.invoice_id}`,
|
|
44
|
+
payment: row.payment_id == null ? null : `payment:${row.payment_id}`,
|
|
45
|
+
...reviewedOf(row, "order"),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const rowOf = (reference) => {
|
|
49
|
+
const thing = requiredThing(database, reference.includes(":") ? reference : `order:${reference}`, "the order");
|
|
50
|
+
if (thing.type !== "order")
|
|
51
|
+
return refuse(`${reference} is not an order`, "order.ref");
|
|
52
|
+
return database.prepare(`${ORDERS} WHERE o.id = ?`).get(thing.id);
|
|
53
|
+
};
|
|
54
|
+
const linked = (reference, type) => {
|
|
55
|
+
if (reference === undefined)
|
|
56
|
+
return null;
|
|
57
|
+
const thing = requiredThing(database, reference, `the ${type}`);
|
|
58
|
+
if (thing.type !== type)
|
|
59
|
+
return refuse(`${reference} is not an ${type}: reference`, "order.ref");
|
|
60
|
+
return thing.id;
|
|
61
|
+
};
|
|
62
|
+
const statusOf = (value) => oneOf(value, ORDER_STATUSES, "an order's status");
|
|
63
|
+
const decide = (reference, by, reason, confirm) => {
|
|
64
|
+
const row = rowOf(reference);
|
|
65
|
+
const ref = `order:${row.id}`;
|
|
66
|
+
assertMayDecide({ id: ref, proposedBy: proposerOf(database, row.author_type, row.author_id), ownerOnly: true }, by, context.decisions);
|
|
67
|
+
if (row.review !== "proposed")
|
|
68
|
+
refuse(`${ref} is already ${String(row.review)}`, "proposal.wrong-state");
|
|
69
|
+
write(database, () => {
|
|
70
|
+
if (confirm) {
|
|
71
|
+
const { actor } = authorActor(context, deciderAuthor(by));
|
|
72
|
+
database
|
|
73
|
+
.prepare("UPDATE orders SET review = 'confirmed', confirmed_by_type = ?, confirmed_by_id = ?, updated_at = ? WHERE id = ?")
|
|
74
|
+
.run(actor.type, actor.id, now(), Number(row.id));
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
database
|
|
78
|
+
.prepare("UPDATE orders SET review = 'rejected', updated_at = ? WHERE id = ?")
|
|
79
|
+
.run(now(), Number(row.id));
|
|
80
|
+
keepDecision("orders", Number(row.id), by, reason);
|
|
81
|
+
});
|
|
82
|
+
return orderOf(rowOf(ref));
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
add: (input) => {
|
|
86
|
+
const purpose = oneOf(input.purpose, ORDER_PURPOSES, "an order's purpose");
|
|
87
|
+
const status = input.status === undefined ? "placed" : statusOf(input.status);
|
|
88
|
+
const supplierName = text(input.supplierName, "an order's supplier name", 200) ??
|
|
89
|
+
refuse("an order names its shop or supplier", "order.field");
|
|
90
|
+
const number = text(input.number, "an order's number", 80);
|
|
91
|
+
if ((input.total === undefined) !== (input.currency === undefined))
|
|
92
|
+
refuse("an order's amount and currency come together", "order.field");
|
|
93
|
+
const currency = input.currency === undefined ? null : currencyOf(input.currency);
|
|
94
|
+
const total = currency === null ? null : minorUnits(input.total, currency, "an order's amount");
|
|
95
|
+
if (total !== null && total < 0)
|
|
96
|
+
refuse("an order's amount is 0 or more", "money.amount");
|
|
97
|
+
const orderDate = isoDate(input.orderDate, "an order's date");
|
|
98
|
+
const fulfilledDate = isoDate(input.fulfilledDate, "an order's delivery date");
|
|
99
|
+
const id = write(database, () => {
|
|
100
|
+
if (number !== null && ordersOfShop(database, supplierName, number).length)
|
|
101
|
+
refuse(`order ${number} from ${supplierName} is already stored; change its status instead`, "order.duplicate");
|
|
102
|
+
const customer = partyThing(input.customer, "the customer") ?? ownerPerson(database);
|
|
103
|
+
const supplier = partyThing(input.supplier, "the supplier");
|
|
104
|
+
const invoice = linked(input.invoice, "invoice");
|
|
105
|
+
const payment = linked(input.payment, "payment");
|
|
106
|
+
const by = written(input);
|
|
107
|
+
const at = now();
|
|
108
|
+
const made = Number(database
|
|
109
|
+
.prepare("INSERT INTO orders (customer_type, customer_id, purpose, supplier_type, supplier_id, supplier_name, number, " +
|
|
110
|
+
"currency, total, status, order_date, fulfilled_date, invoice_id, payment_id, project_id, review, " +
|
|
111
|
+
"author_type, author_id, model, confirmed_by_type, confirmed_by_id, source, metadata, created_at, updated_at) " +
|
|
112
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
|
|
113
|
+
.get(customer.type, customer.id, purpose, supplier?.type ?? null, supplier?.id ?? null, supplierName, number, currency, total, status, orderDate, fulfilledDate, invoice, payment, by.project, by.review, by.actor.type, by.actor.id, by.model, by.confirmedType, by.confirmedId, by.source, by.metadata, at, at)?.id);
|
|
114
|
+
keepEvidence("order", made, by);
|
|
115
|
+
return made;
|
|
116
|
+
});
|
|
117
|
+
return orderOf(rowOf(`order:${id}`));
|
|
118
|
+
},
|
|
119
|
+
get: (reference) => orderOf(rowOf(reference)),
|
|
120
|
+
list: (filter = {}) => {
|
|
121
|
+
const project = filter.project === undefined ? null : projectId(database, filter.project);
|
|
122
|
+
const customer = partyThing(filter.customer, "the customer");
|
|
123
|
+
const by = proposedBySql("o.author_type", "o.author_id", filter.proposedBy);
|
|
124
|
+
const status = filter.status === undefined ? null : statusOf(filter.status);
|
|
125
|
+
const purpose = filter.purpose === undefined ? null : oneOf(filter.purpose, ORDER_PURPOSES, "a purpose");
|
|
126
|
+
const review = filter.review === undefined ? null : oneOf(filter.review, FINANCE_REVIEWS, "a review");
|
|
127
|
+
const number = filter.number === undefined ? null : filter.number.trim();
|
|
128
|
+
const shop = filter.supplierName === undefined ? null : fold(filter.supplierName);
|
|
129
|
+
const limit = Math.min(Math.max(filter.limit ?? 100, 1), 500);
|
|
130
|
+
const rows = database
|
|
131
|
+
.prepare(`${ORDERS} WHERE o.deleted_at IS NULL AND (? IS NULL OR o.status = ?) AND (? IS NULL OR o.purpose = ?) ` +
|
|
132
|
+
"AND (? IS NULL OR o.review = ?) AND (? IS NULL OR o.project_id = ?) AND (? IS NULL OR o.number = ?) " +
|
|
133
|
+
"AND (? IS NULL OR (o.customer_type = ? AND o.customer_id = ?)) " +
|
|
134
|
+
`AND ${by.sql} ORDER BY o.created_at DESC, o.id DESC`)
|
|
135
|
+
.all(status, status, purpose, purpose, review, review, project, project, number, number, customer?.type ?? null, customer?.type ?? null, customer?.id ?? null, ...by.params);
|
|
136
|
+
return rows
|
|
137
|
+
.filter((row) => shop === null || fold(String(row.supplier_name)) === shop)
|
|
138
|
+
.slice(0, limit)
|
|
139
|
+
.map(orderOf);
|
|
140
|
+
},
|
|
141
|
+
setStatus: (reference, { status, date, by }) => {
|
|
142
|
+
ownerOnly(by, "moves an order on");
|
|
143
|
+
const next = statusOf(status);
|
|
144
|
+
const day = isoDate(date, "an order's delivery date");
|
|
145
|
+
const row = rowOf(reference);
|
|
146
|
+
if (row.review === "rejected" || row.deleted_at != null)
|
|
147
|
+
refuse(`order:${row.id} is ${row.deleted_at != null ? "deleted" : "rejected"}`, "order.status");
|
|
148
|
+
write(database, () => {
|
|
149
|
+
database
|
|
150
|
+
.prepare("UPDATE orders SET status = ?, fulfilled_date = coalesce(?, fulfilled_date), updated_at = ? WHERE id = ?")
|
|
151
|
+
.run(next, next === "delivered" ? day : null, now(), Number(row.id));
|
|
152
|
+
});
|
|
153
|
+
return orderOf(rowOf(`order:${row.id}`));
|
|
154
|
+
},
|
|
155
|
+
confirm: (reference, by, reason) => decide(reference, by, reason, true),
|
|
156
|
+
reject: (reference, by, reason) => decide(reference, by, reason, false),
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
/** Orders of one shop with one number that still count: neither rejected nor deleted, the name in any letter case. */
|
|
160
|
+
const ordersOfShop = (database, supplierName, number) => {
|
|
161
|
+
const name = fold(supplierName);
|
|
162
|
+
return database
|
|
163
|
+
.prepare("SELECT id, supplier_name FROM orders WHERE number = ? AND deleted_at IS NULL AND review <> 'rejected'")
|
|
164
|
+
.all(number.trim())
|
|
165
|
+
.filter((row) => fold(String(row.supplier_name)) === name);
|
|
166
|
+
};
|
|
167
|
+
/** The one stored order of a shop with a number, as a later mail names it; refuses none and more than one. */
|
|
168
|
+
export const orderOfShop = (context, supplierName, number) => {
|
|
169
|
+
const rows = ordersOfShop(context.database, supplierName, number);
|
|
170
|
+
if (rows.length === 0)
|
|
171
|
+
throw new CliError("not_found", `no stored order ${number} from ${supplierName}`, { reason: "order.not-found" });
|
|
172
|
+
if (rows.length > 1)
|
|
173
|
+
return refuse(`more than one order ${number} from ${supplierName}`, "order.ref");
|
|
174
|
+
return `order:${rows[0]?.id}`;
|
|
175
|
+
};
|
|
176
|
+
export const ordersStoreOver = (context) => {
|
|
177
|
+
const operations = orderOperationsOver(context);
|
|
178
|
+
return {
|
|
179
|
+
add: async (input) => operations.add(input),
|
|
180
|
+
get: async (reference) => operations.get(reference),
|
|
181
|
+
list: async (filter) => operations.list(filter),
|
|
182
|
+
setStatus: async (reference, input) => operations.setStatus(reference, input),
|
|
183
|
+
confirm: async (reference, by, reason) => operations.confirm(reference, by, reason),
|
|
184
|
+
reject: async (reference, by, reason) => operations.reject(reference, by, reason),
|
|
185
|
+
};
|
|
186
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { invoiceLine, paymentLine } from "../../domain/finance.js";
|
|
2
|
+
import { orderLine } from "../../domain/orders.js";
|
|
2
3
|
import { filesAccountSql, providerLabel, providerNamed } from "../../domain/providers.js";
|
|
3
4
|
import { oneLine } from "../../domain/ref-summaries.js";
|
|
4
5
|
import { formatReference, parseReference } from "../../domain/references.js";
|
|
@@ -199,6 +200,13 @@ const READERS = {
|
|
|
199
200
|
at: "x.created_at",
|
|
200
201
|
deleted: "x.deleted_at",
|
|
201
202
|
})),
|
|
203
|
+
order: byId("order", "orders", () => ({
|
|
204
|
+
title: "json_object('id', x.id, 'number', x.number, 'supplierName', x.supplier_name, 'total', x.total, " +
|
|
205
|
+
"'currency', x.currency, 'status', x.status, 'review', x.review)",
|
|
206
|
+
holder: "(SELECT name FROM projects WHERE id = x.project_id)",
|
|
207
|
+
at: "x.created_at",
|
|
208
|
+
deleted: "x.deleted_at",
|
|
209
|
+
})),
|
|
202
210
|
};
|
|
203
211
|
/** Mail saved before the mail tables existed is still messages, so a miss there is read again as a message. */
|
|
204
212
|
const FALLBACK = { email: "message", email_thread: "chat" };
|
|
@@ -304,6 +312,8 @@ const titleOf = (kind, reference, found) => {
|
|
|
304
312
|
return fieldsOf(found.title, invoiceLine);
|
|
305
313
|
case "payment":
|
|
306
314
|
return fieldsOf(found.title, paymentLine);
|
|
315
|
+
case "order":
|
|
316
|
+
return fieldsOf(found.title, orderLine);
|
|
307
317
|
case "folder":
|
|
308
318
|
return reference.type === "folder" && reference.path !== null && found.title !== null
|
|
309
319
|
? `${found.title}/${reference.path}`
|