Skip to content

fix(mcp): truthful serial-tracked SO block message + close-out discoverability#882

Open
dougborg wants to merge 2 commits into
mainfrom
fix/mcp-close-out-friction
Open

fix(mcp): truthful serial-tracked SO block message + close-out discoverability#882
dougborg wants to merge 2 commits into
mainfrom
fix/mcp-close-out-friction

Conversation

@dougborg
Copy link
Copy Markdown
Owner

Summary

Three operator-facing friction fixes surfaced by the 2026-05-29 Spot Bikes
end-of-day close-out (5 POs received + 2 SOs closed). All are docs/message
changes — no logic or wire-shape changes — so they're safe to ship ahead of
the heavier blockers they point at.

  1. Truthful serial-tracked SO block message (Bug 7). The block told
    operators to "Pass serial_numbers via the rows= override", but following
    that hits a Katana 422 (serial numbers have already been assigned)
    whenever the serial was minted on the row's linked MO — the normal
    close-out case. The message now adds the caveat and names the only working
    path (Katana UI "Deliver all", which performs the MO→SO transfer the public
    API cannot). Mirrored in katana://help/tools.

  2. picked_date discoverability (Bug 4). There is no separate
    picked_date / delivery_date parameter — completed_at is the single
    backdating knob for both order types (shipped in feat(mcp): fulfill_order should accept a backdated timestamp (done_date for MO / delivery_date for SO) #778, just hard to find).
    The help resource now says so explicitly.

  3. Landed-cost discoverability on receive (Bug 3). receive_purchase_order
    doesn't set customs/freight/duties, and the MCP-native path
    (modify_purchase_order(add_additional_costs=[…]), which can target a
    receipt group_id) wasn't discoverable at receive time — so agents fell
    back to the Katana UI. Now pointed at via a next_action, the receive
    docstring, the help resource, and the list_additional_costs description.

Scope / what this is NOT

The underlying serial-transfer blocker stays open — the public API genuinely
cannot transfer an MO-minted serial onto a linked SO row; that needs a
design decision (UI-API access vs. documented fallback). Tracked in #784
(reopened) and verified end-to-end in #849. The additional_costs-on-
receive feature (vs. this discoverability pointer) is #879. Bug 6
serial input notes are on #804.

Test plan

  • uv run poe agent-check (ruff format/check, ty) — clean
  • uv run poe test — 3780 passed
  • uv run pyright on every edited file — 0 errors
  • Browser render suite for the affected files — 26 passed
  • New regression assertions pin the block-message caveat and the receive
    next_action

Refs #784 #849 #804 #879

🤖 Generated with Claude Code

…erability

The serial-tracked SO fulfillment block told operators to "Pass
serial_numbers via the rows= override", but following that hits a Katana
422 ("serial numbers have already been assigned") whenever the serial was
minted on the row's linked MO — the normal close-out case. Add the caveat
and name the working fallback (Katana UI "Deliver all"), which performs the
MO->SO transfer the public API cannot. Mirror the caveat in
katana://help/tools.

Also close two discoverability gaps surfaced in the 2026-05-29 close-out:

- picked_date: clarify in help that there is no separate picked_date /
  delivery_date parameter — completed_at is the single backdating knob for
  both order types (already shipped in #778, just hard to find).
- landed costs on receive: receive_purchase_order now points operators at
  the MCP-native apportionment path (modify_purchase_order add_additional_costs
  with the receipt group_id) via a next_action, its docstring, the help
  resource, and the list_additional_costs description — instead of leaving
  it as an undiscovered UI-only step.

Regression assertions pin the block-message caveat and the receive
next_action. The underlying transfer blocker stays open (#784 reopened,
verified in #849); the receive additional_costs field is #879.

Refs #784 #849 #804 #879

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 30, 2026 00:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates MCP operator-facing guidance for order fulfillment and purchase-order receiving, focusing on clearer serial-tracked SO messaging, landed-cost follow-up discoverability, and backdating help text.

Changes:

  • Updates fulfill_order warnings/help for serial-tracked sales orders and completed_at discoverability.
  • Adds landed-cost next-action/doc guidance after receive_purchase_order.
  • Adds regression assertions for the new warning and next-action text.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
katana_mcp_server/src/katana_mcp/tools/foundation/orders.py Updates serial-tracked SO block warning text.
katana_mcp_server/src/katana_mcp/resources/help.py Adds landed-cost, serial-transfer caveat, and completed_at help text.
katana_mcp_server/src/katana_mcp/tools/foundation/purchase_orders.py Adds landed-cost follow-up guidance to receive success actions and docstring.
katana_mcp_server/src/katana_mcp/tools/foundation/reference.py Expands list_additional_costs docstring with receipt-group usage.
katana_mcp_server/tests/tools/test_orders.py Pins the serial-tracked SO caveat text.
katana_mcp_server/tests/tools/test_purchase_orders.py Pins landed-cost next-action discoverability after receive.

Comment on lines +1307 to +1311
"unassigned. Caveat: if a serial was minted on the linked "
"manufacturing order, Katana's public API cannot transfer it "
"onto this row (it returns 'serial numbers have already been "
"assigned'); fulfill that row from the Katana UI (\"Deliver "
'all") instead.'
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — reconciled in 4680265. You are right that the blanket reading was wrong: add_serial_numbers(resource_type="SalesOrderRow") transfer does work for SO rows that are not linked to an MO (the existing contract/test cover that path). The failure is narrower: when the target SO row's production is driven by a linked MO (the common serial-tracked close-out case), Katana hard-422s the transfer with "is linked, serial info must be updated on MO", and fulfill_order(... rows=[{serial_numbers}]) 422s with "already been assigned". This message/help text is scoped to exactly that MO-linked case. I updated the add_serial_numbers docstring (serial_numbers.py) to document the same MO-linked exception so both tools tell one consistent story and point at the same UI fallback. Verified live in the 2026-05-29 close-out; tracked end-to-end in #849 (and #784, reopened).

Comment on lines +1785 to +1792
**Caveat (serial minted on a linked MO):** the public-API write paths
cannot *transfer* a serial that is currently attached to the row's linked
manufacturing order — `POST /sales_order_fulfillments` returns "serial
numbers have already been assigned" and `add_serial_numbers(SalesOrderRow)`
returns "is linked, serial info must be updated on MO". That common
close-out case (finished good came off an MO with the serial already
minted) has no MCP-native path today; fulfill it from the Katana UI
("Deliver all"), which performs the MO→SO transfer atomically.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — reconciled in 4680265. You are right that the blanket reading was wrong: add_serial_numbers(resource_type="SalesOrderRow") transfer does work for SO rows that are not linked to an MO (the existing contract/test cover that path). The failure is narrower: when the target SO row's production is driven by a linked MO (the common serial-tracked close-out case), Katana hard-422s the transfer with "is linked, serial info must be updated on MO", and fulfill_order(... rows=[{serial_numbers}]) 422s with "already been assigned". This message/help text is scoped to exactly that MO-linked case. I updated the add_serial_numbers docstring (serial_numbers.py) to document the same MO-linked exception so both tools tell one consistent story and point at the same UI fallback. Verified live in the 2026-05-29 close-out; tracked end-to-end in #849 (and #784, reopened).

…-linked limitation

Addresses Copilot review on #882: the new fulfill_order block message and
help text say an MO-minted serial can't be transferred onto a linked SO row,
but add_serial_numbers' docstring described resource_type="SalesOrderRow"
transfer as always working — a contradiction.

The transfer path does work for SO rows NOT linked to an MO. It is rejected
(422 "is linked, serial info must be updated on MO") only when the target SO
row's production is driven by a linked MO. Document that exception on the
transfer bullet so both tools tell the same story and point operators at the
same UI fallback.

Doc-only; no behavior change.

Refs #784 #849

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants