--- /private/tmp/claude-501/-Users-josema3-OPS/46273fc8-4251-483a-abb0-07885d3163b2/scratchpad/ops_orig/class/providers/SuperPDPProvider.class.php 2026-09-03 18:38:10 +++ /private/tmp/claude-501/-Users-josema3-OPS/46273fc8-4251-483a-abb0-07885d3163b2/scratchpad/einv_dev/class/providers/SuperPDPProvider.class.php 2026-09-05 03:03:46 @@ -1881,6 +1881,10 @@ $alreadyExist = 0; $syncedFlows = 0; $postponedFlows = 0; // Flows left unread on purpose, retried on the next run (see 'postponeflow') + $pendingQueued = 0; // Flows recorded in the manual-action queue instead of aborting the whole run + $providershort = preg_replace('/ViaPartner$/', '', (string) $this->providerName); + dol_include_once('/einvoicing/class/einvoicingsyncpending.class.php'); + $syncPending = new EInvoicingSyncPending($db); $call_id = null; $i = 0; $flow = null; @@ -2055,6 +2059,31 @@ $actions[$rescode]['businessmessage'] .= $form->textwithpicto('', "ERROR_SYNCFLOW - Failed to synchronize flow " . $flow['flowId'] . ": " . $res['message'], 1, 'help', '', 0, 2, 'help'); } } + // Manual-action business errors used to abort the whole run, and every flow queued behind + // it with it. Record the flow in the pending queue and carry on instead: one incoming + // flow that needs a manual action (a missing product, a missing thirdparty) must not + // freeze the status refresh of every other flow. The queued flow is retried on demand, + // and it is not lost when it drifts out of the rolling synchronization window. + if (in_array($rescode, array('THIRDPARTY_NOT_FOUND', 'PRODUCT_NOT_FOUND', 'SUPPLIER_INVOICE_FOUND_WITH_BAD_AMOUNT'))) { + // Normalize the manual actions the protocol computed (create / associate an existing + // product / set default...) into a compact list the pending list renders as icons. + $manualactions = array(); + if (!empty($res['allactiondata']) && is_array($res['allactiondata'])) { + foreach ($res['allactiondata'] as $akey => $adata) { + if (!empty($adata['url'])) { + $manualactions[] = array('key' => $akey, 'url' => $adata['url'], 'label' => ($adata['label'] ?? '')); + } + } + } elseif (!empty($res['actionurl'])) { + $manualactions[] = array('key' => ($rescode == 'THIRDPARTY_NOT_FOUND' ? 'createthirdparty' : 'create'), 'url' => $res['actionurl'], 'label' => ''); + } + $syncPending->queueFromFlow($flow, $providershort, $rescode, ($res['message'] ?? ''), $manualactions, $user, ($res['action'] ?? ''), ($res['actiondata'] ?? array())); + dol_syslog(__METHOD__ . " Flow " . $flow['flowId'] . " queued for manual action (" . $rescode . "), synchronization continues.", LOG_WARNING, 0, "_einvoicing"); + $results_messages[] = "Flow " . $flow['flowId'] . " queued for manual action (" . $rescode . "): " . $res['message'] . ""; + $pendingQueued++; + continue; + } + dol_syslog(__METHOD__ . " Failed to synchronize flow " . $flow['flowId'] . ": " . $res['message'], LOG_DEBUG, 0, "_einvoicing"); $results_messages[] = "ERROR_SYNCFLOW - Failed to synchronize flow " . $flow['flowId'] . ": " . $res['message']; @@ -2073,6 +2102,11 @@ $syncedFlows++; //$lastsuccessfullSyncronizedFlow = $flow['flowId']; } + + // A flow that finally synchronized (or now already exists) leaves the pending queue. + if ($res['res'] >= 0) { + $syncPending->resolveByFlowId($flow['flowId'], $providershort, $user); + } } catch (Exception $e) { $results_messages[] = "Exception occurred while synchronizing flow " . $flow['flowId'] . ": " . $e->getMessage(); $error++; @@ -2152,6 +2186,10 @@ // Counted apart from the skipped ones: those flows were not stored, they come back next run $messages[] = $langs->trans("TotalPostponedSync") . ": " . $postponedFlows . ""; } + if ($pendingQueued > 0) { + // Flows put in the manual-action queue during this run (missing product/thirdparty, ...) + $messages[] = $langs->trans("TotalQueuedForManualAction") . ": " . $pendingQueued . ""; + } // Processing result that will be saved in DB $processingResult = ''; @@ -2183,6 +2221,7 @@ 'totalFlows' => $totalFlows, 'alreadyExist' => $alreadyExist, 'syncedFlows' => $syncedFlows, + 'pendingQueued' => $pendingQueued, 'batchlimit' => $batchlimit, 'actions' => $actions, 'details' => $results_messages