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:
@@ -29,7 +29,10 @@ app/
|
|||||||
ticket_validation.py SKU validation rules (company mismatch, return/device mismatch)
|
ticket_validation.py SKU validation rules (company mismatch, return/device mismatch)
|
||||||
tracking.py suggest_jira_status() from tracking numbers
|
tracking.py suggest_jira_status() from tracking numbers
|
||||||
external_links.py JIRA/Google Maps URL builders (View in JIRA, Look Up Address)
|
external_links.py JIRA/Google Maps URL builders (View in JIRA, Look Up Address)
|
||||||
workers.py QThread workers + save_orders()/load_orders_by_view()/etc.
|
workers.py QThread workers + save_orders()/load_orders_by_view()/etc. -
|
||||||
|
also create_test_shipment_order()/delete_test_shipments()
|
||||||
|
(synthetic source="test" tickets for exercising the
|
||||||
|
Create Return Label flow without a real JIRA ticket)
|
||||||
services/
|
services/
|
||||||
base.py NormalizedOrder TypedDict, OrderService ABC
|
base.py NormalizedOrder TypedDict, OrderService ABC
|
||||||
jira_service.py JIRA REST API v3 /search/jql, paginated
|
jira_service.py JIRA REST API v3 /search/jql, paginated
|
||||||
@@ -182,6 +185,59 @@ and copy its store_id into `TEST_SHIPSTATION_SIGNIFY_STORE_ID` /
|
|||||||
"Return label creation failed" / "invalid store" error on `store_id: 367672` (Oak
|
"Return label creation failed" / "invalid store" error on `store_id: 367672` (Oak
|
||||||
Street's *production* `se-367672`, prefix stripped in the echoed error per point 8
|
Street's *production* `se-367672`, prefix stripped in the echoed error per point 8
|
||||||
above) - happening because `TEST_SHIPSTATION_OAKSTREET_STORE_ID` was still blank.
|
above) - happening because `TEST_SHIPSTATION_OAKSTREET_STORE_ID` was still blank.
|
||||||
|
The fix requires **two separate manual stores in the sandbox account** (one per
|
||||||
|
company), not one shared - mirrors production, which already has two independent
|
||||||
|
store IDs (`SHIPSTATION_SIGNIFY_STORE_ID`/`SHIPSTATION_OAKSTREET_STORE_ID`), and the
|
||||||
|
`TEST_*` settings already have two independent slots for exactly this. There was no
|
||||||
|
way to confirm via API whether one store could technically be shared across
|
||||||
|
companies in the sandbox (still no listing/inspection endpoint) - moot anyway, since
|
||||||
|
per-company stores is the already-intended design.
|
||||||
|
|
||||||
|
**`store_id` is schema-optional, but don't take that as license to drop it**: checked
|
||||||
|
ShipStation's own request schema for `POST /v2/labels` - `shipment.store_id` is NOT
|
||||||
|
in the list of required fields (unlike `shipment.ship_to`), and nothing in the schema
|
||||||
|
ties a store to a specific carrier/warehouse. But this app's own store_id check
|
||||||
|
exists for a real, previously-confirmed reason (see Return Labels point 1 above): a
|
||||||
|
label can come back `status: completed` from the API while being invisible anywhere
|
||||||
|
in ShipStation's own UI. The schema being lenient doesn't mean this account's actual
|
||||||
|
behavior is - keep requiring it. **Confirmed live, 2026-09-18** (three direct
|
||||||
|
`POST /v2/labels` probes against the sandbox key, harmless - sandbox labels, no real
|
||||||
|
cost): omitting `store_id` entirely succeeds cleanly (`200`, `status: "completed"`,
|
||||||
|
real label `se-201075505`) - so it genuinely is optional API-side, exactly as the
|
||||||
|
schema says. Whether that label is actually visible in ShipStation's own UI (the
|
||||||
|
thing that would tell us if the "invisible label" concern really extends to
|
||||||
|
store_id, or was specific to unlinked return labels) still needs a human to check the
|
||||||
|
sandbox account's Orders/Shipments list for `external_shipment_id:
|
||||||
|
STORE-PROBE-no-store-id-at-all`.
|
||||||
|
|
||||||
|
**A separate, unrelated test-mode gap found 2026-09-18**: `TEST_SIGNIFY_RETURN_CARRIER_ID`/
|
||||||
|
`TEST_OAKSTREET_RETURN_CARRIER_ID` were blank (only the `*_SERVICE_CODE` halves had
|
||||||
|
been filled in earlier), producing "No return-label Carrier ID/Service Code
|
||||||
|
configured" on Step 1. Not a new discovery - just the confirmed shared sandbox test
|
||||||
|
carrier (`se-6366092`, UPS) from earlier in this section, re-verified live and filled
|
||||||
|
into both settings. Worth knowing for next time: `TEST_SIGNIFY_RETURN_SERVICE_CODE`/
|
||||||
|
`TEST_OAKSTREET_RETURN_SERVICE_CODE` being set doesn't imply their carrier-ID
|
||||||
|
counterparts are - check both halves of a `TEST_*_RETURN_*` pair, not just one.
|
||||||
|
|
||||||
|
**The newer ShipStation web UI's URL is NOT the `se-` store ID - confirmed, don't
|
||||||
|
reuse it.** Creating a manual store at `ship15.shipstation.com/settings/stores/...`
|
||||||
|
shows a UUID in the URL (e.g. `081cf783-32b2-4b52-b9f8-767531d0ac47`), not an
|
||||||
|
`se-XXXXX` ID. This is ShipStation's newer/redesigned web UI - that UUID is its own
|
||||||
|
internal routing ID, a completely different ID space from the V2 API's store IDs, not
|
||||||
|
just a missing `se-` prefix (the `se-` prefix quirk documented above only ever
|
||||||
|
applies to genuinely `se-`-shaped IDs missing their prefix, not arbitrary UUIDs).
|
||||||
|
Confirmed by direct probe: a known-fake-but-correctly-shaped ID (`se-999999999`)
|
||||||
|
gets the expected clean `400 "invalid store"`, but that UUID (tried both raw and
|
||||||
|
with `se-` prepended) gets a `500 "An unexpected error occurred"` instead - a
|
||||||
|
different failure shape entirely, meaning the API doesn't even recognize it as a
|
||||||
|
candidate ID, let alone a wrong one. **Don't paste that URL UUID into
|
||||||
|
`TEST_SHIPSTATION_*_STORE_ID` and expect it to work.** The real `se-` ID for a
|
||||||
|
store created in this newer UI needs to come from somewhere else - check the store's
|
||||||
|
own settings *page content* (not the URL) for an API/Integration section that
|
||||||
|
displays it as text, or fall back to ShipStation support (their own help docs say
|
||||||
|
this explicitly: "contact our support team and tell them the name of the manual
|
||||||
|
store" is a valid way to get a store_id when the List Stores API isn't an option -
|
||||||
|
see the store_id dead-end note above).
|
||||||
|
|
||||||
**Production readiness check, 2026-09-16 (read-only, no labels created, no cost)**:
|
**Production readiness check, 2026-09-16 (read-only, no labels created, no cost)**:
|
||||||
before a first real production test of the return-label flow, ran `GET /v2/carriers`,
|
before a first real production test of the return-label flow, ran `GET /v2/carriers`,
|
||||||
@@ -201,6 +257,21 @@ production API key and diffed the results against `.env`:
|
|||||||
check. If it fails, expect the same "invalid store" error shape as the test-mode one;
|
check. If it fails, expect the same "invalid store" error shape as the test-mode one;
|
||||||
the fix then is re-checking the store_id in ShipStation's own UI, not the code.
|
the fix then is re-checking the store_id in ShipStation's own UI, not the code.
|
||||||
|
|
||||||
|
**Testing the emailed-return-label SKU flow without a real ticket**: "Create Test
|
||||||
|
Shipment (Email SKU)..." (Data menu, Test Mode only) creates a synthetic
|
||||||
|
`source="test"` Order carrying that company's configured emailed-return-label SKU
|
||||||
|
(resolved from `EMAILED_LABEL_SKUS` + `COMPANY_SKU_MAP` at creation time, not
|
||||||
|
hardcoded to SH007/OK012) and a placeholder shipping address, then drops it on the
|
||||||
|
Active tab (`status="Created"`) so it can be selected and run through the *real*
|
||||||
|
Create Return Label flow - same code path a JIRA ticket uses, no separate test-only
|
||||||
|
logic to keep in sync. `source="test"` guarantees `save_orders()` (which only ever
|
||||||
|
matches on `source == "jira"`) can never touch or overwrite one of these, so a real
|
||||||
|
Import from JIRA is safe to run with test shipments still sitting on the board.
|
||||||
|
"Delete Test Shipments..." cleans them up by that same `source` marker, real tickets
|
||||||
|
untouched. **Deliberately gated to Test Mode** - the same flow against production
|
||||||
|
settings would create a real, paid UPS shipment to a fake address, not just a
|
||||||
|
sandbox test label.
|
||||||
|
|
||||||
**Real limitations of ShipStation's sandbox (confirmed via their own docs, not
|
**Real limitations of ShipStation's sandbox (confirmed via their own docs, not
|
||||||
assumed) that constrain what test mode can actually verify:**
|
assumed) that constrain what test mode can actually verify:**
|
||||||
- Branded Labels / Branded Tracking Pages are NOT available in sandbox. This directly
|
- Branded Labels / Branded Tracking Pages are NOT available in sandbox. This directly
|
||||||
@@ -289,16 +360,22 @@ change without a deliberate separate conversation about it.
|
|||||||
|
|
||||||
## Known-pending / not yet built
|
## Known-pending / not yet built
|
||||||
|
|
||||||
- **Immediate next step, as of the last working session**: the warehouse_id issue is
|
- **Immediate next step, as of the last working session**: the warehouse_id gap is
|
||||||
now fixed code-side ("Create Test Warehouse(s)..." in the Data menu, see ShipStation
|
fixed and confirmed (both `TEST_SHIPSTATION_*_WAREHOUSE_ID` settings are populated).
|
||||||
Test Mode above) but hasn't been run/confirmed yet - run it, confirm the two
|
The store_id gap is still open and turned out to be more involved than the
|
||||||
`TEST_SHIPSTATION_*_WAREHOUSE_ID` settings got saved. Then a *second*, separate
|
warehouse one: two manual stores were created in the sandbox account, one per
|
||||||
test-mode gap surfaced right after: an "invalid store" error on Oak Street's
|
company (see ShipStation Test Mode above for why two, not one), but ShipStation's
|
||||||
production store_id, because `TEST_SHIPSTATION_*_STORE_ID` is blank and there is no
|
newer web UI (`ship15.shipstation.com`) only exposes a UUID in the URL, not the
|
||||||
API-based fix for that one (see ShipStation Test Mode above) - both
|
`se-XXXXX` ID this app's `TEST_SHIPSTATION_*_STORE_ID` settings need - confirmed via
|
||||||
`TEST_SHIPSTATION_SIGNIFY_STORE_ID` and `TEST_SHIPSTATION_OAKSTREET_STORE_ID` still
|
direct API probe that this UUID is not a usable store_id at all (a different,
|
||||||
need to be filled in by hand from the sandbox account's own ShipStation UI before the
|
`500`-shaped failure than a genuinely wrong-but-well-formed ID gets). The real
|
||||||
emailed-return-label flow can be tested end-to-end in test mode.
|
`se-` ID for each new store still needs to be found (store settings page content,
|
||||||
|
or ShipStation support) before the emailed-return-label flow can be tested
|
||||||
|
end-to-end in test mode. Also still open: whether a label with NO store_id at all
|
||||||
|
is actually visible in ShipStation's UI - confirmed live that the API accepts a
|
||||||
|
request with the field omitted (see ShipStation Test Mode above), which, if it
|
||||||
|
turns out to be visible too, could make chasing the real store_id unnecessary for
|
||||||
|
test mode specifically.
|
||||||
- **EOD JIRA push**: deliberately deferred. `packed`, `serial_numbers`,
|
- **EOD JIRA push**: deliberately deferred. `packed`, `serial_numbers`,
|
||||||
`tracking_numbers`, `shipping_method`, `assignee` are all captured and ready for it
|
`tracking_numbers`, `shipping_method`, `assignee` are all captured and ready for it
|
||||||
whenever it's prioritized - would need the exact JIRA custom field IDs (outbound
|
whenever it's prioritized - would need the exact JIRA custom field IDs (outbound
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from PyQt6.QtWidgets import (
|
|||||||
QTabWidget,
|
QTabWidget,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QDialog,
|
QDialog,
|
||||||
|
QInputDialog,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app import config
|
from app import config
|
||||||
@@ -53,6 +54,8 @@ from app.workers import (
|
|||||||
save_dummy_outbound_label_id,
|
save_dummy_outbound_label_id,
|
||||||
save_pack_data,
|
save_pack_data,
|
||||||
reset_local_database,
|
reset_local_database,
|
||||||
|
create_test_shipment_order,
|
||||||
|
delete_test_shipments,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -184,6 +187,24 @@ class MainWindow(QMainWindow):
|
|||||||
create_test_warehouses_action.triggered.connect(self._on_create_test_warehouses_clicked)
|
create_test_warehouses_action.triggered.connect(self._on_create_test_warehouses_clicked)
|
||||||
data_menu.addAction(create_test_warehouses_action)
|
data_menu.addAction(create_test_warehouses_action)
|
||||||
|
|
||||||
|
data_menu.addSeparator()
|
||||||
|
|
||||||
|
create_test_shipment_action = QAction("Create Test Shipment (Email SKU)...", self)
|
||||||
|
create_test_shipment_action.setStatusTip(
|
||||||
|
"Test Mode only - creates a synthetic ticket carrying that company's emailed-"
|
||||||
|
"return-label SKU, so Create Return Label can be run against it in the sandbox"
|
||||||
|
)
|
||||||
|
create_test_shipment_action.triggered.connect(self._on_create_test_shipment_clicked)
|
||||||
|
data_menu.addAction(create_test_shipment_action)
|
||||||
|
|
||||||
|
delete_test_shipments_action = QAction("Delete Test Shipments...", self)
|
||||||
|
delete_test_shipments_action.setStatusTip(
|
||||||
|
"Removes every synthetic ticket created by Create Test Shipment - real JIRA "
|
||||||
|
"tickets are not affected"
|
||||||
|
)
|
||||||
|
delete_test_shipments_action.triggered.connect(self._on_delete_test_shipments_clicked)
|
||||||
|
data_menu.addAction(delete_test_shipments_action)
|
||||||
|
|
||||||
orders_menu = menu_bar.addMenu("&Orders")
|
orders_menu = menu_bar.addMenu("&Orders")
|
||||||
|
|
||||||
self._pack_ticket_action = QAction("Pack Ticket", self)
|
self._pack_ticket_action = QAction("Pack Ticket", self)
|
||||||
@@ -386,6 +407,63 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
QMessageBox.information(self, "Create Test Warehouse(s)", "\n".join(lines))
|
QMessageBox.information(self, "Create Test Warehouse(s)", "\n".join(lines))
|
||||||
|
|
||||||
|
def _on_create_test_shipment_clicked(self) -> None:
|
||||||
|
if not config.is_shipstation_test_mode():
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Test Mode is off",
|
||||||
|
"Turn on ShipStation Test Mode first (Data menu). This ticket is meant "
|
||||||
|
"to be run through Create Return Label against the sandbox account, not "
|
||||||
|
"production - a real store_id/carrier under production settings would "
|
||||||
|
"create an actual, paid shipment to a fake address.",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
company, ok = QInputDialog.getItem(
|
||||||
|
self,
|
||||||
|
"Create Test Shipment",
|
||||||
|
"Company (picks that company's configured emailed-return-label SKU):",
|
||||||
|
["Signify Health", "Oak Street Health"],
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
order = create_test_shipment_order(company)
|
||||||
|
except ValueError as exc:
|
||||||
|
QMessageBox.critical(self, "Could not create test shipment", str(exc))
|
||||||
|
return
|
||||||
|
|
||||||
|
self._refresh_everything()
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Test Shipment Created",
|
||||||
|
f"Created {order.ticket_number} ({company}, SKU {order.skus[0]}) on the "
|
||||||
|
"Active tab.\n\n"
|
||||||
|
"Select it there and use Create Return Label to run Step 1 (dummy "
|
||||||
|
"shipment) and Step 2 (return label) against the sandbox account, the "
|
||||||
|
"same code path a real ticket would use.\n\n"
|
||||||
|
"Use Delete Test Shipments (Data menu) to clean it up afterward.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_delete_test_shipments_clicked(self) -> None:
|
||||||
|
confirm = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Delete Test Shipments",
|
||||||
|
"Removes every synthetic ticket created by Create Test Shipment "
|
||||||
|
"(ticket numbers starting with TEST-EMAIL-). Real JIRA tickets are not "
|
||||||
|
"affected.\n\nContinue?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
|
QMessageBox.StandardButton.No,
|
||||||
|
)
|
||||||
|
if confirm != QMessageBox.StandardButton.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
count = delete_test_shipments()
|
||||||
|
self._refresh_everything()
|
||||||
|
QMessageBox.information(self, "Delete Test Shipments", f"Deleted {count} test shipment(s).")
|
||||||
|
|
||||||
def _on_order_double_clicked(self, order) -> None:
|
def _on_order_double_clicked(self, order) -> None:
|
||||||
dialog = OrderDetailDialog(order, self)
|
dialog = OrderDetailDialog(order, self)
|
||||||
dialog.exec()
|
dialog.exec()
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ pattern rather than blocking the UI.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
import uuid
|
||||||
from typing import List, Tuple, TypedDict
|
from typing import List, Tuple, TypedDict
|
||||||
|
|
||||||
from PyQt6.QtCore import QThread, pyqtSignal
|
from PyQt6.QtCore import QThread, pyqtSignal
|
||||||
from sqlalchemy import select, delete
|
from sqlalchemy import select, delete
|
||||||
|
|
||||||
|
from app import config
|
||||||
from app.database import get_session
|
from app.database import get_session
|
||||||
from app.models import Order
|
from app.models import Order
|
||||||
from app.schedule import get_cutoff_time, is_past_cutoff_today
|
from app.schedule import get_cutoff_time, is_past_cutoff_today
|
||||||
@@ -301,6 +303,97 @@ def reset_local_database() -> int:
|
|||||||
session.close()
|
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]:
|
def load_all_orders() -> List[Order]:
|
||||||
session = get_session()
|
session = get_session()
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user