<?php
/* Copyright (C) 2026  Eduard Wisch  <data@data-it-solution.de>
 *
 * 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.
 */

/**
 * \file    bankimport/class/banktransaction.class.php
 * \ingroup bankimport
 * \brief   Class for bank transactions from FinTS import
 */

require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';

/**
 * Class BankImportTransaction
 * Represents a bank transaction imported via FinTS
 */
class BankImportTransaction extends CommonObject
{
	/**
	 * Send notification via GlobalNotify (if available)
	 *
	 * @param string $type       'error', 'warning', 'info', 'action'
	 * @param string $title      Title
	 * @param string $message    Message
	 * @param string $actionUrl  URL for action button
	 * @param string $actionLabel Label for action button
	 * @return bool True if sent via GlobalNotify
	 */
	public static function notify($type, $title, $message, $actionUrl = '', $actionLabel = '')
	{
		global $db;

		if (!isModEnabled('globalnotify')) {
			dol_syslog("BankImport [{$type}]: {$title} - {$message}", LOG_WARNING);
			return false;
		}

		$classFile = dol_buildpath('/globalnotify/class/globalnotify.class.php', 0);
		if (!file_exists($classFile)) {
			dol_syslog("BankImport [{$type}]: {$title} - {$message}", LOG_WARNING);
			return false;
		}

		require_once $classFile;

		if (!class_exists('GlobalNotify')) {
			dol_syslog("BankImport [{$type}]: {$title} - {$message}", LOG_WARNING);
			return false;
		}

		try {
			switch ($type) {
				case 'error':
					GlobalNotify::error('bankimport', $title, $message, $actionUrl, $actionLabel);
					break;
				case 'warning':
					GlobalNotify::warning('bankimport', $title, $message, $actionUrl, $actionLabel);
					break;
				case 'action':
					GlobalNotify::actionRequired('bankimport', $title, $message, $actionUrl, $actionLabel ?: 'Aktion erforderlich');
					break;
				default:
					GlobalNotify::info('bankimport', $title, $message, $actionUrl, $actionLabel);
			}
			return true;
		} catch (Exception $e) {
			dol_syslog("GlobalNotify error: ".$e->getMessage(), LOG_ERR);
			return false;
		}
	}

	/**
	 * @var string ID to identify managed object
	 */
	public $element = 'banktransaction';

	/**
	 * @var string Name of table without prefix where object is stored
	 */
	public $table_element = 'bankimport_transaction';

	/**
	 * @var string Primary key name
	 */
	public $pk = 'rowid';

	/**
	 * @var int Entity
	 */
	public $entity;

	/**
	 * @var string Unique reference (hash)
	 */
	public $ref;

	/**
	 * @var string IBAN
	 */
	public $iban;

	/**
	 * @var string BIC
	 */
	public $bic;

	/**
	 * @var int Creation timestamp
	 */
	public $datec;

	/**
	 * @var int Transaction date
	 */
	public $date_trans;

	/**
	 * @var int Value date
	 */
	public $date_value;

	/**
	 * @var string Counterparty name
	 */
	public $name;

	/**
	 * @var string Counterparty IBAN
	 */
	public $counterparty_iban;

	/**
	 * @var string Counterparty BIC
	 */
	public $counterparty_bic;

	/**
	 * @var float Amount
	 */
	public $amount;

	/**
	 * @var string Currency
	 */
	public $currency = 'EUR';

	/**
	 * @var string Short label
	 */
	public $label;

	/**
	 * @var string Full description
	 */
	public $description;

	/**
	 * @var string End-to-end ID
	 */
	public $end_to_end_id;

	/**
	 * @var string Mandate ID
	 */
	public $mandate_id;

	/**
	 * @var int Link to llx_bank
	 */
	public $fk_bank;

	/**
	 * @var int Link to llx_facture
	 */
	public $fk_facture;

	/**
	 * @var int Link to llx_facture_fourn
	 */
	public $fk_facture_fourn;

	/**
	 * @var int Link to llx_paiement
	 */
	public $fk_paiement;

	/**
	 * @var int Link to llx_paiementfourn
	 */
	public $fk_paiementfourn;

	/**
	 * @var int Link to llx_salary
	 */
	public $fk_salary;

	/**
	 * @var int Link to llx_don
	 */
	public $fk_don;

	/**
	 * @var int Link to llx_loan
	 */
	public $fk_loan;

	/**
	 * @var int Link to llx_societe
	 */
	public $fk_societe;

	/**
	 * @var int Link to llx_bankimport_statement
	 */
	public $fk_statement;

	/**
	 * @var int Status (0=new, 1=matched, 2=reconciled, 9=ignored)
	 */
	public $status = 0;

	/**
	 * @var string Import batch key
	 */
	public $import_key;

	/**
	 * @var int User who created
	 */
	public $fk_user_creat;

	/**
	 * @var int User who modified
	 */
	public $fk_user_modif;

	/**
	 * @var int User who matched
	 */
	public $fk_user_match;

	/**
	 * @var int Match date
	 */
	public $date_match;

	/**
	 * @var string Private note
	 */
	public $note_private;

	/**
	 * @var string Public note
	 */
	public $note_public;

	// Status constants
	const STATUS_NEW = 0;
	const STATUS_MATCHED = 1;
	const STATUS_RECONCILED = 2;
	const STATUS_IGNORED = 9;

	/**
	 * Constructor
	 *
	 * @param DoliDB $db Database handler
	 */
	public function __construct($db)
	{
		global $conf;

		$this->db = $db;
		$this->entity = $conf->entity;
	}

	/**
	 * Create transaction in database
	 *
	 * @param User $user User that creates
	 * @param bool $notrigger Disable triggers
	 * @return int <0 if KO, Id of created object if OK
	 */
	public function create($user, $notrigger = false)
	{
		global $conf;

		$error = 0;
		$now = dol_now();

		// Generate reference hash if not set
		if (empty($this->ref)) {
			$this->ref = $this->generateRef();
		}

		$this->db->begin();

		$sql = "INSERT INTO ".MAIN_DB_PREFIX."bankimport_transaction (";
		$sql .= "ref, entity, iban, bic, datec, date_trans, date_value,";
		$sql .= "name, counterparty_iban, counterparty_bic,";
		$sql .= "amount, currency, label, description, end_to_end_id, mandate_id,";
		$sql .= "status, import_key, fk_user_creat";
		$sql .= ") VALUES (";
		$sql .= "'".$this->db->escape($this->ref)."',";
		$sql .= ((int) $this->entity).",";
		$sql .= ($this->iban ? "'".$this->db->escape($this->iban)."'" : "NULL").",";
		$sql .= ($this->bic ? "'".$this->db->escape($this->bic)."'" : "NULL").",";
		$sql .= "'".$this->db->idate($now)."',";
		$sql .= "'".$this->db->idate($this->date_trans)."',";
		$sql .= ($this->date_value ? "'".$this->db->idate($this->date_value)."'" : "NULL").",";
		$sql .= ($this->name ? "'".$this->db->escape($this->name)."'" : "NULL").",";
		$sql .= ($this->counterparty_iban ? "'".$this->db->escape($this->counterparty_iban)."'" : "NULL").",";
		$sql .= ($this->counterparty_bic ? "'".$this->db->escape($this->counterparty_bic)."'" : "NULL").",";
		$sql .= ((float) $this->amount).",";
		$sql .= "'".$this->db->escape($this->currency)."',";
		$sql .= ($this->label ? "'".$this->db->escape($this->label)."'" : "NULL").",";
		$sql .= ($this->description ? "'".$this->db->escape($this->description)."'" : "NULL").",";
		$sql .= ($this->end_to_end_id ? "'".$this->db->escape($this->end_to_end_id)."'" : "NULL").",";
		$sql .= ($this->mandate_id ? "'".$this->db->escape($this->mandate_id)."'" : "NULL").",";
		$sql .= ((int) $this->status).",";
		$sql .= ($this->import_key ? "'".$this->db->escape($this->import_key)."'" : "NULL").",";
		$sql .= ((int) $user->id);
		$sql .= ")";

		dol_syslog(get_class($this)."::create", LOG_DEBUG);
		$resql = $this->db->query($sql);

		if ($resql) {
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."bankimport_transaction");
			$this->datec = $now;
			$this->fk_user_creat = $user->id;

			$this->db->commit();
			return $this->id;
		} else {
			$this->error = $this->db->lasterror();
			$this->db->rollback();
			return -1;
		}
	}

	/**
	 * Load transaction from database
	 *
	 * @param int $id Id of transaction to load
	 * @param string $ref Reference
	 * @return int <0 if KO, 0 if not found, >0 if OK
	 */
	public function fetch($id, $ref = '')
	{
		$sql = "SELECT t.*";
		$sql .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction as t";
		if ($id > 0) {
			$sql .= " WHERE t.rowid = ".((int) $id);
		} else {
			$sql .= " WHERE t.ref = '".$this->db->escape($ref)."' AND t.entity = ".((int) $this->entity);
		}

		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
		$resql = $this->db->query($sql);

		if ($resql) {
			if ($this->db->num_rows($resql)) {
				$obj = $this->db->fetch_object($resql);

				$this->id = $obj->rowid;
				$this->ref = $obj->ref;
				$this->entity = $obj->entity;
				$this->iban = $obj->iban;
				$this->bic = $obj->bic;
				$this->datec = $this->db->jdate($obj->datec);
				$this->date_trans = $this->db->jdate($obj->date_trans);
				$this->date_value = $this->db->jdate($obj->date_value);
				$this->name = $obj->name;
				$this->counterparty_iban = $obj->counterparty_iban;
				$this->counterparty_bic = $obj->counterparty_bic;
				$this->amount = $obj->amount;
				$this->currency = $obj->currency;
				$this->label = $obj->label;
				$this->description = $obj->description;
				$this->end_to_end_id = $obj->end_to_end_id;
				$this->mandate_id = $obj->mandate_id;
				$this->fk_bank = $obj->fk_bank;
				$this->fk_facture = $obj->fk_facture;
				$this->fk_facture_fourn = $obj->fk_facture_fourn;
				$this->fk_paiement = $obj->fk_paiement;
				$this->fk_paiementfourn = $obj->fk_paiementfourn;
				$this->fk_salary = $obj->fk_salary;
				$this->fk_don = $obj->fk_don;
				$this->fk_loan = $obj->fk_loan;
				$this->fk_societe = $obj->fk_societe;
				$this->fk_statement = $obj->fk_statement;
				$this->status = $obj->status;
				$this->import_key = $obj->import_key;
				$this->fk_user_creat = $obj->fk_user_creat;
				$this->fk_user_modif = $obj->fk_user_modif;
				$this->fk_user_match = $obj->fk_user_match;
				$this->date_match = $this->db->jdate($obj->date_match);
				$this->note_private = $obj->note_private;
				$this->note_public = $obj->note_public;

				$this->db->free($resql);
				return 1;
			} else {
				$this->db->free($resql);
				return 0;
			}
		} else {
			$this->error = $this->db->lasterror();
			return -1;
		}
	}

	/**
	 * Update transaction in database
	 *
	 * @param User $user User that modifies
	 * @param bool $notrigger Disable triggers
	 * @return int <0 if KO, >0 if OK
	 */
	public function update($user, $notrigger = false)
	{
		$error = 0;

		$this->db->begin();

		$sql = "UPDATE ".MAIN_DB_PREFIX."bankimport_transaction SET";
		$sql .= " iban = ".($this->iban ? "'".$this->db->escape($this->iban)."'" : "NULL").",";
		$sql .= " bic = ".($this->bic ? "'".$this->db->escape($this->bic)."'" : "NULL").",";
		$sql .= " date_trans = '".$this->db->idate($this->date_trans)."',";
		$sql .= " date_value = ".($this->date_value ? "'".$this->db->idate($this->date_value)."'" : "NULL").",";
		$sql .= " name = ".($this->name ? "'".$this->db->escape($this->name)."'" : "NULL").",";
		$sql .= " counterparty_iban = ".($this->counterparty_iban ? "'".$this->db->escape($this->counterparty_iban)."'" : "NULL").",";
		$sql .= " counterparty_bic = ".($this->counterparty_bic ? "'".$this->db->escape($this->counterparty_bic)."'" : "NULL").",";
		$sql .= " amount = ".((float) $this->amount).",";
		$sql .= " currency = '".$this->db->escape($this->currency)."',";
		$sql .= " label = ".($this->label ? "'".$this->db->escape($this->label)."'" : "NULL").",";
		$sql .= " description = ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL").",";
		$sql .= " end_to_end_id = ".($this->end_to_end_id ? "'".$this->db->escape($this->end_to_end_id)."'" : "NULL").",";
		$sql .= " mandate_id = ".($this->mandate_id ? "'".$this->db->escape($this->mandate_id)."'" : "NULL").",";
		$sql .= " fk_bank = ".($this->fk_bank > 0 ? ((int) $this->fk_bank) : "NULL").",";
		$sql .= " fk_facture = ".($this->fk_facture > 0 ? ((int) $this->fk_facture) : "NULL").",";
		$sql .= " fk_facture_fourn = ".($this->fk_facture_fourn > 0 ? ((int) $this->fk_facture_fourn) : "NULL").",";
		$sql .= " fk_paiement = ".($this->fk_paiement > 0 ? ((int) $this->fk_paiement) : "NULL").",";
		$sql .= " fk_paiementfourn = ".($this->fk_paiementfourn > 0 ? ((int) $this->fk_paiementfourn) : "NULL").",";
		$sql .= " fk_salary = ".($this->fk_salary > 0 ? ((int) $this->fk_salary) : "NULL").",";
		$sql .= " fk_don = ".($this->fk_don > 0 ? ((int) $this->fk_don) : "NULL").",";
		$sql .= " fk_loan = ".($this->fk_loan > 0 ? ((int) $this->fk_loan) : "NULL").",";
		$sql .= " fk_societe = ".($this->fk_societe > 0 ? ((int) $this->fk_societe) : "NULL").",";
		$sql .= " fk_statement = ".($this->fk_statement > 0 ? ((int) $this->fk_statement) : "NULL").",";
		$sql .= " status = ".((int) $this->status).",";
		$sql .= " fk_user_modif = ".((int) $user->id).",";
		$sql .= " note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
		$sql .= " note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
		$sql .= " WHERE rowid = ".((int) $this->id);

		dol_syslog(get_class($this)."::update", LOG_DEBUG);
		$resql = $this->db->query($sql);

		if ($resql) {
			$this->fk_user_modif = $user->id;
			$this->db->commit();
			return 1;
		} else {
			$this->error = $this->db->lasterror();
			$this->db->rollback();
			return -1;
		}
	}

	/**
	 * Delete transaction from database
	 *
	 * @param User $user User that deletes
	 * @param bool $notrigger Disable triggers
	 * @return int <0 if KO, >0 if OK
	 */
	public function delete($user, $notrigger = false)
	{
		$this->db->begin();

		$sql = "DELETE FROM ".MAIN_DB_PREFIX."bankimport_transaction";
		$sql .= " WHERE rowid = ".((int) $this->id);

		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
		$resql = $this->db->query($sql);

		if ($resql) {
			$this->db->commit();
			return 1;
		} else {
			$this->error = $this->db->lasterror();
			$this->db->rollback();
			return -1;
		}
	}

	/**
	 * Generate unique reference hash
	 *
	 * @return string Reference hash
	 */
	public function generateRef()
	{
		// Create hash from: date + amount + name + description
		$data = $this->date_trans . '|' . $this->amount . '|' . $this->name . '|' . $this->description;
		return substr(md5($data), 0, 32);
	}

	/**
	 * Check if transaction already exists (duplicate detection)
	 *
	 * @return int 0 if not exists, rowid if exists
	 */
	public function exists()
	{
		$ref = $this->generateRef();

		$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."bankimport_transaction";
		$sql .= " WHERE ref = '".$this->db->escape($ref)."'";
		$sql .= " AND entity = ".((int) $this->entity);

		$resql = $this->db->query($sql);
		if ($resql) {
			if ($this->db->num_rows($resql) > 0) {
				$obj = $this->db->fetch_object($resql);
				return $obj->rowid;
			}
		}
		return 0;
	}

	/**
	 * Import transactions from FinTS fetch result
	 *
	 * @param array $transactions Array of transaction data from FinTS
	 * @param string $iban IBAN of the account
	 * @param User $user User doing the import
	 * @return array Array with 'imported' and 'skipped' counts
	 */
	public function importFromFinTS($transactions, $iban, $user)
	{
		$imported = 0;
		$skipped = 0;
		$errors = array();

		$importKey = date('YmdHis') . '_' . $user->id;

		foreach ($transactions as $tx) {
			$trans = new BankImportTransaction($this->db);
			$trans->iban = $iban;
			$trans->date_trans = $tx['date'];
			$trans->date_value = $tx['bookingDate'] ?? $tx['date'];
			$trans->name = $tx['name'] ?? '';
			$trans->counterparty_iban = $tx['iban'] ?? '';
			$trans->counterparty_bic = $tx['bic'] ?? '';
			$trans->amount = $tx['amount'];
			$trans->currency = $tx['currency'] ?? 'EUR';
			$trans->label = $tx['bookingText'] ?? '';
			$trans->description = $tx['reference'] ?? '';
			$trans->end_to_end_id = $tx['endToEndId'] ?? '';
			$trans->import_key = $importKey;
			$trans->status = self::STATUS_NEW;

			// Check for duplicate
			if ($trans->exists()) {
				$skipped++;
				continue;
			}

			// Create transaction
			$result = $trans->create($user);
			if ($result > 0) {
				$imported++;
			} else {
				$errors[] = $trans->error;
			}
		}

		// Send notification if new transactions were imported
		if ($imported > 0) {
			// Count unmatched transactions
			$unmatchedCount = $this->countUnmatchedTransactions();

			if ($unmatchedCount > 0) {
				self::notify(
					'action',
					$imported.' neue Bankbuchungen',
					"{$unmatchedCount} Buchungen warten auf Zuordnung zu Rechnungen",
					dol_buildpath('/bankimport/list.php?status=0', 1),
					'Zahlungsabgleich'
				);
			} else {
				// All matched - just info
				self::notify(
					'info',
					$imported.' Bankbuchungen importiert',
					'Alle Buchungen wurden automatisch zugeordnet',
					dol_buildpath('/bankimport/list.php', 1),
					'Anzeigen'
				);
			}
		}

		return array(
			'imported' => $imported,
			'skipped' => $skipped,
			'errors' => $errors,
			'import_key' => $importKey
		);
	}

	/**
	 * Count unmatched/new transactions waiting for reconciliation
	 *
	 * @return int Number of unmatched transactions
	 */
	public function countUnmatchedTransactions()
	{
		$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."bankimport_transaction";
		$sql .= " WHERE status = ".self::STATUS_NEW;
		$sql .= " AND entity = ".((int) $this->entity);

		$resql = $this->db->query($sql);
		if ($resql) {
			$obj = $this->db->fetch_object($resql);
			return (int) $obj->cnt;
		}
		return 0;
	}

	/**
	 * Fetch list of transactions with filters
	 *
	 * @param string $sortfield Sort field
	 * @param string $sortorder Sort order (ASC/DESC)
	 * @param int $limit Limit
	 * @param int $offset Offset
	 * @param array $filter Filters array
	 * @param string $mode 'list' returns array of objects, 'count' returns total count
	 * @return array|int Array of transactions, count or -1 on error
	 */
	public function fetchAll($sortfield = 'date_trans', $sortorder = 'DESC', $limit = 0, $offset = 0, $filter = array(), $mode = 'list')
	{
		$sql = "SELECT t.rowid";
		$sql .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction as t";
		$sql .= " WHERE t.entity = ".((int) $this->entity);

		// Apply filters
		if (!empty($filter['ref'])) {
			$sql .= " AND t.ref LIKE '%".$this->db->escape($filter['ref'])."%'";
		}
		if (!empty($filter['name'])) {
			$sql .= " AND t.name LIKE '%".$this->db->escape($filter['name'])."%'";
		}
		if (!empty($filter['description'])) {
			$sql .= " AND (t.description LIKE '%".$this->db->escape($filter['description'])."%'";
			$sql .= " OR t.label LIKE '%".$this->db->escape($filter['description'])."%')";
		}
		if (!empty($filter['amount_min'])) {
			$sql .= " AND t.amount >= ".((float) $filter['amount_min']);
		}
		if (!empty($filter['amount_max'])) {
			$sql .= " AND t.amount <= ".((float) $filter['amount_max']);
		}
		if (!empty($filter['date_from'])) {
			$sql .= " AND t.date_trans >= '".$this->db->idate($filter['date_from'])."'";
		}
		if (!empty($filter['date_to'])) {
			$sql .= " AND t.date_trans <= '".$this->db->idate($filter['date_to'])."'";
		}
		if (isset($filter['status']) && $filter['status'] !== '' && $filter['status'] >= 0) {
			$sql .= " AND t.status = ".((int) $filter['status']);
		}
		if (!empty($filter['iban'])) {
			$sql .= " AND t.iban LIKE '%".$this->db->escape($filter['iban'])."%'";
		}

		// Count mode - just return total count
		if ($mode == 'count') {
			$sqlcount = preg_replace('/SELECT t\.rowid/', 'SELECT COUNT(*) as total', $sql);
			$resqlcount = $this->db->query($sqlcount);
			if ($resqlcount) {
				$objcount = $this->db->fetch_object($resqlcount);
				return (int) $objcount->total;
			}
			return 0;
		}

		// Sort and limit
		$sql .= $this->db->order($sortfield, $sortorder);
		if ($limit > 0) {
			$sql .= $this->db->plimit($limit, $offset);
		}

		dol_syslog(get_class($this)."::fetchAll", LOG_DEBUG);
		$resql = $this->db->query($sql);

		if ($resql) {
			$result = array();

			while ($obj = $this->db->fetch_object($resql)) {
				$trans = new BankImportTransaction($this->db);
				$trans->fetch($obj->rowid);
				$result[] = $trans;
			}

			$this->db->free($resql);
			return $result;
		} else {
			$this->error = $this->db->lasterror();
			return -1;
		}
	}

	/**
	 * Get status label
	 *
	 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
	 * @return string Label
	 */
	public function getLibStatut($mode = 0)
	{
		return $this->LibStatut($this->status, $mode);
	}

	/**
	 * Get status label for a given status
	 *
	 * @param int $status Status value
	 * @param int $mode Mode
	 * @return string Label
	 */
	public function LibStatut($status, $mode = 0)
	{
		global $langs;

		$statusLabels = array(
			self::STATUS_NEW => array('short' => 'New', 'long' => 'NewTransaction', 'picto' => 'status0'),
			self::STATUS_MATCHED => array('short' => 'Matched', 'long' => 'TransactionMatched', 'picto' => 'status4'),
			self::STATUS_RECONCILED => array('short' => 'Reconciled', 'long' => 'TransactionReconciled', 'picto' => 'status6'),
			self::STATUS_IGNORED => array('short' => 'Ignored', 'long' => 'TransactionIgnored', 'picto' => 'status5'),
		);

		$statusInfo = $statusLabels[$status] ?? array('short' => 'Unknown', 'long' => 'Unknown', 'picto' => 'status0');

		if ($mode == 0) {
			return $langs->trans($statusInfo['long']);
		} elseif ($mode == 1) {
			return $langs->trans($statusInfo['short']);
		} elseif ($mode == 2) {
			return img_picto($langs->trans($statusInfo['long']), $statusInfo['picto']).' '.$langs->trans($statusInfo['short']);
		} elseif ($mode == 3) {
			return img_picto($langs->trans($statusInfo['long']), $statusInfo['picto']);
		} elseif ($mode == 4) {
			return img_picto($langs->trans($statusInfo['long']), $statusInfo['picto']).' '.$langs->trans($statusInfo['long']);
		} elseif ($mode == 5) {
			return $langs->trans($statusInfo['short']).' '.img_picto($langs->trans($statusInfo['long']), $statusInfo['picto']);
		} elseif ($mode == 6) {
			return $langs->trans($statusInfo['long']).' '.img_picto($langs->trans($statusInfo['long']), $statusInfo['picto']);
		}

		return '';
	}

	/**
	 * Set status of transaction
	 *
	 * @param int $status New status
	 * @param User $user User making the change
	 * @return int <0 if KO, >0 if OK
	 */
	public function setStatus($status, $user)
	{
		$this->status = $status;
		if ($status == self::STATUS_MATCHED || $status == self::STATUS_RECONCILED) {
			$this->fk_user_match = $user->id;
			$this->date_match = dol_now();
		}
		return $this->update($user);
	}

	/**
	 * Try to auto-match transaction with Dolibarr invoices
	 * Uses multiple criteria: amount, name, invoice reference, IBAN
	 *
	 * @return array Array of potential matches with scores
	 */
	public function findMatches()
	{
		$matches = array();
		$seenIds = array();

		// Skip if already matched
		if ($this->status == self::STATUS_MATCHED || $this->status == self::STATUS_RECONCILED) {
			return $matches;
		}

		// Build search text from description and end-to-end-id
		$searchText = strtoupper($this->description . ' ' . $this->end_to_end_id . ' ' . $this->label);

		// For incoming payments (positive amount), search customer invoices
		if ($this->amount > 0) {
			$matches = array_merge($matches, $this->findCustomerInvoiceMatches($searchText, $seenIds));
		}

		// For outgoing payments (negative amount), search supplier invoices
		if ($this->amount < 0) {
			$matches = array_merge($matches, $this->findSupplierInvoiceMatches($searchText, $seenIds));

			// FALLBACK: If best match has amount difference > 5 EUR, try multi-invoice matching
			$absAmount = abs($this->amount);
			$needsMultiMatch = true;

			if (!empty($matches)) {
				$bestMatch = $matches[0];
				// If best single match is close enough in amount, no need for multi-match
				if (abs($bestMatch['amount'] - $absAmount) <= 5.00) {
					$needsMultiMatch = false;
				}
			}

			if ($needsMultiMatch) {
				// Try to find supplier by IBAN or name
				$socid = $this->findSupplierForMultiMatch($searchText);

				if ($socid > 0) {
					// Tolerance: 3% of amount (for cash discounts/skonto) or minimum 5 EUR
					$tolerance = max($absAmount * 0.03, 5.00);
					$multiMatch = $this->findMultipleSupplierInvoiceMatches($searchText, $socid, $absAmount, $tolerance);

					if ($multiMatch && count($multiMatch['invoices']) > 1) {
						// Add as a special "multi" match
						$matches[] = array(
							'type' => 'multi_facture_fourn',
							'id' => 0, // Special marker
							'ref' => count($multiMatch['invoices']).' '.$this->getLangsTransNoLoad("Invoices"),
							'amount' => $multiMatch['total'],
							'socname' => $multiMatch['invoices'][0]['socname'],
							'socid' => $socid,
							'match_score' => $multiMatch['match_score'],
							'match_reasons' => array('multi_invoice', 'ref_supplier'),
							'invoices' => $multiMatch['invoices'],
							'difference' => $multiMatch['difference']
						);
					}
				}
			}
		}

		// Sort by: 1) Amount difference (closer to transaction amount is better), 2) Score
		$absAmount = abs($this->amount);
		usort($matches, function($a, $b) use ($absAmount) {
			$diffA = abs($a['amount'] - $absAmount);
			$diffB = abs($b['amount'] - $absAmount);

			// If one match is much closer in amount (within 10% of transaction), prefer it
			$threshold = $absAmount * 0.10;
			if ($diffA < $threshold && $diffB >= $threshold) {
				return -1; // A is better (closer amount)
			}
			if ($diffB < $threshold && $diffA >= $threshold) {
				return 1; // B is better (closer amount)
			}

			// Otherwise sort by score
			return $b['match_score'] - $a['match_score'];
		});

		return $matches;
	}

	/**
	 * Find supplier ID for multi-invoice matching
	 * Uses IBAN or name matching
	 *
	 * @param string $searchText Search text
	 * @return int Societe ID or 0
	 */
	protected function findSupplierForMultiMatch($searchText)
	{
		// First try by IBAN
		if (!empty($this->counterparty_iban)) {
			$socid = $this->findSocieteByIban($this->counterparty_iban);
			if ($socid > 0) {
				return $socid;
			}
		}

		// Then try by name similarity
		if (!empty($this->name)) {
			$sql = "SELECT s.rowid, s.nom";
			$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
			$sql .= " WHERE s.entity = ".((int) $this->entity);
			$sql .= " AND s.fournisseur = 1"; // Must be a supplier

			$resql = $this->db->query($sql);
			if ($resql) {
				$bestSocid = 0;
				$bestSimilarity = 0;

				while ($obj = $this->db->fetch_object($resql)) {
					$similarity = $this->calculateNameSimilarity($this->name, $obj->nom);
					if ($similarity > 70 && $similarity > $bestSimilarity) {
						$bestSimilarity = $similarity;
						$bestSocid = $obj->rowid;
					}
				}

				if ($bestSocid > 0) {
					return $bestSocid;
				}
			}
		}

		return 0;
	}

	/**
	 * Get translation without loading langs (fallback)
	 *
	 * @param string $key Translation key
	 * @return string
	 */
	protected function getLangsTransNoLoad($key)
	{
		global $langs;
		if (is_object($langs)) {
			return $langs->trans($key);
		}
		return $key;
	}

	/**
	 * Find matching customer invoices
	 *
	 * @param string $searchText Text to search in
	 * @param array &$seenIds Already seen invoice IDs
	 * @return array Matches
	 */
	protected function findCustomerInvoiceMatches($searchText, &$seenIds)
	{
		$matches = array();

		// 1. Search by invoice reference in description (highest priority)
		$sql = "SELECT f.rowid, f.ref, f.ref_client, f.total_ttc, f.date_lim_reglement,";
		$sql .= " s.nom as socname, s.rowid as socid";
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
		$sql .= " WHERE f.entity = ".((int) $this->entity);
		$sql .= " AND f.fk_statut = 1"; // Unpaid

		$resql = $this->db->query($sql);
		if ($resql) {
			while ($obj = $this->db->fetch_object($resql)) {
				$score = 0;
				$matchReasons = array();

				// Check if invoice reference appears in description
				if (stripos($searchText, $obj->ref) !== false) {
					$score += 80;
					$matchReasons[] = 'ref';
				}

				// Check client reference
				if (!empty($obj->ref_client) && stripos($searchText, $obj->ref_client) !== false) {
					$score += 70;
					$matchReasons[] = 'ref_client';
				}

				// Check exact amount match
				if (abs($obj->total_ttc - $this->amount) < 0.01) {
					$score += 50;
					$matchReasons[] = 'amount';
				} elseif (abs($obj->total_ttc - $this->amount) < 1.00) {
					// Close amount (within 1 EUR)
					$score += 20;
					$matchReasons[] = 'amount_close';
				}

				// Check name similarity
				if (!empty($this->name) && !empty($obj->socname)) {
					$nameSimilarity = $this->calculateNameSimilarity($this->name, $obj->socname);
					if ($nameSimilarity > 80) {
						$score += 40;
						$matchReasons[] = 'name_exact';
					} elseif ($nameSimilarity > 50) {
						$score += 20;
						$matchReasons[] = 'name_similar';
					}
				}

				// Check IBAN match
				if (!empty($this->counterparty_iban)) {
					$ibanMatch = $this->checkSocieteIban($obj->socid, $this->counterparty_iban);
					if ($ibanMatch) {
						$score += 60;
						$matchReasons[] = 'iban';
					}
				}

				// Only include if score is significant
				if ($score >= 50 && !isset($seenIds['facture_'.$obj->rowid])) {
					$seenIds['facture_'.$obj->rowid] = true;
					$matches[] = array(
						'type' => 'facture',
						'id' => $obj->rowid,
						'ref' => $obj->ref,
						'amount' => $obj->total_ttc,
						'socname' => $obj->socname,
						'socid' => $obj->socid,
						'match_score' => min($score, 100),
						'match_reasons' => $matchReasons,
						'date_due' => $obj->date_lim_reglement
					);
				}
			}
		}

		return $matches;
	}

	/**
	 * Find matching supplier invoices
	 *
	 * @param string $searchText Text to search in
	 * @param array &$seenIds Already seen invoice IDs
	 * @return array Matches
	 */
	protected function findSupplierInvoiceMatches($searchText, &$seenIds)
	{
		$matches = array();
		$absAmount = abs($this->amount);

		$sql = "SELECT f.rowid, f.ref, f.ref_supplier, f.total_ttc, f.date_lim_reglement,";
		$sql .= " s.nom as socname, s.rowid as socid";
		$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
		$sql .= " WHERE f.entity = ".((int) $this->entity);
		$sql .= " AND f.fk_statut = 1"; // Unpaid

		$resql = $this->db->query($sql);
		if ($resql) {
			while ($obj = $this->db->fetch_object($resql)) {
				$score = 0;
				$matchReasons = array();

				// Check if invoice reference appears in description
				if (stripos($searchText, $obj->ref) !== false) {
					$score += 80;
					$matchReasons[] = 'ref';
				}

				// Check supplier reference
				if (!empty($obj->ref_supplier) && stripos($searchText, $obj->ref_supplier) !== false) {
					$score += 70;
					$matchReasons[] = 'ref_supplier';
				}

				// Check exact amount match
				if (abs($obj->total_ttc - $absAmount) < 0.01) {
					$score += 50;
					$matchReasons[] = 'amount';
				} elseif (abs($obj->total_ttc - $absAmount) < 1.00) {
					$score += 20;
					$matchReasons[] = 'amount_close';
				}

				// Check name similarity
				if (!empty($this->name) && !empty($obj->socname)) {
					$nameSimilarity = $this->calculateNameSimilarity($this->name, $obj->socname);
					if ($nameSimilarity > 80) {
						$score += 40;
						$matchReasons[] = 'name_exact';
					} elseif ($nameSimilarity > 50) {
						$score += 20;
						$matchReasons[] = 'name_similar';
					}
				}

				// Check IBAN match
				if (!empty($this->counterparty_iban)) {
					$ibanMatch = $this->checkSocieteIban($obj->socid, $this->counterparty_iban);
					if ($ibanMatch) {
						$score += 60;
						$matchReasons[] = 'iban';
					}
				}

				// Only include if score is significant
				if ($score >= 50 && !isset($seenIds['facture_fourn_'.$obj->rowid])) {
					$seenIds['facture_fourn_'.$obj->rowid] = true;
					$matches[] = array(
						'type' => 'facture_fourn',
						'id' => $obj->rowid,
						'ref' => $obj->ref,
						'amount' => $obj->total_ttc,
						'socname' => $obj->socname,
						'socid' => $obj->socid,
						'match_score' => min($score, 100),
						'match_reasons' => $matchReasons,
						'date_due' => $obj->date_lim_reglement
					);
				}
			}
		}

		return $matches;
	}

	/**
	 * Calculate name similarity percentage
	 *
	 * @param string $name1 First name
	 * @param string $name2 Second name
	 * @return int Similarity percentage (0-100)
	 */
	protected function calculateNameSimilarity($name1, $name2)
	{
		// Normalize names
		$name1 = strtolower(trim($name1));
		$name2 = strtolower(trim($name2));

		// Remove common suffixes
		$suffixes = array(' gmbh', ' ag', ' kg', ' ohg', ' e.k.', ' ug', ' gbr', ' se', ' co. kg', ' gmbh & co. kg');
		foreach ($suffixes as $suffix) {
			$name1 = str_replace($suffix, '', $name1);
			$name2 = str_replace($suffix, '', $name2);
		}

		$name1 = trim($name1);
		$name2 = trim($name2);

		// Check if one contains the other
		if (strpos($name1, $name2) !== false || strpos($name2, $name1) !== false) {
			return 90;
		}

		// Use similar_text for fuzzy matching
		$similarity = 0;
		similar_text($name1, $name2, $similarity);

		return (int) $similarity;
	}

	/**
	 * Find multiple supplier invoices that together match the transaction amount
	 * Uses the logic: oldest invoices first, check if ref_supplier appears in description
	 *
	 * @param string $searchText Text to search in (description)
	 * @param int $socid Societe ID (supplier)
	 * @param float $targetAmount Target amount (absolute value of transaction)
	 * @param float $tolerance Tolerance for amount matching (default 5.00)
	 * @return array|null Array with 'invoices' and 'total' or null if no match
	 */
	protected function findMultipleSupplierInvoiceMatches($searchText, $socid, $targetAmount, $tolerance = 5.00)
	{
		$matchedInvoices = array();
		$runningTotal = 0.0;

		// Get all unpaid supplier invoices for this supplier, ordered by date (oldest first)
		$sql = "SELECT f.rowid, f.ref, f.ref_supplier, f.total_ttc, f.datef, f.date_lim_reglement,";
		$sql .= " s.nom as socname, s.rowid as socid";
		$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
		$sql .= " WHERE f.entity = ".((int) $this->entity);
		$sql .= " AND f.fk_statut = 1"; // Unpaid
		$sql .= " AND f.fk_soc = ".((int) $socid);
		$sql .= " ORDER BY f.datef ASC, f.rowid ASC"; // Oldest first

		$resql = $this->db->query($sql);
		if (!$resql) {
			return null;
		}

		while ($obj = $this->db->fetch_object($resql)) {
			// Check if this invoice's ref_supplier appears in the search text
			$refFound = false;
			if (!empty($obj->ref_supplier)) {
				// Try different formats: with and without leading zeros, etc.
				$refClean = preg_replace('/^0+/', '', $obj->ref_supplier); // Remove leading zeros
				if (stripos($searchText, $obj->ref_supplier) !== false ||
					stripos($searchText, $refClean) !== false) {
					$refFound = true;
				}
			}

			// Also check the internal ref
			if (!$refFound && stripos($searchText, $obj->ref) !== false) {
				$refFound = true;
			}

			if ($refFound) {
				// Calculate remaining amount for this invoice
				$remainToPay = $this->getInvoiceRemainToPay('facture_fourn', $obj->rowid, $obj->total_ttc);

				if ($remainToPay > 0) {
					$matchedInvoices[] = array(
						'type' => 'facture_fourn',
						'id' => $obj->rowid,
						'ref' => $obj->ref,
						'ref_supplier' => $obj->ref_supplier,
						'amount' => $remainToPay,
						'total_ttc' => $obj->total_ttc,
						'socname' => $obj->socname,
						'socid' => $obj->socid,
						'datef' => $obj->datef,
						'date_due' => $obj->date_lim_reglement
					);
					$runningTotal += $remainToPay;
				}
			}

			// Stop if we've reached or exceeded the target
			if ($runningTotal >= $targetAmount - $tolerance) {
				break;
			}
		}

		$this->db->free($resql);

		// Check if total matches within tolerance
		if (!empty($matchedInvoices) && abs($runningTotal - $targetAmount) <= $tolerance) {
			return array(
				'invoices' => $matchedInvoices,
				'total' => $runningTotal,
				'difference' => $targetAmount - $runningTotal,
				'match_score' => abs($runningTotal - $targetAmount) < 0.01 ? 100 : 98
			);
		}

		return null;
	}

	/**
	 * Get remaining amount to pay for an invoice
	 *
	 * @param string $type 'facture' or 'facture_fourn'
	 * @param int $invoiceId Invoice ID
	 * @param float $totalTtc Total TTC of invoice
	 * @return float Remaining amount
	 */
	protected function getInvoiceRemainToPay($type, $invoiceId, $totalTtc)
	{
		if ($type == 'facture_fourn') {
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
			$invoice = new FactureFournisseur($this->db);
			if ($invoice->fetch($invoiceId) > 0) {
				$alreadyPaid = $invoice->getSommePaiement();
				$creditnotes = $invoice->getSumCreditNotesUsed();
				$deposits = $invoice->getSumDepositsUsed();
				return price2num($invoice->total_ttc - $alreadyPaid - $creditnotes - $deposits, 'MT');
			}
		} else {
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
			$invoice = new Facture($this->db);
			if ($invoice->fetch($invoiceId) > 0) {
				$alreadyPaid = $invoice->getSommePaiement();
				$creditnotes = $invoice->getSumCreditNotesUsed();
				$deposits = $invoice->getSumDepositsUsed();
				return price2num($invoice->total_ttc - $alreadyPaid - $creditnotes - $deposits, 'MT');
			}
		}
		return $totalTtc;
	}

	/**
	 * Check if IBAN matches any bank account of a societe
	 *
	 * @param int $socid Societe ID
	 * @param string $iban IBAN to check
	 * @return bool True if match found
	 */
	protected function checkSocieteIban($socid, $iban)
	{
		if (empty($socid) || empty($iban)) {
			return false;
		}

		// Normalize IBAN (remove spaces)
		$iban = str_replace(' ', '', strtoupper($iban));

		$sql = "SELECT iban_prefix FROM ".MAIN_DB_PREFIX."societe_rib";
		$sql .= " WHERE fk_soc = ".((int) $socid);
		$sql .= " AND status = 1"; // Active

		$resql = $this->db->query($sql);
		if ($resql) {
			while ($obj = $this->db->fetch_object($resql)) {
				$storedIban = str_replace(' ', '', strtoupper($obj->iban_prefix));
				if ($storedIban === $iban) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Find third party by IBAN
	 *
	 * @param string $iban IBAN to search
	 * @return int|null Societe ID or null
	 */
	public function findSocieteByIban($iban)
	{
		if (empty($iban)) {
			return null;
		}

		$iban = str_replace(' ', '', strtoupper($iban));

		$sql = "SELECT fk_soc FROM ".MAIN_DB_PREFIX."societe_rib";
		$sql .= " WHERE REPLACE(UPPER(iban_prefix), ' ', '') = '".$this->db->escape($iban)."'";
		$sql .= " AND status = 1";
		$sql .= " LIMIT 1";

		$resql = $this->db->query($sql);
		if ($resql && $this->db->num_rows($resql) > 0) {
			$obj = $this->db->fetch_object($resql);
			return (int) $obj->fk_soc;
		}

		return null;
	}

	/**
	 * Auto-detect and link third party by IBAN
	 *
	 * @param User $user User doing the action
	 * @return int Societe ID if found and linked, 0 if not found
	 */
	public function autoLinkSociete($user)
	{
		if (!empty($this->fk_societe)) {
			return $this->fk_societe; // Already linked
		}

		$socid = $this->findSocieteByIban($this->counterparty_iban);
		if ($socid > 0) {
			$this->fk_societe = $socid;
			$this->update($user);
			return $socid;
		}

		return 0;
	}

	/**
	 * Confirm payment: create Paiement or PaiementFourn in Dolibarr
	 * Links the bankimport transaction to the created payment and bank line.
	 *
	 * @param User   $user          User performing the action
	 * @param string $type          'facture' or 'facture_fourn'
	 * @param int    $invoiceId     Invoice ID
	 * @param int    $bankAccountId Dolibarr bank account ID (llx_bank_account.rowid)
	 * @return int >0 if OK (payment ID), <0 if error
	 */
	public function confirmPayment($user, $type, $invoiceId, $bankAccountId)
	{
		global $conf, $langs;

		$error = 0;
		$this->db->begin();

		// Look up payment type ID for 'VIR' (bank transfer)
		$paiementTypeId = 0;
		$sql = "SELECT id FROM ".MAIN_DB_PREFIX."c_paiement WHERE code = 'VIR' AND active = 1";
		$resql = $this->db->query($sql);
		if ($resql && $this->db->num_rows($resql) > 0) {
			$obj = $this->db->fetch_object($resql);
			$paiementTypeId = (int) $obj->id;
		}
		if (empty($paiementTypeId)) {
			$this->error = 'Payment type VIR not found in c_paiement';
			$this->db->rollback();
			return -1;
		}

		if ($type == 'facture') {
			// Customer invoice payment
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
			require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';

			$invoice = new Facture($this->db);
			if ($invoice->fetch($invoiceId) <= 0) {
				$this->error = 'Invoice not found: '.$invoiceId;
				$this->db->rollback();
				return -2;
			}

			// Calculate remaining amount
			$alreadyPaid = $invoice->getSommePaiement();
			$creditnotes = $invoice->getSumCreditNotesUsed();
			$deposits = $invoice->getSumDepositsUsed();
			$remaintopay = price2num($invoice->total_ttc - $alreadyPaid - $creditnotes - $deposits, 'MT');

			if ($remaintopay <= 0) {
				$this->error = $langs->trans("InvoiceAlreadyPaid");
				$this->db->rollback();
				return -5;
			}

			// Use the lesser of: transaction amount or remain to pay
			$payAmount = min(abs($this->amount), $remaintopay);

			$paiement = new Paiement($this->db);
			$paiement->datepaye = $this->date_trans;
			$paiement->amounts = array($invoiceId => $payAmount);
			$paiement->multicurrency_amounts = array($invoiceId => $payAmount);
			$paiement->paiementid = $paiementTypeId;
			$paiement->paiementcode = 'VIR';
			$paiement->num_payment = $this->end_to_end_id ?: $this->ref;
			$paiement->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name.' - '.dol_trunc($this->description, 100);

			$paymentId = $paiement->create($user, 1);
			if ($paymentId < 0) {
				$this->error = $paiement->error;
				$this->errors = $paiement->errors;
				$error++;
			}

			if (!$error) {
				$bankLineId = $paiement->addPaymentToBank(
					$user,
					'payment',
					'(CustomerInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiement = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture = $invoiceId;
					$this->fk_societe = $invoice->socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			}

			if ($error) {
				$this->db->rollback();
				return -3;
			}

			$this->db->commit();
			return $paymentId;

		} elseif ($type == 'facture_fourn') {
			// Supplier invoice payment
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';

			$invoice = new FactureFournisseur($this->db);
			if ($invoice->fetch($invoiceId) <= 0) {
				$this->error = 'Supplier invoice not found: '.$invoiceId;
				$this->db->rollback();
				return -2;
			}

			$alreadyPaid = $invoice->getSommePaiement();
			$creditnotes = $invoice->getSumCreditNotesUsed();
			$deposits = $invoice->getSumDepositsUsed();
			$remaintopay = price2num($invoice->total_ttc - $alreadyPaid - $creditnotes - $deposits, 'MT');

			if ($remaintopay <= 0) {
				$this->error = $langs->trans("InvoiceAlreadyPaid");
				$this->db->rollback();
				return -5;
			}

			$payAmount = min(abs($this->amount), $remaintopay);

			$paiementfourn = new PaiementFourn($this->db);
			$paiementfourn->datepaye = $this->date_trans;
			$paiementfourn->amounts = array($invoiceId => $payAmount);
			$paiementfourn->multicurrency_amounts = array($invoiceId => $payAmount);
			$paiementfourn->paiementid = $paiementTypeId;
			$paiementfourn->paiementcode = 'VIR';
			$paiementfourn->num_payment = $this->end_to_end_id ?: $this->ref;
			$paiementfourn->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name.' - '.dol_trunc($this->description, 100);

			$paymentId = $paiementfourn->create($user, 1);
			if ($paymentId < 0) {
				$this->error = $paiementfourn->error;
				$this->errors = $paiementfourn->errors;
				$error++;
			}

			if (!$error) {
				$bankLineId = $paiementfourn->addPaymentToBank(
					$user,
					'payment_supplier',
					'(SupplierInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiementfourn = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture_fourn = $invoiceId;
					$this->fk_societe = $invoice->socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			}

			if ($error) {
				$this->db->rollback();
				return -3;
			}

			$this->db->commit();
			return $paymentId;
		}

		$this->error = 'Unknown type: '.$type;
		$this->db->rollback();
		return -4;
	}

	/**
	 * Confirm payment for multiple invoices (batch payment)
	 * Creates a single payment that covers multiple invoices
	 *
	 * @param User   $user          User performing the action
	 * @param array  $invoices      Array of invoice data: [['type' => 'facture_fourn', 'id' => X, 'amount' => Y], ...]
	 * @param int    $bankAccountId Dolibarr bank account ID
	 * @return int >0 if OK (payment ID), <0 if error
	 */
	public function confirmMultiplePayment($user, $invoices, $bankAccountId)
	{
		global $conf, $langs;

		if (empty($invoices)) {
			$this->error = 'No invoices provided';
			return -1;
		}

		$error = 0;
		$this->db->begin();

		// Look up payment type ID for 'VIR' (bank transfer)
		$paiementTypeId = 0;
		$sql = "SELECT id FROM ".MAIN_DB_PREFIX."c_paiement WHERE code = 'VIR' AND active = 1";
		$resql = $this->db->query($sql);
		if ($resql && $this->db->num_rows($resql) > 0) {
			$obj = $this->db->fetch_object($resql);
			$paiementTypeId = (int) $obj->id;
		}
		if (empty($paiementTypeId)) {
			$this->error = 'Payment type VIR not found in c_paiement';
			$this->db->rollback();
			return -1;
		}

		// Determine if these are customer or supplier invoices
		$firstType = $invoices[0]['type'];
		$isSupplier = ($firstType == 'facture_fourn');

		// Build amounts array for payment
		$amounts = array();
		$multicurrency_amounts = array();
		$totalPayment = 0;
		$socid = 0;

		foreach ($invoices as $inv) {
			$invoiceId = (int) $inv['id'];
			$payAmount = (float) $inv['amount'];

			if ($payAmount <= 0) {
				continue;
			}

			$amounts[$invoiceId] = $payAmount;
			$multicurrency_amounts[$invoiceId] = $payAmount;
			$totalPayment += $payAmount;

			// Get socid from first invoice
			if ($socid == 0) {
				if ($isSupplier) {
					require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
					$invoice = new FactureFournisseur($this->db);
					if ($invoice->fetch($invoiceId) > 0) {
						$socid = $invoice->socid;
					}
				} else {
					require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
					$invoice = new Facture($this->db);
					if ($invoice->fetch($invoiceId) > 0) {
						$socid = $invoice->socid;
					}
				}
			}
		}

		if (empty($amounts)) {
			$this->error = 'No valid invoice amounts';
			$this->db->rollback();
			return -2;
		}

		// Check for cash discount (Skonto): If paid amount is less than invoice sum
		$actualAmount = abs($this->amount);
		$hasDiscount = false;
		$discountAmount = 0;
		$discountFactor = 1.0;

		if ($actualAmount < $totalPayment && $actualAmount > 0) {
			$discountAmount = $totalPayment - $actualAmount;
			// Only apply discount logic if within 5% (typical cash discount range)
			if ($discountAmount <= ($totalPayment * 0.05)) {
				$hasDiscount = true;
				$discountFactor = $actualAmount / $totalPayment;
				// Adjust amounts proportionally
				foreach ($amounts as $invoiceId => $amount) {
					$amounts[$invoiceId] = price2num($amount * $discountFactor, 'MT');
					$multicurrency_amounts[$invoiceId] = $amounts[$invoiceId];
				}
			}
		}

		if ($isSupplier) {
			// Supplier invoice payment
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';

			$paiementfourn = new PaiementFourn($this->db);
			$paiementfourn->datepaye = $this->date_trans;
			$paiementfourn->amounts = $amounts;
			$paiementfourn->multicurrency_amounts = $multicurrency_amounts;
			$paiementfourn->paiementid = $paiementTypeId;
			$paiementfourn->paiementcode = 'VIR';
			$paiementfourn->num_payment = $this->end_to_end_id ?: $this->ref;
			$paiementfourn->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name.' - '.count($invoices).' '.$langs->trans("Invoices");

			$paymentId = $paiementfourn->create($user, 1);
			if ($paymentId < 0) {
				$this->error = $paiementfourn->error;
				$this->errors = $paiementfourn->errors;
				$error++;
			}

			if (!$error) {
				$bankLineId = $paiementfourn->addPaymentToBank(
					$user,
					'payment_supplier',
					'(SupplierInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					// If we had a cash discount, close the invoices with discount code
					if ($hasDiscount) {
						foreach ($invoices as $inv) {
							$invObj = new FactureFournisseur($this->db);
							if ($invObj->fetch($inv['id']) > 0) {
								// Check if invoice still has remaining amount
								$remaining = $invObj->getRemainToPay();
								if ($remaining > 0 && $remaining <= $discountAmount) {
									// Close with discount code
									$invObj->setPaid($user, 'discount_vat', $langs->trans("CashDiscount").' ('.price($remaining).' '.$this->currency.')');
								}
							}
						}
					}

					$this->fk_paiementfourn = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_societe = $socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					// Store first invoice as reference (or could store all in note)
					$this->fk_facture_fourn = $invoices[0]['id'];
					$discountNote = $hasDiscount ? ' (Skonto: '.price($discountAmount).' '.$this->currency.')' : '';
					$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice payment: '.implode(', ', array_column($invoices, 'ref')).$discountNote;
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			}
		} else {
			// Customer invoice payment
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
			require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';

			$paiement = new Paiement($this->db);
			$paiement->datepaye = $this->date_trans;
			$paiement->amounts = $amounts;
			$paiement->multicurrency_amounts = $multicurrency_amounts;
			$paiement->paiementid = $paiementTypeId;
			$paiement->paiementcode = 'VIR';
			$paiement->num_payment = $this->end_to_end_id ?: $this->ref;
			$paiement->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name.' - '.count($invoices).' '.$langs->trans("Invoices");

			$paymentId = $paiement->create($user, 1);
			if ($paymentId < 0) {
				$this->error = $paiement->error;
				$this->errors = $paiement->errors;
				$error++;
			}

			if (!$error) {
				$bankLineId = $paiement->addPaymentToBank(
					$user,
					'payment',
					'(CustomerInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					// If we had a cash discount, close the invoices with discount code
					if ($hasDiscount) {
						foreach ($invoices as $inv) {
							$invObj = new Facture($this->db);
							if ($invObj->fetch($inv['id']) > 0) {
								// Check if invoice still has remaining amount
								$remaining = $invObj->getRemainToPay();
								if ($remaining > 0 && $remaining <= $discountAmount) {
									// Close with discount code
									$invObj->setPaid($user, 'discount_vat', $langs->trans("CashDiscount").' ('.price($remaining).' '.$this->currency.')');
								}
							}
						}
					}

					$this->fk_paiement = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_societe = $socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->fk_facture = $invoices[0]['id'];
					$discountNote = $hasDiscount ? ' (Skonto: '.price($discountAmount).' '.$this->currency.')' : '';
					$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice payment: '.implode(', ', array_column($invoices, 'ref')).$discountNote;
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			}
		}

		if ($error) {
			$this->db->rollback();
			return -3;
		}

		$this->db->commit();
		return $paymentId;
	}

	/**
	 * Link transaction to an existing payment (for already paid invoices)
	 * Finds the existing payment for the invoice and creates the bank entry.
	 * If no payment exists (invoice marked paid without payment record), creates both payment and bank entry.
	 *
	 * @param User   $user          User performing the action
	 * @param string $invoiceType   Invoice type ('facture' or 'facture_fourn')
	 * @param int    $invoiceId     Invoice ID
	 * @param int    $bankAccountId Dolibarr bank account ID
	 * @return int >0 if OK (bank line ID), <0 if error
	 */
	public function linkExistingPayment($user, $invoiceType, $invoiceId, $bankAccountId)
	{
		global $conf, $langs;

		$error = 0;
		$bankLineId = 0;
		$this->db->begin();

		// Look up payment type ID for 'VIR' (bank transfer)
		$paiementTypeId = 0;
		$sql = "SELECT id FROM ".MAIN_DB_PREFIX."c_paiement WHERE code = 'VIR' AND active = 1";
		$resql = $this->db->query($sql);
		if ($resql && $this->db->num_rows($resql) > 0) {
			$obj = $this->db->fetch_object($resql);
			$paiementTypeId = (int) $obj->id;
		}
		if (empty($paiementTypeId)) {
			$this->error = 'Payment type VIR not found';
			$this->db->rollback();
			return -1;
		}

		if ($invoiceType == 'facture_fourn') {
			// Supplier invoice
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';

			$invoice = new FactureFournisseur($this->db);
			if ($invoice->fetch($invoiceId) <= 0) {
				$this->error = 'Invoice not found';
				$this->db->rollback();
				return -1;
			}

			// Find the payment(s) for this invoice
			$sql = "SELECT pfp.fk_paiementfourn as payment_id, pfp.amount";
			$sql .= " FROM ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pfp";
			$sql .= " WHERE pfp.fk_facturefourn = ".((int) $invoiceId);
			$sql .= " ORDER BY pfp.rowid DESC";
			$sql .= " LIMIT 1";

			$resql = $this->db->query($sql);
			$paymentExists = ($resql && $this->db->num_rows($resql) > 0);

			if ($paymentExists) {
				// Payment exists - link bank entry to it
				$obj = $this->db->fetch_object($resql);
				$paymentId = (int) $obj->payment_id;

				$paiementfourn = new PaiementFourn($this->db);
				if ($paiementfourn->fetch($paymentId) <= 0) {
					$this->error = 'Payment not found';
					$this->db->rollback();
					return -3;
				}

				// Check if payment already has a bank entry
				if (!empty($paiementfourn->bank_line) && $paiementfourn->bank_line > 0) {
					// Just link transaction to existing bank entry
					$this->fk_paiementfourn = $paymentId;
					$this->fk_bank = $paiementfourn->bank_line;
					$this->fk_facture_fourn = $invoiceId;
					$this->fk_societe = $invoice->socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();

					$result = $this->update($user);
					if ($result <= 0) {
						$this->error = 'Failed to update transaction: '.$this->error;
						$this->db->rollback();
						return -6;
					}

					$this->db->commit();
					return $paiementfourn->bank_line > 0 ? $paiementfourn->bank_line : 1;
				}

				// Create bank entry for existing payment
				$bankLineId = $paiementfourn->addPaymentToBank(
					$user,
					'payment_supplier',
					'(SupplierInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiementfourn = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture_fourn = $invoiceId;
					$this->fk_societe = $invoice->socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			} else {
				// No payment exists - create new payment AND bank entry
				// This handles invoices marked as paid without actual payment record
				$amounts = array($invoiceId => $invoice->total_ttc);
				$multicurrency_amounts = array($invoiceId => $invoice->total_ttc);

				$paiementfourn = new PaiementFourn($this->db);
				$paiementfourn->datepaye = $this->date_trans;
				$paiementfourn->amounts = $amounts;
				$paiementfourn->multicurrency_amounts = $multicurrency_amounts;
				$paiementfourn->paiementid = $paiementTypeId;
				$paiementfourn->paiementcode = 'VIR';
				$paiementfourn->num_payment = $this->end_to_end_id ?: $this->ref;
				$paiementfourn->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name;

				$paymentId = $paiementfourn->create($user, 1);
				if ($paymentId < 0) {
					$this->error = $paiementfourn->error;
					$this->errors = $paiementfourn->errors;
					$error++;
				}

				if (!$error) {
					$bankLineId = $paiementfourn->addPaymentToBank(
						$user,
						'payment_supplier',
						'(SupplierInvoicePayment)',
						$bankAccountId,
						$this->name,
						''
					);

					if ($bankLineId > 0) {
						$this->fk_paiementfourn = $paymentId;
						$this->fk_bank = $bankLineId;
						$this->fk_facture_fourn = $invoiceId;
						$this->fk_societe = $invoice->socid;
						$this->status = self::STATUS_MATCHED;
						$this->fk_user_match = $user->id;
						$this->date_match = dol_now();
						$this->update($user);
					} else {
						$this->error = 'Failed to add payment to bank';
						$error++;
					}
				}
			}
		} elseif ($invoiceType == 'facture') {
			// Customer invoice
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
			require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';

			$invoice = new Facture($this->db);
			if ($invoice->fetch($invoiceId) <= 0) {
				$this->error = 'Invoice not found';
				$this->db->rollback();
				return -1;
			}

			// Find the payment(s) for this invoice
			$sql = "SELECT pfp.fk_paiement as payment_id, pfp.amount";
			$sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pfp";
			$sql .= " WHERE pfp.fk_facture = ".((int) $invoiceId);
			$sql .= " ORDER BY pfp.rowid DESC";
			$sql .= " LIMIT 1";

			$resql = $this->db->query($sql);
			$paymentExists = ($resql && $this->db->num_rows($resql) > 0);

			if ($paymentExists) {
				// Payment exists - link bank entry to it
				$obj = $this->db->fetch_object($resql);
				$paymentId = (int) $obj->payment_id;

				$paiement = new Paiement($this->db);
				if ($paiement->fetch($paymentId) <= 0) {
					$this->error = 'Payment not found';
					$this->db->rollback();
					return -3;
				}

				// Check if payment already has a bank entry
				if (!empty($paiement->bank_line) && $paiement->bank_line > 0) {
					// Just link transaction to existing bank entry
					$this->fk_paiement = $paymentId;
					$this->fk_bank = $paiement->bank_line;
					$this->fk_facture = $invoiceId;
					$this->fk_societe = $invoice->socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->update($user);

					$this->db->commit();
					return $paiement->bank_line;
				}

				// Create bank entry for existing payment
				$bankLineId = $paiement->addPaymentToBank(
					$user,
					'payment',
					'(CustomerInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiement = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture = $invoiceId;
					$this->fk_societe = $invoice->socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			} else {
				// No payment exists - create new payment AND bank entry
				$amounts = array($invoiceId => $invoice->total_ttc);
				$multicurrency_amounts = array($invoiceId => $invoice->total_ttc);

				$paiement = new Paiement($this->db);
				$paiement->datepaye = $this->date_trans;
				$paiement->amounts = $amounts;
				$paiement->multicurrency_amounts = $multicurrency_amounts;
				$paiement->paiementid = $paiementTypeId;
				$paiement->paiementcode = 'VIR';
				$paiement->num_payment = $this->end_to_end_id ?: $this->ref;
				$paiement->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name;

				$paymentId = $paiement->create($user, 1);
				if ($paymentId < 0) {
					$this->error = $paiement->error;
					$this->errors = $paiement->errors;
					$error++;
				}

				if (!$error) {
					$bankLineId = $paiement->addPaymentToBank(
						$user,
						'payment',
						'(CustomerInvoicePayment)',
						$bankAccountId,
						$this->name,
						''
					);

					if ($bankLineId > 0) {
						$this->fk_paiement = $paymentId;
						$this->fk_bank = $bankLineId;
						$this->fk_facture = $invoiceId;
						$this->fk_societe = $invoice->socid;
						$this->status = self::STATUS_MATCHED;
						$this->fk_user_match = $user->id;
						$this->date_match = dol_now();
						$this->update($user);
					} else {
						$this->error = 'Failed to add payment to bank';
						$error++;
					}
				}
			}
		} else {
			$this->error = 'Unknown invoice type: '.$invoiceType;
			$this->db->rollback();
			return -4;
		}

		if ($error) {
			$this->db->rollback();
			return -5;
		}

		$this->db->commit();
		return $bankLineId;
	}

	/**
	 * Link multiple already paid invoices to this bank transaction
	 * Finds existing payments for these invoices and links them to a single bank entry
	 *
	 * @param User $user User making the link
	 * @param array $invoices Array of ['type' => 'facture'|'facture_fourn', 'id' => int]
	 * @param int $bankAccountId Dolibarr bank account ID
	 * @return int <0 if KO, >0 bank line ID if OK
	 */
	public function linkMultipleExistingPayments($user, $invoices, $bankAccountId)
	{
		global $conf, $langs;

		if (empty($invoices)) {
			$this->error = 'No invoices provided';
			return -1;
		}

		$error = 0;
		$bankLineId = 0;
		$this->db->begin();

		// Determine invoice type (all must be same type)
		$firstType = $invoices[0]['type'];
		$isSupplier = ($firstType == 'facture_fourn');

		// Collect all payment IDs and check they exist
		$paymentIds = array();
		$socid = 0;
		$invoiceRefs = array();

		foreach ($invoices as $inv) {
			$invoiceId = (int) $inv['id'];

			if ($isSupplier) {
				require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
				$invoice = new FactureFournisseur($this->db);
				if ($invoice->fetch($invoiceId) <= 0) {
					$this->error = 'Invoice not found: '.$invoiceId;
					$this->db->rollback();
					return -2;
				}
				$invoiceRefs[] = $invoice->ref;
				if ($socid == 0) {
					$socid = $invoice->socid;
				}

				// Find payment for this invoice
				$sql = "SELECT pfp.fk_paiementfourn as payment_id";
				$sql .= " FROM ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pfp";
				$sql .= " WHERE pfp.fk_facturefourn = ".((int) $invoiceId);
				$sql .= " ORDER BY pfp.rowid DESC LIMIT 1";
				$resql = $this->db->query($sql);
				if ($resql && $this->db->num_rows($resql) > 0) {
					$obj = $this->db->fetch_object($resql);
					$paymentIds[$obj->payment_id] = $obj->payment_id;
				}
			} else {
				require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
				$invoice = new Facture($this->db);
				if ($invoice->fetch($invoiceId) <= 0) {
					$this->error = 'Invoice not found: '.$invoiceId;
					$this->db->rollback();
					return -2;
				}
				$invoiceRefs[] = $invoice->ref;
				if ($socid == 0) {
					$socid = $invoice->socid;
				}

				// Find payment for this invoice
				$sql = "SELECT pfp.fk_paiement as payment_id";
				$sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pfp";
				$sql .= " WHERE pfp.fk_facture = ".((int) $invoiceId);
				$sql .= " ORDER BY pfp.rowid DESC LIMIT 1";
				$resql = $this->db->query($sql);
				if ($resql && $this->db->num_rows($resql) > 0) {
					$obj = $this->db->fetch_object($resql);
					$paymentIds[$obj->payment_id] = $obj->payment_id;
				}
			}
		}

		// Look up payment type ID for 'VIR' (bank transfer) - needed if we create new payment
		$paiementTypeId = 0;
		$sql = "SELECT id FROM ".MAIN_DB_PREFIX."c_paiement WHERE code = 'VIR' AND active = 1";
		$resql = $this->db->query($sql);
		if ($resql && $this->db->num_rows($resql) > 0) {
			$obj = $this->db->fetch_object($resql);
			$paiementTypeId = (int) $obj->id;
		}

		// If no payments found, we need to create new payment(s)
		if (empty($paymentIds)) {
			// No existing payment - create one for all invoices
			if ($isSupplier) {
				require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';

				// Build amounts array from invoices
				$amounts = array();
				$multicurrency_amounts = array();
				foreach ($invoices as $inv) {
					$invoice = new FactureFournisseur($this->db);
					if ($invoice->fetch($inv['id']) > 0) {
						$amounts[$inv['id']] = $invoice->total_ttc;
						$multicurrency_amounts[$inv['id']] = $invoice->total_ttc;
					}
				}

				$paiementfourn = new PaiementFourn($this->db);
				$paiementfourn->datepaye = $this->date_trans;
				$paiementfourn->amounts = $amounts;
				$paiementfourn->multicurrency_amounts = $multicurrency_amounts;
				$paiementfourn->paiementid = $paiementTypeId;
				$paiementfourn->paiementcode = 'VIR';
				$paiementfourn->num_payment = $this->end_to_end_id ?: $this->ref;
				$paiementfourn->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name;

				$paymentId = $paiementfourn->create($user, 1);
				if ($paymentId < 0) {
					$this->error = $paiementfourn->error;
					$this->errors = $paiementfourn->errors;
					$this->db->rollback();
					return -3;
				}

				$bankLineId = $paiementfourn->addPaymentToBank(
					$user,
					'payment_supplier',
					'(SupplierInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiementfourn = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture_fourn = $invoices[0]['id'];
					$this->fk_societe = $socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice link: '.implode(', ', $invoiceRefs);
					$this->update($user);

					$this->db->commit();
					return $bankLineId;
				} else {
					$this->error = 'Failed to add payment to bank';
					$this->db->rollback();
					return -5;
				}
			} else {
				require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';

				// Build amounts array from invoices
				$amounts = array();
				$multicurrency_amounts = array();
				foreach ($invoices as $inv) {
					$invoice = new Facture($this->db);
					if ($invoice->fetch($inv['id']) > 0) {
						$amounts[$inv['id']] = $invoice->total_ttc;
						$multicurrency_amounts[$inv['id']] = $invoice->total_ttc;
					}
				}

				$paiement = new Paiement($this->db);
				$paiement->datepaye = $this->date_trans;
				$paiement->amounts = $amounts;
				$paiement->multicurrency_amounts = $multicurrency_amounts;
				$paiement->paiementid = $paiementTypeId;
				$paiement->paiementcode = 'VIR';
				$paiement->num_payment = $this->end_to_end_id ?: $this->ref;
				$paiement->note_private = $langs->trans("PaymentCreatedByBankImport").' - '.$this->name;

				$paymentId = $paiement->create($user, 1);
				if ($paymentId < 0) {
					$this->error = $paiement->error;
					$this->errors = $paiement->errors;
					$this->db->rollback();
					return -3;
				}

				$bankLineId = $paiement->addPaymentToBank(
					$user,
					'payment',
					'(CustomerInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiement = $paymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture = $invoices[0]['id'];
					$this->fk_societe = $socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice link: '.implode(', ', $invoiceRefs);
					$this->update($user);

					$this->db->commit();
					return $bankLineId;
				} else {
					$this->error = 'Failed to add payment to bank';
					$this->db->rollback();
					return -5;
				}
			}
		}

		// Use the first payment to link to bank
		$firstPaymentId = reset($paymentIds);

		if ($isSupplier) {
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';
			$paiementfourn = new PaiementFourn($this->db);
			if ($paiementfourn->fetch($firstPaymentId) <= 0) {
				$this->error = 'Payment not found';
				$this->db->rollback();
				return -4;
			}

			// Check if payment already has a bank entry
			if (!empty($paiementfourn->bank_line) && $paiementfourn->bank_line > 0) {
				// Payment already has a bank entry in Dolibarr
				// Just link this imported transaction to the existing payment WITHOUT creating new bank entry
				$bankLineId = $paiementfourn->bank_line;

				$this->fk_paiementfourn = $firstPaymentId;
				$this->fk_bank = $bankLineId;
				$this->fk_facture_fourn = $invoices[0]['id'];
				$this->fk_societe = $socid;
				$this->status = self::STATUS_MATCHED;
				$this->fk_user_match = $user->id;
				$this->date_match = dol_now();
				$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice link: '.implode(', ', $invoiceRefs);

				$result = $this->update($user);
				if ($result <= 0) {
					$this->error = 'Failed to update transaction: '.$this->error;
					$this->db->rollback();
					return -6;
				}

				$this->db->commit();
				return $bankLineId > 0 ? $bankLineId : 1;
			} else {
				// Create bank entry for existing payment
				$bankLineId = $paiementfourn->addPaymentToBank(
					$user,
					'payment_supplier',
					'(SupplierInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiementfourn = $firstPaymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture_fourn = $invoices[0]['id'];
					$this->fk_societe = $socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice link: '.implode(', ', $invoiceRefs);
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			}
		} else {
			require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
			$paiement = new Paiement($this->db);
			if ($paiement->fetch($firstPaymentId) <= 0) {
				$this->error = 'Payment not found';
				$this->db->rollback();
				return -4;
			}

			// Check if payment already has a bank entry
			if (!empty($paiement->bank_line) && $paiement->bank_line > 0) {
				// Payment already has a bank entry in Dolibarr
				// Just link this imported transaction to the existing payment WITHOUT creating new bank entry
				$bankLineId = $paiement->bank_line;

				$this->fk_paiement = $firstPaymentId;
				$this->fk_bank = $bankLineId;
				$this->fk_facture = $invoices[0]['id'];
				$this->fk_societe = $socid;
				$this->status = self::STATUS_MATCHED;
				$this->fk_user_match = $user->id;
				$this->date_match = dol_now();
				$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice link: '.implode(', ', $invoiceRefs);

				$result = $this->update($user);
				if ($result <= 0) {
					$this->error = 'Failed to update transaction: '.$this->error;
					$this->db->rollback();
					return -6;
				}

				$this->db->commit();
				return $bankLineId > 0 ? $bankLineId : 1;
			} else {
				// Create bank entry for existing payment
				$bankLineId = $paiement->addPaymentToBank(
					$user,
					'payment',
					'(CustomerInvoicePayment)',
					$bankAccountId,
					$this->name,
					''
				);

				if ($bankLineId > 0) {
					$this->fk_paiement = $firstPaymentId;
					$this->fk_bank = $bankLineId;
					$this->fk_facture = $invoices[0]['id'];
					$this->fk_societe = $socid;
					$this->status = self::STATUS_MATCHED;
					$this->fk_user_match = $user->id;
					$this->date_match = dol_now();
					$this->note_private = ($this->note_private ? $this->note_private."\n" : '').'Multi-invoice link: '.implode(', ', $invoiceRefs);
					$this->update($user);
				} else {
					$this->error = 'Failed to add payment to bank';
					$error++;
				}
			}
		}

		if ($error) {
			$this->db->rollback();
			return -5;
		}

		$this->db->commit();
		return $bankLineId > 0 ? $bankLineId : 1;
	}

	/**
	 * Unlink payment from this transaction
	 * Resets all links and sets status back to NEW
	 * Note: This does NOT delete the payment in Dolibarr, just removes the link
	 *
	 * @param User $user User making the change
	 * @return int <0 if KO, >0 if OK
	 */
	public function unlinkPayment($user)
	{
		$this->db->begin();

		// Reset all links
		$this->fk_bank = null;
		$this->fk_facture = null;
		$this->fk_facture_fourn = null;
		$this->fk_paiement = null;
		$this->fk_paiementfourn = null;
		$this->fk_societe = null;
		$this->fk_salary = null;
		$this->fk_don = null;
		$this->fk_loan = null;

		// Reset status to NEW
		$this->status = self::STATUS_NEW;
		$this->fk_user_match = null;
		$this->date_match = null;

		$result = $this->update($user);

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

	/**
	 * Link transaction to a Dolibarr object
	 *
	 * @param string $type Object type (facture, facture_fourn, societe, etc.)
	 * @param int $id Object ID
	 * @param User $user User making the link
	 * @return int <0 if KO, >0 if OK
	 */
	public function linkTo($type, $id, $user)
	{
		switch ($type) {
			case 'facture':
				$this->fk_facture = $id;
				break;
			case 'facture_fourn':
				$this->fk_facture_fourn = $id;
				break;
			case 'societe':
				$this->fk_societe = $id;
				break;
			case 'bank':
				$this->fk_bank = $id;
				break;
			case 'paiement':
				$this->fk_paiement = $id;
				break;
			case 'paiementfourn':
				$this->fk_paiementfourn = $id;
				break;
			case 'salary':
				$this->fk_salary = $id;
				break;
			case 'don':
				$this->fk_don = $id;
				break;
			case 'loan':
				$this->fk_loan = $id;
				break;
			default:
				$this->error = 'Unknown link type: '.$type;
				return -1;
		}

		$this->status = self::STATUS_MATCHED;
		return $this->update($user);
	}
}
