feat(dashboard): add logs page, pagination, quick mapping modal, price pre-validation
- Add /logs page with per-order sync run details, filters (Toate/Importate/Fara Mapare/Erori) - Add price pre-validation (validate_prices + ensure_prices) to prevent ORA-20000 on direct articles - Add find_new_orders() to detect orders not yet in Oracle COMENZI - Extend missing_skus table with order context (order_count, order_numbers, customers) - Add server-side pagination on /api/validate/missing-skus and /missing-skus page - Replace confusing "Skip"/"Err" with "Fara Mapare"/"Erori" terminology - Add inline mapping modal on dashboard (replaces navigation to /mappings) - Add 2-row stat cards: orders (Comenzi Noi/Ready/Importate/Fara Mapare/Erori) + articles - Add ID_POL/ID_GESTIUNE/ID_SECTIE to config.py and .env - Update .gitignore (venv, *.db, api/api/, logs/) - 33/33 unit tests pass, E2E verified with Playwright Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from . import order_reader, validation_service, import_service, sqlite_service
|
||||
from ..config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,12 +54,31 @@ async def run_sync(id_pol: int = None, id_sectie: int = None) -> dict:
|
||||
|
||||
_current_sync["progress"] = f"Validating {len(orders)} orders..."
|
||||
|
||||
# Step 2: Validate SKUs (blocking Oracle call -> run in thread)
|
||||
# Step 2a: Find new orders (not yet in Oracle)
|
||||
all_order_numbers = [o.number for o in orders]
|
||||
new_orders = await asyncio.to_thread(
|
||||
validation_service.find_new_orders, all_order_numbers
|
||||
)
|
||||
|
||||
# Step 2b: Validate SKUs (blocking Oracle call -> run in thread)
|
||||
all_skus = order_reader.get_all_skus(orders)
|
||||
validation = await asyncio.to_thread(validation_service.validate_skus, all_skus)
|
||||
importable, skipped = validation_service.classify_orders(orders, validation)
|
||||
|
||||
# Track missing SKUs
|
||||
# Step 2c: Build SKU context from skipped orders
|
||||
sku_context = {} # {sku: {"orders": [], "customers": []}}
|
||||
for order, missing_skus_list in skipped:
|
||||
customer = order.billing.company_name or \
|
||||
f"{order.billing.firstname} {order.billing.lastname}"
|
||||
for sku in missing_skus_list:
|
||||
if sku not in sku_context:
|
||||
sku_context[sku] = {"orders": [], "customers": []}
|
||||
if order.number not in sku_context[sku]["orders"]:
|
||||
sku_context[sku]["orders"].append(order.number)
|
||||
if customer not in sku_context[sku]["customers"]:
|
||||
sku_context[sku]["customers"].append(customer)
|
||||
|
||||
# Track missing SKUs with context
|
||||
for sku in validation["missing"]:
|
||||
product_name = ""
|
||||
for order in orders:
|
||||
@@ -67,7 +88,41 @@ async def run_sync(id_pol: int = None, id_sectie: int = None) -> dict:
|
||||
break
|
||||
if product_name:
|
||||
break
|
||||
await sqlite_service.track_missing_sku(sku, product_name)
|
||||
ctx = sku_context.get(sku, {})
|
||||
await sqlite_service.track_missing_sku(
|
||||
sku, product_name,
|
||||
order_count=len(ctx.get("orders", [])),
|
||||
order_numbers=json.dumps(ctx.get("orders", [])) if ctx.get("orders") else None,
|
||||
customers=json.dumps(ctx.get("customers", [])) if ctx.get("customers") else None,
|
||||
)
|
||||
|
||||
# Step 2d: Pre-validate prices for importable articles
|
||||
id_pol = id_pol or settings.ID_POL
|
||||
if id_pol and importable:
|
||||
_current_sync["progress"] = "Validating prices..."
|
||||
# Gather all CODMATs from importable orders
|
||||
all_codmats = set()
|
||||
for order in importable:
|
||||
for item in order.items:
|
||||
if item.sku in validation["mapped"]:
|
||||
# Mapped SKUs resolve to codmat via ARTICOLE_TERTI (handled by import)
|
||||
pass
|
||||
elif item.sku in validation["direct"]:
|
||||
all_codmats.add(item.sku)
|
||||
# For mapped SKUs, we'd need the ARTICOLE_TERTI lookup - direct SKUs = codmat
|
||||
if all_codmats:
|
||||
price_result = await asyncio.to_thread(
|
||||
validation_service.validate_prices, all_codmats, id_pol
|
||||
)
|
||||
if price_result["missing_price"]:
|
||||
logger.info(
|
||||
f"Auto-adding price 0 for {len(price_result['missing_price'])} "
|
||||
f"direct articles in policy {id_pol}"
|
||||
)
|
||||
await asyncio.to_thread(
|
||||
validation_service.ensure_prices,
|
||||
price_result["missing_price"], id_pol
|
||||
)
|
||||
|
||||
# Step 3: Record skipped orders
|
||||
for order, missing_skus in skipped:
|
||||
@@ -138,6 +193,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None) -> dict:
|
||||
"status": status,
|
||||
"json_files": json_count,
|
||||
"total_orders": len(orders),
|
||||
"new_orders": len(new_orders),
|
||||
"imported": imported_count,
|
||||
"skipped": len(skipped),
|
||||
"errors": error_count,
|
||||
|
||||
Reference in New Issue
Block a user