diff --git a/api/app/routers/mappings.py b/api/app/routers/mappings.py index 81214b0..1ba68c9 100644 --- a/api/app/routers/mappings.py +++ b/api/app/routers/mappings.py @@ -52,6 +52,7 @@ class MappingLine(BaseModel): class MappingBatchCreate(BaseModel): sku: str mappings: list[MappingLine] + auto_restore: bool = False # HTML page @router.get("/mappings", response_class=HTMLResponse) @@ -141,7 +142,7 @@ async def create_batch_mapping(data: MappingBatchCreate): try: results = [] for m in data.mappings: - r = mapping_service.create_mapping(data.sku, m.codmat, m.cantitate_roa, m.procent_pret) + r = mapping_service.create_mapping(data.sku, m.codmat, m.cantitate_roa, m.procent_pret, auto_restore=data.auto_restore) results.append(r) # Mark SKU as resolved in missing_skus tracking await sqlite_service.resolve_missing_sku(data.sku) diff --git a/api/app/services/mapping_service.py b/api/app/services/mapping_service.py index 9121762..0f9b153 100644 --- a/api/app/services/mapping_service.py +++ b/api/app/services/mapping_service.py @@ -145,8 +145,11 @@ def get_mappings(search: str = "", page: int = 1, per_page: int = 50, "counts": counts, } -def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret: float = 100): - """Create a new mapping. Returns dict or raises HTTPException on duplicate.""" +def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret: float = 100, auto_restore: bool = False): + """Create a new mapping. Returns dict or raises HTTPException on duplicate. + + When auto_restore=True, soft-deleted records are restored+updated instead of raising 409. + """ if not sku or not sku.strip(): raise HTTPException(status_code=400, detail="SKU este obligatoriu") if not codmat or not codmat.strip(): @@ -170,11 +173,22 @@ def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret WHERE sku = :sku AND codmat = :codmat AND sters = 1 """, {"sku": sku, "codmat": codmat}) if cur.fetchone()[0] > 0: - raise HTTPException( - status_code=409, - detail="Maparea a fost ștearsă anterior", - headers={"X-Can-Restore": "true"} - ) + if auto_restore: + cur.execute(""" + UPDATE ARTICOLE_TERTI SET sters = 0, activ = 1, + cantitate_roa = :cantitate_roa, procent_pret = :procent_pret, + data_modif = SYSDATE + WHERE sku = :sku AND codmat = :codmat AND sters = 1 + """, {"sku": sku, "codmat": codmat, + "cantitate_roa": cantitate_roa, "procent_pret": procent_pret}) + conn.commit() + return {"sku": sku, "codmat": codmat} + else: + raise HTTPException( + status_code=409, + detail="Maparea a fost ștearsă anterior", + headers={"X-Can-Restore": "true"} + ) cur.execute(""" INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ, sters, data_creare, id_util_creare) diff --git a/api/app/static/js/mappings.js b/api/app/static/js/mappings.js index 54b2052..e7896f4 100644 --- a/api/app/static/js/mappings.js +++ b/api/app/static/js/mappings.js @@ -280,7 +280,7 @@ async function openEditModal(sku, codmat, cantitate, procent) { editingMapping = { sku, codmat }; document.getElementById('addModalTitle').textContent = 'Editare Mapare'; document.getElementById('inputSku').value = sku; - document.getElementById('inputSku').readOnly = true; + document.getElementById('inputSku').readOnly = false; document.getElementById('pctWarning').style.display = 'none'; const container = document.getElementById('codmatLines'); @@ -292,6 +292,15 @@ async function openEditModal(sku, codmat, cantitate, procent) { const data = await res.json(); const allMappings = (data.mappings || []).filter(m => m.sku === sku && !m.sters); + // Show product name if available + const productName = allMappings[0]?.product_name || ''; + const productNameEl = document.getElementById('addModalProductName'); + const productNameText = document.getElementById('inputProductName'); + if (productName && productNameEl && productNameText) { + productNameText.textContent = productName; + productNameEl.style.display = ''; + } + if (allMappings.length === 0) { // Fallback to single line with passed values addCodmatLine(); @@ -307,6 +316,9 @@ async function openEditModal(sku, codmat, cantitate, procent) { const lines = container.querySelectorAll('.codmat-line'); const line = lines[lines.length - 1]; line.querySelector('.cl-codmat').value = m.codmat; + if (m.denumire) { + line.querySelector('.cl-selected').textContent = m.denumire; + } line.querySelector('.cl-cantitate').value = m.cantitate_roa; line.querySelector('.cl-procent').value = m.procent_pret; } @@ -436,22 +448,23 @@ async function saveMapping() { }); } else { // Multi-CODMAT set: delete all existing then create new batch - const existRes = await fetch(`/api/mappings?search=${encodeURIComponent(editingMapping.sku)}&per_page=100`); + const oldSku = editingMapping.sku; + const existRes = await fetch(`/api/mappings?search=${encodeURIComponent(oldSku)}&per_page=100`); const existData = await existRes.json(); - const existing = (existData.mappings || []).filter(m => m.sku === editingMapping.sku && !m.sters); + const existing = (existData.mappings || []).filter(m => m.sku === oldSku && !m.sters); - // Delete each existing CODMAT + // Delete each existing CODMAT for old SKU for (const m of existing) { await fetch(`/api/mappings/${encodeURIComponent(m.sku)}/${encodeURIComponent(m.codmat)}`, { method: 'DELETE' }); } - // Create new batch + // Create new batch with auto_restore (handles just-soft-deleted records) res = await fetch('/api/mappings/batch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ sku, mappings }) + body: JSON.stringify({ sku, mappings, auto_restore: true }) }); } } else if (mappings.length === 1) { diff --git a/api/app/templates/mappings.html b/api/app/templates/mappings.html index 32dd257..16bb21f 100644 --- a/api/app/templates/mappings.html +++ b/api/app/templates/mappings.html @@ -154,5 +154,5 @@ {% endblock %} {% block scripts %} - + {% endblock %}