Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Agent
51790accf9 fix(sync): dedup order_items by sku before insert to avoid UNIQUE crash
Production sync was failing every minute with:
  UNIQUE constraint failed: order_items.order_number, order_items.sku

GoMag occasionally returns the same SKU on multiple lines within one order
(configurable products, promo splits). The order_items PK is
(order_number, sku), so the raw batch insert violates UNIQUE and aborts
the entire sync — blocking partner-mismatch updates, address refresh,
and items repopulation for already-imported orders.

Added _dedup_items_by_sku() helper. Applied in save_orders_batch
(cancelled/already/skipped paths) and add_order_items (retry/sync import
paths). Keeps first price/vat/name, sums quantities on collision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 07:04:49 +00:00
Claude Agent
404bc094cd fix(backfill): init sqlite before reading settings
Script failed with "SQLite not initialized" because module-level connection
state wasn't set up when invoked standalone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 07:02:40 +00:00
2 changed files with 35 additions and 4 deletions

View File

@@ -193,12 +193,16 @@ async def save_orders_batch(orders_data: list[dict]):
VALUES (?, ?, ?)
""", [(d["sync_run_id"], d["order_number"], d["status_at_run"]) for d in orders_data])
# 3. Order items — replace semantics (GoMag source of truth)
# 3. Order items — replace semantics (GoMag source of truth).
# Dedup per-order by SKU (GoMag sometimes returns same SKU twice).
all_items = []
order_numbers_with_items = set()
for d in orders_data:
for item in d.get("items", []):
order_numbers_with_items.add(d["order_number"])
raw_items = d.get("items", [])
if not raw_items:
continue
order_numbers_with_items.add(d["order_number"])
for item in _dedup_items_by_sku(raw_items):
all_items.append((
d["order_number"],
item.get("sku"), item.get("product_name"),
@@ -535,14 +539,40 @@ async def get_web_products_batch(skus: list) -> dict:
# ── order_items ──────────────────────────────────
def _dedup_items_by_sku(items: list) -> list:
"""Deduplicate items by SKU within a single order. Sums quantities on collision.
GoMag occasionally returns the same SKU on multiple lines (configurable products,
promo splits). The order_items primary key is (order_number, sku) so the raw rows
would violate UNIQUE. Keeps first price/vat/name; sums quantity + baseprice*qty.
"""
if not items:
return items
merged: dict = {}
order: list = []
for item in items:
sku = item.get("sku")
if sku is None:
order.append(item)
continue
if sku in merged:
prev = merged[sku]
prev["quantity"] = (prev.get("quantity") or 0) + (item.get("quantity") or 0)
else:
merged[sku] = dict(item)
order.append(merged[sku])
return order
async def add_order_items(order_number: str, items: list):
"""Replace order items — delete any existing rows, then insert fresh batch.
GoMag is source of truth: re-import must reflect quantity changes.
Atomic (DELETE + INSERT in one transaction).
Atomic (DELETE + INSERT in one transaction). Items with the same SKU are
merged (quantities summed) to satisfy the (order_number, sku) PK.
"""
if not items:
return
items = _dedup_items_by_sku(items)
db = await get_sqlite()
try:
await db.execute("DELETE FROM order_items WHERE order_number = ?", (order_number,))

View File

@@ -84,6 +84,7 @@ async def _backfill_one(order_number: str, app_settings: dict, use_oracle: bool)
async def main(order_numbers: list[str]):
database.init_sqlite()
app_settings = await sqlite_service.get_app_settings()
use_oracle = False