diff --git a/api/app/routers/sync.py b/api/app/routers/sync.py
index d323687..dd5aeec 100644
--- a/api/app/routers/sync.py
+++ b/api/app/routers/sync.py
@@ -317,6 +317,29 @@ def _get_articole_terti_for_skus(skus: set) -> dict:
return result
+def _get_nom_articole_for_direct_skus(skus: set) -> dict:
+ """Query NOM_ARTICOLE for SKUs that exist directly as CODMAT (direct mapping)."""
+ from .. import database
+ result = {}
+ sku_list = list(skus)
+ conn = database.get_oracle_connection()
+ try:
+ with conn.cursor() as cur:
+ for i in range(0, len(sku_list), 500):
+ batch = sku_list[i:i+500]
+ placeholders = ",".join([f":s{j}" for j in range(len(batch))])
+ params = {f"s{j}": sku for j, sku in enumerate(batch)}
+ cur.execute(f"""
+ SELECT codmat, denumire FROM NOM_ARTICOLE
+ WHERE codmat IN ({placeholders}) AND sters = 0 AND inactiv = 0
+ """, params)
+ for row in cur:
+ result[row[0]] = row[1] or ""
+ finally:
+ database.pool.release(conn)
+ return result
+
+
@router.get("/api/sync/order/{order_number}")
async def order_detail(order_number: str):
"""Get order detail with line items (R9), enriched with ARTICOLE_TERTI data."""
@@ -334,6 +357,23 @@ async def order_detail(order_number: str):
if sku and sku in codmat_map:
item["codmat_details"] = codmat_map[sku]
+ # Enrich direct SKUs (SKU=CODMAT in NOM_ARTICOLE, no ARTICOLE_TERTI entry)
+ direct_skus = {item["sku"] for item in items
+ if item.get("sku") and item.get("mapping_status") == "direct"
+ and not item.get("codmat_details")}
+ if direct_skus:
+ nom_map = await asyncio.to_thread(_get_nom_articole_for_direct_skus, direct_skus)
+ for item in items:
+ sku = item.get("sku")
+ if sku and sku in nom_map and not item.get("codmat_details"):
+ item["codmat_details"] = [{
+ "codmat": sku,
+ "cantitate_roa": 1,
+ "procent_pret": 100,
+ "denumire": nom_map[sku],
+ "direct": True
+ }]
+
# Enrich with invoice data
order = detail.get("order", {})
if order.get("factura_numar") and order.get("factura_data"):
diff --git a/api/app/services/mapping_service.py b/api/app/services/mapping_service.py
index 0f9b153..25bfc6a 100644
--- a/api/app/services/mapping_service.py
+++ b/api/app/services/mapping_service.py
@@ -159,6 +159,24 @@ def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret
with database.pool.acquire() as conn:
with conn.cursor() as cur:
+ # Validate CODMAT exists in NOM_ARTICOLE
+ cur.execute("""
+ SELECT COUNT(*) FROM NOM_ARTICOLE
+ WHERE codmat = :codmat AND sters = 0 AND inactiv = 0
+ """, {"codmat": codmat})
+ if cur.fetchone()[0] == 0:
+ raise HTTPException(status_code=400, detail="CODMAT-ul nu exista in nomenclator")
+
+ # Warn if SKU is already a direct CODMAT in NOM_ARTICOLE
+ if sku == codmat:
+ cur.execute("""
+ SELECT COUNT(*) FROM NOM_ARTICOLE
+ WHERE codmat = :sku AND sters = 0 AND inactiv = 0
+ """, {"sku": sku})
+ if cur.fetchone()[0] > 0:
+ raise HTTPException(status_code=409,
+ detail="SKU-ul exista direct in nomenclator ca CODMAT, nu necesita mapare")
+
# Check for active duplicate
cur.execute("""
SELECT COUNT(*) FROM ARTICOLE_TERTI
diff --git a/api/app/static/js/dashboard.js b/api/app/static/js/dashboard.js
index 1936b1f..8748a29 100644
--- a/api/app/static/js/dashboard.js
+++ b/api/app/static/js/dashboard.js
@@ -478,6 +478,9 @@ function renderCodmatCell(item) {
}
if (item.codmat_details.length === 1) {
const d = item.codmat_details[0];
+ if (d.direct) {
+ return `${esc(d.codmat)} direct`;
+ }
return `${esc(d.codmat)}`;
}
return item.codmat_details.map(d =>
@@ -596,7 +599,7 @@ async function openDashOrderDetail(orderNumber) {
if (mobileContainer) {
mobileContainer.innerHTML = '
${esc(d.codmat)}`).join(' ')
+ ? item.codmat_details.map(d => `${esc(d.codmat)}${d.direct ? ' direct' : ''}`).join(' ')
: `${esc(item.codmat || '–')}`;
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
return `${escHtml(details[0].codmat)} — ${escHtml(details[0].denumire || '')}).