fix(import): resolve correct id_articol for duplicate CODMATs + gestiune setting
Unified id_articol selection logic in Python (resolve_codmat_ids) and PL/SQL (resolve_id_articol): filters sters=0 AND inactiv=0, prefers article with stock in configured gestiune, falls back to MAX(id_articol). Eliminates mismatch where Python and PL/SQL could pick different id_articol for the same CODMAT, causing ORA-20000 price-not-found errors. - Add resolve_codmat_ids helper in validation_service.py (single batch query) - Refactor validate_skus/validate_prices/ensure_prices to use it - Add resolve_id_articol function in PL/SQL package body - Add p_id_gestiune parameter to importa_comanda (spec + body) - Add /api/settings/gestiuni endpoint and id_gestiune setting - Add gestiune dropdown in settings UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ class AppSettingsUpdate(BaseModel):
|
|||||||
discount_id_pol: str = ""
|
discount_id_pol: str = ""
|
||||||
id_pol: str = ""
|
id_pol: str = ""
|
||||||
id_sectie: str = ""
|
id_sectie: str = ""
|
||||||
|
id_gestiune: str = ""
|
||||||
gomag_api_key: str = ""
|
gomag_api_key: str = ""
|
||||||
gomag_api_shop: str = ""
|
gomag_api_shop: str = ""
|
||||||
gomag_order_days_back: str = "7"
|
gomag_order_days_back: str = "7"
|
||||||
@@ -597,6 +598,7 @@ async def get_app_settings():
|
|||||||
"discount_id_pol": s.get("discount_id_pol", ""),
|
"discount_id_pol": s.get("discount_id_pol", ""),
|
||||||
"id_pol": s.get("id_pol", ""),
|
"id_pol": s.get("id_pol", ""),
|
||||||
"id_sectie": s.get("id_sectie", ""),
|
"id_sectie": s.get("id_sectie", ""),
|
||||||
|
"id_gestiune": s.get("id_gestiune", ""),
|
||||||
"gomag_api_key": s.get("gomag_api_key", "") or config_settings.GOMAG_API_KEY,
|
"gomag_api_key": s.get("gomag_api_key", "") or config_settings.GOMAG_API_KEY,
|
||||||
"gomag_api_shop": s.get("gomag_api_shop", "") or config_settings.GOMAG_API_SHOP,
|
"gomag_api_shop": s.get("gomag_api_shop", "") or config_settings.GOMAG_API_SHOP,
|
||||||
"gomag_order_days_back": s.get("gomag_order_days_back", "") or str(config_settings.GOMAG_ORDER_DAYS_BACK),
|
"gomag_order_days_back": s.get("gomag_order_days_back", "") or str(config_settings.GOMAG_ORDER_DAYS_BACK),
|
||||||
@@ -616,6 +618,7 @@ async def update_app_settings(config: AppSettingsUpdate):
|
|||||||
await sqlite_service.set_app_setting("discount_id_pol", config.discount_id_pol)
|
await sqlite_service.set_app_setting("discount_id_pol", config.discount_id_pol)
|
||||||
await sqlite_service.set_app_setting("id_pol", config.id_pol)
|
await sqlite_service.set_app_setting("id_pol", config.id_pol)
|
||||||
await sqlite_service.set_app_setting("id_sectie", config.id_sectie)
|
await sqlite_service.set_app_setting("id_sectie", config.id_sectie)
|
||||||
|
await sqlite_service.set_app_setting("id_gestiune", config.id_gestiune)
|
||||||
await sqlite_service.set_app_setting("gomag_api_key", config.gomag_api_key)
|
await sqlite_service.set_app_setting("gomag_api_key", config.gomag_api_key)
|
||||||
await sqlite_service.set_app_setting("gomag_api_shop", config.gomag_api_shop)
|
await sqlite_service.set_app_setting("gomag_api_shop", config.gomag_api_shop)
|
||||||
await sqlite_service.set_app_setting("gomag_order_days_back", config.gomag_order_days_back)
|
await sqlite_service.set_app_setting("gomag_order_days_back", config.gomag_order_days_back)
|
||||||
@@ -624,6 +627,26 @@ async def update_app_settings(config: AppSettingsUpdate):
|
|||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/settings/gestiuni")
|
||||||
|
async def get_gestiuni():
|
||||||
|
"""Get list of warehouses from Oracle for dropdown."""
|
||||||
|
def _query():
|
||||||
|
conn = database.get_oracle_connection()
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id_gestiune, nume_gestiune FROM nom_gestiuni WHERE sters=0 AND inactiv=0 ORDER BY id_gestiune"
|
||||||
|
)
|
||||||
|
return [{"id": str(row[0]), "label": f"{row[0]} - {row[1]}"} for row in cur]
|
||||||
|
finally:
|
||||||
|
database.pool.release(conn)
|
||||||
|
try:
|
||||||
|
return await asyncio.to_thread(_query)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get_gestiuni error: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/settings/sectii")
|
@router.get("/api/settings/sectii")
|
||||||
async def get_sectii():
|
async def get_sectii():
|
||||||
"""Get list of sections from Oracle for dropdown."""
|
"""Get list of sections from Oracle for dropdown."""
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ def build_articles_json(items, order=None, settings=None) -> str:
|
|||||||
return json.dumps(articles)
|
return json.dumps(articles)
|
||||||
|
|
||||||
|
|
||||||
def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_settings: dict = None) -> dict:
|
def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_settings: dict = None, id_gestiune: int = None) -> dict:
|
||||||
"""Import a single order into Oracle ROA.
|
"""Import a single order into Oracle ROA.
|
||||||
|
|
||||||
Returns dict with:
|
Returns dict with:
|
||||||
@@ -255,6 +255,7 @@ def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_se
|
|||||||
addr_fact_id, # p_id_adresa_facturare
|
addr_fact_id, # p_id_adresa_facturare
|
||||||
id_pol, # p_id_pol
|
id_pol, # p_id_pol
|
||||||
id_sectie, # p_id_sectie
|
id_sectie, # p_id_sectie
|
||||||
|
id_gestiune, # p_id_gestiune
|
||||||
id_comanda # v_id_comanda (OUT)
|
id_comanda # v_id_comanda (OUT)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -334,9 +334,17 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
# (can happen if previous import committed partially without rollback)
|
# (can happen if previous import committed partially without rollback)
|
||||||
await _fix_stale_error_orders(existing_map, run_id)
|
await _fix_stale_error_orders(existing_map, run_id)
|
||||||
|
|
||||||
|
# Load app settings early (needed for id_gestiune in SKU validation)
|
||||||
|
app_settings = await sqlite_service.get_app_settings()
|
||||||
|
id_pol = id_pol or int(app_settings.get("id_pol") or 0) or settings.ID_POL
|
||||||
|
id_sectie = id_sectie or int(app_settings.get("id_sectie") or 0) or settings.ID_SECTIE
|
||||||
|
id_gestiune = int(app_settings.get("id_gestiune") or 0) or None # None = orice gestiune
|
||||||
|
logger.info(f"Sync params: ID_POL={id_pol}, ID_SECTIE={id_sectie}, ID_GESTIUNE={id_gestiune}")
|
||||||
|
_log_line(run_id, f"Parametri import: ID_POL={id_pol}, ID_SECTIE={id_sectie}, ID_GESTIUNE={id_gestiune}")
|
||||||
|
|
||||||
# Step 2b: Validate SKUs (reuse same connection)
|
# Step 2b: Validate SKUs (reuse same connection)
|
||||||
all_skus = order_reader.get_all_skus(orders)
|
all_skus = order_reader.get_all_skus(orders)
|
||||||
validation = await asyncio.to_thread(validation_service.validate_skus, all_skus, conn)
|
validation = await asyncio.to_thread(validation_service.validate_skus, all_skus, conn, id_gestiune)
|
||||||
importable, skipped = validation_service.classify_orders(orders, validation)
|
importable, skipped = validation_service.classify_orders(orders, validation)
|
||||||
|
|
||||||
# ── Split importable into truly_importable vs already_in_roa ──
|
# ── Split importable into truly_importable vs already_in_roa ──
|
||||||
@@ -390,12 +398,6 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Step 2d: Pre-validate prices for importable articles
|
# Step 2d: Pre-validate prices for importable articles
|
||||||
# Load app settings (for transport/discount CODMAT config AND id_pol/id_sectie override)
|
|
||||||
app_settings = await sqlite_service.get_app_settings()
|
|
||||||
id_pol = id_pol or int(app_settings.get("id_pol") or 0) or settings.ID_POL
|
|
||||||
id_sectie = id_sectie or int(app_settings.get("id_sectie") or 0) or settings.ID_SECTIE
|
|
||||||
logger.info(f"Sync params: ID_POL={id_pol}, ID_SECTIE={id_sectie}")
|
|
||||||
_log_line(run_id, f"Parametri import: ID_POL={id_pol}, ID_SECTIE={id_sectie}")
|
|
||||||
if id_pol and (truly_importable or already_in_roa):
|
if id_pol and (truly_importable or already_in_roa):
|
||||||
_update_progress("validation", "Validating prices...", 0, len(truly_importable))
|
_update_progress("validation", "Validating prices...", 0, len(truly_importable))
|
||||||
_log_line(run_id, "Validare preturi...")
|
_log_line(run_id, "Validare preturi...")
|
||||||
@@ -505,7 +507,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
result = await asyncio.to_thread(
|
result = await asyncio.to_thread(
|
||||||
import_service.import_single_order,
|
import_service.import_single_order,
|
||||||
order, id_pol=id_pol, id_sectie=id_sectie,
|
order, id_pol=id_pol, id_sectie=id_sectie,
|
||||||
app_settings=app_settings
|
app_settings=app_settings, id_gestiune=id_gestiune
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build order items data for storage (R9)
|
# Build order items data for storage (R9)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from .. import database
|
from .. import database
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -29,7 +28,67 @@ def check_orders_in_roa(min_date, conn) -> dict:
|
|||||||
return existing
|
return existing
|
||||||
|
|
||||||
|
|
||||||
def validate_skus(skus: set[str], conn=None) -> dict:
|
def resolve_codmat_ids(codmats: set[str], id_gestiune: int = None, conn=None) -> dict[str, int]:
|
||||||
|
"""Resolve CODMATs to best id_articol: prefers article with stock, then MAX(id_articol).
|
||||||
|
Filters: sters=0 AND inactiv=0.
|
||||||
|
Returns: {codmat: id_articol}
|
||||||
|
"""
|
||||||
|
if not codmats:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
codmat_list = list(codmats)
|
||||||
|
|
||||||
|
# Build stoc subquery dynamically for index optimization
|
||||||
|
if id_gestiune is not None:
|
||||||
|
stoc_filter = "AND s.id_gestiune = :id_gestiune"
|
||||||
|
else:
|
||||||
|
stoc_filter = ""
|
||||||
|
|
||||||
|
own_conn = conn is None
|
||||||
|
if own_conn:
|
||||||
|
conn = database.get_oracle_connection()
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for i in range(0, len(codmat_list), 500):
|
||||||
|
batch = codmat_list[i:i+500]
|
||||||
|
placeholders = ",".join([f":c{j}" for j in range(len(batch))])
|
||||||
|
params = {f"c{j}": cm for j, cm in enumerate(batch)}
|
||||||
|
if id_gestiune is not None:
|
||||||
|
params["id_gestiune"] = id_gestiune
|
||||||
|
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT codmat, id_articol FROM (
|
||||||
|
SELECT na.codmat, na.id_articol,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY na.codmat
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM stoc s
|
||||||
|
WHERE s.id_articol = na.id_articol
|
||||||
|
{stoc_filter}
|
||||||
|
AND s.an = EXTRACT(YEAR FROM SYSDATE)
|
||||||
|
AND s.luna = EXTRACT(MONTH FROM SYSDATE)
|
||||||
|
AND s.cants + s.cant - s.cante > 0
|
||||||
|
) THEN 0 ELSE 1 END,
|
||||||
|
na.id_articol DESC
|
||||||
|
) AS rn
|
||||||
|
FROM nom_articole na
|
||||||
|
WHERE na.codmat IN ({placeholders})
|
||||||
|
AND na.sters = 0 AND na.inactiv = 0
|
||||||
|
) WHERE rn = 1
|
||||||
|
""", params)
|
||||||
|
for row in cur:
|
||||||
|
result[row[0]] = row[1]
|
||||||
|
finally:
|
||||||
|
if own_conn:
|
||||||
|
database.pool.release(conn)
|
||||||
|
|
||||||
|
logger.info(f"resolve_codmat_ids: {len(result)}/{len(codmats)} resolved (gestiune={id_gestiune})")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def validate_skus(skus: set[str], conn=None, id_gestiune: int = None) -> dict:
|
||||||
"""Validate a set of SKUs against Oracle.
|
"""Validate a set of SKUs against Oracle.
|
||||||
Returns: {mapped: set, direct: set, missing: set, direct_id_map: {codmat: id_articol}}
|
Returns: {mapped: set, direct: set, missing: set, direct_id_map: {codmat: id_articol}}
|
||||||
- mapped: found in ARTICOLE_TERTI (active)
|
- mapped: found in ARTICOLE_TERTI (active)
|
||||||
@@ -41,8 +100,6 @@ def validate_skus(skus: set[str], conn=None) -> dict:
|
|||||||
return {"mapped": set(), "direct": set(), "missing": set(), "direct_id_map": {}}
|
return {"mapped": set(), "direct": set(), "missing": set(), "direct_id_map": {}}
|
||||||
|
|
||||||
mapped = set()
|
mapped = set()
|
||||||
direct = set()
|
|
||||||
direct_id_map = {}
|
|
||||||
sku_list = list(skus)
|
sku_list = list(skus)
|
||||||
|
|
||||||
own_conn = conn is None
|
own_conn = conn is None
|
||||||
@@ -64,18 +121,14 @@ def validate_skus(skus: set[str], conn=None) -> dict:
|
|||||||
for row in cur:
|
for row in cur:
|
||||||
mapped.add(row[0])
|
mapped.add(row[0])
|
||||||
|
|
||||||
# Check NOM_ARTICOLE for remaining — also fetch id_articol
|
# Resolve remaining SKUs via resolve_codmat_ids (consistent id_articol selection)
|
||||||
remaining = [s for s in batch if s not in mapped]
|
all_remaining = [s for s in sku_list if s not in mapped]
|
||||||
if remaining:
|
if all_remaining:
|
||||||
placeholders2 = ",".join([f":n{j}" for j in range(len(remaining))])
|
direct_id_map = resolve_codmat_ids(set(all_remaining), id_gestiune, conn)
|
||||||
params2 = {f"n{j}": sku for j, sku in enumerate(remaining)}
|
direct = set(direct_id_map.keys())
|
||||||
cur.execute(f"""
|
else:
|
||||||
SELECT codmat, id_articol FROM NOM_ARTICOLE
|
direct_id_map = {}
|
||||||
WHERE codmat IN ({placeholders2}) AND sters = 0 AND inactiv = 0
|
direct = set()
|
||||||
""", params2)
|
|
||||||
for row in cur:
|
|
||||||
direct.add(row[0])
|
|
||||||
direct_id_map[row[0]] = row[1]
|
|
||||||
finally:
|
finally:
|
||||||
if own_conn:
|
if own_conn:
|
||||||
database.pool.release(conn)
|
database.pool.release(conn)
|
||||||
@@ -113,37 +166,15 @@ def validate_prices(codmats: set[str], id_pol: int, conn=None, direct_id_map: di
|
|||||||
if not codmats:
|
if not codmats:
|
||||||
return {"has_price": set(), "missing_price": set()}
|
return {"has_price": set(), "missing_price": set()}
|
||||||
|
|
||||||
codmat_to_id = {}
|
codmat_to_id = dict(direct_id_map) if direct_id_map else {}
|
||||||
ids_with_price = set()
|
ids_with_price = set()
|
||||||
codmat_list = list(codmats)
|
|
||||||
|
|
||||||
# Pre-populate from direct_id_map if available
|
|
||||||
if direct_id_map:
|
|
||||||
for cm in codmat_list:
|
|
||||||
if cm in direct_id_map:
|
|
||||||
codmat_to_id[cm] = direct_id_map[cm]
|
|
||||||
|
|
||||||
own_conn = conn is None
|
own_conn = conn is None
|
||||||
if own_conn:
|
if own_conn:
|
||||||
conn = database.get_oracle_connection()
|
conn = database.get_oracle_connection()
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
# Step 1: Get ID_ARTICOL for CODMATs not already in direct_id_map
|
# Check which ID_ARTICOLs have a price in the policy
|
||||||
remaining = [cm for cm in codmat_list if cm not in codmat_to_id]
|
|
||||||
if remaining:
|
|
||||||
for i in range(0, len(remaining), 500):
|
|
||||||
batch = remaining[i:i+500]
|
|
||||||
placeholders = ",".join([f":c{j}" for j in range(len(batch))])
|
|
||||||
params = {f"c{j}": cm for j, cm in enumerate(batch)}
|
|
||||||
|
|
||||||
cur.execute(f"""
|
|
||||||
SELECT id_articol, codmat FROM NOM_ARTICOLE
|
|
||||||
WHERE codmat IN ({placeholders})
|
|
||||||
""", params)
|
|
||||||
for row in cur:
|
|
||||||
codmat_to_id[row[1]] = row[0]
|
|
||||||
|
|
||||||
# Step 2: Check which ID_ARTICOLs have a price in the policy
|
|
||||||
id_list = list(codmat_to_id.values())
|
id_list = list(codmat_to_id.values())
|
||||||
for i in range(0, len(id_list), 500):
|
for i in range(0, len(id_list), 500):
|
||||||
batch = id_list[i:i+500]
|
batch = id_list[i:i+500]
|
||||||
@@ -191,28 +222,10 @@ def ensure_prices(codmats: set[str], id_pol: int, conn=None, direct_id_map: dict
|
|||||||
return
|
return
|
||||||
id_valuta = row[0]
|
id_valuta = row[0]
|
||||||
|
|
||||||
# Build batch params using direct_id_map where available
|
# Build batch params using direct_id_map (already resolved via resolve_codmat_ids)
|
||||||
batch_params = []
|
batch_params = []
|
||||||
need_lookup = []
|
|
||||||
codmat_id_map = dict(direct_id_map) if direct_id_map else {}
|
codmat_id_map = dict(direct_id_map) if direct_id_map else {}
|
||||||
|
|
||||||
for codmat in codmats:
|
|
||||||
if codmat not in codmat_id_map:
|
|
||||||
need_lookup.append(codmat)
|
|
||||||
|
|
||||||
# Batch lookup remaining CODMATs
|
|
||||||
if need_lookup:
|
|
||||||
for i in range(0, len(need_lookup), 500):
|
|
||||||
batch = need_lookup[i:i+500]
|
|
||||||
placeholders = ",".join([f":c{j}" for j in range(len(batch))])
|
|
||||||
params = {f"c{j}": cm for j, cm in enumerate(batch)}
|
|
||||||
cur.execute(f"""
|
|
||||||
SELECT codmat, id_articol FROM NOM_ARTICOLE
|
|
||||||
WHERE codmat IN ({placeholders}) AND sters = 0 AND inactiv = 0
|
|
||||||
""", params)
|
|
||||||
for r in cur:
|
|
||||||
codmat_id_map[r[0]] = r[1]
|
|
||||||
|
|
||||||
for codmat in codmats:
|
for codmat in codmats:
|
||||||
id_articol = codmat_id_map.get(codmat)
|
id_articol = codmat_id_map.get(codmat)
|
||||||
if not id_articol:
|
if not id_articol:
|
||||||
|
|||||||
@@ -9,12 +9,22 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
|
|
||||||
async function loadDropdowns() {
|
async function loadDropdowns() {
|
||||||
try {
|
try {
|
||||||
const [sectiiRes, politiciRes] = await Promise.all([
|
const [sectiiRes, politiciRes, gestiuniRes] = await Promise.all([
|
||||||
fetch('/api/settings/sectii'),
|
fetch('/api/settings/sectii'),
|
||||||
fetch('/api/settings/politici')
|
fetch('/api/settings/politici'),
|
||||||
|
fetch('/api/settings/gestiuni')
|
||||||
]);
|
]);
|
||||||
const sectii = await sectiiRes.json();
|
const sectii = await sectiiRes.json();
|
||||||
const politici = await politiciRes.json();
|
const politici = await politiciRes.json();
|
||||||
|
const gestiuni = await gestiuniRes.json();
|
||||||
|
|
||||||
|
const gestiuneEl = document.getElementById('settIdGestiune');
|
||||||
|
if (gestiuneEl) {
|
||||||
|
gestiuneEl.innerHTML = '<option value="">— orice gestiune —</option>';
|
||||||
|
gestiuni.forEach(g => {
|
||||||
|
gestiuneEl.innerHTML += `<option value="${escHtml(g.id)}">${escHtml(g.label)}</option>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const sectieEl = document.getElementById('settIdSectie');
|
const sectieEl = document.getElementById('settIdSectie');
|
||||||
if (sectieEl) {
|
if (sectieEl) {
|
||||||
@@ -65,6 +75,7 @@ async function loadSettings() {
|
|||||||
if (el('settDiscountIdPol')) el('settDiscountIdPol').value = data.discount_id_pol || '';
|
if (el('settDiscountIdPol')) el('settDiscountIdPol').value = data.discount_id_pol || '';
|
||||||
if (el('settIdPol')) el('settIdPol').value = data.id_pol || '';
|
if (el('settIdPol')) el('settIdPol').value = data.id_pol || '';
|
||||||
if (el('settIdSectie')) el('settIdSectie').value = data.id_sectie || '';
|
if (el('settIdSectie')) el('settIdSectie').value = data.id_sectie || '';
|
||||||
|
if (el('settIdGestiune')) el('settIdGestiune').value = data.id_gestiune || '';
|
||||||
if (el('settGomagApiKey')) el('settGomagApiKey').value = data.gomag_api_key || '';
|
if (el('settGomagApiKey')) el('settGomagApiKey').value = data.gomag_api_key || '';
|
||||||
if (el('settGomagApiShop')) el('settGomagApiShop').value = data.gomag_api_shop || '';
|
if (el('settGomagApiShop')) el('settGomagApiShop').value = data.gomag_api_shop || '';
|
||||||
if (el('settGomagDaysBack')) el('settGomagDaysBack').value = data.gomag_order_days_back || '7';
|
if (el('settGomagDaysBack')) el('settGomagDaysBack').value = data.gomag_order_days_back || '7';
|
||||||
@@ -86,6 +97,7 @@ async function saveSettings() {
|
|||||||
discount_id_pol: el('settDiscountIdPol')?.value?.trim() || '',
|
discount_id_pol: el('settDiscountIdPol')?.value?.trim() || '',
|
||||||
id_pol: el('settIdPol')?.value?.trim() || '',
|
id_pol: el('settIdPol')?.value?.trim() || '',
|
||||||
id_sectie: el('settIdSectie')?.value?.trim() || '',
|
id_sectie: el('settIdSectie')?.value?.trim() || '',
|
||||||
|
id_gestiune: el('settIdGestiune')?.value?.trim() || '',
|
||||||
gomag_api_key: el('settGomagApiKey')?.value?.trim() || '',
|
gomag_api_key: el('settGomagApiKey')?.value?.trim() || '',
|
||||||
gomag_api_shop: el('settGomagApiShop')?.value?.trim() || '',
|
gomag_api_shop: el('settGomagApiShop')?.value?.trim() || '',
|
||||||
gomag_order_days_back: el('settGomagDaysBack')?.value?.trim() || '7',
|
gomag_order_days_back: el('settGomagDaysBack')?.value?.trim() || '7',
|
||||||
|
|||||||
@@ -38,6 +38,12 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header py-2 px-3 fw-semibold">Import ROA</div>
|
<div class="card-header py-2 px-3 fw-semibold">Import ROA</div>
|
||||||
<div class="card-body py-2 px-3">
|
<div class="card-body py-2 px-3">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label mb-0 small">Gestiune (ID_GESTIUNE)</label>
|
||||||
|
<select class="form-select form-select-sm" id="settIdGestiune">
|
||||||
|
<option value="">— orice gestiune —</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label mb-0 small">Secție (ID_SECTIE)</label>
|
<label class="form-label mb-0 small">Secție (ID_SECTIE)</label>
|
||||||
<select class="form-select form-select-sm" id="settIdSectie">
|
<select class="form-select form-select-sm" id="settIdSectie">
|
||||||
@@ -146,5 +152,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/settings.js?v=3"></script>
|
<script src="{{ request.scope.get('root_path', '') }}/static/js/settings.js?v=4"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_COMENZI AS
|
|||||||
p_id_adresa_facturare IN NUMBER DEFAULT NULL,
|
p_id_adresa_facturare IN NUMBER DEFAULT NULL,
|
||||||
p_id_pol IN NUMBER DEFAULT NULL,
|
p_id_pol IN NUMBER DEFAULT NULL,
|
||||||
p_id_sectie IN NUMBER DEFAULT NULL,
|
p_id_sectie IN NUMBER DEFAULT NULL,
|
||||||
|
p_id_gestiune IN NUMBER DEFAULT NULL,
|
||||||
v_id_comanda OUT NUMBER);
|
v_id_comanda OUT NUMBER);
|
||||||
|
|
||||||
-- Functii pentru managementul erorilor (pentru orchestrator VFP)
|
-- Functii pentru managementul erorilor (pentru orchestrator VFP)
|
||||||
@@ -88,6 +89,56 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
|
|||||||
g_last_error := NULL;
|
g_last_error := NULL;
|
||||||
END clear_error;
|
END clear_error;
|
||||||
|
|
||||||
|
-- ================================================================
|
||||||
|
-- Functie helper: selecteaza id_articol corect pentru un CODMAT
|
||||||
|
-- Prioritate: sters=0 AND inactiv=0, preferinta stoc, MAX(id_articol) fallback
|
||||||
|
-- ================================================================
|
||||||
|
FUNCTION resolve_id_articol(p_codmat IN VARCHAR2, p_id_gest IN NUMBER) RETURN NUMBER IS
|
||||||
|
v_result NUMBER;
|
||||||
|
BEGIN
|
||||||
|
IF p_id_gest IS NOT NULL THEN
|
||||||
|
-- Cu gestiune specifica — Oracle poate folosi index pe stoc(id_gestiune, an, luna)
|
||||||
|
BEGIN
|
||||||
|
SELECT id_articol INTO v_result FROM (
|
||||||
|
SELECT na.id_articol
|
||||||
|
FROM nom_articole na
|
||||||
|
WHERE na.codmat = p_codmat AND na.sters = 0 AND na.inactiv = 0
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM stoc s
|
||||||
|
WHERE s.id_articol = na.id_articol
|
||||||
|
AND s.id_gestiune = p_id_gest
|
||||||
|
AND s.an = EXTRACT(YEAR FROM SYSDATE)
|
||||||
|
AND s.luna = EXTRACT(MONTH FROM SYSDATE)
|
||||||
|
AND s.cants + s.cant - s.cante > 0
|
||||||
|
) THEN 0 ELSE 1 END,
|
||||||
|
na.id_articol DESC
|
||||||
|
) WHERE ROWNUM = 1;
|
||||||
|
EXCEPTION WHEN NO_DATA_FOUND THEN v_result := NULL;
|
||||||
|
END;
|
||||||
|
ELSE
|
||||||
|
-- Fara gestiune — cauta stoc in orice gestiune
|
||||||
|
BEGIN
|
||||||
|
SELECT id_articol INTO v_result FROM (
|
||||||
|
SELECT na.id_articol
|
||||||
|
FROM nom_articole na
|
||||||
|
WHERE na.codmat = p_codmat AND na.sters = 0 AND na.inactiv = 0
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM stoc s
|
||||||
|
WHERE s.id_articol = na.id_articol
|
||||||
|
AND s.an = EXTRACT(YEAR FROM SYSDATE)
|
||||||
|
AND s.luna = EXTRACT(MONTH FROM SYSDATE)
|
||||||
|
AND s.cants + s.cant - s.cante > 0
|
||||||
|
) THEN 0 ELSE 1 END,
|
||||||
|
na.id_articol DESC
|
||||||
|
) WHERE ROWNUM = 1;
|
||||||
|
EXCEPTION WHEN NO_DATA_FOUND THEN v_result := NULL;
|
||||||
|
END;
|
||||||
|
END IF;
|
||||||
|
RETURN v_result;
|
||||||
|
END resolve_id_articol;
|
||||||
|
|
||||||
-- ================================================================
|
-- ================================================================
|
||||||
-- Procedura principala pentru importul unei comenzi
|
-- Procedura principala pentru importul unei comenzi
|
||||||
-- ================================================================
|
-- ================================================================
|
||||||
@@ -99,6 +150,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
|
|||||||
p_id_adresa_facturare IN NUMBER DEFAULT NULL,
|
p_id_adresa_facturare IN NUMBER DEFAULT NULL,
|
||||||
p_id_pol IN NUMBER DEFAULT NULL,
|
p_id_pol IN NUMBER DEFAULT NULL,
|
||||||
p_id_sectie IN NUMBER DEFAULT NULL,
|
p_id_sectie IN NUMBER DEFAULT NULL,
|
||||||
|
p_id_gestiune IN NUMBER DEFAULT NULL,
|
||||||
v_id_comanda OUT NUMBER) IS
|
v_id_comanda OUT NUMBER) IS
|
||||||
v_data_livrare DATE;
|
v_data_livrare DATE;
|
||||||
v_sku VARCHAR2(100);
|
v_sku VARCHAR2(100);
|
||||||
@@ -203,8 +255,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
|
|||||||
-- Cauta mai intai in ARTICOLE_TERTI (mapari speciale / seturi)
|
-- Cauta mai intai in ARTICOLE_TERTI (mapari speciale / seturi)
|
||||||
v_found_mapping := FALSE;
|
v_found_mapping := FALSE;
|
||||||
|
|
||||||
FOR rec IN (SELECT at.codmat, at.cantitate_roa, at.procent_pret,
|
FOR rec IN (SELECT at.codmat, at.cantitate_roa, at.procent_pret
|
||||||
(SELECT MAX(na.id_articol) FROM nom_articole na WHERE na.codmat = at.codmat) AS id_articol
|
|
||||||
FROM articole_terti at
|
FROM articole_terti at
|
||||||
WHERE at.sku = v_sku
|
WHERE at.sku = v_sku
|
||||||
AND at.activ = 1
|
AND at.activ = 1
|
||||||
@@ -212,6 +263,14 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
|
|||||||
ORDER BY at.procent_pret DESC) LOOP
|
ORDER BY at.procent_pret DESC) LOOP
|
||||||
|
|
||||||
v_found_mapping := TRUE;
|
v_found_mapping := TRUE;
|
||||||
|
v_id_articol := resolve_id_articol(rec.codmat, p_id_gestiune);
|
||||||
|
IF v_id_articol IS NULL THEN
|
||||||
|
v_articole_eroare := v_articole_eroare + 1;
|
||||||
|
g_last_error := g_last_error || CHR(10) ||
|
||||||
|
'Articol activ negasit pentru CODMAT: ' || rec.codmat;
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
v_cantitate_roa := rec.cantitate_roa * v_cantitate_web;
|
v_cantitate_roa := rec.cantitate_roa * v_cantitate_web;
|
||||||
v_pret_unitar := CASE WHEN v_pret_web IS NOT NULL
|
v_pret_unitar := CASE WHEN v_pret_web IS NOT NULL
|
||||||
THEN (v_pret_web * rec.procent_pret / 100) / rec.cantitate_roa
|
THEN (v_pret_web * rec.procent_pret / 100) / rec.cantitate_roa
|
||||||
@@ -220,7 +279,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
|
|||||||
|
|
||||||
BEGIN
|
BEGIN
|
||||||
PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda,
|
PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda,
|
||||||
V_ID_ARTICOL => rec.id_articol,
|
V_ID_ARTICOL => v_id_articol,
|
||||||
V_ID_POL => NVL(v_id_pol_articol, p_id_pol),
|
V_ID_POL => NVL(v_id_pol_articol, p_id_pol),
|
||||||
V_CANTITATE => v_cantitate_roa,
|
V_CANTITATE => v_cantitate_roa,
|
||||||
V_PRET => v_pret_unitar,
|
V_PRET => v_pret_unitar,
|
||||||
@@ -236,40 +295,34 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
|
|||||||
END;
|
END;
|
||||||
END LOOP;
|
END LOOP;
|
||||||
|
|
||||||
-- Daca nu s-a gasit mapare, cauta direct in NOM_ARTICOLE
|
-- Daca nu s-a gasit mapare, cauta direct in NOM_ARTICOLE via resolve_id_articol
|
||||||
IF NOT v_found_mapping THEN
|
IF NOT v_found_mapping THEN
|
||||||
BEGIN
|
v_id_articol := resolve_id_articol(v_sku, p_id_gestiune);
|
||||||
SELECT id_articol, codmat
|
IF v_id_articol IS NULL THEN
|
||||||
INTO v_id_articol, v_codmat
|
v_articole_eroare := v_articole_eroare + 1;
|
||||||
FROM nom_articole
|
g_last_error := g_last_error || CHR(10) ||
|
||||||
WHERE codmat = v_sku
|
'SKU negasit in ARTICOLE_TERTI si NOM_ARTICOLE (activ): ' || v_sku;
|
||||||
AND id_articol = (SELECT MAX(id_articol) FROM nom_articole WHERE codmat = v_sku);
|
ELSE
|
||||||
|
v_codmat := v_sku;
|
||||||
v_pret_unitar := NVL(v_pret_web, 0);
|
v_pret_unitar := NVL(v_pret_web, 0);
|
||||||
|
|
||||||
PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda,
|
BEGIN
|
||||||
V_ID_ARTICOL => v_id_articol,
|
PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda,
|
||||||
V_ID_POL => NVL(v_id_pol_articol, p_id_pol),
|
V_ID_ARTICOL => v_id_articol,
|
||||||
V_CANTITATE => v_cantitate_web,
|
V_ID_POL => NVL(v_id_pol_articol, p_id_pol),
|
||||||
V_PRET => v_pret_unitar,
|
V_CANTITATE => v_cantitate_web,
|
||||||
V_ID_UTIL => c_id_util,
|
V_PRET => v_pret_unitar,
|
||||||
V_ID_SECTIE => p_id_sectie,
|
V_ID_UTIL => c_id_util,
|
||||||
V_PTVA => v_vat);
|
V_ID_SECTIE => p_id_sectie,
|
||||||
v_articole_procesate := v_articole_procesate + 1;
|
V_PTVA => v_vat);
|
||||||
EXCEPTION
|
v_articole_procesate := v_articole_procesate + 1;
|
||||||
WHEN NO_DATA_FOUND THEN
|
EXCEPTION
|
||||||
v_articole_eroare := v_articole_eroare + 1;
|
WHEN OTHERS THEN
|
||||||
g_last_error := g_last_error || CHR(10) ||
|
v_articole_eroare := v_articole_eroare + 1;
|
||||||
'SKU negasit in ARTICOLE_TERTI si NOM_ARTICOLE: ' || v_sku;
|
g_last_error := g_last_error || CHR(10) ||
|
||||||
WHEN TOO_MANY_ROWS THEN
|
'Eroare adaugare articol ' || v_sku || ' (CODMAT: ' || v_codmat || '): ' || SQLERRM;
|
||||||
v_articole_eroare := v_articole_eroare + 1;
|
END;
|
||||||
g_last_error := g_last_error || CHR(10) ||
|
END IF;
|
||||||
'Multiple articole gasite pentru SKU: ' || v_sku;
|
|
||||||
WHEN OTHERS THEN
|
|
||||||
v_articole_eroare := v_articole_eroare + 1;
|
|
||||||
g_last_error := g_last_error || CHR(10) ||
|
|
||||||
'Eroare adaugare articol ' || v_sku || ' (CODMAT: ' || v_codmat || '): ' || SQLERRM;
|
|
||||||
END;
|
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
END; -- End BEGIN block pentru articol individual
|
END; -- End BEGIN block pentru articol individual
|
||||||
|
|||||||
Reference in New Issue
Block a user