fix(partner-mismatch): fix 3 infinite-loop bugs in mismatch detection cycle
Three root causes caused partner_mismatch=1 to loop indefinitely: 1. No-CUI company orders (is_pj=1, no cod_fiscal): old code flagged as mismatch every cycle. Fixed by requiring new_cf to be non-null for PF→PJ detection. Stale flags from old code cleared via new clear_stale_partner_mismatches_no_cui() for out-of-window orders. 2. same_partner resync path did not update cod_fiscal_gomag in SQLite. On next cycle GoMag returned a CUI but stored_cf was still NULL → re-detected as mismatch. Fixed by also calling update_partner_resync_data (not just update_partner_mismatch_batch) in the same_partner branch. 3. GoMag sends CUI with space: 'RO 17922480'. The _strip_ro() regex ^RO left the space → ' 17922480' != '17922480' → false mismatch. Fixed: changed regex to ^RO\s* and added .strip(). Also adds diagnostic logger.info lines for mismatch detection/resync counts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1440,6 +1440,36 @@ async def update_partner_mismatch_batch(updates: list) -> None:
|
|||||||
await db.close()
|
await db.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def clear_stale_partner_mismatches_no_cui(exclude_numbers: set) -> int:
|
||||||
|
"""Clear partner_mismatch=1 for orders with cod_fiscal_gomag=NULL that are NOT in the
|
||||||
|
current sync batch. These were flagged by old code (before the no-CUI fix) and will
|
||||||
|
never self-correct because they fall outside the active sync window.
|
||||||
|
Returns number of rows cleared.
|
||||||
|
"""
|
||||||
|
db = await get_sqlite()
|
||||||
|
try:
|
||||||
|
if exclude_numbers:
|
||||||
|
placeholders = ",".join("?" * len(exclude_numbers))
|
||||||
|
sql = f"""
|
||||||
|
UPDATE orders SET partner_mismatch = 0, updated_at = datetime('now')
|
||||||
|
WHERE partner_mismatch = 1
|
||||||
|
AND cod_fiscal_gomag IS NULL
|
||||||
|
AND order_number NOT IN ({placeholders})
|
||||||
|
"""
|
||||||
|
await db.execute(sql, list(exclude_numbers))
|
||||||
|
else:
|
||||||
|
await db.execute("""
|
||||||
|
UPDATE orders SET partner_mismatch = 0, updated_at = datetime('now')
|
||||||
|
WHERE partner_mismatch = 1 AND cod_fiscal_gomag IS NULL
|
||||||
|
""")
|
||||||
|
await db.commit()
|
||||||
|
cursor = await db.execute("SELECT changes()")
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
return row[0] if row else 0
|
||||||
|
finally:
|
||||||
|
await db.close()
|
||||||
|
|
||||||
|
|
||||||
async def update_partner_resync_data(order_number: str, data: dict) -> None:
|
async def update_partner_resync_data(order_number: str, data: dict) -> None:
|
||||||
"""Update partner fields + clear partner_mismatch after a successful resync."""
|
"""Update partner fields + clear partner_mismatch after a successful resync."""
|
||||||
db = await get_sqlite()
|
db = await get_sqlite()
|
||||||
|
|||||||
@@ -641,11 +641,12 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
def _strip_ro(cf):
|
def _strip_ro(cf):
|
||||||
if not cf:
|
if not cf:
|
||||||
return ""
|
return ""
|
||||||
return re.sub(r'^RO', '', cf.strip().upper())
|
# Strip optional "RO" prefix + any surrounding whitespace
|
||||||
|
return re.sub(r'^RO\s*', '', cf.strip().upper()).strip()
|
||||||
|
|
||||||
is_mismatch = False
|
is_mismatch = False
|
||||||
if new_data["is_pj"] and not stored_cf:
|
if new_data["is_pj"] and new_cf and not stored_cf:
|
||||||
is_mismatch = True # PF→PJ
|
is_mismatch = True # PF→PJ (doar dacă are CUI — fără CUI nu putem confirma)
|
||||||
elif not new_data["is_pj"] and stored_cf:
|
elif not new_data["is_pj"] and stored_cf:
|
||||||
is_mismatch = True # PJ→PF
|
is_mismatch = True # PJ→PF
|
||||||
elif new_data["is_pj"] and stored_cf and _strip_ro(new_cf) != _strip_ro(stored_cf):
|
elif new_data["is_pj"] and stored_cf and _strip_ro(new_cf) != _strip_ro(stored_cf):
|
||||||
@@ -657,17 +658,28 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
|
|
||||||
await sqlite_service.update_partner_mismatch_batch(mismatch_updates)
|
await sqlite_service.update_partner_mismatch_batch(mismatch_updates)
|
||||||
|
|
||||||
|
# Clear stale mismatches for orders outside the current sync window
|
||||||
|
# that have no CUI stored (flagged by old code before the no-CUI fix)
|
||||||
|
current_batch_numbers = {o.number for o in already_in_roa}
|
||||||
|
cleared = await sqlite_service.clear_stale_partner_mismatches_no_cui(current_batch_numbers)
|
||||||
|
if cleared:
|
||||||
|
logger.info(f"Partner mismatch: cleared {cleared} stale no-CUI flags from previous sync window")
|
||||||
|
|
||||||
# Auto-resync uninvoiced orders with partner mismatch (max 5/cycle)
|
# Auto-resync uninvoiced orders with partner mismatch (max 5/cycle)
|
||||||
MAX_PARTNER_RESYNC_PER_CYCLE = 5
|
MAX_PARTNER_RESYNC_PER_CYCLE = 5
|
||||||
|
total_mismatched = sum(1 for v in mismatch_map.values() if v == 1)
|
||||||
|
logger.info(f"Partner mismatch detection: {len(already_in_roa)} orders checked, {total_mismatched} mismatches found")
|
||||||
mismatched_uninvoiced = [
|
mismatched_uninvoiced = [
|
||||||
o for o in already_in_roa
|
o for o in already_in_roa
|
||||||
if mismatch_map.get(o.number) == 1
|
if mismatch_map.get(o.number) == 1
|
||||||
and not stored_partner_data.get(o.number, {}).get("factura_numar")
|
and not stored_partner_data.get(o.number, {}).get("factura_numar")
|
||||||
][:MAX_PARTNER_RESYNC_PER_CYCLE]
|
][:MAX_PARTNER_RESYNC_PER_CYCLE]
|
||||||
|
logger.info(f"Partner auto-resync: {len(mismatched_uninvoiced)} uninvoiced orders queued")
|
||||||
|
|
||||||
if mismatched_uninvoiced:
|
if mismatched_uninvoiced:
|
||||||
resync_ok = 0
|
resync_ok = 0
|
||||||
for _order in mismatched_uninvoiced:
|
for _order in mismatched_uninvoiced:
|
||||||
|
logger.info(f"Partner resync attempt: #{_order.number}")
|
||||||
try:
|
try:
|
||||||
await _resync_partner_for_order(
|
await _resync_partner_for_order(
|
||||||
order=_order,
|
order=_order,
|
||||||
@@ -676,6 +688,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
run_id=run_id,
|
run_id=run_id,
|
||||||
)
|
)
|
||||||
resync_ok += 1
|
resync_ok += 1
|
||||||
|
logger.info(f"Partner resync success: #{_order.number}")
|
||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
_log_line(run_id, f"#{_order.number} EROARE resync partener: {_e}")
|
_log_line(run_id, f"#{_order.number} EROARE resync partener: {_e}")
|
||||||
logger.error(f"Partner resync error for {_order.number}: {_e}")
|
logger.error(f"Partner resync error for {_order.number}: {_e}")
|
||||||
@@ -1310,9 +1323,14 @@ async def _resync_partner_for_order(order, stored: dict, app_settings: dict, run
|
|||||||
resync_result = await asyncio.to_thread(_do_resync)
|
resync_result = await asyncio.to_thread(_do_resync)
|
||||||
|
|
||||||
if resync_result.get("same_partner"):
|
if resync_result.get("same_partner"):
|
||||||
await sqlite_service.update_partner_mismatch_batch([
|
# Update cod_fiscal_gomag so next detection doesn't re-flag this order
|
||||||
{"order_number": order_number, "partner_mismatch": 0}
|
await sqlite_service.update_partner_resync_data(order_number, {
|
||||||
])
|
"id_partener": resync_result["new_partner_id"],
|
||||||
|
"cod_fiscal_gomag": cod_fiscal_override or new_partner_data["cod_fiscal"],
|
||||||
|
"cod_fiscal_roa": None,
|
||||||
|
"denumire_roa": stored.get("denumire_roa"),
|
||||||
|
"partner_mismatch": 0,
|
||||||
|
})
|
||||||
_log_line(run_id, f"#{order_number} RESYNC: partener neschimbat, mismatch cleared")
|
_log_line(run_id, f"#{order_number} RESYNC: partener neschimbat, mismatch cleared")
|
||||||
else:
|
else:
|
||||||
new_partner_id = resync_result["new_partner_id"]
|
new_partner_id = resync_result["new_partner_id"]
|
||||||
|
|||||||
Reference in New Issue
Block a user