Add Create Test Shipment (Email SKU) for testing the return-label flow, plus test-mode/store_id investigation notes

Adds synthetic source="test" tickets (create_test_shipment_order/delete_test_shipments)
carrying a company's real emailed-return-label SKU, so Step 1/Step 2 can be exercised
against the sandbox without a real JIRA ticket or risking a real customer's. Gated to
Test Mode - the same flow against production would create a real paid shipment.

CLAUDE.md also captures this session's ShipStation sandbox findings: store_id is
schema-optional but empirically required for label visibility, the newer ship15 web UI's
URL is NOT the se- store ID (confirmed via direct probe - different failure shape than a
wrong-but-well-formed ID), and two separate test stores/carriers are needed per company,
mirroring production.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-09-28 11:45:09 -05:00
co-authored by Claude Sonnet 5
parent 82e6148ca9
commit a7d2e7870c
3 changed files with 259 additions and 11 deletions
+93
View File
@@ -9,11 +9,13 @@ pattern rather than blocking the UI.
from __future__ import annotations
import datetime as dt
import uuid
from typing import List, Tuple, TypedDict
from PyQt6.QtCore import QThread, pyqtSignal
from sqlalchemy import select, delete
from app import config
from app.database import get_session
from app.models import Order
from app.schedule import get_cutoff_time, is_past_cutoff_today
@@ -301,6 +303,97 @@ def reset_local_database() -> int:
session.close()
def _emailed_label_sku_for_company(company: str) -> str:
"""Picks whichever configured EMAILED_LABEL_SKUS entry actually
resolves to the given company via COMPANY_SKU_MAP, rather than
hardcoding SH007/OK012 - stays correct if either setting changes."""
from app.companies import parse_mapping, resolve_company_by_sku
from app.return_labels import get_emailed_label_skus
sku_map = parse_mapping(config.get("COMPANY_SKU_MAP", config.DEFAULT_COMPANY_SKU_MAP))
for sku in sorted(get_emailed_label_skus()):
if resolve_company_by_sku(sku, sku_map) == company:
return sku.upper()
return ""
def create_test_shipment_order(company: str) -> Order:
"""
Creates a synthetic, non-JIRA Order row carrying that company's
configured emailed-return-label SKU, purely so the existing "Create
Return Label" flow (return_label_dialog.py + shipstation_send.py) can
be exercised end-to-end - dummy shipment, then real return label -
against a throwaway ticket instead of risking a real customer's.
source="test" keeps this completely separate from real JIRA rows:
save_orders() only ever matches on source == "jira"/"shipstation", so
Import from JIRA can never touch or overwrite one of these, and
delete_test_shipments() cleans them up by that same marker.
status="Created" so it lands on the Active tab like a real open
ticket would - that's where staff would naturally go to select it and
run Create Return Label.
"""
sku = _emailed_label_sku_for_company(company)
if not sku:
raise ValueError(
f"No EMAILED_LABEL_SKUS entry resolves to '{company}' via COMPANY_SKU_MAP - "
"check both settings."
)
ticket_number = f"TEST-EMAIL-{uuid.uuid4().hex[:8].upper()}"
order = Order(
source="test",
external_id=ticket_number,
ticket_number=ticket_number,
company=company,
skus=[sku],
line_items=[{"sku": sku, "item_name": "TEST - Emailed Return Label"}],
shipping_info={
"name": "TEST ORDER - DO NOT SHIP",
"phone": "555-555-5555",
"email": "",
"address1": "123 Test St",
"address2": "",
"city": "Austin",
"state": "TX",
"zip": "78701",
},
creator="Test Shipment Generator",
assignee="",
description=(
"Synthetic test order created via Data > Create Test Shipment (Email SKU) - "
"not a real ticket. Safe to delete with Data > Delete Test Shipments."
),
summary=f"TEST - Emailed Return Label ({company})",
status="Created",
source_created_at=dt.datetime.now(),
)
session = get_session()
try:
session.add(order)
session.commit()
finally:
session.close()
return order
def delete_test_shipments() -> int:
"""Removes every synthetic order created by create_test_shipment_order()
(source == "test") - real JIRA-sourced rows are untouched, since those
always have source == "jira"."""
session = get_session()
try:
result = session.execute(delete(Order).where(Order.source == "test"))
session.commit()
return result.rowcount or 0
finally:
session.close()
def load_all_orders() -> List[Order]:
session = get_session()
try: