fix(mappings): resolve 409 error on multi-CODMAT edit and make SKU editable
Batch create after soft-delete was rejected because create_mapping() treated soft-deleted records as conflicts. Added auto_restore param that restores+updates instead of 409 when called from edit flow. Also removed readOnly on SKU input in edit modal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ class MappingLine(BaseModel):
|
|||||||
class MappingBatchCreate(BaseModel):
|
class MappingBatchCreate(BaseModel):
|
||||||
sku: str
|
sku: str
|
||||||
mappings: list[MappingLine]
|
mappings: list[MappingLine]
|
||||||
|
auto_restore: bool = False
|
||||||
|
|
||||||
# HTML page
|
# HTML page
|
||||||
@router.get("/mappings", response_class=HTMLResponse)
|
@router.get("/mappings", response_class=HTMLResponse)
|
||||||
@@ -141,7 +142,7 @@ async def create_batch_mapping(data: MappingBatchCreate):
|
|||||||
try:
|
try:
|
||||||
results = []
|
results = []
|
||||||
for m in data.mappings:
|
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)
|
results.append(r)
|
||||||
# Mark SKU as resolved in missing_skus tracking
|
# Mark SKU as resolved in missing_skus tracking
|
||||||
await sqlite_service.resolve_missing_sku(data.sku)
|
await sqlite_service.resolve_missing_sku(data.sku)
|
||||||
|
|||||||
@@ -145,8 +145,11 @@ def get_mappings(search: str = "", page: int = 1, per_page: int = 50,
|
|||||||
"counts": counts,
|
"counts": counts,
|
||||||
}
|
}
|
||||||
|
|
||||||
def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret: float = 100):
|
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."""
|
"""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():
|
if not sku or not sku.strip():
|
||||||
raise HTTPException(status_code=400, detail="SKU este obligatoriu")
|
raise HTTPException(status_code=400, detail="SKU este obligatoriu")
|
||||||
if not codmat or not codmat.strip():
|
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
|
WHERE sku = :sku AND codmat = :codmat AND sters = 1
|
||||||
""", {"sku": sku, "codmat": codmat})
|
""", {"sku": sku, "codmat": codmat})
|
||||||
if cur.fetchone()[0] > 0:
|
if cur.fetchone()[0] > 0:
|
||||||
raise HTTPException(
|
if auto_restore:
|
||||||
status_code=409,
|
cur.execute("""
|
||||||
detail="Maparea a fost ștearsă anterior",
|
UPDATE ARTICOLE_TERTI SET sters = 0, activ = 1,
|
||||||
headers={"X-Can-Restore": "true"}
|
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("""
|
cur.execute("""
|
||||||
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ, sters, data_creare, id_util_creare)
|
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ, sters, data_creare, id_util_creare)
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ async function openEditModal(sku, codmat, cantitate, procent) {
|
|||||||
editingMapping = { sku, codmat };
|
editingMapping = { sku, codmat };
|
||||||
document.getElementById('addModalTitle').textContent = 'Editare Mapare';
|
document.getElementById('addModalTitle').textContent = 'Editare Mapare';
|
||||||
document.getElementById('inputSku').value = sku;
|
document.getElementById('inputSku').value = sku;
|
||||||
document.getElementById('inputSku').readOnly = true;
|
document.getElementById('inputSku').readOnly = false;
|
||||||
document.getElementById('pctWarning').style.display = 'none';
|
document.getElementById('pctWarning').style.display = 'none';
|
||||||
|
|
||||||
const container = document.getElementById('codmatLines');
|
const container = document.getElementById('codmatLines');
|
||||||
@@ -292,6 +292,15 @@ async function openEditModal(sku, codmat, cantitate, procent) {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const allMappings = (data.mappings || []).filter(m => m.sku === sku && !m.sters);
|
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) {
|
if (allMappings.length === 0) {
|
||||||
// Fallback to single line with passed values
|
// Fallback to single line with passed values
|
||||||
addCodmatLine();
|
addCodmatLine();
|
||||||
@@ -307,6 +316,9 @@ async function openEditModal(sku, codmat, cantitate, procent) {
|
|||||||
const lines = container.querySelectorAll('.codmat-line');
|
const lines = container.querySelectorAll('.codmat-line');
|
||||||
const line = lines[lines.length - 1];
|
const line = lines[lines.length - 1];
|
||||||
line.querySelector('.cl-codmat').value = m.codmat;
|
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-cantitate').value = m.cantitate_roa;
|
||||||
line.querySelector('.cl-procent').value = m.procent_pret;
|
line.querySelector('.cl-procent').value = m.procent_pret;
|
||||||
}
|
}
|
||||||
@@ -436,22 +448,23 @@ async function saveMapping() {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Multi-CODMAT set: delete all existing then create new batch
|
// 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 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) {
|
for (const m of existing) {
|
||||||
await fetch(`/api/mappings/${encodeURIComponent(m.sku)}/${encodeURIComponent(m.codmat)}`, {
|
await fetch(`/api/mappings/${encodeURIComponent(m.sku)}/${encodeURIComponent(m.codmat)}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new batch
|
// Create new batch with auto_restore (handles just-soft-deleted records)
|
||||||
res = await fetch('/api/mappings/batch', {
|
res = await fetch('/api/mappings/batch', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ sku, mappings })
|
body: JSON.stringify({ sku, mappings, auto_restore: true })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (mappings.length === 1) {
|
} else if (mappings.length === 1) {
|
||||||
|
|||||||
@@ -154,5 +154,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/js/mappings.js?v=5"></script>
|
<script src="/static/js/mappings.js?v=7"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user