feat: integer cents money handling, order page fixes, and vendor filter persistence
Money Handling Architecture: - Store all monetary values as integer cents (€105.91 = 10591) - Add app/utils/money.py with Money class and conversion helpers - Add static/shared/js/money.js for frontend formatting - Update all database models to use _cents columns (Product, Order, etc.) - Update CSV processor to convert prices to cents on import - Add Alembic migration for Float to Integer conversion - Create .architecture-rules/money.yaml with 7 validation rules - Add docs/architecture/money-handling.md documentation Order Details Page Fixes: - Fix customer name showing 'undefined undefined' - use flat field names - Fix vendor info empty - add vendor_name/vendor_code to OrderDetailResponse - Fix shipping address using wrong nested object structure - Enrich order detail API response with vendor info Vendor Filter Persistence Fixes: - Fix orders.js: restoreSavedVendor now sets selectedVendor and filters - Fix orders.js: init() only loads orders if no saved vendor to restore - Fix marketplace-letzshop.js: restoreSavedVendor calls selectVendor() - Fix marketplace-letzshop.js: clearVendorSelection clears TomSelect dropdown - Align vendor selector placeholder text between pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
206
scripts/check_letzshop_shipment.py
Normal file
206
scripts/check_letzshop_shipment.py
Normal file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check shipment status directly from Letzshop API.
|
||||
|
||||
Usage:
|
||||
python scripts/check_letzshop_shipment.py YOUR_API_KEY [SHIPMENT_ID_OR_ORDER_NUMBER]
|
||||
|
||||
Example:
|
||||
python scripts/check_letzshop_shipment.py abc123 nvDv5RQEmCwbjo
|
||||
python scripts/check_letzshop_shipment.py abc123 R532332163
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
|
||||
ENDPOINT = "https://letzshop.lu/graphql"
|
||||
|
||||
# Query template - state is interpolated since Letzshop has enum issues
|
||||
QUERY_SHIPMENTS_TEMPLATE = """
|
||||
query GetShipmentsPaginated($first: Int!, $after: String) {{
|
||||
shipments(state: {state}, first: $first, after: $after) {{
|
||||
pageInfo {{
|
||||
hasNextPage
|
||||
endCursor
|
||||
}}
|
||||
nodes {{
|
||||
id
|
||||
number
|
||||
state
|
||||
order {{
|
||||
id
|
||||
number
|
||||
email
|
||||
total
|
||||
completedAt
|
||||
locale
|
||||
shipAddress {{
|
||||
firstName
|
||||
lastName
|
||||
company
|
||||
streetName
|
||||
streetNumber
|
||||
city
|
||||
zipCode
|
||||
phone
|
||||
country {{
|
||||
iso
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
inventoryUnits {{
|
||||
id
|
||||
state
|
||||
variant {{
|
||||
id
|
||||
sku
|
||||
mpn
|
||||
price
|
||||
tradeId {{
|
||||
number
|
||||
parser
|
||||
}}
|
||||
product {{
|
||||
name {{
|
||||
en
|
||||
fr
|
||||
de
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def search_shipment(api_key: str, search_term: str):
|
||||
"""Search for a shipment across all states."""
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
}
|
||||
|
||||
# States to search through
|
||||
states = ["shipped", "confirmed", "unconfirmed", "declined"]
|
||||
|
||||
print(f"Searching for: {search_term}")
|
||||
print("=" * 60)
|
||||
|
||||
for state in states:
|
||||
print(f"\nSearching in state: {state}...")
|
||||
|
||||
query = QUERY_SHIPMENTS_TEMPLATE.format(state=state)
|
||||
|
||||
# Paginate through results
|
||||
has_next = True
|
||||
after = None
|
||||
page = 0
|
||||
|
||||
while has_next and page < 20: # Max 20 pages to avoid infinite loop
|
||||
page += 1
|
||||
variables = {"first": 50, "after": after}
|
||||
|
||||
response = requests.post(
|
||||
ENDPOINT,
|
||||
headers=headers,
|
||||
json={"query": query, "variables": variables},
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f" Error: HTTP {response.status_code}")
|
||||
break
|
||||
|
||||
data = response.json()
|
||||
|
||||
if "errors" in data:
|
||||
print(f" GraphQL errors: {data['errors']}")
|
||||
break
|
||||
|
||||
result = data.get("data", {}).get("shipments", {})
|
||||
nodes = result.get("nodes", [])
|
||||
page_info = result.get("pageInfo", {})
|
||||
|
||||
# Search for matching shipment
|
||||
for shipment in nodes:
|
||||
shipment_id = shipment.get("id", "")
|
||||
shipment_number = shipment.get("number", "")
|
||||
order = shipment.get("order", {})
|
||||
order_number = order.get("number", "")
|
||||
|
||||
# Check if this matches our search term
|
||||
if (search_term in shipment_id or
|
||||
search_term in shipment_number or
|
||||
search_term in order_number or
|
||||
search_term == shipment_id or
|
||||
search_term == order_number):
|
||||
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"FOUND SHIPMENT!")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
print(f"\n--- Shipment Info ---")
|
||||
print(f" ID: {shipment.get('id')}")
|
||||
print(f" Number: {shipment.get('number')}")
|
||||
print(f" State: {shipment.get('state')}")
|
||||
|
||||
print(f"\n--- Order Info ---")
|
||||
print(f" Order ID: {order.get('id')}")
|
||||
print(f" Order Number: {order.get('number')}")
|
||||
print(f" Email: {order.get('email')}")
|
||||
print(f" Total: {order.get('total')}")
|
||||
print(f" Completed At: {order.get('completedAt')}")
|
||||
|
||||
ship_addr = order.get('shipAddress', {})
|
||||
if ship_addr:
|
||||
print(f"\n--- Shipping Address ---")
|
||||
print(f" Name: {ship_addr.get('firstName')} {ship_addr.get('lastName')}")
|
||||
print(f" Street: {ship_addr.get('streetName')} {ship_addr.get('streetNumber')}")
|
||||
print(f" City: {ship_addr.get('zipCode')} {ship_addr.get('city')}")
|
||||
country = ship_addr.get('country', {})
|
||||
print(f" Country: {country.get('iso')}")
|
||||
|
||||
print(f"\n--- Inventory Units ---")
|
||||
units = shipment.get('inventoryUnits', [])
|
||||
for i, unit in enumerate(units, 1):
|
||||
print(f" Unit {i}:")
|
||||
print(f" ID: {unit.get('id')}")
|
||||
print(f" State: {unit.get('state')}")
|
||||
variant = unit.get('variant', {})
|
||||
print(f" SKU: {variant.get('sku')}")
|
||||
trade_id = variant.get('tradeId', {})
|
||||
print(f" GTIN: {trade_id.get('number')}")
|
||||
product = variant.get('product', {})
|
||||
name = product.get('name', {})
|
||||
print(f" Product: {name.get('en')}")
|
||||
|
||||
print(f"\n--- Raw Response ---")
|
||||
print(json.dumps(shipment, indent=2, default=str))
|
||||
return shipment
|
||||
|
||||
has_next = page_info.get("hasNextPage", False)
|
||||
after = page_info.get("endCursor")
|
||||
|
||||
if not has_next:
|
||||
print(f" Searched {page} page(s), {len(nodes)} shipments in last page")
|
||||
|
||||
print(f"\nShipment not found for: {search_term}")
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python scripts/check_letzshop_shipment.py YOUR_API_KEY [SHIPMENT_ID_OR_ORDER_NUMBER]")
|
||||
print("\nExample:")
|
||||
print(" python scripts/check_letzshop_shipment.py abc123 nvDv5RQEmCwbjo")
|
||||
print(" python scripts/check_letzshop_shipment.py abc123 R532332163")
|
||||
sys.exit(1)
|
||||
|
||||
api_key = sys.argv[1]
|
||||
# Default to the order number R532332163
|
||||
search_term = sys.argv[2] if len(sys.argv) > 2 else "R532332163"
|
||||
|
||||
search_shipment(api_key, search_term)
|
||||
Reference in New Issue
Block a user