@umec/admin 0.1.0-alpha.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/LICENSE +21 -0
- package/dist/access.d.ts +7 -0
- package/dist/access.js +9 -0
- package/dist/access.js.map +1 -0
- package/dist/carriers.d.ts +9 -0
- package/dist/carriers.js +13 -0
- package/dist/carriers.js.map +1 -0
- package/dist/catalog/index.d.ts +27 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/chunk-4DR4K72B.js +24 -0
- package/dist/chunk-4DR4K72B.js.map +1 -0
- package/dist/chunk-E7ATMICY.js +51 -0
- package/dist/chunk-E7ATMICY.js.map +1 -0
- package/dist/chunk-G3O62WUE.js +57 -0
- package/dist/chunk-G3O62WUE.js.map +1 -0
- package/dist/chunk-ODK4R57S.js +131 -0
- package/dist/chunk-ODK4R57S.js.map +1 -0
- package/dist/chunk-RK7FIONL.js +27 -0
- package/dist/chunk-RK7FIONL.js.map +1 -0
- package/dist/chunk-STQU4LQU.js +402 -0
- package/dist/chunk-STQU4LQU.js.map +1 -0
- package/dist/chunk-TIK7KC4O.js +210 -0
- package/dist/chunk-TIK7KC4O.js.map +1 -0
- package/dist/chunk-TKJ6KSXM.js +23 -0
- package/dist/chunk-TKJ6KSXM.js.map +1 -0
- package/dist/chunk-VUT3UCCR.js +200 -0
- package/dist/chunk-VUT3UCCR.js.map +1 -0
- package/dist/chunk-W2O6EQWT.js +57 -0
- package/dist/chunk-W2O6EQWT.js.map +1 -0
- package/dist/chunk-ZEA6G75Z.js +129 -0
- package/dist/chunk-ZEA6G75Z.js.map +1 -0
- package/dist/csv.d.ts +4 -0
- package/dist/csv.js +9 -0
- package/dist/csv.js.map +1 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.js +17 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +141 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory/index.d.ts +27 -0
- package/dist/inventory/index.js +21 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/offers.d.ts +10 -0
- package/dist/offers.js +9 -0
- package/dist/offers.js.map +1 -0
- package/dist/orders/index.d.ts +49 -0
- package/dist/orders/index.js +34 -0
- package/dist/orders/index.js.map +1 -0
- package/dist/product-fields.d.ts +12 -0
- package/dist/product-fields.js +14 -0
- package/dist/product-fields.js.map +1 -0
- package/dist/products.d.ts +48 -0
- package/dist/products.js +27 -0
- package/dist/products.js.map +1 -0
- package/dist/unshipped-alert.d.ts +19 -0
- package/dist/unshipped-alert.js +12 -0
- package/dist/unshipped-alert.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import {
|
|
2
|
+
authenticateAdmin,
|
|
3
|
+
requireAdminAuth
|
|
4
|
+
} from "./chunk-W2O6EQWT.js";
|
|
5
|
+
import {
|
|
6
|
+
csvAttachment,
|
|
7
|
+
jsonError,
|
|
8
|
+
jsonOk,
|
|
9
|
+
readJsonObject
|
|
10
|
+
} from "./chunk-E7ATMICY.js";
|
|
11
|
+
import {
|
|
12
|
+
CARRIER_LABELS,
|
|
13
|
+
parseOptionalCarrier
|
|
14
|
+
} from "./chunk-4DR4K72B.js";
|
|
15
|
+
import {
|
|
16
|
+
toCsv
|
|
17
|
+
} from "./chunk-TKJ6KSXM.js";
|
|
18
|
+
import {
|
|
19
|
+
loadProductNamesFromDb
|
|
20
|
+
} from "./chunk-ZEA6G75Z.js";
|
|
21
|
+
|
|
22
|
+
// src/orders/export.ts
|
|
23
|
+
import { getServerEnv } from "@umec/core/env";
|
|
24
|
+
var EXPORTABLE_STATUSES = ["pending", "shipped", "refunded", "cancelled"];
|
|
25
|
+
function parseJson(value) {
|
|
26
|
+
if (!value) return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(value);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function formatItems(itemsJson, names) {
|
|
34
|
+
const items = parseJson(itemsJson);
|
|
35
|
+
if (!Array.isArray(items) || items.length === 0) return { labels: "", quantity: 0 };
|
|
36
|
+
const labels = items.map((item) => {
|
|
37
|
+
const sku = typeof item.sku === "string" ? item.sku : "";
|
|
38
|
+
const quantity2 = typeof item.quantity === "number" ? item.quantity : 0;
|
|
39
|
+
const name = sku && names[sku] ? names[sku] : sku || "\u5546\u54C1";
|
|
40
|
+
return `${name} x ${quantity2}`;
|
|
41
|
+
});
|
|
42
|
+
const quantity = items.reduce((sum, item) => {
|
|
43
|
+
return sum + (typeof item.quantity === "number" ? item.quantity : 0);
|
|
44
|
+
}, 0);
|
|
45
|
+
return { labels: labels.join("; "), quantity };
|
|
46
|
+
}
|
|
47
|
+
function formatAddress(addressJson) {
|
|
48
|
+
const shipping = parseJson(addressJson);
|
|
49
|
+
if (!shipping?.address) return { postal: "", address: "" };
|
|
50
|
+
const address = shipping.address;
|
|
51
|
+
const parts = [address.state, address.city, address.line1, address.line2].filter((part) => typeof part === "string" && part.trim() !== "").map((part) => part.trim());
|
|
52
|
+
return {
|
|
53
|
+
postal: typeof address.postal_code === "string" ? address.postal_code : "",
|
|
54
|
+
address: parts.join(" ")
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function formatNotes(customFieldsJson) {
|
|
58
|
+
const fields = parseJson(customFieldsJson);
|
|
59
|
+
if (!fields) return "";
|
|
60
|
+
return Object.entries(fields).flatMap(([key, value]) => {
|
|
61
|
+
if (typeof value !== "string" || !value.trim()) return [];
|
|
62
|
+
return [`${key}: ${value.trim()}`];
|
|
63
|
+
}).join("; ");
|
|
64
|
+
}
|
|
65
|
+
function createOrdersExportHandler() {
|
|
66
|
+
return async ({ request }) => {
|
|
67
|
+
const auth = await authenticateAdmin(request);
|
|
68
|
+
if (auth instanceof Response) return auth;
|
|
69
|
+
const url = new URL(request.url);
|
|
70
|
+
const status = url.searchParams.get("status") ?? "pending";
|
|
71
|
+
if (!EXPORTABLE_STATUSES.includes(status)) {
|
|
72
|
+
return jsonError("Invalid status", 400);
|
|
73
|
+
}
|
|
74
|
+
const { DB: db } = getServerEnv();
|
|
75
|
+
if (!db) return jsonError("DB not available", 503);
|
|
76
|
+
const result = await db.prepare(
|
|
77
|
+
`SELECT id, email, name, phone, items_json, amount, shipping, address_json,
|
|
78
|
+
custom_fields_json, status, tracking_number, carrier, created_at
|
|
79
|
+
FROM orders
|
|
80
|
+
WHERE status = ?
|
|
81
|
+
ORDER BY created_at ASC`
|
|
82
|
+
).bind(status).all();
|
|
83
|
+
const names = await loadProductNamesFromDb(db);
|
|
84
|
+
const csv = toCsv(
|
|
85
|
+
[
|
|
86
|
+
"id",
|
|
87
|
+
"created_at",
|
|
88
|
+
"name",
|
|
89
|
+
"email",
|
|
90
|
+
"phone",
|
|
91
|
+
"postal_code",
|
|
92
|
+
"address",
|
|
93
|
+
"items",
|
|
94
|
+
"quantity",
|
|
95
|
+
"amount",
|
|
96
|
+
"shipping",
|
|
97
|
+
"notes",
|
|
98
|
+
"tracking_number",
|
|
99
|
+
"carrier",
|
|
100
|
+
"status"
|
|
101
|
+
],
|
|
102
|
+
(result.results ?? []).map((row) => {
|
|
103
|
+
const items = formatItems(row.items_json, names);
|
|
104
|
+
const address = formatAddress(row.address_json);
|
|
105
|
+
return [
|
|
106
|
+
row.id,
|
|
107
|
+
row.created_at ?? "",
|
|
108
|
+
row.name ?? "",
|
|
109
|
+
row.email,
|
|
110
|
+
row.phone ?? "",
|
|
111
|
+
address.postal,
|
|
112
|
+
address.address,
|
|
113
|
+
items.labels,
|
|
114
|
+
items.quantity,
|
|
115
|
+
row.amount ?? "",
|
|
116
|
+
row.shipping ?? "",
|
|
117
|
+
formatNotes(row.custom_fields_json),
|
|
118
|
+
row.tracking_number ?? "",
|
|
119
|
+
row.carrier ?? "",
|
|
120
|
+
row.status
|
|
121
|
+
];
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
return csvAttachment(`orders-${status}.csv`, csv);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/orders/mark-shipped.ts
|
|
129
|
+
import { markShipped, writeAuditLog } from "@umec/core/db";
|
|
130
|
+
import { getServerEnv as getServerEnv2 } from "@umec/core/env";
|
|
131
|
+
function createMarkShippedHandler() {
|
|
132
|
+
return async ({ request }) => {
|
|
133
|
+
const auth = await authenticateAdmin(request);
|
|
134
|
+
if (auth instanceof Response) return auth;
|
|
135
|
+
const payload = await readJsonObject(request);
|
|
136
|
+
if (payload instanceof Response) return payload;
|
|
137
|
+
const { id } = payload;
|
|
138
|
+
if (!id || typeof id !== "string") {
|
|
139
|
+
return jsonError("Invalid id", 400);
|
|
140
|
+
}
|
|
141
|
+
const carrier = parseOptionalCarrier(payload.carrier);
|
|
142
|
+
if (carrier && typeof carrier === "object" && "error" in carrier) {
|
|
143
|
+
return jsonError(carrier.error, 400);
|
|
144
|
+
}
|
|
145
|
+
const trackingNumber = payload.tracking_number === void 0 || payload.tracking_number === null || payload.tracking_number === "" ? null : typeof payload.tracking_number === "string" ? payload.tracking_number.trim() : null;
|
|
146
|
+
if (payload.tracking_number !== void 0 && payload.tracking_number !== null && payload.tracking_number !== "" && trackingNumber === null) {
|
|
147
|
+
return jsonError("Invalid tracking_number", 400);
|
|
148
|
+
}
|
|
149
|
+
const { DB: db } = getServerEnv2();
|
|
150
|
+
if (!db) return jsonError("DB not available", 503);
|
|
151
|
+
const result = await markShipped(db, id, {
|
|
152
|
+
trackingNumber,
|
|
153
|
+
carrier
|
|
154
|
+
});
|
|
155
|
+
if (result === "not_pending") {
|
|
156
|
+
return jsonError("Order is not pending", 409);
|
|
157
|
+
}
|
|
158
|
+
console.log(
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
type: "admin.audit",
|
|
161
|
+
action: "mark-shipped",
|
|
162
|
+
actor: auth.email,
|
|
163
|
+
targetId: id
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
await writeAuditLog(db, {
|
|
167
|
+
entityType: "order",
|
|
168
|
+
entityId: id,
|
|
169
|
+
action: "mark-shipped",
|
|
170
|
+
adminEmail: auth.email,
|
|
171
|
+
actor: "human",
|
|
172
|
+
detail: { tracking_number: trackingNumber, carrier }
|
|
173
|
+
});
|
|
174
|
+
return jsonOk({ ok: true });
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/orders/mark-shipped-bulk.ts
|
|
179
|
+
import { markShipped as markShipped2, writeAuditLog as writeAuditLog2 } from "@umec/core/db";
|
|
180
|
+
import { getServerEnv as getServerEnv3 } from "@umec/core/env";
|
|
181
|
+
var MAX_BULK_SHIP = 100;
|
|
182
|
+
function createMarkShippedBulkHandler() {
|
|
183
|
+
return async ({ request }) => {
|
|
184
|
+
const auth = await authenticateAdmin(request);
|
|
185
|
+
if (auth instanceof Response) return auth;
|
|
186
|
+
const payload = await readJsonObject(request);
|
|
187
|
+
if (payload instanceof Response) return payload;
|
|
188
|
+
const { ids } = payload;
|
|
189
|
+
if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => typeof id === "string" && id)) {
|
|
190
|
+
return jsonError("Invalid ids", 400);
|
|
191
|
+
}
|
|
192
|
+
if (ids.length > MAX_BULK_SHIP) {
|
|
193
|
+
return jsonError(`ids must contain at most ${MAX_BULK_SHIP} items`, 400);
|
|
194
|
+
}
|
|
195
|
+
const carrier = parseOptionalCarrier(payload.carrier);
|
|
196
|
+
if (carrier && typeof carrier === "object" && "error" in carrier) {
|
|
197
|
+
return jsonError(carrier.error, 400);
|
|
198
|
+
}
|
|
199
|
+
const trackingNumber = typeof payload.tracking_number === "string" && payload.tracking_number.trim() ? payload.tracking_number.trim() : null;
|
|
200
|
+
const { DB: db } = getServerEnv3();
|
|
201
|
+
if (!db) return jsonError("DB not available", 503);
|
|
202
|
+
const uniqueIds = [...new Set(ids)];
|
|
203
|
+
const shipped = [];
|
|
204
|
+
const skipped = [];
|
|
205
|
+
const shippedAt = Date.now();
|
|
206
|
+
for (const id of uniqueIds) {
|
|
207
|
+
const result = await markShipped2(db, id, {
|
|
208
|
+
trackingNumber,
|
|
209
|
+
carrier,
|
|
210
|
+
shippedAt
|
|
211
|
+
});
|
|
212
|
+
if (result === "not_pending") {
|
|
213
|
+
skipped.push(id);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
shipped.push(id);
|
|
217
|
+
console.log(
|
|
218
|
+
JSON.stringify({
|
|
219
|
+
type: "admin.audit",
|
|
220
|
+
action: "mark-shipped",
|
|
221
|
+
actor: auth.email,
|
|
222
|
+
targetId: id
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
await writeAuditLog2(db, {
|
|
226
|
+
entityType: "order",
|
|
227
|
+
entityId: id,
|
|
228
|
+
action: "mark-shipped",
|
|
229
|
+
adminEmail: auth.email,
|
|
230
|
+
actor: "human",
|
|
231
|
+
detail: { tracking_number: trackingNumber, carrier, bulk: true }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return jsonOk({ shipped, skipped });
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/orders/page.ts
|
|
239
|
+
import { getServerEnv as getServerEnv4 } from "@umec/core/env";
|
|
240
|
+
function parseJson2(value) {
|
|
241
|
+
if (!value) return null;
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(value);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function formatOrderDate(value) {
|
|
249
|
+
if (value == null || value === "") return "-";
|
|
250
|
+
const timestamp = typeof value === "string" ? Number(value) : value;
|
|
251
|
+
if (!Number.isFinite(timestamp)) return "-";
|
|
252
|
+
return new Intl.DateTimeFormat("ja-JP", {
|
|
253
|
+
dateStyle: "medium",
|
|
254
|
+
timeStyle: "short",
|
|
255
|
+
timeZone: "Asia/Tokyo"
|
|
256
|
+
}).format(new Date(timestamp));
|
|
257
|
+
}
|
|
258
|
+
function formatYen(value) {
|
|
259
|
+
if (value == null) return "-";
|
|
260
|
+
return new Intl.NumberFormat("ja-JP", {
|
|
261
|
+
style: "currency",
|
|
262
|
+
currency: "JPY",
|
|
263
|
+
maximumFractionDigits: 0
|
|
264
|
+
}).format(value);
|
|
265
|
+
}
|
|
266
|
+
function formatOrderItems(itemsJson, names) {
|
|
267
|
+
const items = parseJson2(itemsJson);
|
|
268
|
+
if (!Array.isArray(items) || items.length === 0) return ["-"];
|
|
269
|
+
return items.map((item) => {
|
|
270
|
+
const sku = typeof item.sku === "string" ? item.sku : "";
|
|
271
|
+
const quantity = typeof item.quantity === "number" ? item.quantity : 0;
|
|
272
|
+
const productName = sku && names[sku] ? names[sku] : sku || "\u5546\u54C1";
|
|
273
|
+
return `${productName} x ${quantity}`;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function stringPart(value) {
|
|
277
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
278
|
+
}
|
|
279
|
+
function formatOrderAddress(addressJson) {
|
|
280
|
+
const shipping = parseJson2(addressJson);
|
|
281
|
+
if (!shipping?.address) return ["-"];
|
|
282
|
+
const address = shipping.address;
|
|
283
|
+
const postalCode = stringPart(address.postal_code);
|
|
284
|
+
const lines = [
|
|
285
|
+
postalCode ? `\u3012${postalCode}` : "",
|
|
286
|
+
stringPart(address.state) + stringPart(address.city),
|
|
287
|
+
stringPart(address.line1),
|
|
288
|
+
stringPart(address.line2)
|
|
289
|
+
].filter(Boolean);
|
|
290
|
+
return lines.length > 0 ? lines : ["-"];
|
|
291
|
+
}
|
|
292
|
+
function formatCustomFields(customFieldsJson) {
|
|
293
|
+
const cf = parseJson2(customFieldsJson);
|
|
294
|
+
if (!cf) return [];
|
|
295
|
+
return Object.entries(cf).flatMap(([key, value]) => {
|
|
296
|
+
if (typeof value !== "string" || !value.trim()) return [];
|
|
297
|
+
return [{ key, value: value.trim() }];
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function carrierLabel(code) {
|
|
301
|
+
if (!code) return "";
|
|
302
|
+
return CARRIER_LABELS[code] ?? code;
|
|
303
|
+
}
|
|
304
|
+
async function loadOrdersAdminPage(request) {
|
|
305
|
+
const denied = await requireAdminAuth(request);
|
|
306
|
+
if (denied) return { response: denied };
|
|
307
|
+
const { DB: db } = getServerEnv4();
|
|
308
|
+
let orders = [];
|
|
309
|
+
let names = {};
|
|
310
|
+
let dbNotice = "";
|
|
311
|
+
if (db) {
|
|
312
|
+
try {
|
|
313
|
+
const result = await db.prepare(
|
|
314
|
+
`SELECT * FROM orders ORDER BY
|
|
315
|
+
CASE WHEN status = 'pending' THEN 0 ELSE 1 END,
|
|
316
|
+
created_at DESC
|
|
317
|
+
LIMIT 100`
|
|
318
|
+
).all();
|
|
319
|
+
orders = result.results ?? [];
|
|
320
|
+
names = await loadProductNamesFromDb(db);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error("[admin/orders] order query failed:", error);
|
|
323
|
+
dbNotice = "Orders could not be loaded.";
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
dbNotice = "DB binding is not available.";
|
|
327
|
+
}
|
|
328
|
+
const pendingCount = orders.filter((order) => order.status === "pending").length;
|
|
329
|
+
return { orders, names, dbNotice, pendingCount };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/orders/update-tracking.ts
|
|
333
|
+
import { getOrder, updateTracking, writeAuditLog as writeAuditLog3 } from "@umec/core/db";
|
|
334
|
+
import { getServerEnv as getServerEnv5 } from "@umec/core/env";
|
|
335
|
+
function createUpdateTrackingHandler() {
|
|
336
|
+
return async ({ request }) => {
|
|
337
|
+
const auth = await authenticateAdmin(request);
|
|
338
|
+
if (auth instanceof Response) return auth;
|
|
339
|
+
const payload = await readJsonObject(request);
|
|
340
|
+
if (payload instanceof Response) return payload;
|
|
341
|
+
const { id, tracking_number: trackingNumber } = payload;
|
|
342
|
+
if (!id || typeof id !== "string") return jsonError("Invalid id", 400);
|
|
343
|
+
if (typeof trackingNumber !== "string" || trackingNumber.trim() === "") {
|
|
344
|
+
return jsonError("Invalid tracking_number", 400);
|
|
345
|
+
}
|
|
346
|
+
const carrier = parseOptionalCarrier(payload.carrier);
|
|
347
|
+
if (carrier && typeof carrier === "object" && "error" in carrier) {
|
|
348
|
+
return jsonError(carrier.error, 400);
|
|
349
|
+
}
|
|
350
|
+
const { DB: db } = getServerEnv5();
|
|
351
|
+
if (!db) return jsonError("DB not available", 503);
|
|
352
|
+
const existing = await getOrder(db, id);
|
|
353
|
+
if (!existing) return jsonError("Order not found", 404);
|
|
354
|
+
const nextTracking = trackingNumber.trim();
|
|
355
|
+
const nextCarrier = carrier === null ? existing.carrier : carrier;
|
|
356
|
+
const result = await updateTracking(db, id, nextTracking, nextCarrier);
|
|
357
|
+
if (result === "not_found") return jsonError("Order not found", 404);
|
|
358
|
+
console.log(
|
|
359
|
+
JSON.stringify({
|
|
360
|
+
type: "admin.audit",
|
|
361
|
+
action: "update-tracking",
|
|
362
|
+
actor: auth.email,
|
|
363
|
+
targetId: id,
|
|
364
|
+
tracking_number: nextTracking,
|
|
365
|
+
carrier: nextCarrier
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
await writeAuditLog3(db, {
|
|
369
|
+
entityType: "order",
|
|
370
|
+
entityId: id,
|
|
371
|
+
action: "update-tracking",
|
|
372
|
+
adminEmail: auth.email,
|
|
373
|
+
actor: "human",
|
|
374
|
+
detail: {
|
|
375
|
+
before: { tracking_number: existing.tracking_number, carrier: existing.carrier },
|
|
376
|
+
after: { tracking_number: nextTracking, carrier: nextCarrier }
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return jsonOk({
|
|
380
|
+
ok: true,
|
|
381
|
+
id,
|
|
382
|
+
tracking_number: nextTracking,
|
|
383
|
+
carrier: nextCarrier
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export {
|
|
389
|
+
createOrdersExportHandler,
|
|
390
|
+
createMarkShippedHandler,
|
|
391
|
+
MAX_BULK_SHIP,
|
|
392
|
+
createMarkShippedBulkHandler,
|
|
393
|
+
formatOrderDate,
|
|
394
|
+
formatYen,
|
|
395
|
+
formatOrderItems,
|
|
396
|
+
formatOrderAddress,
|
|
397
|
+
formatCustomFields,
|
|
398
|
+
carrierLabel,
|
|
399
|
+
loadOrdersAdminPage,
|
|
400
|
+
createUpdateTrackingHandler
|
|
401
|
+
};
|
|
402
|
+
//# sourceMappingURL=chunk-STQU4LQU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/orders/export.ts","../src/orders/mark-shipped.ts","../src/orders/mark-shipped-bulk.ts","../src/orders/page.ts","../src/orders/update-tracking.ts"],"sourcesContent":["import type { OrdersRow } from \"@umec/core/db\";\nimport { getServerEnv } from \"@umec/core/env\";\nimport { authenticateAdmin } from \"../access.js\";\nimport { toCsv } from \"../csv.js\";\nimport { csvAttachment, jsonError, type AdminApiHandler } from \"../http.js\";\nimport { loadProductNamesFromDb } from \"../products.js\";\n\nconst EXPORTABLE_STATUSES = [\"pending\", \"shipped\", \"refunded\", \"cancelled\"] as const;\n\ntype OrderExportRow = Pick<\n OrdersRow,\n | \"id\"\n | \"email\"\n | \"name\"\n | \"phone\"\n | \"items_json\"\n | \"amount\"\n | \"shipping\"\n | \"address_json\"\n | \"custom_fields_json\"\n | \"status\"\n | \"tracking_number\"\n | \"carrier\"\n | \"created_at\"\n>;\n\ntype CheckoutItem = {\n sku?: unknown;\n quantity?: unknown;\n};\n\ntype ShippingAddress = {\n name?: unknown;\n phone?: unknown;\n address?: {\n postal_code?: unknown;\n state?: unknown;\n city?: unknown;\n line1?: unknown;\n line2?: unknown;\n };\n};\n\nfunction parseJson<T>(value: string | null): T | null {\n if (!value) return null;\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n}\n\nfunction formatItems(itemsJson: string | null, names: Record<string, string>) {\n const items = parseJson<CheckoutItem[]>(itemsJson);\n if (!Array.isArray(items) || items.length === 0) return { labels: \"\", quantity: 0 };\n\n const labels = items.map((item) => {\n const sku = typeof item.sku === \"string\" ? item.sku : \"\";\n const quantity = typeof item.quantity === \"number\" ? item.quantity : 0;\n const name = sku && names[sku] ? names[sku] : sku || \"商品\";\n return `${name} x ${quantity}`;\n });\n const quantity = items.reduce((sum, item) => {\n return sum + (typeof item.quantity === \"number\" ? item.quantity : 0);\n }, 0);\n return { labels: labels.join(\"; \"), quantity };\n}\n\nfunction formatAddress(addressJson: string | null) {\n const shipping = parseJson<ShippingAddress>(addressJson);\n if (!shipping?.address) return { postal: \"\", address: \"\" };\n\n const address = shipping.address;\n const parts = [address.state, address.city, address.line1, address.line2]\n .filter((part): part is string => typeof part === \"string\" && part.trim() !== \"\")\n .map((part) => part.trim());\n return {\n postal: typeof address.postal_code === \"string\" ? address.postal_code : \"\",\n address: parts.join(\" \"),\n };\n}\n\nfunction formatNotes(customFieldsJson: string | null) {\n const fields = parseJson<Record<string, unknown>>(customFieldsJson);\n if (!fields) return \"\";\n return Object.entries(fields)\n .flatMap(([key, value]) => {\n if (typeof value !== \"string\" || !value.trim()) return [];\n return [`${key}: ${value.trim()}`];\n })\n .join(\"; \");\n}\n\nexport function createOrdersExportHandler(): AdminApiHandler {\n return async ({ request }) => {\n const auth = await authenticateAdmin(request);\n if (auth instanceof Response) return auth;\n\n const url = new URL(request.url);\n const status = url.searchParams.get(\"status\") ?? \"pending\";\n if (!(EXPORTABLE_STATUSES as readonly string[]).includes(status)) {\n return jsonError(\"Invalid status\", 400);\n }\n\n const { DB: db } = getServerEnv();\n if (!db) return jsonError(\"DB not available\", 503);\n\n const result = await db\n .prepare(\n `SELECT id, email, name, phone, items_json, amount, shipping, address_json,\n custom_fields_json, status, tracking_number, carrier, created_at\n FROM orders\n WHERE status = ?\n ORDER BY created_at ASC`,\n )\n .bind(status)\n .all<OrderExportRow>();\n\n const names = await loadProductNamesFromDb(db);\n const csv = toCsv(\n [\n \"id\",\n \"created_at\",\n \"name\",\n \"email\",\n \"phone\",\n \"postal_code\",\n \"address\",\n \"items\",\n \"quantity\",\n \"amount\",\n \"shipping\",\n \"notes\",\n \"tracking_number\",\n \"carrier\",\n \"status\",\n ],\n (result.results ?? []).map((row: OrderExportRow) => {\n const items = formatItems(row.items_json, names);\n const address = formatAddress(row.address_json);\n return [\n row.id,\n row.created_at ?? \"\",\n row.name ?? \"\",\n row.email,\n row.phone ?? \"\",\n address.postal,\n address.address,\n items.labels,\n items.quantity,\n row.amount ?? \"\",\n row.shipping ?? \"\",\n formatNotes(row.custom_fields_json),\n row.tracking_number ?? \"\",\n row.carrier ?? \"\",\n row.status,\n ];\n }),\n );\n\n return csvAttachment(`orders-${status}.csv`, csv);\n };\n}\n","import { markShipped, writeAuditLog } from \"@umec/core/db\";\nimport { getServerEnv } from \"@umec/core/env\";\nimport { authenticateAdmin } from \"../access.js\";\nimport { parseOptionalCarrier } from \"../carriers.js\";\nimport { jsonError, jsonOk, readJsonObject, type AdminApiHandler } from \"../http.js\";\n\nexport function createMarkShippedHandler(): AdminApiHandler {\n return async ({ request }) => {\n const auth = await authenticateAdmin(request);\n if (auth instanceof Response) return auth;\n\n const payload = await readJsonObject(request);\n if (payload instanceof Response) return payload;\n\n const { id } = payload;\n if (!id || typeof id !== \"string\") {\n return jsonError(\"Invalid id\", 400);\n }\n\n const carrier = parseOptionalCarrier(payload.carrier);\n if (carrier && typeof carrier === \"object\" && \"error\" in carrier) {\n return jsonError(carrier.error, 400);\n }\n\n const trackingNumber =\n payload.tracking_number === undefined ||\n payload.tracking_number === null ||\n payload.tracking_number === \"\"\n ? null\n : typeof payload.tracking_number === \"string\"\n ? payload.tracking_number.trim()\n : null;\n if (\n payload.tracking_number !== undefined &&\n payload.tracking_number !== null &&\n payload.tracking_number !== \"\" &&\n trackingNumber === null\n ) {\n return jsonError(\"Invalid tracking_number\", 400);\n }\n\n const { DB: db } = getServerEnv();\n if (!db) return jsonError(\"DB not available\", 503);\n\n const result = await markShipped(db, id, {\n trackingNumber,\n carrier,\n });\n\n if (result === \"not_pending\") {\n return jsonError(\"Order is not pending\", 409);\n }\n\n console.log(\n JSON.stringify({\n type: \"admin.audit\",\n action: \"mark-shipped\",\n actor: auth.email,\n targetId: id,\n }),\n );\n\n await writeAuditLog(db, {\n entityType: \"order\",\n entityId: id,\n action: \"mark-shipped\",\n adminEmail: auth.email,\n actor: \"human\",\n detail: { tracking_number: trackingNumber, carrier },\n });\n\n return jsonOk({ ok: true });\n };\n}\n","import { markShipped, writeAuditLog } from \"@umec/core/db\";\nimport { getServerEnv } from \"@umec/core/env\";\nimport { authenticateAdmin } from \"../access.js\";\nimport { parseOptionalCarrier } from \"../carriers.js\";\nimport { jsonError, jsonOk, readJsonObject, type AdminApiHandler } from \"../http.js\";\n\nexport const MAX_BULK_SHIP = 100;\n\nexport function createMarkShippedBulkHandler(): AdminApiHandler {\n return async ({ request }) => {\n const auth = await authenticateAdmin(request);\n if (auth instanceof Response) return auth;\n\n const payload = await readJsonObject(request);\n if (payload instanceof Response) return payload;\n\n const { ids } = payload;\n if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => typeof id === \"string\" && id)) {\n return jsonError(\"Invalid ids\", 400);\n }\n if (ids.length > MAX_BULK_SHIP) {\n return jsonError(`ids must contain at most ${MAX_BULK_SHIP} items`, 400);\n }\n\n const carrier = parseOptionalCarrier(payload.carrier);\n if (carrier && typeof carrier === \"object\" && \"error\" in carrier) {\n return jsonError(carrier.error, 400);\n }\n\n const trackingNumber =\n typeof payload.tracking_number === \"string\" && payload.tracking_number.trim()\n ? payload.tracking_number.trim()\n : null;\n\n const { DB: db } = getServerEnv();\n if (!db) return jsonError(\"DB not available\", 503);\n\n const uniqueIds = [...new Set(ids)];\n const shipped: string[] = [];\n const skipped: string[] = [];\n const shippedAt = Date.now();\n\n for (const id of uniqueIds) {\n const result = await markShipped(db, id, {\n trackingNumber,\n carrier,\n shippedAt,\n });\n\n if (result === \"not_pending\") {\n skipped.push(id);\n continue;\n }\n\n shipped.push(id);\n console.log(\n JSON.stringify({\n type: \"admin.audit\",\n action: \"mark-shipped\",\n actor: auth.email,\n targetId: id,\n }),\n );\n await writeAuditLog(db, {\n entityType: \"order\",\n entityId: id,\n action: \"mark-shipped\",\n adminEmail: auth.email,\n actor: \"human\",\n detail: { tracking_number: trackingNumber, carrier, bulk: true },\n });\n }\n\n return jsonOk({ shipped, skipped });\n };\n}\n","import type { OrdersRow } from \"@umec/core/db\";\nimport { getServerEnv } from \"@umec/core/env\";\nimport { requireAdminAuth } from \"../access.js\";\nimport { CARRIER_LABELS } from \"../carriers.js\";\nimport { loadProductNamesFromDb } from \"../products.js\";\n\nexport type CheckoutItem = {\n sku?: unknown;\n quantity?: unknown;\n};\n\nexport type ShippingAddress = {\n name?: unknown;\n phone?: unknown;\n address?: {\n postal_code?: unknown;\n state?: unknown;\n city?: unknown;\n line1?: unknown;\n line2?: unknown;\n country?: unknown;\n };\n};\n\nexport type OrdersAdminPageData = {\n orders: OrdersRow[];\n names: Record<string, string>;\n dbNotice: string;\n pendingCount: number;\n};\n\nexport type OrdersAdminPageLoad = { response: Response } | OrdersAdminPageData;\n\nfunction parseJson<T>(value: string | null): T | null {\n if (!value) return null;\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n}\n\nexport function formatOrderDate(value: number | string | null) {\n if (value == null || value === \"\") return \"-\";\n const timestamp = typeof value === \"string\" ? Number(value) : value;\n if (!Number.isFinite(timestamp)) return \"-\";\n\n return new Intl.DateTimeFormat(\"ja-JP\", {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n timeZone: \"Asia/Tokyo\",\n }).format(new Date(timestamp));\n}\n\nexport function formatYen(value: number | null) {\n if (value == null) return \"-\";\n return new Intl.NumberFormat(\"ja-JP\", {\n style: \"currency\",\n currency: \"JPY\",\n maximumFractionDigits: 0,\n }).format(value);\n}\n\nexport function formatOrderItems(itemsJson: string | null, names: Record<string, string>) {\n const items = parseJson<CheckoutItem[]>(itemsJson);\n if (!Array.isArray(items) || items.length === 0) return [\"-\"];\n\n return items.map((item) => {\n const sku = typeof item.sku === \"string\" ? item.sku : \"\";\n const quantity = typeof item.quantity === \"number\" ? item.quantity : 0;\n const productName = sku && names[sku] ? names[sku] : sku || \"商品\";\n return `${productName} x ${quantity}`;\n });\n}\n\nfunction stringPart(value: unknown) {\n return typeof value === \"string\" && value.trim() ? value.trim() : \"\";\n}\n\nexport function formatOrderAddress(addressJson: string | null) {\n const shipping = parseJson<ShippingAddress>(addressJson);\n if (!shipping?.address) return [\"-\"];\n\n const address = shipping.address;\n const postalCode = stringPart(address.postal_code);\n const lines = [\n postalCode ? `〒${postalCode}` : \"\",\n stringPart(address.state) + stringPart(address.city),\n stringPart(address.line1),\n stringPart(address.line2),\n ].filter(Boolean);\n\n return lines.length > 0 ? lines : [\"-\"];\n}\n\nexport function formatCustomFields(customFieldsJson: string | null): { key: string; value: string }[] {\n const cf = parseJson<Record<string, unknown>>(customFieldsJson);\n if (!cf) return [];\n\n return Object.entries(cf).flatMap(([key, value]) => {\n if (typeof value !== \"string\" || !value.trim()) return [];\n return [{ key, value: value.trim() }];\n });\n}\n\nexport function carrierLabel(code: string | null) {\n if (!code) return \"\";\n return (CARRIER_LABELS as Record<string, string>)[code] ?? code;\n}\n\nexport async function loadOrdersAdminPage(request: Request): Promise<OrdersAdminPageLoad> {\n const denied = await requireAdminAuth(request);\n if (denied) return { response: denied };\n\n const { DB: db } = getServerEnv();\n let orders: OrdersRow[] = [];\n let names: Record<string, string> = {};\n let dbNotice = \"\";\n\n if (db) {\n try {\n const result = await db\n .prepare(\n `SELECT * FROM orders ORDER BY\n CASE WHEN status = 'pending' THEN 0 ELSE 1 END,\n created_at DESC\n LIMIT 100`,\n )\n .all<OrdersRow>();\n orders = result.results ?? [];\n names = await loadProductNamesFromDb(db);\n } catch (error) {\n console.error(\"[admin/orders] order query failed:\", error);\n dbNotice = \"Orders could not be loaded.\";\n }\n } else {\n dbNotice = \"DB binding is not available.\";\n }\n\n const pendingCount = orders.filter((order) => order.status === \"pending\").length;\n return { orders, names, dbNotice, pendingCount };\n}\n","import { getOrder, updateTracking, writeAuditLog } from \"@umec/core/db\";\nimport { getServerEnv } from \"@umec/core/env\";\nimport { authenticateAdmin } from \"../access.js\";\nimport { parseOptionalCarrier } from \"../carriers.js\";\nimport { jsonError, jsonOk, readJsonObject, type AdminApiHandler } from \"../http.js\";\n\nexport function createUpdateTrackingHandler(): AdminApiHandler {\n return async ({ request }) => {\n const auth = await authenticateAdmin(request);\n if (auth instanceof Response) return auth;\n\n const payload = await readJsonObject(request);\n if (payload instanceof Response) return payload;\n\n const { id, tracking_number: trackingNumber } = payload;\n if (!id || typeof id !== \"string\") return jsonError(\"Invalid id\", 400);\n if (typeof trackingNumber !== \"string\" || trackingNumber.trim() === \"\") {\n return jsonError(\"Invalid tracking_number\", 400);\n }\n\n const carrier = parseOptionalCarrier(payload.carrier);\n if (carrier && typeof carrier === \"object\" && \"error\" in carrier) {\n return jsonError(carrier.error, 400);\n }\n\n const { DB: db } = getServerEnv();\n if (!db) return jsonError(\"DB not available\", 503);\n\n const existing = await getOrder(db, id);\n if (!existing) return jsonError(\"Order not found\", 404);\n\n const nextTracking = trackingNumber.trim();\n const nextCarrier = carrier === null ? existing.carrier : carrier;\n\n const result = await updateTracking(db, id, nextTracking, nextCarrier);\n if (result === \"not_found\") return jsonError(\"Order not found\", 404);\n\n console.log(\n JSON.stringify({\n type: \"admin.audit\",\n action: \"update-tracking\",\n actor: auth.email,\n targetId: id,\n tracking_number: nextTracking,\n carrier: nextCarrier,\n }),\n );\n\n await writeAuditLog(db, {\n entityType: \"order\",\n entityId: id,\n action: \"update-tracking\",\n adminEmail: auth.email,\n actor: \"human\",\n detail: {\n before: { tracking_number: existing.tracking_number, carrier: existing.carrier },\n after: { tracking_number: nextTracking, carrier: nextCarrier },\n },\n });\n\n return jsonOk({\n ok: true,\n id,\n tracking_number: nextTracking,\n carrier: nextCarrier,\n });\n };\n}\n"],"mappings":"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","names":["quantity","getServerEnv","getServerEnv","markShipped","writeAuditLog","getServerEnv","getServerEnv","markShipped","writeAuditLog","getServerEnv","parseJson","getServerEnv","writeAuditLog","getServerEnv","getServerEnv","writeAuditLog"]}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import {
|
|
2
|
+
authenticateAdmin,
|
|
3
|
+
requireAdminAuth
|
|
4
|
+
} from "./chunk-W2O6EQWT.js";
|
|
5
|
+
import {
|
|
6
|
+
jsonError,
|
|
7
|
+
jsonOk,
|
|
8
|
+
readJsonObject
|
|
9
|
+
} from "./chunk-E7ATMICY.js";
|
|
10
|
+
import {
|
|
11
|
+
isFieldError,
|
|
12
|
+
parseCreateProductFields,
|
|
13
|
+
parseSku,
|
|
14
|
+
parseUpdateProductFields
|
|
15
|
+
} from "./chunk-ODK4R57S.js";
|
|
16
|
+
import {
|
|
17
|
+
listProductRows,
|
|
18
|
+
parseImagesJson
|
|
19
|
+
} from "./chunk-ZEA6G75Z.js";
|
|
20
|
+
|
|
21
|
+
// src/catalog/create-product.ts
|
|
22
|
+
import { createProduct, writeAuditLog } from "@umec/core/db";
|
|
23
|
+
import { getServerEnv } from "@umec/core/env";
|
|
24
|
+
function createCreateProductHandler() {
|
|
25
|
+
return async ({ request }) => {
|
|
26
|
+
const auth = await authenticateAdmin(request);
|
|
27
|
+
if (auth instanceof Response) return auth;
|
|
28
|
+
const payload = await readJsonObject(request);
|
|
29
|
+
if (payload instanceof Response) return payload;
|
|
30
|
+
const sku = parseSku(payload.sku);
|
|
31
|
+
if (isFieldError(sku)) return jsonError(sku.error, 400);
|
|
32
|
+
const fields = parseCreateProductFields(payload);
|
|
33
|
+
if (isFieldError(fields)) return jsonError(fields.error, 400);
|
|
34
|
+
const { DB: db } = getServerEnv();
|
|
35
|
+
if (!db) return jsonError("DB not available", 503);
|
|
36
|
+
try {
|
|
37
|
+
const result = await createProduct(db, sku, fields, auth.email);
|
|
38
|
+
if (result === "conflict") return jsonError("SKU already exists", 409);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("[create-product] insert failed:", error);
|
|
41
|
+
return jsonError("Failed to create product", 500);
|
|
42
|
+
}
|
|
43
|
+
console.log(
|
|
44
|
+
JSON.stringify({
|
|
45
|
+
type: "admin.audit",
|
|
46
|
+
action: "create-product",
|
|
47
|
+
actor: auth.email,
|
|
48
|
+
targetId: sku
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
await writeAuditLog(db, {
|
|
52
|
+
entityType: "product",
|
|
53
|
+
entityId: sku,
|
|
54
|
+
action: "create-product",
|
|
55
|
+
adminEmail: auth.email,
|
|
56
|
+
actor: "human",
|
|
57
|
+
detail: {
|
|
58
|
+
name: fields.name,
|
|
59
|
+
price: fields.price,
|
|
60
|
+
shipping_units: fields.shippingUnits,
|
|
61
|
+
active: fields.active
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return jsonOk({
|
|
65
|
+
ok: true,
|
|
66
|
+
sku,
|
|
67
|
+
name: fields.name,
|
|
68
|
+
price: fields.price,
|
|
69
|
+
active: fields.active
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/catalog/discontinue-product.ts
|
|
75
|
+
import { discontinueProduct, writeAuditLog as writeAuditLog2 } from "@umec/core/db";
|
|
76
|
+
import { getServerEnv as getServerEnv2 } from "@umec/core/env";
|
|
77
|
+
function createDiscontinueProductHandler() {
|
|
78
|
+
return async ({ request }) => {
|
|
79
|
+
const auth = await authenticateAdmin(request);
|
|
80
|
+
if (auth instanceof Response) return auth;
|
|
81
|
+
const payload = await readJsonObject(request);
|
|
82
|
+
if (payload instanceof Response) return payload;
|
|
83
|
+
const sku = parseSku(payload.sku);
|
|
84
|
+
if (isFieldError(sku)) return jsonError(sku.error, 400);
|
|
85
|
+
const { DB: db } = getServerEnv2();
|
|
86
|
+
if (!db) return jsonError("DB not available", 503);
|
|
87
|
+
const result = await discontinueProduct(db, sku, auth.email);
|
|
88
|
+
if (result === "not_found") return jsonError("SKU not found", 404);
|
|
89
|
+
if (result === "already_inactive") {
|
|
90
|
+
return jsonOk({ ok: true, sku, active: 0 });
|
|
91
|
+
}
|
|
92
|
+
console.log(
|
|
93
|
+
JSON.stringify({
|
|
94
|
+
type: "admin.audit",
|
|
95
|
+
action: "discontinue-product",
|
|
96
|
+
actor: auth.email,
|
|
97
|
+
targetId: sku
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
await writeAuditLog2(db, {
|
|
101
|
+
entityType: "product",
|
|
102
|
+
entityId: sku,
|
|
103
|
+
action: "discontinue-product",
|
|
104
|
+
adminEmail: auth.email,
|
|
105
|
+
actor: "human",
|
|
106
|
+
detail: { before: { active: 1 }, after: { active: 0 } }
|
|
107
|
+
});
|
|
108
|
+
return jsonOk({ ok: true, sku, active: 0 });
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/catalog/page.ts
|
|
113
|
+
import { getServerEnv as getServerEnv3 } from "@umec/core/env";
|
|
114
|
+
function formatYen(value) {
|
|
115
|
+
return new Intl.NumberFormat("ja-JP", {
|
|
116
|
+
style: "currency",
|
|
117
|
+
currency: "JPY",
|
|
118
|
+
maximumFractionDigits: 0
|
|
119
|
+
}).format(value);
|
|
120
|
+
}
|
|
121
|
+
function imagesText(row) {
|
|
122
|
+
return parseImagesJson(row.images_json).join("\n");
|
|
123
|
+
}
|
|
124
|
+
async function loadCatalogAdminPage(request) {
|
|
125
|
+
const denied = await requireAdminAuth(request);
|
|
126
|
+
if (denied) return { response: denied };
|
|
127
|
+
const { DB: db } = getServerEnv3();
|
|
128
|
+
let products = [];
|
|
129
|
+
let dbNotice = "";
|
|
130
|
+
if (db) {
|
|
131
|
+
try {
|
|
132
|
+
const rows = await listProductRows(db, { includeInactive: true });
|
|
133
|
+
const stockResult = await db.prepare("SELECT sku, stock FROM inventory").all();
|
|
134
|
+
const stockMap = new Map(
|
|
135
|
+
(stockResult.results ?? []).map((row) => [row.sku, row.stock])
|
|
136
|
+
);
|
|
137
|
+
products = rows.map((row) => ({ ...row, stock: stockMap.get(row.sku) ?? null }));
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error("[admin/products] query failed:", error);
|
|
140
|
+
dbNotice = "Products could not be loaded.";
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
dbNotice = "DB binding is not available.";
|
|
144
|
+
}
|
|
145
|
+
return { products, dbNotice };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/catalog/update-product.ts
|
|
149
|
+
import { updateProduct, writeAuditLog as writeAuditLog3 } from "@umec/core/db";
|
|
150
|
+
import { getServerEnv as getServerEnv4 } from "@umec/core/env";
|
|
151
|
+
function createUpdateProductHandler() {
|
|
152
|
+
return async ({ request }) => {
|
|
153
|
+
const auth = await authenticateAdmin(request);
|
|
154
|
+
if (auth instanceof Response) return auth;
|
|
155
|
+
const payload = await readJsonObject(request);
|
|
156
|
+
if (payload instanceof Response) return payload;
|
|
157
|
+
const sku = parseSku(payload.sku);
|
|
158
|
+
if (isFieldError(sku)) return jsonError(sku.error, 400);
|
|
159
|
+
const fields = parseUpdateProductFields(payload);
|
|
160
|
+
if (isFieldError(fields)) return jsonError(fields.error, 400);
|
|
161
|
+
const { DB: db } = getServerEnv4();
|
|
162
|
+
if (!db) return jsonError("DB not available", 503);
|
|
163
|
+
const { result, before, after } = await updateProduct(db, sku, fields, auth.email);
|
|
164
|
+
if (result === "not_found" || !before || !after) return jsonError("SKU not found", 404);
|
|
165
|
+
console.log(
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
type: "admin.audit",
|
|
168
|
+
action: "update-product",
|
|
169
|
+
actor: auth.email,
|
|
170
|
+
targetId: sku
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
await writeAuditLog3(db, {
|
|
174
|
+
entityType: "product",
|
|
175
|
+
entityId: sku,
|
|
176
|
+
action: "update-product",
|
|
177
|
+
adminEmail: auth.email,
|
|
178
|
+
actor: "human",
|
|
179
|
+
detail: {
|
|
180
|
+
before: {
|
|
181
|
+
name: before.name,
|
|
182
|
+
price: before.price,
|
|
183
|
+
active: before.active
|
|
184
|
+
},
|
|
185
|
+
after: {
|
|
186
|
+
name: after.name,
|
|
187
|
+
price: after.price,
|
|
188
|
+
active: after.active
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return jsonOk({
|
|
193
|
+
ok: true,
|
|
194
|
+
sku,
|
|
195
|
+
name: after.name,
|
|
196
|
+
price: after.price,
|
|
197
|
+
active: after.active
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
createCreateProductHandler,
|
|
204
|
+
createDiscontinueProductHandler,
|
|
205
|
+
formatYen,
|
|
206
|
+
imagesText,
|
|
207
|
+
loadCatalogAdminPage,
|
|
208
|
+
createUpdateProductHandler
|
|
209
|
+
};
|
|
210
|
+
//# sourceMappingURL=chunk-TIK7KC4O.js.map
|