feat(partner): detect and resync partner mismatches on already-imported orders
Detects PF↔PJ transitions and CUI changes after import; auto-resyncs uninvoiced orders (max 5/cycle) and shows visual alert for invoiced ones. - SQLite: partner_mismatch column + batch helpers - sync_service: detection loop + _resync_partner_for_order - dashboard: red dot + attention card indicator - modal: alert with contextual message and resync button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -340,8 +340,9 @@ async function loadDashOrders() {
|
||||
const diffs = c.diffs || 0;
|
||||
|
||||
const incompleteAddr = c.incomplete_addresses || 0;
|
||||
const partnerMismatches = c.partner_mismatches || 0;
|
||||
|
||||
if (errors === 0 && unmapped === 0 && nefact === 0 && incompleteAddr === 0 && diffs === 0) {
|
||||
if (errors === 0 && unmapped === 0 && nefact === 0 && incompleteAddr === 0 && diffs === 0 && partnerMismatches === 0) {
|
||||
attnEl.innerHTML = '<div class="attention-card attention-ok"><i class="bi bi-check-circle"></i> Totul in ordine</div>';
|
||||
} else {
|
||||
let items = [];
|
||||
@@ -350,6 +351,7 @@ async function loadDashOrders() {
|
||||
if (nefact > 0) items.push(`<span class="attention-item attention-warning" onclick="document.querySelector('.filter-pill[data-status=UNINVOICED]')?.click()"><i class="bi bi-receipt"></i> ${nefact} nefacturate</span>`);
|
||||
if (c.incomplete_addresses > 0) items.push(`<span class="attention-item attention-warning"><i class="bi bi-geo-alt"></i> ${c.incomplete_addresses} adrese incomplete</span>`);
|
||||
if (diffs > 0) items.push(`<span class="attention-item attention-warning" onclick="document.querySelector('.filter-pill[data-status=DIFFS]')?.click()"><i class="bi bi-exclamation-diamond"></i> ${diffs} diferente ANAF</span>`);
|
||||
if (partnerMismatches > 0) items.push(`<span class="attention-item attention-error" onclick="document.querySelector('.filter-pill[data-status=DIFFS]')?.click()"><i class="bi bi-people"></i> ${partnerMismatches} partener schimbat</span>`);
|
||||
attnEl.innerHTML = '<div class="attention-card attention-alert">' + items.join('') + '</div>';
|
||||
}
|
||||
}
|
||||
@@ -509,6 +511,8 @@ function diffDots(o, mobile) {
|
||||
d += `<span style="${s};background:var(--compare)" title="Denumire ANAF"></span>`;
|
||||
if (o.address_mismatch===1)
|
||||
d += `<span style="${s};background:var(--info)" title="Adresa diferita"></span>`;
|
||||
if (o.partner_mismatch===1)
|
||||
d += `<span style="${s};background:var(--error)" title="Partener schimbat"></span>`;
|
||||
if (o.price_match===false)
|
||||
d += `<span style="${s};background:var(--error)" title="Pret GoMag > ROA"></span>`;
|
||||
return d;
|
||||
|
||||
@@ -531,6 +531,8 @@ async function renderOrderDetailModal(orderNumber, opts) {
|
||||
// Restore original structure (may have been replaced by PF indicator)
|
||||
cuiRoa.innerHTML = '<small class="text-muted">CUI:</small> <span class="font-data" id="detailCuiRoaVal"></span><span id="detailPartnerAnafArea"></span>';
|
||||
}
|
||||
const partnerMismatchEl = document.getElementById('detailPartnerMismatch');
|
||||
if (partnerMismatchEl) { partnerMismatchEl.style.display = 'none'; partnerMismatchEl.innerHTML = ''; }
|
||||
const denomMismatch = document.getElementById('detailDenomMismatch');
|
||||
if (denomMismatch) { denomMismatch.style.display = 'none'; denomMismatch.innerHTML = ''; }
|
||||
const addressBlock = document.getElementById('detailAddressBlock');
|
||||
@@ -940,6 +942,33 @@ function _renderHeaderInfo(order) {
|
||||
document.getElementById('detailIdPartener').innerHTML = '<span class="text-muted">\u2014</span>';
|
||||
}
|
||||
|
||||
// Partner mismatch alert
|
||||
if (pi && pi.partner_mismatch) {
|
||||
const pmEl = document.getElementById('detailPartnerMismatch');
|
||||
if (pmEl) {
|
||||
const isInvoiced = !!(order.invoice && order.invoice.facturat);
|
||||
let mismatchType = '';
|
||||
if (pi.cod_fiscal_gomag && !pi.cod_fiscal_roa) {
|
||||
mismatchType = 'PF → PJ: comanda importata ca persoana fizica, acum are CUI in GoMag.';
|
||||
} else if (!pi.cod_fiscal_gomag && pi.cod_fiscal_roa) {
|
||||
mismatchType = 'PJ → PF: comanda importata cu CUI, acum GoMag nu mai are companie.';
|
||||
} else if (pi.cod_fiscal_gomag && pi.cod_fiscal_roa && pi.cod_fiscal_gomag !== pi.cod_fiscal_roa) {
|
||||
mismatchType = `CUI schimbat: GoMag are ${esc(pi.cod_fiscal_gomag)}, ROA are ${esc(pi.cod_fiscal_roa)}.`;
|
||||
} else {
|
||||
mismatchType = 'Date partener diferite fata de momentul importului.';
|
||||
}
|
||||
const resyncBtn = isInvoiced
|
||||
? `<button class="btn btn-sm btn-outline-warning mt-1" onclick="resyncPartner('${esc(order.order_number)}', this)"><i class="bi bi-person-check"></i> Actualizeaza partener in ROA</button>`
|
||||
: '';
|
||||
pmEl.innerHTML = `<div class="denom-mismatch" style="border-color:var(--error)">
|
||||
<span class="denom-mismatch-title" style="color:var(--error-text)"><i class="bi bi-people"></i> Partener schimbat in GoMag</span><br>
|
||||
<span style="font-size:13px">${mismatchType}</span>
|
||||
${resyncBtn}
|
||||
</div>`;
|
||||
pmEl.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Denomination mismatch alert
|
||||
if (isPJ && pi.anaf_denumire_mismatch && pi.denumire_anaf) {
|
||||
const denomEl = document.getElementById('detailDenomMismatch');
|
||||
@@ -1022,6 +1051,7 @@ function _renderHeaderInfo(order) {
|
||||
if (addr && addr.livrare_roa && !addrMatch(addr.livrare_gomag, addr.livrare_roa)) badges.push({label:'Adr. livr.', cls:'diff-badge-addr', aria:'Adresa livrare diferita'});
|
||||
if (addr && addr.facturare_roa && !addrMatch(addr.facturare_gomag, addr.facturare_roa)) badges.push({label:'Adr. fact.', cls:'diff-badge-addr', aria:'Adresa facturare diferita'});
|
||||
if (order.price_check && order.price_check.mismatches > 0) badges.push({label:'Preturi (' + order.price_check.mismatches + ')', cls:'diff-badge-price', aria:'Preturi diferite: ' + order.price_check.mismatches});
|
||||
if (pi && pi.partner_mismatch) badges.push({label:'Partener', cls:'diff-badge-anaf', aria:'Partener schimbat in GoMag'});
|
||||
let insertAfter = orderNumEl;
|
||||
badges.forEach(b => {
|
||||
const el = document.createElement('span');
|
||||
@@ -1033,3 +1063,24 @@ function _renderHeaderInfo(order) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Partner Resync ────────────────────────────────
|
||||
|
||||
async function resyncPartner(orderNumber, btnEl) {
|
||||
if (!confirm('Actualizeaza partenerul acestei comenzi in ROA Oracle?\n\nAtentie: Comanda este facturata. Verificati manual dupa actualizare.')) return;
|
||||
if (btnEl) { btnEl.disabled = true; btnEl.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Se actualizeaza...'; }
|
||||
try {
|
||||
const res = await fetch(`${window.ROOT_PATH || ''}/api/orders/${encodeURIComponent(orderNumber)}/resync-partner`, { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (btnEl) { btnEl.innerHTML = '<i class="bi bi-check-circle"></i> Actualizat'; btnEl.className = 'btn btn-sm btn-success mt-1'; }
|
||||
setTimeout(() => renderOrderDetailModal(orderNumber, {}), 1500);
|
||||
} else {
|
||||
if (btnEl) { btnEl.disabled = false; btnEl.innerHTML = '<i class="bi bi-person-check"></i> Actualizeaza partener in ROA'; }
|
||||
alert('Eroare: ' + (data.message || 'Resync esuat'));
|
||||
}
|
||||
} catch(e) {
|
||||
if (btnEl) { btnEl.disabled = false; btnEl.innerHTML = '<i class="bi bi-person-check"></i> Actualizeaza partener in ROA'; }
|
||||
alert('Eroare de retea: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user