<?php
/* Copyright (C) 2025		Mohamed Daoud			<mdaoud@dolicloud.com>
 * Copyright (C) 2025		Laurent Destailleur		<eldy@users.sourceforge.net>
 * Copyright (C) 2026		Charlene Benke			<charlene@patas-monkey.com>
 * Copyright (C) 2026       Frédéric France         <frederic.france@free.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

/**
 * \file    einvoicing/class/actions_einvoicing.class.php
 * \ingroup einvoicing
 * \brief   Hook of module
 */

if ((float) DOL_VERSION < 19) {
	dol_include_once('/einvoicing/compat/commonhookactions.class.php');
} else {
	require_once DOL_DOCUMENT_ROOT . '/core/class/commonhookactions.class.php';
}
require_once __DIR__ . "/einvoicing.class.php";
dol_include_once('/einvoicing/class/providers/PDPProviderManager.class.php');


/**
 * Class for hooks of module
 */
class ActionsEInvoicing extends CommonHookActions  // @phan-suppress-current-line PhanRedefinedExtendedClass
{
	/**
	 * systemMessage
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function messageOfTheDay($parameters, $object, &$action, $hookmanager)
	{
		return 0;
	}

	/**
	 * Hook called after a PDF is created
	 *
	 * @param 	array   		$parameters 	Hook parameters
	 * @param 	CommonObject 	$object 		The object related to the PDF (invoice, order, etc.)
	 * @param 	string  		$action     	Current action
	 * @param 	HookManager 	$hookmanager 	Hook manager instance
	 * @return 	int    			0 or 1
	 */
	public function afterPDFCreation($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs;

		dol_syslog(__METHOD__ . " Hook afterPDFCreation called for object " . get_class($object));

		$outputlangs = $langs;

		// Invoice pdf path
		$pdfPath = $parameters['file'];

		$einvoicing = new EInvoicing($db);
		$checkConfig = $einvoicing->checkModulePrerequisites();
		if ($checkConfig < 0) {
			dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
			return 0;
		}

		$invoiceObject = $parameters['object'];

		// Check if it's an invoice
		if ($invoiceObject instanceof Facture) {
			/** @var Facture $invoiceObject */

			$needEinvoice = $einvoicing->needEInvoiceManagement($invoiceObject);
			if ($needEinvoice) {
				// Get current status of e-invoice
				$currentStatusDetails = $einvoicing->fetchLastknownInvoiceStatus($invoiceObject->id, $invoiceObject->ref);

				if (!isset($currentStatusDetails['code']) ||
					($currentStatusDetails['code'] != $einvoicing::STATUS_IGNORE && $currentStatusDetails['code'] != $einvoicing::STATUS_IGNORE_2)) {
					if ($invoiceObject->status != $invoiceObject::STATUS_DRAFT	// Never generate/transmit an e-invoice for a DRAFT (note: at validation the invoice has already status VALIDATED when Dolibarr regenerates the final PDF, so the legitimate flow is preserved).
						&& !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')
						&& getDolGlobalString('EINVOICING_EINVOICE_IN_REAL_TIME')) {
						// Call function to create Factur-X document
						require_once __DIR__ . '/protocols/ProtocolManager.class.php';

						$usedProtocols = getDolGlobalString('EINVOICING_PROTOCOL');
						$ProtocolManager = new ProtocolManager($db);
						$protocol = $ProtocolManager->getProtocol($usedProtocols);

						$messagecss = '';
						$message = '';
						// Check configuration
						$result = $einvoicing->checkRequiredinformations($invoiceObject);
						if ($result['res'] < 0) {			// Error case
							$message = $langs->trans("InvoiceNotgeneratedDueToConfigurationIssues") . ': <br>' . $result['message'];

							dol_syslog(__METHOD__ . " " . $message);

							if (getDolGlobalString('EINVOICING_EINVOICE_CANCEL_IF_EINVOICE_FAILS')) {
								// Add more conditions like thirdparty nature to avoid blocking invoice creation for non FR companies
								// or for thirdparties that are not subject to E-invoicing obligation
								$messagecss = 'errors';
								setEventMessages($message, array(), $messagecss);
								return -1;
							} else {
								$messagecss = 'warnings';
								setEventMessages($message, array(), $messagecss);
								$this->warnings[] = $message;
								return 0;
							}
						} elseif ($result['res'] == 0) {	// Warning case
							$message = $langs->trans("InvoiceGeneratedWithWarnings") . ': <br>' . $result['message'];
							$this->warnings[] = $message;

							dol_syslog(__METHOD__ . " " . $message);
							$messagecss = 'warnings';
							//setEventMessages($message, array(), $messagecss);
						}

						// Recipient directory reachability (opt-in): a recipient that is not routable does not make
						// the e-invoice document invalid, only undeliverable, so keep generating it and only warn.
						// The actual transmission is what gets blocked, by the send_to_pdp gate below.
						$routecheck = $einvoicing->checkRecipientRoutableForSend($invoiceObject);
						if (!$routecheck['ok']) {
							$warnmsg = $langs->trans("EInvoiceGeneratedButRecipientNotRoutable") . ': <br>' . $routecheck['message'];
							dol_syslog(__METHOD__ . " " . strip_tags($warnmsg), LOG_WARNING);
							setEventMessages($warnmsg, array(), 'warnings');
							$this->warnings[] = $warnmsg;
						}

						$result = $protocol->generateInvoice($invoiceObject, $outputlangs, $pdfPath);		// Generate E-invoice (embed into the real generated file)

						if ($result >= 0) {
							setEventMessages($message, array(), $messagecss);
						}

						if ($result && (!is_numeric($result) || $result > 0)) {
							// No error
							setEventMessages($langs->trans("EInvoiceGenerated"), array(), 'mesgs');

							// Forward non-blocking size warning from the protocol if any
							if (!empty($protocol->warnings)) {
								setEventMessages($langs->trans("InvoiceGeneratedWithWarnings"), $protocol->warnings, 'warnings');
							}

							// If the precheck is set to auto, we call the precheck function.
							$precheckresult = 0; // 0 = skipped , 1 = success, -1 = failed
							if (getDolGlobalString('EINVOICING_PDP') && getDolGlobalString('EINVOICING_AP_PRECHECK') === 'auto') {
								$PDPManager = new PDPProviderManager($db);
								$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));
								$precheckAvailable = $provider->hasValidator();
								if (!empty($currentStatusDetails['file']) && $currentStatusDetails['file'] == 1 && $precheckAvailable) {
									$einvoiceFilePath = $einvoicing->getEInvoiceFilePath($invoiceObject->ref);
									$result = $provider->validateEInvoiceFile($invoiceObject->id, $einvoiceFilePath);
									if ($result['res'] > 0) {
										$precheckresult = 1;
										setEventMessages($langs->trans("InvoicePrecheckSuccessful"), array(), 'mesgs');
									} else {
										$precheckresult = -1;
										setEventMessages($langs->trans("InvoicePrecheckFailed"), array(), 'errors');
									}
								}
							}

							// Optionally transmit to the Access Point right after generation (opt-in + idempotent) and if not yet generated.
							// Without this, validation only generates the Factur-X; the invoice is never sent to the
							// PA (transmission was a manual "send_to_pdp" click only). The 'transmitted' guard prevents
							// re-sending (and creating duplicate flows) when the PDF is regenerated later.
							if (getDolGlobalString('EINVOICING_AUTO_SEND_ON_GENERATION') && empty($currentStatusDetails['transmitted']) && $precheckresult >= 0) {
								dol_syslog("actions_einvoicing: Invoice seems not yet transmitted and EINVOICING_AUTO_SEND_ON_GENERATION is on, so we try to send it");

								require_once __DIR__ . '/providers/PDPProviderManager.class.php';
								$PDPManager = new PDPProviderManager($db);
								$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));
								if (is_object($provider)) {
									$sendres = $provider->sendInvoice($invoiceObject);
									if ($sendres) {
										setEventMessages($langs->trans("InvoiceSuccessfullySentToPDP") . ' - ' . $langs->trans("FlowId") . ': ' . $sendres, null, 'mesgs');
									} else {
										// Don't block validation if auto-send fails: the e-invoice is generated and can still be sent manually.
										$senderrors = $provider->errors ?: array($provider->error);
										$this->warnings = array_merge($this->warnings, (array) $senderrors);
										dol_syslog(__METHOD__ . " auto-send to PA failed: " . implode('; ', (array) $senderrors), LOG_WARNING, 0, "_einvoicing");
									}
								}
							}
						} else {
							if (getDolGlobalString('EINVOICING_EINVOICE_CANCEL_IF_EINVOICE_FAILS')) {
								// If einvoice fails here, it must be always an error
								$this->errors = array_merge($this->errors, $protocol->errors);
								return -1;
							} else {
								if ($result < 0) {
									if ((float) DOL_VERSION < 24.0) {
										$this->errors = array_merge($this->errors, $protocol->errors);
										$this->warnings = array();	// We remove warning array to keep only the error array, because only errors array is managed with version < 24.0 of Dolibarr.
									} else {
										$this->warnings = array_merge($this->errors, $protocol->errors);	// We want to return the error as a warning.
									}
									return -1;
								} else {
									return 0;
								}
							}
						}
					}
				}
			}
		}

		return 0;
	}

	/**
	 * Hook called after an ODT/ODS document is created.
	 *
	 * Invoice templates rendered from an ODT model emit `afterODTCreation` (not `afterPDFCreation`),
	 * so the e-invoice generation must be triggered here too. Same logic as afterPDFCreation:
	 * $parameters['file'] then points to the .odt, and the protocol derives the matching PDF rendition.
	 *
	 * @param 	array   		$parameters 	Hook parameters
	 * @param 	CommonObject 	$object 		The object related to the document (invoice, order, etc.)
	 * @param 	string  		$action     	Current action
	 * @param 	HookManager 	$hookmanager 	Hook manager instance
	 * @return 	int    			0 or 1
	 */
	public function afterODTCreation($parameters, $object, &$action, $hookmanager)
	{
		return $this->afterPDFCreation($parameters, $object, $action, $hookmanager);
	}


	/**
	 * Overload the addMoreActionsButtons function : replacing the parent's function with the one below
	 *
	 * @param	array<string,mixed>	$parameters     Hook metadata (context, etc...)
	 * @param	CommonObject		$object         The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
	 * @param	?string				$action			Current action (if set). Generally create or edit or null
	 * @param	HookManager			$hookmanager	Hook manager propagated to allow calling another hook
	 * @return	int									Return integer < 0 on error, 0 on success, 1 to replace standard code
	 */
	public function addMoreActionsButtons($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs, $user;

		$langs->load("einvoicing@einvoicing");
		$einvoicing = new EInvoicing($db);
		$checkConfig = $einvoicing->checkModulePrerequisites();
		if ($checkConfig < 0) {
			dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
			return 0;
		}

		$forcedisabling = '';
		// Add buttons in invoice card
		if (in_array($object->element, ['facture']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')) {
			// Get current status of e-invoice
			$currentStatusDetails = $einvoicing->fetchLastknownInvoiceStatus($object->id, $object->ref);

			// Already transmitted to the PA (persistent flow_id): regenerate/re-send are locked by default
			// (immutable invoice; correct with a credit note / corrective invoice). Opt out with
			// EINVOICING_ALLOW_RESEND_TRANSMITTED.
			$locked = $einvoicing->isTransmittedLockActive($object->id, $object->ref);

			if (!empty($currentStatusDetails['otherprovider'])) {
				$forcedisabling = $langs->trans("WarningEinvoicingInvoiceStatusDifferentProvider", $currentStatusDetails['otherprovider']);
			}

			$url_button = array();

			if ($object->status == Facture::STATUS_VALIDATED || $object->status == Facture::STATUS_CLOSED) {
				// if E-invoice is not generated, show button to generate e-invoice
				if (
					$currentStatusDetails['code'] == $einvoicing::STATUS_NOT_GENERATED
					|| !array_key_exists($currentStatusDetails['code'], $einvoicing::STATUS_LABEL_KEYS)
				) {
					$url_button[] = array(
						'lang' => 'einvoicing',
						'enabled' => true,
						'perm' => ($forcedisabling ? -1 : ((bool) $user->hasRight("facture", "creer"))),
						'label' => $langs->trans('GenerateEinvoice'),
						//'help' => $langs->trans('GenerateEinvoiceHelp'),
						'url' => '/compta/facture/card.php?id=' . $object->id . '&action=generate_einvoice&token=' . newToken()
					);
				}

				// If the e-invoice is generated but not sent, or if it was sent and a validation error was received,
				// display the button to regenerate the e-invoice.
				// EINVOICING_ALLOW_REGEN_TRANSMITTED forces the regenerate button even on a transmitted-locked
				// invoice (dev only: let's you rebuild the CII/Factur-X to inspect the XML; nothing is re-sent).
				if (getDolGlobalString('EINVOICING_ALLOW_REGEN_TRANSMITTED')) {
					$perm = (bool) $user->hasRight("facture", "creer");
				} elseif (!$locked && in_array($currentStatusDetails['code'], [
					$einvoicing::STATUS_GENERATED,
					$einvoicing::STATUS_ERROR,
					$einvoicing::STATUS_UNKNOWN,
					$einvoicing::STATUS_AWAITING_VALIDATION,		// We may retry to Regenerate/resend. We should get an error if we do, but it is interesting to test the retry.
					$einvoicing::STATUS_AWAITING_ACK				// We may retry to Regenerate/resend. We should get an error if we do, but it is interesting to test the retry.
				])) {
					$perm = (bool) $user->hasRight("facture", "creer");
				} else {
					$perm = false;
				}
				$url_button[] = array(
					'lang' => 'einvoicing',
					'enabled' => true,
					'perm' => ($forcedisabling ? -1 : $perm),
					'label' => $langs->trans('RegenerateEinvoice'),
					'text' => $forcedisabling,
					//'help' => $langs->trans('RegenerateEinvoiceHelp'),
					'url' => '/compta/facture/card.php?id=' . $object->id . '&action=generate_einvoice&token=' . newToken()
				);

				// If the e-invoice is generated, display the button to precheck the e-invoice with the Access Point validation service if available.
				if (getDolGlobalString('EINVOICING_PDP') && getDolGlobalString('EINVOICING_AP_PRECHECK') === 'manuel') {
					$PDPManager = new PDPProviderManager($db);
					$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));
					$precheckAvailable = $provider->hasValidator();
					if (!empty($currentStatusDetails['file']) && $currentStatusDetails['file'] == 1 && $precheckAvailable) {
						$url_button[] = array(
							'lang' => 'einvoicing',
							'enabled' => true,
							'perm' => (bool) $user->hasRight("facture", "creer"),
							'label' => $langs->trans('PrecheckEinvoice'),
							'url' => '/compta/facture/card.php?id=' . $object->id . '&action=precheck_einvoice&token=' . newToken()
						);
					}
				}

				// If the e-invoice is generated but not sent, or if it was sent and a validation error was received,
				// display the button to regenerate the e-invoice
				// Re-send is offered for not-yet-transmitted states, plus AWAITING_* as a deliberate retry
				// affordance. Once REALLY transmitted (persistent flow_id), it is locked by default unless
				// EINVOICING_ALLOW_RESEND_TRANSMITTED is set ($locked already accounts for that opt-out).
				if (!$locked && in_array($currentStatusDetails['code'], [
					$einvoicing::STATUS_GENERATED,
					$einvoicing::STATUS_ERROR,
					$einvoicing::STATUS_UNKNOWN,
					$einvoicing::STATUS_AWAITING_VALIDATION,		// retry affordance (PA will refuse a duplicate)
					$einvoicing::STATUS_AWAITING_ACK				// retry affordance (PA will refuse a duplicate)
				])) {
					$resend = false;
					if (in_array($currentStatusDetails['code'], [$einvoicing::STATUS_AWAITING_VALIDATION, $einvoicing::STATUS_AWAITING_ACK])) {
						$resend = true;
					}
					$url_button[] = array(
						'lang' => 'einvoicing',
						'enabled' => 1,
						'perm' => ($forcedisabling ? -1 : ((bool) $user->hasRight("einvoicing", "write") && ($currentStatusDetails['file'] == 1))),
						'label' => $langs->trans('sendToPDP' . ($resend ? '2' : '')),
						'text' => $forcedisabling,
						//'help' => $langs->trans('SendToPDPHelp'),
						'url' => '/compta/facture/card.php?id=' . $object->id . '&action=send_to_pdp&token=' . newToken()
					);
				}
			}

			if (empty($parameters['context']) || !preg_match('/takepospay/', $parameters['context'])) {
				print '<!-- Current AP: ' . getDolGlobalString('EINVOICING_PDP') . ' -->';
				if (!empty($url_button)) {
					// dolGetButtonAction() only supports an array $url (dropdown mode) since Dolibarr 18;
					// use our own polyfill below that version.
					// Pass the visible label as the 1st arg ($label), not the 2nd ($text). On Dolibarr 18/19
					// the dropdown <a> renders only $label; v22+ falls back to $text when $label is empty,
					// but to keep behavior consistent across versions we always use $label.
					if ((float) DOL_VERSION < 18) {
						print einvoicingDolGetButtonActionDropdown($langs->trans('einvoice'), $url_button);
					} elseif ((float) DOL_VERSION < 22) {
						print dolGetButtonAction($langs->trans('einvoice'), '', 'default', $url_button, '', true);
					} else {
						print dolGetButtonAction('', $langs->trans('einvoice'), 'default', $url_button, '', true);
					}
				}

				// Once transmitted to the PA, the invoice is immutable. The BILL_UNVALIDATE / BILL_MODIFY
				// triggers already refuse the change server-side, but the core "Modify" (re-open) button
				// stays clickable and would just throw an error. There is no clean hook to remove a single
				// core button, so neutralize it client-side (disable + tooltip) for a clear UX. The trigger
				// remains the real enforcement (defense in depth). Honors EINVOICING_ALLOW_RESEND_TRANSMITTED.
				if ($locked && (empty($parameters['context']) || !preg_match('/takepospay/', $parameters['context']))) {
					print "\n<!-- einvoicing: lock core Modify button (transmitted invoice) -->\n";
					// Match by href (action=modif) so it works regardless of the button class / Dolibarr
					// version (top-level butAction or v22+ dropdown item).
					$jsmsg = json_encode($langs->trans('EInvoiceTransmittedModifyDisabled'));
					print '<script>jQuery(function($){var t=' . $jsmsg . ';$("div.tabsAction a[href*=\'action=modif\']").each(function(){$(this).removeClass("butAction").addClass("butActionRefused").removeAttr("href").css("cursor","not-allowed").attr("title",t).on("click",function(e){e.preventDefault();e.stopImmediatePropagation();return false;});});});</script>';
				}
			}
		}


		// Add buttons in supplier invoice card
		if (in_array($object->element, ['invoice_supplier']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI')) {
			// Check if this invoice is present into einvoicing_extlinks table to know if it is an imported invoice from PDP or not
			$sql = "SELECT rowid, provider FROM " . $db->prefix() . "einvoicing_extlinks";
			$sql .= " WHERE element_type = '" . $db->escape($object->element) . "'";
			$sql .= " AND element_id = " . (int) $object->id;
			$sql .= " LIMIT 1";

			$resql = $db->query($sql);
			if ($resql && $db->num_rows($resql) > 0) {
				$db->free($resql);
				// Check if a final status (approved or rejected) has already been sent and validated
				// → in this case, the lifecycle is complete, so we hide the button
				$sqlFinal = "SELECT rowid FROM " . $db->prefix() . "einvoicing_lifecycle_msg";
				$sqlFinal .= " WHERE element_id = " . (int) $object->id;
				$sqlFinal .= " AND element_type = '" . $db->escape($object->element) . "'";
				$sqlFinal .= " AND direction = 'out'";
				$sqlFinal .= " AND lc_status IN (" . (int) EInvoicing::STATUS_APPROVED . ", " . (int) EInvoicing::STATUS_REFUSED . ")";
				$sqlFinal .= " AND lc_validation_status = 'Ok'";
				$sqlFinal .= " LIMIT 1";
				$resqlFinal = $db->query($sqlFinal);
				$hasFinalLifecycle = ($resqlFinal && $db->num_rows($resqlFinal) > 0);
				$db->free($resqlFinal);

				if (!$hasFinalLifecycle) {
					$availableStatuses = $einvoicing->getEinvoiceStatusOptions(1, 1, 1);
					$url_button = array();
					foreach ($availableStatuses as $code => $label) {
						$url_button[] = array(
							'lang' => 'einvoicing',
							'enabled' => true,
							'perm' => ($forcedisabling ? -1 : ((bool) $user->hasRight("fournisseur", "facture", "creer") && empty($forcedisabling))),
							'label' => (string) $label,
							'url' => '/fourn/facture/card.php?id=' . $object->id . '&action=sendStatusMessage&pdpstatuscode=' . $code . '&token=' . newToken()
						);
					}

					if (!empty($url_button)) {
						if ((float) DOL_VERSION < 18) {
							print einvoicingDolGetButtonActionDropdown($langs->trans('einvoice'), $url_button);
						} elseif ((float) DOL_VERSION < 22) {
							print dolGetButtonAction($langs->trans('einvoice'), '', 'default', $url_button, '', true);
						} else {
							print dolGetButtonAction('', $langs->trans('einvoice'), 'default', $url_button, '', true);
						}
					}
				}
			}
		}

		return 0;
	}

	/**
	 * Overload the doActions
	 *
	 * @param	array<string,mixed>	$parameters     Hook metadata (context, etc...)
	 * @param	CommonObject		$object         The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
	 * @param	?string				$action			Current action (if set). Generally create or edit or null
	 * @param	HookManager			$hookmanager	Hook manager propagated to allow calling another hook
	 * @return	int									Return integer < 0 on error, 0 on success, 1 to replace standard code
	 */
	public function doActions($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs, $user;

		if (empty($action)) {
			return 0;
		}

		//dol_syslog(__METHOD__ . " Hook doActions called for object " . get_class($object) . " action=" . $action);

		$einvoicing = new EInvoicing($db);
		$checkConfig = $einvoicing->checkModulePrerequisites();
		if ($checkConfig < 0) {
			dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
			return 0;
		}
		$langs->load("einvoicing@einvoicing");
		$contexts = explode(':', $parameters['context']);

		$outputlangs = $langs;

		$error = 0;
		$currentStatusDetails = null;

		$isFactureContext = isset($object->element) && in_array($object->element, ['facture']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP');
		$isSupplierInvoiceContext = isset($object->element) && in_array($object->element, ['invoice_supplier']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI');
		$isThirdpartyContext = array_intersect(['thirdpartycard', 'thirdpartycomm'], $contexts) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'));

		if (!$isFactureContext && !$isSupplierInvoiceContext && !$isThirdpartyContext) {
			// Nothing relevant to this hook call for the current object/context: skip the transaction entirely.
			return 0;
		}

		$db->begin();

		if ($isFactureContext) {
			'@phan-var-force Facture $object';
			$permissiontoedit = $user->hasRight('facture', 'write');

			if ($action == 'add') {
				// On create, we can do nothing here. We will update the einvoice status into the CREATE trigger.
			} else {
				// Get current status of e-invoice
				$currentStatusDetails = $einvoicing->fetchLastknownInvoiceStatus($object->id, $object->ref);
				// Action to set the E-invoice status manually
				if ($action == 'seteinvoicestatus' && $permissiontoedit) {
					$result = $einvoicing->setEInvoiceStatus($object, GETPOSTINT('seteinvoicestatus'), '');
					if ($result < 0) {
						$error++;
						$this->errors = array_merge($this->errors, $einvoicing->errors);
					}
				}
			}

			// Action to set an invoice-level routing ID override
			if ($action == 'setoverriderouting' && $permissiontoedit && is_array($currentStatusDetails)) {
				$overrideRoutingId = GETPOST('override_routing_id', 'alphanohtml');
				$result = $einvoicing->insertOrUpdateExtLink($object->id, $object->element, '', $currentStatusDetails['code'], $object->ref, $currentStatusDetails['info'], $overrideRoutingId);
				if ($result < 0) {
					$error++;
					$this->errors = array_merge($this->errors, $einvoicing->errors);
				}
			}

			// An invoice already transmitted to the Access Point (a flow_id is assigned, by any provider) is
			// immutable: re-sending it makes the PA refuse a duplicate, and regenerating it would only reset
			// the local status and re-open that trap. Block both by default; correct a transmitted invoice
			// with a credit note / corrective invoice. The operator can opt in (e.g. to test PA retry) via
			// EINVOICING_ALLOW_RESEND_TRANSMITTED. Based on the persistent flow_id, not the resettable status.
			// EINVOICING_ALLOW_REGEN_TRANSMITTED keeps regenerate (generate_einvoice) available on a
			// transmitted-locked invoice for dev/inspection; re-sending (send_to_pdp) stays locked.
			$lockedActions = getDolGlobalString('EINVOICING_ALLOW_REGEN_TRANSMITTED')
				? array('send_to_pdp')
				: array('send_to_pdp', 'generate_einvoice');
			if (in_array($action, $lockedActions) && isset($currentStatusDetails)
				&& $einvoicing->isTransmittedLockActive($object->id, $object->ref)) {
				setEventMessages($langs->trans('EInvoiceAlreadyTransmittedLocked', $currentStatusDetails['flow_id']), null, 'warnings');
				$action = '';
			}

			// Action to send invoice to Access Point
			if (
				$action == 'send_to_pdp' && $permissiontoedit
				&& is_array($currentStatusDetails)
				&& $currentStatusDetails['file'] == 1
				&& in_array($currentStatusDetails['code'], [
					$einvoicing::STATUS_GENERATED,
					$einvoicing::STATUS_ERROR,
					$einvoicing::STATUS_UNKNOWN
				])
			) {
				// Validate thirdparty data before sending to Access Point
				$object->fetch_thirdparty();
				$checkResult = $einvoicing->checkRequiredinformations($object);
				if ($checkResult['res'] < 0) {
					$message = $langs->trans("InvoiceNotSentToPDPDueToThirdpartyIssues") . ': <br>' . $checkResult['message'];
					dol_syslog(__METHOD__ . " " . strip_tags($message));
					setEventMessages($message, array(), 'errors');
					$error++;
				} elseif ($checkResult['res'] == 0) {
					// Non-blocking warning: notify user but proceed with sending
					dol_syslog(__METHOD__ . " " . strip_tags($checkResult['message']));
					setEventMessages($checkResult['message'], array(), 'warnings');
				}

				// Gate on recipient directory reachability (opt-in): do not transmit to a recipient the platform
				// would reject for a routing error (fr:213).
				if (!$error) {
					$routecheck = $einvoicing->checkRecipientRoutableForSend($object);
					if (!$routecheck['ok']) {
						setEventMessages($langs->trans("EInvoiceNotSentRecipientNotRoutable") . ': <br>' . $routecheck['message'], array(), 'errors');
						$error++;
					}
				}

				if (!$error) {
					$PDPManager = new PDPProviderManager($db);
					$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));

					// Send invoice
					$result = $provider->sendInvoice($object);

					if ($result) {
						$messages = array();
						$messages[] = $langs->trans("InvoiceSuccessfullySentToPDP");
						$messages[] = $langs->trans("FlowId") . ": " . $result;
						setEventMessages('', $messages, 'mesgs');
						// Once transmitted, the invoice is locked from re-edit/regenerate/re-send: the
						// BILL_UNVALIDATE / BILL_MODIFY triggers and the guards above key on the persistent
						// flow_id (EInvoicing::isTransmittedLockActive), overridable via EINVOICING_ALLOW_RESEND_TRANSMITTED.
					} else {
						$error++;
						$this->error = $provider->error;
						$this->errors = array_merge($this->errors, $provider->errors);
					}
				}
			}

			// Action to generate the E-invoice alone
			if ($action == 'generate_einvoice' && $permissiontoedit) {
				$object->fetch_thirdparty();
				$invoiceObject = $object;

				// Call function to create E-invoice document
				require_once __DIR__ . '/protocols/ProtocolManager.class.php';

				$usedProtocols = getDolGlobalString('EINVOICING_PROTOCOL');
				$ProtocolManager = new ProtocolManager($db);
				$protocol = $ProtocolManager->getProtocol($usedProtocols);

				// Check configuration
				$result = $einvoicing->checkRequiredinformations($invoiceObject);
				if ($result['res'] < 0) {			// Blocking error, message contains at least one error and may also have warnings
					$message = $langs->trans("InvoiceNotgeneratedDueToConfigurationIssues") . ': <br>' . $result['message'];

					dol_syslog(__METHOD__ . " " . $message);

					setEventMessages($message, array(), 'errors');
					$error++;
				} elseif ($result['res'] == 0) {	// Non blocking error, warning
					$this->warnings[] = $result['message'];

					dol_syslog(__METHOD__ . " " . $result['message']);
				}

				// Recipient directory reachability (opt-in): a recipient that is not routable does not make the
				// e-invoice document invalid, only undeliverable, so keep generating it and only warn. The
				// actual transmission is what gets blocked, by the send_to_pdp gate.
				if (!$error) {
					$routecheck = $einvoicing->checkRecipientRoutableForSend($invoiceObject);
					if (!$routecheck['ok']) {
						setEventMessages($langs->trans("EInvoiceGeneratedButRecipientNotRoutable") . ': <br>' . $routecheck['message'], array(), 'warnings');
						$this->warnings[] = $routecheck['message'];
					}
				}

				// Generate E-invoice by calling the method of the Protocol
				if (!$error) {
					$result = $protocol->generateInvoice($invoiceObject, $outputlangs);
					if ($result && (!is_numeric($result) || $result > 0)) {
						// No error
						dol_syslog(__METHOD__ . " Invoice generated successfully for invoice ID " . $invoiceObject->id);
						// Merge non-blocking size warnings from the protocol
						if (!empty($protocol->warnings)) {
							$this->warnings = array_merge($this->warnings, (array) $protocol->warnings);
						}
						if (!empty($this->warnings)) {
							setEventMessages($langs->trans("InvoiceGeneratedWithWarnings"), $this->warnings, 'warnings');
						} else {
							setEventMessages($langs->trans("EInvoiceGenerated"), array(), 'mesgs');
						}

						// Precheck the e-invoice with the Access Point validation service if available.
						if (getDolGlobalString('EINVOICING_PDP') && getDolGlobalString('EINVOICING_AP_PRECHECK') === 'auto') {
							$PDPManager = new PDPProviderManager($db);
							$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));
							if (is_object($provider) && $provider->hasValidator()) {
								$einvoiceFilePath = $einvoicing->getEInvoiceFilePath($invoiceObject->ref);
								$result = $provider->validateEInvoiceFile($invoiceObject->id, $einvoiceFilePath);
								if ($result['res'] > 0) {
									setEventMessages($langs->trans("InvoicePrecheckSuccessful"), array(), 'mesgs');
								} else {
									setEventMessages($langs->trans("InvoicePrecheckFailed"), array(), 'errors');
									dol_syslog(__METHOD__ . " Invoice precheck failed for invoice ID " . $invoiceObject->id);
								}
							}
						}
					} else {
						// If there is an error, we move warnings into error message
						// Cast to array to avoid TypeError on PHP 8 when property is null
						$this->errors = array_merge($this->errors, (array) $protocol->errors);
						if (!empty($this->warnings)) {
							$this->errors = array_merge($this->errors, (array) $this->warnings);
						}
						$this->warnings = array();
						dol_syslog(__METHOD__ . " " . implode(',', (array) $protocol->errors));
						$error++;
					}
				}
			}

			// Action to precheck the E-invoice with the Access Point validation service (only if not already sent)
			if (
				$action == 'precheck_einvoice' && $permissiontoedit
				&& is_array($currentStatusDetails)
				&& $currentStatusDetails['file'] == 1
			) {
				// Call precheck method of the Access Point provider
				$PDPManager = new PDPProviderManager($db);
				$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));
				$einvoiceFilePath = $einvoicing->getEInvoiceFilePath($object->ref);
				$result = $provider->validateEInvoiceFile($object->id, $einvoiceFilePath);
				if ($result['res'] > 0) {
					setEventMessages($langs->trans("InvoicePrecheckSuccessful"), array(), 'mesgs');
				} else {
					setEventMessages($langs->trans("InvoicePrecheckFailed"), array(), 'errors');
				}
			}
		}


		if ($isSupplierInvoiceContext) {
			$permissiontoedit = $user->hasRight('fournisseur', 'facture', 'creer');

			if ($action == 'confirm_sendStatusMessage' && $permissiontoedit) {
				$PDPManager = new PDPProviderManager($db);
				$provider = $PDPManager->getProvider(getDolGlobalString('EINVOICING_PDP'));
				$pdpstatuscode = GETPOSTINT('pdpstatuscode') ?: 0;
				$statusRaison = GETPOST('statusRaison', 'alpha');

				$result = $provider->sendStatusMessage($object, $pdpstatuscode, $statusRaison); // Send status message

				if ($result['res'] > 0) {
					setEventMessages($result['message'], array(), 'mesgs');
				} else {
					$error++;
					$this->errors = array_merge($this->errors, $provider->errors);
					setEventMessages($result['message'], $provider->errors, 'errors');
				}
			}
		}

		if ($isThirdpartyContext) {
			$permissiontoedit = $user->hasRight('societe', 'creer');

			// $object->id may be empty at hook time if core hasn't fetched the object yet
			$socId = !empty($object->id) ? (int) $object->id : GETPOSTINT('id');

			// Save einvoice ID from creation formonly
			// (action=update excludes intentionally : in edit mode, we are using the routing edit array)
			if ($action == 'add' && !empty($socId) && $permissiontoedit) {
				// Thirdparty routing ID
				$routingId = GETPOST('routing_id', 'alphanohtml');
				if ($routingId !== '') {
					$existing = $einvoicing->fetchDefaultRouting($socId, 'thirdparty');
					if (empty($existing)) {
						$result = $einvoicing->addRouting($socId, $routingId, '', 'thirdparty');
					} else {
						$result = $einvoicing->setDefaultRouting($socId, $routingId, '', '', '', 'thirdparty');
					}
					if ($result < 0) {
						$error++;
						setEventMessages($langs->trans('FailedToSaveRoutingID').' '.$einvoicing->error, null, 'errors');
					}
				}

				// Default product for import
				$routingProductId = GETPOST('routing_product_id', 'aZ09');
				if ($routingProductId !== '' && $routingProductId !== '-1') {
					$existing = $einvoicing->fetchDefaultRouting($socId, 'product');
					if (empty($existing)) {
						$result = $einvoicing->addRouting($socId, $routingProductId, '', 'product');
					} else {
						$result = $einvoicing->setDefaultRouting($socId, $routingProductId, '', '', '', 'product');
					}
					if ($result < 0) {
						$error++;
						setEventMessages($langs->trans('FailedToSaveRoutingID').' '.$einvoicing->error, null, 'errors');
					}
				}
			}

			// Add a new routing entry
			if ($action == 'pdp_addrouting' && !empty($socId) && $permissiontoedit) {
				$newRoutingId = GETPOST('new_routing_id', 'alphanohtml');
				$newRoutingInfo = GETPOST('new_routing_info', 'alphanohtml');
				if (!empty($newRoutingId)) {
					$result = $einvoicing->addRouting($socId, $newRoutingId, $newRoutingInfo, 'thirdparty');
					if ($result < 0) {
						$error++;
						setEventMessages($langs->trans('FailedToSaveRoutingID').' '.$einvoicing->error, null, 'errors');
					}
				}
			}

			// Delete a routing entry
			if ($action == 'pdp_deleterouting' && !empty($socId) && $permissiontoedit) {
				$routingRowid = GETPOSTINT('routing_rowid');
				if ($routingRowid > 0) {
					$result = $einvoicing->deleteRouting($routingRowid, $socId);
					if ($result < 0) {
						$error++;
						setEventMessages($langs->trans('FailedToDeleteRoutingID').' '.$einvoicing->error, null, 'errors');
					}
				}
			}

			// Set a routing entry as default
			if ($action == 'pdp_setdefaultrouting' && !empty($socId) && $permissiontoedit) {
				$routingRowid = GETPOSTINT('routing_rowid');
				if ($routingRowid > 0) {
					$result = $einvoicing->setRoutingAsDefault($routingRowid, $socId);
					if ($result < 0) {
						$error++;
						setEventMessages($langs->trans('FailedToSetDefaultRoutingID').' '.$einvoicing->error, null, 'errors');
					}
				}
			}
		}

		if ($error) {
			$db->rollback();
			return -1;
		} else {
			$db->commit();
			return 0;
		}
	}

	/**
	 * formConfirm
	 *
	 * @param array			$parameters		Array of parameters
	 * @param CommonObject	$object			Object
	 * @param string		$action			Action code
	 * @param Hookmanager	$hookmanager	Hook manager
	 * @return int
	 */
	public function formConfirm($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs, $form;

		if (empty($object->element)) {
			return 0;
		}

		$einvoicing = new EInvoicing($db);
		$checkConfig = $einvoicing->checkModulePrerequisites();
		if ($checkConfig < 0) {
			dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
			return 0;
		}
		$langs->load("einvoicing@einvoicing");

		if (in_array($object->element, ['invoice_supplier']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI')) {
			// Clone confirmation
			if ($action == 'sendStatusMessage') {
				$form = new Form($db);
				$pdpstatuscode = GETPOST('pdpstatuscode', 'alpha');

				$formquestion = array();
				if (in_array($pdpstatuscode, array_values($einvoicing::STATUS_REQUIRING_REASONS))) {
					$formquestion = array(
						'array' => [
							'type' => 'select',
							'name' => 'statusRaison',
							'label' => $langs->trans("SelectStatusReason"),
							'value' => '',
							'values' => $einvoicing->getReasonsByStatus($pdpstatuscode, 1)
						]
					);
				}

				$formconfirm = $form->formconfirm(
					DOL_URL_ROOT . "/fourn/facture/card.php?id={$object->id}&action=confirm_sendStatusMessage&pdpstatuscode={$pdpstatuscode}",
					$langs->trans('SendStatusMessage'),
					$langs->trans('ConfirmSendStatusMessage', (string) $object->ref, (string) $einvoicing->getStatusLabel($pdpstatuscode)),
					'confirm_sendStatusMessage',
					$formquestion,
					'yes',
					1,
					250
				);

				$this->resprints .= $formconfirm;
			}
		}

		return 0;
	}

	/**
	 * Hook called when displaying object card
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function formObjectOptions($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs;

		$einvoicing = new EInvoicing($db);
		$checkConfig = $einvoicing->checkModulePrerequisites();
		if ($checkConfig < 0) {
			dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
			return 0;
		}

		$langs->load("einvoicing@einvoicing");

		if (empty($parameters['tpl_context'])) {	// Do not show the new fields when we are in the public form to register a thirdparty.
			// Add block in invoice card
			if (in_array($object->element, ['facture']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')) {
				'@phan-var-force Facture $object';
				$this->resprints .= $einvoicing->EInvoiceCardBlock($object, $action, $parameters);		// Output fields in card, including js for refreshing state
			}

			// Add block in supplier invoice card
			if (in_array($object->element, ['invoice_supplier']) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI')) {
				'@phan-var-force FactureFournisseur $object';
				$this->resprints .= $einvoicing->supplierInvoiceCardBlock($object, $action, $parameters);		// Output fields in card, including js for refreshing state
			}

			// Add block in product/service card
			if (in_array($object->element, ['product']) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
				'@phan-var-force Product $object';
				$this->resprints .= $einvoicing->productServiceCardBlock($object, $action, $parameters);		// Output fields in card, including js for refreshing state
			}

			// Add block in thirdparty card
			if (in_array($object->element, ['societe']) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
				'@phan-var-force Societe $object';
				$this->resprints .= $einvoicing->thirdpartyCardBlock($object, $action, $parameters);		// Output fields in card
			}
		}

		return 0;
	}


	/**
	 * Complete the $arrayfields with custom fields to be able to use them in list views (like thirdparty or invoice list)
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function completeArrayFields($parameters, $object, &$action, $hookmanager)
	{
		if (in_array('invoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')) {
			// Add fields to invoice list
			$parameters['arrayfields']['einvoicegenerated'] = array(
				'label' => 'EInvoiceFile',
				'checked' => -1,
				'position' => 900,
				'enabled' => 1,
				'perms' => '1'
			);
			$parameters['arrayfields']['pdp_syncstatus'] = array(
				'label' => 'PDPSyncStatus',
				'checked' => 1,
				'position' => 901,
				'enabled' => '1',
				'perms' => '1'
			);
		}

		if (in_array('thirdpartylist', explode(':', $parameters['context'])) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
			// Add fields to invoice list
			$parameters['arrayfields']['routing_id'] = array(
				'label' => 'RoutingIdField',
				'help' => 'SpecificRoutingFieldHelp',
				'checked' => -1,
				'position' => 900,
				'enabled' => 1,
				'perms' => '1'
			);
			$parameters['arrayfields']['routing_product_id'] = array(
				'label' => 'DefaultProductEBilling',
				'checked' => -1,
				'position' => 901,
				'enabled' => '1',
				'perms' => '1'
			);
		}

		return 0;
	}



	/**
	 * Build the sub query returning the last lifecycle status validated by the Access Point for a supplier invoice.
	 * Same selection rule as the one used by EInvoicing::supplierInvoiceCardBlock() so list and card always agree.
	 *
	 * @return string								SQL sub query (without the surrounding parenthesis), correlated on f.rowid
	 */
	protected static function getSupplierLifecycleStatusSubQuery()
	{
		$sql = 'SELECT lc.lc_status FROM ' . MAIN_DB_PREFIX . 'einvoicing_lifecycle_msg as lc';
		$sql .= " WHERE lc.element_type = 'invoice_supplier' AND lc.element_id = f.rowid";
		$sql .= " AND lc.lc_validation_status = 'Ok'";
		$sql .= ' ORDER BY lc.rowid DESC LIMIT 1';

		return $sql;
	}

	/**
	 * Add SELECT fields
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function printFieldListSelect($parameters, $object, &$action, $hookmanager)
	{
		// Invoice list
		if (in_array('invoicelist', explode(':', $parameters['context']))) {
			$this->resprints .= ', ext.syncstatus  AS pdp_syncstatus';
		}

		// Supplier invoice list, Product list, Soc list
		if (in_array('supplierinvoicelist', explode(':', $parameters['context']))) {
			$this->resprints .= ', ext.rowid AS pdplink_id, ext.provider AS pdp_provider';
			// Last known lifecycle status accepted by the Access Point, same source as the one shown on the invoice card
			$this->resprints .= ', (' . self::getSupplierLifecycleStatusSubQuery() . ') AS pdp_lcstatus';
		}

		if (in_array('thirdpartylist', explode(':', $parameters['context']))) {
			$this->resprints .= ', ext.rowid AS pdplink_id, ext.provider AS pdp_provider';
			$this->resprints .= ', rt.routing_id AS routing_id';
		}

		if (in_array('societelist', explode(':', $parameters['context']))) {
			$this->resprints .= ', ext.rowid AS pdplink_id, ext.provider AS pdp_provider';
			$this->resprints .= ', rt.routing_id AS routing_id';
		}

		return 0;
	}

	/**
	 * Add FROM / JOIN
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function printFieldListFrom($parameters, $object, &$action, $hookmanager)
	{
		global $db;

		if (in_array('invoicelist', explode(':', $parameters['context']))) {
			$this->resprints .= " LEFT JOIN " . $db->prefix() . "einvoicing_extlinks as ext ON ext.element_id = f.rowid AND ext.element_type = 'facture'";
		}

		// Supplier invoice list, Product list, Soc list
		$contexts = explode(':', $parameters['context']);

		if (array_intersect($contexts, ['supplierinvoicelist', 'thirdpartylist', 'productservicelist', 'societelist'])) {
			if (in_array('thirdpartylist', $contexts, true)) {
				$this->resprints .= ' LEFT JOIN ' . $db->prefix() . "einvoicing_extlinks as ext ON ext.element_id = s.rowid AND ext.element_type = 'societe'";
				$this->resprints .= ' LEFT JOIN ' . $db->prefix() . "einvoicing_routing rt ON rt.fk_soc = s.rowid";
			}

			if (in_array('supplierinvoicelist', $contexts, true)) {
				$this->resprints .= ' LEFT JOIN ' . $db->prefix() . "einvoicing_extlinks as ext ON ext.element_id = f.rowid AND ext.element_type = 'invoice_supplier'";
			}

			if (in_array('productservicelist', $contexts, true)) {
				$this->resprints .= ' LEFT JOIN ' . $db->prefix() . "einvoicing_extlinks as ext ON ext.element_id = p.rowid AND ext.element_type = 'product'";
			}
		}

		return 0;
	}

	/**
	 * Add WHERE (search filters)
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function printFieldListWhere($parameters, $object, &$action, $hookmanager)
	{
		global $db;

		if (in_array('invoicelist', explode(':', $parameters['context']))) {
			if (GETPOST('search_pdp_syncstatus', 'alpha') !== '' && GETPOST('search_pdp_syncstatus', 'alpha') != -2) {
				$this->resprints .= ' AND ext.syncstatus = ' . ((int) GETPOST('search_pdp_syncstatus'));
			}
		}

		// Supplier invoice list, Product list, Soc list
		$contexts = explode(':', $parameters['context']);
		if (array_intersect($contexts, ['supplierinvoicelist', 'thirdpartylist', 'productservicelist', 'societelist'])) {
			if (GETPOST('search_pdplinked', 'alpha') !== '' && GETPOST('search_pdplinked', 'alpha') == getDolGlobalString('EINVOICING_PDP')) {
				$this->resprints .= " AND ext.provider = '" . $db->escape(getDolGlobalString('EINVOICING_PDP')) . "'";
			}

			if (GETPOST('search_routing_id', 'alpha') !== '' && GETPOST('search_routing_id', 'alpha') != "") {
				$this->resprints .= " AND ext.routing_id = '" . $db->escape(GETPOST('search_routing_id', 'alpha')) . "'";
			}
		}

		if (in_array('supplierinvoicelist', $contexts) && GETPOST('search_pdp_lcstatus', 'alpha') !== '' && GETPOST('search_pdp_lcstatus', 'alpha') != -2) {
			$this->resprints .= ' AND (' . self::getSupplierLifecycleStatusSubQuery() . ') = ' . GETPOSTINT('search_pdp_lcstatus');
		}

		// Supplier invoice lines to bind to accountancy : always exclude invoices abandoned
		// because their refusal was confirmed by the e-invoicing platform (PDP/PA), they must
		// never be transferred to accountancy.
		if (in_array('accountancysupplierlist', $contexts)) {
			require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.facture.class.php';
			dol_include_once('einvoicing/class/helpers/SupplierInvoiceHelper.class.php');

			$this->resprints .= ' AND NOT (f.fk_statut = ' . ((int) FactureFournisseur::STATUS_ABANDONED)
				. " AND f.close_code = '" . $db->escape(SupplierInvoiceHelper::CLOSECODE_PDPREFUSED) . "')";
		}

		return 0;
	}


	/**
	 * Filter options
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function printFieldListOption($parameters, $object, &$action, $hookmanager)
	{
		global $form, $db;

		if (in_array('invoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')) {
			$einvoicing = new EInvoicing($db);
			$checkConfig = $einvoicing->checkModulePrerequisites();
			if ($checkConfig < 0) {
				dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
				return 0;
			}

			// Einvoice generated or not
			if (!empty($parameters['arrayfields']['einvoicegenerated']['checked'])) {
				print '<td class="liste_titre einvoicegenerated">';
				print '</td>';
			}

			// Sync status
			if (empty($parameters['arrayfields']['pdp_syncstatus']) || !empty($parameters['arrayfields']['pdp_syncstatus']['checked'])) {
				print '<td class="liste_titre pdp_syncstatus">';
				$listofoptions = $einvoicing->getEinvoiceStatusOptions(0, 0, 0, 0, 1, 0, 1);

				// Remove option related to E-invoice generation status
				//unset($listofoptions[$einvoicing::STATUS_NOT_GENERATED]);
				//unset($listofoptions[$einvoicing::STATUS_GENERATED]);

				// Remove unknown status because "unknown" means there is no status set so we can't search on it.
				//if (in_array($action, array('add', 'create', 'edit', 'save'))) {
				unset($listofoptions[$einvoicing::STATUS_UNKNOWN]);
				//}

				print $form->selectarray(
					'search_pdp_syncstatus',
					$listofoptions,
					GETPOST('search_pdp_syncstatus', 'alpha'),
					-2,
					0,
					0,
					'',
					0,
					0,
					0,
					'',
					'width100 '
				);
				print '</td>';
			}
		}

		// Supplier invoice list, Product list, Soc list
		if (in_array('supplierinvoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI')) {
			$listofoptions = array(
				getDolGlobalString('EINVOICING_PDP') => getDolGlobalString('EINVOICING_PDP'),
			);
			print '<td class="liste_titre">';
			print $form->selectarray(
				'search_pdplinked',
				$listofoptions,
				GETPOST('search_pdplinked', 'alpha'),
				-2,
				0,
				0,
				'',
				0,
				0,
				0,
				'',
				'width100 '
			);
			print '</td>';

			// E-invoice status of the supplier invoice into the Access Point system
			$einvoicing = new EInvoicing($db);
			print '<td class="liste_titre pdp_lcstatus">';
			print $form->selectarray(
				'search_pdp_lcstatus',
				$einvoicing->getEinvoiceStatusOptions(0, 1),
				GETPOST('search_pdp_lcstatus', 'alpha'),
				-2,
				0,
				0,
				'',
				0,
				0,
				0,
				'',
				'width100 '
			);
			print '</td>';
		}


		if (in_array('thirdpartylist', explode(':', $parameters['context'])) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
			if (!empty($parameters['arrayfields']['einvoicegenerated']['checked'])) {
				print '<td class="liste_titre">';
				print '<input type="text" name="search_routing_id" value="' . dolPrintHTMLForAttribute(GETPOST('search_routing_id', 'alpha')) . '" class="minwidth50 maxwidth100">';
				print '</td>';
			}
		}

		// @phan-suppress-next-line PhanPluginEmptyStatementIf
		if (in_array('productlist', explode(':', $parameters['context'])) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
			// None yet
		}

		return 0;
	}


	/**
	 * Column titles
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function printFieldListTitle($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs;

		if (in_array('invoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')) {
			$einvoicing = new EInvoicing($db);
			$checkConfig = $einvoicing->checkModulePrerequisites();
			if ($checkConfig < 0) {
				dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
				return 0;
			}

			// Einvoice generated or not
			if (!empty($parameters['arrayfields']['einvoicegenerated']['checked'])) {
				print_liste_field_titre($langs->transnoentitiesnoconv('EInvoiceFile'), '', '', '', $parameters['param'] ?? '', '', $parameters['sortfield'] ?? '', $parameters['sotorder'] ?? '', 'center ');
			}

			// syncstatus
			if (empty($parameters['arrayfields']['pdp_syncstatus']) || !empty($parameters['arrayfields']['pdp_syncstatus']['checked'])) {
				print_liste_field_titre($langs->transnoentitiesnoconv('PDPSyncStatus'), '', '', '', $parameters['param'] ?? '', '', $parameters['sortfield'] ?? '', $parameters['sotorder'] ?? '', 'center ');
			}
		}

		// Supplier invoice list, Product list, Soc list
		if (in_array('supplierinvoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI')) {
			print_liste_field_titre($langs->transnoentitiesnoconv('einvoicingSourceTitle'));
			print_liste_field_titre($langs->transnoentitiesnoconv('einvoicingInvoiceStatus'), '', '', '', $parameters['param'] ?? '', '', '', '', 'center ');
		}

		if (in_array('thirdpartylist', explode(':', $parameters['context'])) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
			if (!empty($parameters['arrayfields']['einvoicegenerated']['checked'])) {
				print_liste_field_titre($langs->transnoentitiesnoconv('einvoicingThirdPartyRoutingTitle'));
			}
		}

		// @phan-suppress-next-line PhanPluginEmptyStatementIf
		if (in_array('productlist', explode(':', $parameters['context'])) && (!getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP') || !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI'))) {
			// None yet
		}

		return 0;
	}


	/**
	 * Row values
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function printFieldListValue($parameters, $object, &$action, $hookmanager)
	{
		global $db, $langs;

		if (in_array('invoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_DOLI_TO_AP')) {
			$obj = $parameters['obj'];

			$einvoicing = new EInvoicing($db);
			$checkConfig = $einvoicing->checkModulePrerequisites();
			if ($checkConfig < 0) {
				dol_syslog(__METHOD__ . "EINVOICING Module is not correctly configured.");
				return 0;
			}

			// E-invoice generation status
			if (!empty($parameters['arrayfields']['einvoicegenerated']['checked'])) {
				$tmparray = $einvoicing->fetchLastknownInvoiceStatus($obj->id, $obj->ref);
				$einvoiceGenerated = $tmparray['file'];
				print '<td class="center tdoverflowmax100">';
				if ($einvoiceGenerated) {
					print '<i class="fas fa-check-circle" style="color:green;" title="' . $langs->trans('EInvoiceGenerated') . '"></i>';
				}
				print '</td>';
				if (isset($parameters['i']) && empty($parameters['i'])) {
					if (!array_key_exists('totalarray', $parameters)) {
						$parameters['totalarray'] = array('nbfield' => 0);
					} elseif (!array_key_exists('nbfield', $parameters['totalarray'])) {
						$parameters['totalarray']['nbfield'] = 0;
					}

					$parameters['totalarray']['nbfield']++;
					$parameters['totalarray']['nbfield']++;
				}
			}

			// E-invoice sync status
			if (empty($parameters['arrayfields']['pdp_syncstatus']) || !empty($parameters['arrayfields']['pdp_syncstatus']['checked'])) {
				$currentStatusDetails = $obj->pdp_syncstatus ? $einvoicing->getStatusLabel($obj->pdp_syncstatus) : '-';
				print '<td class="center tdoverflowmax100" title="' . dolPrintHTMLForAttribute($currentStatusDetails) . '">';
				print $currentStatusDetails;
				print '</td>';
				if (isset($parameters['i']) && empty($parameters['i'])) {
					$parameters['totalarray']['nbfield']++;
				}
			}
		}

		// Supplier invoice list, Product list, Soc list
		if (in_array('supplierinvoicelist', explode(':', $parameters['context'])) && !getDolGlobalString('EINVOICING_DISABLE_SYNC_AP_TO_DOLI')) {
			$obj = $parameters['obj'];

			print '<td class="tdoverflowmax100">';
			if ($obj->pdplink_id) {
				print dolPrintHTML($obj->pdp_provider);
			}
			print '</td>';
			if (isset($parameters['i']) && empty($parameters['i'])) {
				$parameters['totalarray']['nbfield']++;
			}

			// E-invoice status of the supplier invoice into the Access Point system
			$einvoicing = new EInvoicing($db);
			$currentStatusDetails = $obj->pdp_lcstatus ? $einvoicing->getStatusLabel((int) $obj->pdp_lcstatus) : '-';
			print '<td class="center tdoverflowmax100" title="' . dolPrintHTMLForAttribute($currentStatusDetails) . '">';
			print $currentStatusDetails;
			print '</td>';
			if (isset($parameters['i']) && empty($parameters['i'])) {
				$parameters['totalarray']['nbfield']++;
			}
		}

		if (in_array('thirdpartylist', explode(':', $parameters['context']), true)) {
			if (!empty($parameters['arrayfields']['einvoicegenerated']['checked'])) {
				$obj = $parameters['obj'];

				print '<td class="tdoverflowmax125">';
				if ($obj->pdplink_id) {
					print dolPrintHTML($obj->routing_id);
				}
				print '</td>';
				if (isset($parameters['i']) && empty($parameters['i'])) {
					$parameters['totalarray']['nbfield']++;
				}
			}
		}

		return 0;
	}


	/**
	 * isEditable
	 *
	 * @param array<string,mixed> 	$parameters		Array of parameters
	 * @param CommonObject			$object			Object invoice
	 * @param string		 		$action			Code action
	 * @param Hookmanager			$hookmanager	Hookmanager
	 * @return int									Result
	 */
	public function isEditable($parameters, $object, &$action, $hookmanager)
	{
		global $langs, $db;

		// Only target customer invoices
		if (!in_array($object->element, ['facture'])) {
			return 0;
		}

		$einvoicing = new EInvoicing($db);
		$currentStatusDetails = $einvoicing->fetchLastknownInvoiceStatus($object->id, $object->ref);

		// Block modification if invoice is already transmitted to PDP
		if ($currentStatusDetails['transmitted'] == 1) {
			$langs->load("einvoicing@einvoicing");

			$this->results = [
				'result' => -100, 	// Custom error code. Must be higher that core reserve code between -1...-50
				'error'  => $langs->trans('InvoiceLinkedToPdpCannotBeModified')
			];

			return 1;
		}

		return 0;
	}

	/**
	 * Called by Societe::mergeCompany() when two thirdparties are merged, so the e-invoicing
	 * routing IDs registered on the absorbed thirdparty (llx_einvoicing_routing.fk_soc) are not
	 * silently orphaned and lost on the surviving one.
	 *
	 * @param array{soc_origin:int,soc_dest:int} 	$parameters		Array of parameters (soc_origin = absorbed thirdparty id, soc_dest = surviving thirdparty id)
	 * @param CommonObject							$object			Destination thirdparty object
	 * @param string								$action			Code action
	 * @param Hookmanager							$hookmanager	Hookmanager
	 * @return int									0 on success/nothing to do, -1 on error (sets $this->error/$this->errors)
	 */
	public function replaceThirdparty($parameters, $object, &$action, $hookmanager)
	{
		global $db;

		$socOrigin = (int) ($parameters['soc_origin'] ?? 0);
		$socDest = (int) ($parameters['soc_dest'] ?? 0);

		if ($socOrigin <= 0 || $socDest <= 0) {
			return 0;
		}

		$sql = "UPDATE " . $db->prefix() . "einvoicing_routing";
		$sql .= " SET fk_soc = " . (int) $socDest;
		$sql .= " WHERE fk_soc = " . (int) $socOrigin;

		$resql = $db->query($sql);
		if (!$resql) {
			dol_syslog(__METHOD__ . " Failed to reassign einvoicing_routing.fk_soc from " . $socOrigin . " to " . $socDest . ": " . $db->lasterror(), LOG_ERR);
			$this->error = $db->lasterror();
			$this->errors[] = $this->error;
			return -1;
		}

		return 0;
	}
}
