feat(mcp): order_items.create accepts exclude_from_invoice + doc updates
Task 9 of the non-invoiceable-order-extras plan. order_items.create MCP tool: - Input schema gains an optional boolean 'exclude_from_invoice'. - Tool description rewritten to explain the supplier-side component use case, the PO + driver + collection flow, the silent drop from the customer invoice and delivery note, and the force-zero selling price. - operationData hint / nextActions block updated — the prior text documented a '409 order_locked' refusal that no longer fires (Task 2 removed the pending gate). - Handler forwards the boolean to createOrderItemForOrder (which itself forces selling_price=0 when the flag is set — Task 2). Also fixed a TypeScript strict-mode complaint in the T7 test that the previous commit left behind (vi.fn mock-call tuple type needed an explicit parameter so mock.calls[0] is typed). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f7a79e671a
commit
12657d126b
2 changed files with 22 additions and 11 deletions
|
|
@ -14,7 +14,7 @@ const {
|
|||
// Spy on the PDF renderer so the test can assert which lines the
|
||||
// route passes in. Default returns a tiny PDF buffer so the upload
|
||||
// step in the route has something to upload.
|
||||
const renderDeliveryNotePdfMock = vi.fn(async () =>
|
||||
const renderDeliveryNotePdfMock = vi.fn(async (_input: unknown) =>
|
||||
Buffer.from("%PDF-1.4\n"),
|
||||
);
|
||||
return {
|
||||
|
|
@ -391,7 +391,7 @@ describe("POST /api/invoicing/delivery-note", () => {
|
|||
// sees them. Capturing this directly via the render mock is
|
||||
// what catches the load-bearing mutation in this split.
|
||||
expect(renderDeliveryNotePdfMock).toHaveBeenCalledTimes(1);
|
||||
const renderInput = renderDeliveryNotePdfMock.mock.calls[0][0] as {
|
||||
const renderInput = renderDeliveryNotePdfMock.mock.calls[0]?.[0] as unknown as {
|
||||
lines: Array<{ description?: string }>;
|
||||
};
|
||||
expect(renderInput.lines).toHaveLength(2);
|
||||
|
|
|
|||
|
|
@ -472,10 +472,10 @@ const TOOL_HINTS: Record<string, { hint: string; nextActions: string[] }> = {
|
|||
],
|
||||
},
|
||||
"order_items.create": {
|
||||
hint: "Create can fail with 409 order_locked if any line item on the parent order is past 'pending'. Returns per-row created/failed arrays.",
|
||||
hint: "Create returns per-row created/failed arrays. Pass exclude_from_invoice: true to mark the line as supplier-side (gets a PO + driver + collection but never appears on the customer invoice or delivery note); the server forces selling_price to 0 when the flag is set.",
|
||||
nextActions: [
|
||||
"For orders with POs in flight, split first via orders.split and add the line to the continuation order",
|
||||
"selling_price auto-derives from cost_price × (1 + markup/100) when omitted",
|
||||
"selling_price auto-derives from cost_price × (1 + markup/100) when omitted (and is forced to 0 when exclude_from_invoice is true)",
|
||||
"For non-invoiceable extras — the supplier-side component of a customer-approved 'one-line' quote — set exclude_from_invoice: true so the line rides along for PO/driver/collection but drops out of the customer invoice and delivery note",
|
||||
],
|
||||
},
|
||||
"orders.add_attachment": {
|
||||
|
|
@ -993,12 +993,15 @@ const tools = [
|
|||
name: "order_items.create",
|
||||
description:
|
||||
"Add a new line item to an existing order. Mirrors the REST POST /api/v1/orders/{id}/items endpoint.\n\n" +
|
||||
"**Strict pending gate**: refuses with 409 (`order_locked`) if any line item on the order is past " +
|
||||
"`item_status === \"pending\"`. Adding a line to an order with a PO in flight would orphan the " +
|
||||
"PO's pricing / supplier reference. Operator must split the order (use `orders.split` to create a " +
|
||||
"continuation order) or finish the existing PO first.\n\n" +
|
||||
"`selling_price` auto-derives from `cost_price * (1 + markup / 100)` when omitted. " +
|
||||
"Per-row failures never abort the whole call (returns `created[]` + `failed[]`).",
|
||||
"Per-row failures never abort the whole call (returns `created[]` + `failed[]`).\n\n" +
|
||||
"Set `exclude_from_invoice: true` for a supplier-side component line — the supplier-side split of a " +
|
||||
"customer-approved single-line quote (e.g. the customer bought 'M20 bolt, nut and washer' as one priced " +
|
||||
"line, but we have to raise three PO lines to actually buy it). The line gets a PO + driver + collection " +
|
||||
"like any other row, rides along for the n8n status flip, but is dropped from the QuickBooks `items` array " +
|
||||
"and the customer delivery-note `lines:` so the customer never sees it. When the flag is set, `selling_price` " +
|
||||
"is forced to 0 regardless of what the caller passes (the flag is authoritative — the price column is " +
|
||||
"not consulted downstream).",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
required: ["order_id", "description"],
|
||||
|
|
@ -1008,11 +1011,15 @@ const tools = [
|
|||
quantity: { type: "number", description: "Order quantity. Default 1." },
|
||||
cost_price: { type: "number", description: "Cost price (supplier cost). Default 0." },
|
||||
markup: { type: "number", description: "Margin %. Default 0." },
|
||||
selling_price: { type: "number", description: "Customer-facing unit price. Auto-derived from cost_price × (1 + markup/100) when omitted." },
|
||||
selling_price: { type: "number", description: "Customer-facing unit price. Auto-derived from cost_price × (1 + markup/100) when omitted. Forced to 0 when exclude_from_invoice is true." },
|
||||
sku: { type: "string" },
|
||||
unit: { type: "string" },
|
||||
supplier: { type: "string", description: "PB supplier relation id (15-char)." },
|
||||
notes: { type: "string" },
|
||||
exclude_from_invoice: {
|
||||
type: "boolean",
|
||||
description: "Mark this line as supplier-side only. See the description above for the full contract. Default false.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -4250,6 +4257,10 @@ export async function POST(req: NextRequest) {
|
|||
unit: typeof args.unit === "string" ? args.unit : undefined,
|
||||
supplier: typeof args.supplier === "string" ? args.supplier : undefined,
|
||||
notes: typeof args.notes === "string" ? args.notes : undefined,
|
||||
exclude_from_invoice:
|
||||
typeof args.exclude_from_invoice === "boolean"
|
||||
? args.exclude_from_invoice
|
||||
: undefined,
|
||||
},
|
||||
);
|
||||
return NextResponse.json({
|
||||
|
|
|
|||
Loading…
Reference in a new issue