Fix external_shipment_id placement and test-mode store_id ticket persistence in the return-label flow

external_shipment_id was at the top level of the POST /v2/labels payload in both
_create_dummy_outbound_label() and create_return_label_from_dummy() - ShipStation's
schema only supports it nested under shipment, so it was silently ignored and
auto-generated ("SEAuto-...") on every label this flow ever created. Moved it inside
the shipment dict in both functions. Confirmed end-to-end on a real production
ticket (AR-166098): Order # now correctly shows the real ticket number, and
ship_from/ship_to are correct for a return.

Also: store_id is now only required in production - ShipStation's sandbox
environment cannot have stores/Order Sources at all (confirmed in the real sandbox
dashboard), so test mode omits it from the request instead of requiring an
impossible value. And three per-ticket write-backs (mark_shipstation_sent,
save_dummy_outbound_label_id, save_pack_data) were hardcoded to source == "jira",
silently no-opping for source == "test" tickets created by Create Test Shipment -
now match on ticket_number alone, since source is only ever "jira" or "test" and
ticket_number is already unique across both.

CLAUDE.md records the full investigation and closes out the long-standing
"return-label flow real-world verification" known-pending item.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-10-01 15:53:54 -05:00
co-authored by Claude Sonnet 5
parent a7d2e7870c
commit d8f59b7725
3 changed files with 214 additions and 113 deletions
+16 -5
View File
@@ -443,11 +443,16 @@ def load_orders_by_view() -> Tuple[List[Order], List[Order], List[Order]]:
def mark_shipstation_sent(ticket_number: str) -> None:
"""Stamps shipstation_sent_at after a CONFIRMED emergency API send -
called once ShipStation's own response confirms creation succeeded."""
called once ShipStation's own response confirms creation succeeded.
Matches by ticket_number alone, not source == "jira" - source is only
ever "jira" or "test" (synthetic tickets from create_test_shipment_order()),
and ticket_number is already unique across both, so restricting to "jira"
here just means this silently no-ops for test tickets instead of erroring."""
session = get_session()
try:
order = session.execute(
select(Order).where(Order.source == "jira", Order.ticket_number == ticket_number)
select(Order).where(Order.ticket_number == ticket_number)
).scalar_one_or_none()
if order is not None:
order.shipstation_sent_at = dt.datetime.now()
@@ -459,11 +464,14 @@ def mark_shipstation_sent(ticket_number: str) -> None:
def save_dummy_outbound_label_id(ticket_number: str, label_id: str) -> None:
"""Persists step 1's result (the dummy shipment's label_id) so step 2
(the real return label) can be triggered separately - including in a
later session, after the dummy has been verified in ShipStation."""
later session, after the dummy has been verified in ShipStation.
Matches by ticket_number alone - see mark_shipstation_sent() above for why
this must not be restricted to source == "jira"."""
session = get_session()
try:
order = session.execute(
select(Order).where(Order.source == "jira", Order.ticket_number == ticket_number)
select(Order).where(Order.ticket_number == ticket_number)
).scalar_one_or_none()
if order is not None:
order.dummy_outbound_label_id = label_id
@@ -478,11 +486,14 @@ def save_pack_data(ticket_number: str, serial_numbers: dict, packed: bool) -> No
dialog. Staff-entered data, not sourced from JIRA - this is the
beginning of the eventual end-of-day push back to JIRA (deferred for
now), so nothing here gets overwritten by a JIRA re-import.
Matches by ticket_number alone, not source == "jira" - see
mark_shipstation_sent() for why.
"""
session = get_session()
try:
order = session.execute(
select(Order).where(Order.source == "jira", Order.ticket_number == ticket_number)
select(Order).where(Order.ticket_number == ticket_number)
).scalar_one_or_none()
if order is None:
return