* * 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/confirm.php * \ingroup bankimport * \brief Payment confirmation page - match bank transactions to invoices */ // Load Dolibarr environment $res = 0; if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; } $tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1; while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; } if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; } if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; } if (!$res && file_exists("../main.inc.php")) { $res = @include "../main.inc.php"; } if (!$res && file_exists("../../main.inc.php")) { $res = @include "../../main.inc.php"; } if (!$res) { die("Include of main fails"); } require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; dol_include_once('/bankimport/class/banktransaction.class.php'); dol_include_once('/bankimport/lib/bankimport.lib.php'); /** * @var Conf $conf * @var DoliDB $db * @var Translate $langs * @var User $user */ $langs->loadLangs(array("bankimport@bankimport", "banks", "bills")); $action = GETPOST('action', 'aZ09'); // Security check if (!$user->hasRight('bankimport', 'write')) { accessforbidden(); } $bankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID'); /* * Actions */ // Confirm single payment if ($action == 'confirmpayment' && !empty($bankAccountId)) { $transid = GETPOSTINT('transid'); $matchtype = GETPOST('matchtype', 'alpha'); $matchid = GETPOSTINT('matchid'); if ($transid > 0 && !empty($matchtype) && $matchid > 0) { $trans = new BankImportTransaction($db); if ($trans->fetch($transid) > 0) { if ($trans->status != BankImportTransaction::STATUS_NEW) { setEventMessages($langs->trans("TransactionAlreadyProcessed"), null, 'warnings'); } else { $result = $trans->confirmPayment($user, $matchtype, $matchid, $bankAccountId); if ($result > 0) { setEventMessages($langs->trans("PaymentCreatedSuccessfully", dol_escape_htmltag($trans->name), price(abs($trans->amount))), null, 'mesgs'); } else { setEventMessages($trans->error, $trans->errors, 'errors'); } } } } header("Location: ".$_SERVER["PHP_SELF"]."?token=".newToken()); exit; } // Confirm multiple invoices payment if ($action == 'confirmmulti' && !empty($bankAccountId)) { $transid = GETPOSTINT('transid'); $invoiceIds = GETPOST('invoices', 'array'); if ($transid > 0 && !empty($invoiceIds)) { $trans = new BankImportTransaction($db); if ($trans->fetch($transid) > 0) { if ($trans->status != BankImportTransaction::STATUS_NEW) { setEventMessages($langs->trans("TransactionAlreadyProcessed"), null, 'warnings'); } else { // Build invoices array with amounts require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; $invoices = array(); foreach ($invoiceIds as $invId) { $invId = (int) $invId; if ($invId > 0) { $invoice = new FactureFournisseur($db); if ($invoice->fetch($invId) > 0) { $alreadyPaid = $invoice->getSommePaiement(); $creditnotes = $invoice->getSumCreditNotesUsed(); $deposits = $invoice->getSumDepositsUsed(); $remainToPay = price2num($invoice->total_ttc - $alreadyPaid - $creditnotes - $deposits, 'MT'); if ($remainToPay > 0) { $invoices[] = array( 'type' => 'facture_fourn', 'id' => $invId, 'ref' => $invoice->ref, 'amount' => $remainToPay ); } } } } if (!empty($invoices)) { $result = $trans->confirmMultiplePayment($user, $invoices, $bankAccountId); if ($result > 0) { setEventMessages($langs->trans("PaymentCreatedSuccessfully", dol_escape_htmltag($trans->name), price(abs($trans->amount))).' ('.count($invoices).' '.$langs->trans("Invoices").')', null, 'mesgs'); } else { setEventMessages($trans->error, $trans->errors, 'errors'); } } else { setEventMessages($langs->trans("NoInvoicesSelected"), null, 'errors'); } } } } header("Location: ".$_SERVER["PHP_SELF"]."?token=".newToken()); exit; } // Confirm all high-score matches if ($action == 'confirmall' && !empty($bankAccountId)) { $transaction = new BankImportTransaction($db); $transactions = $transaction->fetchAll('date_trans', 'DESC', 0, 0, array('status' => BankImportTransaction::STATUS_NEW)); $created = 0; $failed = 0; if (is_array($transactions)) { foreach ($transactions as $trans) { $matches = $trans->findMatches(); if (!empty($matches) && $matches[0]['match_score'] >= 80) { $bestMatch = $matches[0]; // Handle multi-invoice matches if ($bestMatch['type'] == 'multi_facture_fourn' && !empty($bestMatch['invoices'])) { $invoices = array(); foreach ($bestMatch['invoices'] as $inv) { $invoices[] = array( 'type' => 'facture_fourn', 'id' => $inv['id'], 'ref' => $inv['ref'], 'amount' => $inv['amount'] ); } $result = $trans->confirmMultiplePayment($user, $invoices, $bankAccountId); } else { $result = $trans->confirmPayment($user, $bestMatch['type'], $bestMatch['id'], $bankAccountId); } if ($result > 0) { $created++; } else { $failed++; } } } } if ($created > 0 || $failed > 0) { setEventMessages($langs->trans("PaymentsCreatedSummary", $created, $failed), null, $failed > 0 ? 'warnings' : 'mesgs'); } else { setEventMessages($langs->trans("NoNewMatchesFound"), null, 'warnings'); } header("Location: ".$_SERVER["PHP_SELF"]."?token=".newToken()); exit; } // Ignore transaction if ($action == 'ignore') { $transid = GETPOSTINT('transid'); if ($transid > 0) { $trans = new BankImportTransaction($db); if ($trans->fetch($transid) > 0 && $trans->status == BankImportTransaction::STATUS_NEW) { $trans->setStatus(BankImportTransaction::STATUS_IGNORED, $user); setEventMessages($langs->trans("StatusUpdated"), null, 'mesgs'); } } header("Location: ".$_SERVER["PHP_SELF"]."?token=".newToken()); exit; } /* * View */ $form = new Form($db); $title = $langs->trans("PaymentConfirmation"); llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-bankimport page-confirm'); print load_fiche_titre($title, '', 'bank'); // Check if bank account is configured if (empty($bankAccountId)) { print '
'; print img_warning().' '.$langs->trans("ErrorNoBankAccountConfigured"); print ' '.$langs->trans("GoToSetup").''; print '
'; llxFooter(); $db->close(); exit; } // Description print '
'.$langs->trans("PaymentConfirmationDesc").'
'; // Fetch all new transactions and find matches $transaction = new BankImportTransaction($db); $transactions = $transaction->fetchAll('date_trans', 'DESC', 0, 0, array('status' => BankImportTransaction::STATUS_NEW)); $pendingMatches = array(); // transactions with matches $noMatches = array(); // transactions without matches if (is_array($transactions)) { foreach ($transactions as $trans) { $matches = $trans->findMatches(); if (!empty($matches)) { $pendingMatches[] = array('transaction' => $trans, 'matches' => $matches); } else { $noMatches[] = $trans; } } } // Confirm all button (if high-score matches exist) $highScoreCount = 0; foreach ($pendingMatches as $pm) { if ($pm['matches'][0]['match_score'] >= 80) { $highScoreCount++; } } if ($highScoreCount > 0) { print '
'; print ''; print $langs->trans("ConfirmAllHighScore").' ('.$highScoreCount.')'; print ''; print '
'; } // Match reasons translation $reasonLabels = array( 'ref' => $langs->trans("MatchByRef"), 'ref_client' => $langs->trans("MatchByClientRef"), 'ref_supplier' => $langs->trans("MatchBySupplierRef"), 'amount' => $langs->trans("MatchByAmount"), 'amount_close' => $langs->trans("MatchByAmountClose"), 'name_exact' => $langs->trans("MatchByNameExact"), 'name_similar' => $langs->trans("MatchByNameSimilar"), 'iban' => $langs->trans("MatchByIBAN"), 'multi_invoice' => $langs->trans("MatchByMultiInvoice") ); if (!empty($pendingMatches)) { print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; foreach ($pendingMatches as $pm) { $trans = $pm['transaction']; $bestMatch = $pm['matches'][0]; $isMultiInvoice = ($bestMatch['type'] == 'multi_facture_fourn'); // Score color $scoreColor = $bestMatch['match_score'] >= 80 ? '#4caf50' : ($bestMatch['match_score'] >= 60 ? '#ff9800' : '#9e9e9e'); print ''; // Transaction date print ''; // Counterparty name + description print ''; // Transaction amount print ''; // Arrow print ''; // Invoice reference(s) print ''; // Third party print ''; // Invoice amount print ''; // Score print ''; // Match reasons print ''; // Actions print ''; print ''; } print '
'.$langs->trans("Date").''.$langs->trans("Counterparty").''.$langs->trans("Amount").' ('.$langs->trans("Transaction").')'.$langs->trans("Invoice").''.$langs->trans("ThirdParty").''.$langs->trans("Amount").' ('.$langs->trans("Invoice").')'.$langs->trans("Score").''.$langs->trans("MatchReason").''.$langs->trans("Action").'
'.dol_print_date($trans->date_trans, 'day').''; print ''.dol_escape_htmltag(dol_trunc($trans->name, 30)).''; if ($trans->description) { print '
'.dol_escape_htmltag(dol_trunc($trans->description, 50)).''; } print '
'; if ($trans->amount >= 0) { print '+'.price($trans->amount, 0, $langs, 1, -1, 2, $trans->currency).''; } else { print ''.price($trans->amount, 0, $langs, 1, -1, 2, $trans->currency).''; } print ''; if ($isMultiInvoice && !empty($bestMatch['invoices'])) { // Multi-invoice display require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; print ''.count($bestMatch['invoices']).' '.$langs->trans("Invoices").':
'; foreach ($bestMatch['invoices'] as $invData) { $inv = new FactureFournisseur($db); $inv->fetch($invData['id']); print $inv->getNomUrl(1).' ('.price($invData['amount'], 0, $langs, 1, -1, 2, 'EUR').')
'; } } elseif ($bestMatch['type'] == 'facture') { require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; $inv = new Facture($db); $inv->fetch($bestMatch['id']); print $inv->getNomUrl(1); } else { require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; $inv = new FactureFournisseur($db); $inv->fetch($bestMatch['id']); print $inv->getNomUrl(1); } print '
'; if ($bestMatch['socid'] > 0) { require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; $soc = new Societe($db); $soc->fetch($bestMatch['socid']); print $soc->getNomUrl(1); } else { print dol_escape_htmltag($bestMatch['socname']); } print ''; print price($bestMatch['amount'], 0, $langs, 1, -1, 2, 'EUR'); if ($isMultiInvoice && isset($bestMatch['difference']) && abs($bestMatch['difference']) > 0.01) { $diffColor = $bestMatch['difference'] > 0 ? 'orange' : 'green'; print '
'.($bestMatch['difference'] > 0 ? '+' : '').price($bestMatch['difference'], 0, $langs, 1, -1, 2, 'EUR').''; } print '
'.$bestMatch['match_score'].'%'; if (!empty($bestMatch['match_reasons'])) { foreach ($bestMatch['match_reasons'] as $reason) { $label = $reasonLabels[$reason] ?? $reason; print ''.$label.' '; } } print ''; if ($isMultiInvoice && !empty($bestMatch['invoices'])) { // Multi-invoice: Form with checkboxes for selection print '
'; print ''; print ''; print ''; print '
'; foreach ($bestMatch['invoices'] as $invData) { print ''; } print '
'; print ''; print '
'; } else { // Single invoice: Confirm payment button print 'id.'&matchtype='.urlencode($bestMatch['type']).'&matchid='.$bestMatch['id'].'&token='.newToken().'">'; print $langs->trans("ConfirmPayment"); print ''; } print '
'; // Ignore button print 'id.'&token='.newToken().'">'; print $langs->trans("SetAsIgnored"); print ''; // Show alternatives if multiple matches if (count($pm['matches']) > 1) { print '
'; print '+'.($count = count($pm['matches']) - 1).' '.$langs->trans("Alternatives"); print ''; } print '
'; print '
'; } else { print '
'; print $langs->trans("NoNewMatchesFound"); print '
'; } // Show unmatched transactions count if (!empty($noMatches)) { print '
'; print '
'; print img_picto('', 'info', 'class="pictofixedwidth"'); print $langs->trans("UnmatchedTransactions", count($noMatches)); print ' '.$langs->trans("ShowAll").''; print '
'; } llxFooter(); $db->close();