WFDASH-WEBAPP/src/lib/server/order-number-duplicate-link-service.ts

78 lines
No EOL
2.8 KiB
TypeScript

/**
* PREREQUISITE: Before this code can run against a real PocketBase
* instance, the operator must add two fields to the `orders`
* collection in the PB admin UI (per the AGENTS.md convention — PB
* schema is not self-service from code):
* - `order_number_duplicate` (Relation → orders, single)
* - `order_number_duplicate_checked` (Bool)
* Without these fields, the `.update()` below will 500 in production.
*/
/**
* order-number-duplicate-link-service — detects when an order's
* `order_number` is reused by ANOTHER order (any status, no time
* limit). Catches two cases that have bitten the operator:
* - internal ORD-number collisions from a partial intake race
* - operator not realising a PO was already entered
*
* Visibility only. Nothing downstream reads this to drive pricing,
* supplier assignment, or processing — a wrong flag costs nothing, the
* operator just ignores the pill. That's what justifies writing the
* match automatically with no human confirmation step, unlike the
* order↔quote matcher's deliberately conservative suggest-only policy
* (see order-quote-link-service.ts) — that policy exists because a
* wrong quote link puts wrong prices in front of a buyer.
*
* NOTE: customer PO refs (e.g. "PO-12345") and placeholders like
* "NO-REF" are ALLOWED to repeat by design — one blanket customer PO
* is often split into several partial-delivery orders. This matcher
* flags any exact-string duplicate regardless of shape; the order
* review collision check (orders-service.ts) is the only place that
* gates on ORD-YYYY-MM-NNN shape.
*/
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
const ORDER_COLLECTION = "orders";
export interface OrderNumberDuplicateResult {
orderId: string;
duplicateOrderId: string | null;
}
function esc(s: string): string {
return (s || "").replace(/"/g, '\\"');
}
export async function suggestOrderNumberDuplicate(
orderId: string,
): Promise<OrderNumberDuplicateResult> {
const pb = await getAdminPocketBase();
pb.autoCancellation(false);
const order = await pb
.collection(ORDER_COLLECTION)
.getOne<{ id: string; order_number?: string }>(orderId);
if (!order.order_number) {
return { orderId, duplicateOrderId: null };
}
const others = await pb
.collection(ORDER_COLLECTION)
.getFullList<{ id: string; created: string }>({
filter: `order_number = "${esc(order.order_number)}" && id != "${esc(orderId)}"`,
fields: "id,created",
sort: "created",
batch: 500,
});
const duplicateOrderId = others.length > 0 ? others[0].id : null;
await pb.collection(ORDER_COLLECTION).update(orderId, {
order_number_duplicate: duplicateOrderId,
order_number_duplicate_checked: true,
});
return { orderId, duplicateOrderId };
}