--- /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