diff --git a/card.php b/card.php index 573235c..5486992 100755 --- a/card.php +++ b/card.php @@ -765,21 +765,20 @@ if ($object->id > 0) { $isPaid = ($obj->fk_statut == 2); - // For paid invoices, check if payment is already linked to a bank entry - $hasLinkedBankEntry = false; + // For paid invoices, check if already linked via BankImport transaction + $isLinkedViaBankImport = false; if ($isPaid) { - $sqlPay = "SELECT pf.fk_bank FROM ".MAIN_DB_PREFIX."paiementfourn pf"; - $sqlPay .= " JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn pff ON pf.rowid = pff.fk_paiementfourn"; - $sqlPay .= " WHERE pff.fk_facturefourn = ".((int) $obj->rowid); - $sqlPay .= " AND pf.fk_bank IS NOT NULL AND pf.fk_bank > 0"; - $resqlPay = $db->query($sqlPay); - if ($resqlPay && $db->num_rows($resqlPay) > 0) { - $hasLinkedBankEntry = true; + $sqlBi = "SELECT rowid FROM ".MAIN_DB_PREFIX."bankimport_transaction"; + $sqlBi .= " WHERE fk_facture_fourn = ".((int) $obj->rowid); + $sqlBi .= " AND status > 0"; + $resqlBi = $db->query($sqlBi); + if ($resqlBi && $db->num_rows($resqlBi) > 0) { + $isLinkedViaBankImport = true; } } - // Show if unpaid OR if paid but not yet linked to bank - if ($remainToPay > 0 || ($isPaid && !$hasLinkedBankEntry)) { + // Show if unpaid OR if paid but not yet linked via BankImport + if ($remainToPay > 0 || ($isPaid && !$isLinkedViaBankImport)) { $invoiceList[] = array( 'id' => $obj->rowid, 'ref' => $obj->ref, @@ -831,21 +830,20 @@ if ($object->id > 0) { $isPaid = ($obj->fk_statut == 2); - // For paid invoices, check if payment is already linked to a bank entry - $hasLinkedBankEntry = false; + // For paid invoices, check if already linked via BankImport transaction + $isLinkedViaBankImport = false; if ($isPaid) { - $sqlPay = "SELECT p.fk_bank FROM ".MAIN_DB_PREFIX."paiement p"; - $sqlPay .= " JOIN ".MAIN_DB_PREFIX."paiement_facture pf ON p.rowid = pf.fk_paiement"; - $sqlPay .= " WHERE pf.fk_facture = ".((int) $obj->rowid); - $sqlPay .= " AND p.fk_bank IS NOT NULL AND p.fk_bank > 0"; - $resqlPay = $db->query($sqlPay); - if ($resqlPay && $db->num_rows($resqlPay) > 0) { - $hasLinkedBankEntry = true; + $sqlBi = "SELECT rowid FROM ".MAIN_DB_PREFIX."bankimport_transaction"; + $sqlBi .= " WHERE fk_facture = ".((int) $obj->rowid); + $sqlBi .= " AND status > 0"; + $resqlBi = $db->query($sqlBi); + if ($resqlBi && $db->num_rows($resqlBi) > 0) { + $isLinkedViaBankImport = true; } } - // Show if unpaid OR if paid but not yet linked to bank - if ($remainToPay > 0 || ($isPaid && !$hasLinkedBankEntry)) { + // Show if unpaid OR if paid but not yet linked via BankImport + if ($remainToPay > 0 || ($isPaid && !$isLinkedViaBankImport)) { $invoiceList[] = array( 'id' => $obj->rowid, 'ref' => $obj->ref, diff --git a/core/modules/modBankImport.class.php b/core/modules/modBankImport.class.php index 993db47..bdcdf8b 100755 --- a/core/modules/modBankImport.class.php +++ b/core/modules/modBankImport.class.php @@ -76,7 +76,7 @@ class modBankImport extends DolibarrModules $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@bankimport' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '2.0'; + $this->version = '2.5'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; diff --git a/langs/de_DE/bankimport.lang b/langs/de_DE/bankimport.lang index 81a4d36..717be3b 100755 --- a/langs/de_DE/bankimport.lang +++ b/langs/de_DE/bankimport.lang @@ -354,3 +354,13 @@ CannotUnlinkThisStatus = Verknüpfung kann bei diesem Status nicht aufgehoben we Payment = Zahlung LinkedInvoices = Verknüpfte Rechnungen NoInvoicesLinkedToPayment = Keine Rechnungen mit dieser Zahlung verknüpft + +# +# Repair Page +# +RepairOrphanedTransactions = Verwaiste Buchungen reparieren +RepairOrphanedTransactionsDesc = Diese Seite findet BankImport-Buchungen, die noch als "Neu" markiert sind, obwohl die Zahlung bereits in Dolibarr existiert und mit der Bank verknüpft ist. Dies kann bei älteren Modulversionen vorkommen. +NoOrphanedTransactionsFound = Keine verwaisten Buchungen gefunden. Alle BankImport-Buchungen sind korrekt verknüpft. +OrphanedTransactionsFound = %s verwaiste Buchung(en) gefunden +RepairAll = Alle reparieren +Repair = Reparieren diff --git a/langs/en_US/bankimport.lang b/langs/en_US/bankimport.lang index 09f69a2..b2f7b68 100755 --- a/langs/en_US/bankimport.lang +++ b/langs/en_US/bankimport.lang @@ -250,3 +250,13 @@ CannotUnlinkThisStatus = Cannot unlink with this status Payment = Payment LinkedInvoices = Linked Invoices NoInvoicesLinkedToPayment = No invoices linked to this payment + +# +# Repair Page +# +RepairOrphanedTransactions = Repair Orphaned Transactions +RepairOrphanedTransactionsDesc = This page finds BankImport transactions that are still marked as "New" even though the payment already exists in Dolibarr and is linked to the bank. This can happen with older module versions. +NoOrphanedTransactionsFound = No orphaned transactions found. All BankImport transactions are correctly linked. +OrphanedTransactionsFound = %s orphaned transaction(s) found +RepairAll = Repair All +Repair = Repair diff --git a/repair.php b/repair.php new file mode 100644 index 0000000..d43af60 --- /dev/null +++ b/repair.php @@ -0,0 +1,396 @@ + + * + * 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/repair.php + * \ingroup bankimport + * \brief Repair orphaned BankImport transactions + */ + +// 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'; +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", "admin")); + +// Security check - only admin +if (!$user->admin) { + accessforbidden(); +} + +$action = GETPOST('action', 'aZ09'); + +/* + * Actions + */ + +// Repair a single transaction +if ($action == 'repair' && GETPOSTINT('transid') > 0) { + $transid = GETPOSTINT('transid'); + $paymentId = GETPOSTINT('payment_id'); + $bankId = GETPOSTINT('bank_id'); + $invoiceId = GETPOSTINT('invoice_id'); + $invoiceType = GETPOST('invoice_type', 'alpha'); + + $db->begin(); + + $sql = "UPDATE ".MAIN_DB_PREFIX."bankimport_transaction SET"; + $sql .= " status = 1"; + if ($paymentId > 0) { + if ($invoiceType == 'facture') { + $sql .= ", fk_paiement = ".((int) $paymentId); + } else { + $sql .= ", fk_paiementfourn = ".((int) $paymentId); + } + } + if ($bankId > 0) { + $sql .= ", fk_bank = ".((int) $bankId); + } + if ($invoiceId > 0) { + if ($invoiceType == 'facture') { + $sql .= ", fk_facture = ".((int) $invoiceId); + } else { + $sql .= ", fk_facture_fourn = ".((int) $invoiceId); + } + } + $sql .= " WHERE rowid = ".((int) $transid); + + $result = $db->query($sql); + if ($result) { + $db->commit(); + setEventMessages($langs->trans("RecordModifiedSuccessfully"), null, 'mesgs'); + } else { + $db->rollback(); + setEventMessages($db->lasterror(), null, 'errors'); + } + + header("Location: ".$_SERVER["PHP_SELF"]."?token=".newToken()); + exit; +} + +// Repair all found orphans +if ($action == 'repairall') { + $orphans = findOrphanedTransactions($db, $conf); + $repaired = 0; + $failed = 0; + + $db->begin(); + + foreach ($orphans as $orphan) { + $sql = "UPDATE ".MAIN_DB_PREFIX."bankimport_transaction SET"; + $sql .= " status = 1"; + if (!empty($orphan['payment_id'])) { + if ($orphan['invoice_type'] == 'facture') { + $sql .= ", fk_paiement = ".((int) $orphan['payment_id']); + } else { + $sql .= ", fk_paiementfourn = ".((int) $orphan['payment_id']); + } + } + if (!empty($orphan['bank_id'])) { + $sql .= ", fk_bank = ".((int) $orphan['bank_id']); + } + if (!empty($orphan['invoice_id'])) { + if ($orphan['invoice_type'] == 'facture') { + $sql .= ", fk_facture = ".((int) $orphan['invoice_id']); + } else { + $sql .= ", fk_facture_fourn = ".((int) $orphan['invoice_id']); + } + } + $sql .= " WHERE rowid = ".((int) $orphan['trans_id']); + + if ($db->query($sql)) { + $repaired++; + } else { + $failed++; + } + } + + if ($failed == 0) { + $db->commit(); + setEventMessages($langs->trans("RecordsModified", $repaired), null, 'mesgs'); + } else { + $db->rollback(); + setEventMessages($langs->trans("ErrorsOccurred", $failed), null, 'errors'); + } + + header("Location: ".$_SERVER["PHP_SELF"]."?token=".newToken()); + exit; +} + +/** + * Find orphaned BankImport transactions + * These are transactions with status=0 (NEW) but where payments already exist in Dolibarr + */ +function findOrphanedTransactions($db, $conf) +{ + $orphans = array(); + + // Find customer invoice orphans + $sql = "SELECT bt.rowid as trans_id, bt.ref as trans_ref, bt.amount, bt.name, bt.date_trans,"; + $sql .= " f.rowid as invoice_id, f.ref as invoice_ref, f.total_ttc,"; + $sql .= " p.rowid as payment_id, p.fk_bank as bank_id"; + $sql .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction bt"; + $sql .= " JOIN ".MAIN_DB_PREFIX."facture f ON ("; + $sql .= " bt.description LIKE CONCAT('%', f.ref, '%')"; + $sql .= " OR bt.end_to_end_id LIKE CONCAT('%', f.ref, '%')"; + $sql .= " )"; + $sql .= " JOIN ".MAIN_DB_PREFIX."paiement_facture pf ON pf.fk_facture = f.rowid"; + $sql .= " JOIN ".MAIN_DB_PREFIX."paiement p ON p.rowid = pf.fk_paiement"; + $sql .= " WHERE bt.status = 0"; + $sql .= " AND bt.fk_paiement IS NULL"; + $sql .= " AND p.fk_bank IS NOT NULL"; + $sql .= " AND f.entity = ".$conf->entity; + + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $orphans[] = array( + 'trans_id' => $obj->trans_id, + 'trans_ref' => $obj->trans_ref, + 'trans_amount' => $obj->amount, + 'trans_name' => $obj->name, + 'trans_date' => $obj->date_trans, + 'invoice_id' => $obj->invoice_id, + 'invoice_ref' => $obj->invoice_ref, + 'invoice_amount' => $obj->total_ttc, + 'payment_id' => $obj->payment_id, + 'bank_id' => $obj->bank_id, + 'invoice_type' => 'facture' + ); + } + } + + // Find supplier invoice orphans + $sql = "SELECT bt.rowid as trans_id, bt.ref as trans_ref, bt.amount, bt.name, bt.date_trans,"; + $sql .= " f.rowid as invoice_id, f.ref as invoice_ref, f.ref_supplier, f.total_ttc,"; + $sql .= " p.rowid as payment_id, p.fk_bank as bank_id"; + $sql .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction bt"; + $sql .= " JOIN ".MAIN_DB_PREFIX."facture_fourn f ON ("; + $sql .= " bt.description LIKE CONCAT('%', f.ref, '%')"; + $sql .= " OR bt.description LIKE CONCAT('%', f.ref_supplier, '%')"; + $sql .= " OR bt.end_to_end_id LIKE CONCAT('%', f.ref, '%')"; + $sql .= " )"; + $sql .= " JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn pff ON pff.fk_facturefourn = f.rowid"; + $sql .= " JOIN ".MAIN_DB_PREFIX."paiementfourn p ON p.rowid = pff.fk_paiementfourn"; + $sql .= " WHERE bt.status = 0"; + $sql .= " AND bt.fk_paiementfourn IS NULL"; + $sql .= " AND p.fk_bank IS NOT NULL"; + $sql .= " AND f.entity = ".$conf->entity; + + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $orphans[] = array( + 'trans_id' => $obj->trans_id, + 'trans_ref' => $obj->trans_ref, + 'trans_amount' => $obj->amount, + 'trans_name' => $obj->name, + 'trans_date' => $obj->date_trans, + 'invoice_id' => $obj->invoice_id, + 'invoice_ref' => $obj->invoice_ref, + 'invoice_ref_supplier' => $obj->ref_supplier, + 'invoice_amount' => $obj->total_ttc, + 'payment_id' => $obj->payment_id, + 'bank_id' => $obj->bank_id, + 'invoice_type' => 'facture_fourn' + ); + } + } + + return $orphans; +} + +/* + * View + */ + +$title = $langs->trans("RepairOrphanedTransactions"); + +llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-bankimport page-repair'); + +print load_fiche_titre($title, '', 'tools'); + +// Find orphans +$orphans = findOrphanedTransactions($db, $conf); + +print '
| '.$langs->trans("Date").' | '; + print ''.$langs->trans("TransactionRef").' | '; + print ''.$langs->trans("Counterparty").' | '; + print ''.$langs->trans("Amount").' | '; + print '→ | '; + print ''.$langs->trans("Invoice").' | '; + print ''.$langs->trans("Amount").' | '; + print ''.$langs->trans("Payment").' | '; + print ''.$langs->trans("BankEntry").' | '; + print ''.$langs->trans("Action").' | '; + print '
|---|---|---|---|---|---|---|---|---|---|
| '.dol_print_date($orphan['trans_date'], 'day').' | '; + + // Transaction ref + print ''; + print ''; + print dol_trunc($orphan['trans_ref'], 12); + print ''; + print ' | '; + + // Counterparty + print ''.dol_escape_htmltag($orphan['trans_name']).' | '; + + // Transaction amount + print ''; + if ($orphan['trans_amount'] >= 0) { + print ''.price($orphan['trans_amount'], 0, $langs, 1, -1, 2, 'EUR').''; + } else { + print ''.price($orphan['trans_amount'], 0, $langs, 1, -1, 2, 'EUR').''; + } + print ' | '; + + // Arrow + print '→ | '; + + // Invoice + print ''; + if ($orphan['invoice_type'] == 'facture') { + require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + $inv = new Facture($db); + $inv->fetch($orphan['invoice_id']); + print $inv->getNomUrl(1); + } else { + require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; + $inv = new FactureFournisseur($db); + $inv->fetch($orphan['invoice_id']); + print $inv->getNomUrl(1); + if (!empty($orphan['invoice_ref_supplier'])) { + print ' ('.$orphan['invoice_ref_supplier'].')'; + } + } + print ' | '; + + // Invoice amount + print ''.price($orphan['invoice_amount'], 0, $langs, 1, -1, 2, 'EUR').' | '; + + // Payment + print ''; + if ($orphan['invoice_type'] == 'facture') { + require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php'; + $pay = new Paiement($db); + $pay->fetch($orphan['payment_id']); + print $pay->getNomUrl(1); + } else { + require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php'; + $pay = new PaiementFourn($db); + $pay->fetch($orphan['payment_id']); + print $pay->getNomUrl(1); + } + print ' | '; + + // Bank entry + print ''; + print ''; + print img_picto('', 'bank_account', 'class="pictofixedwidth"'); + print '#'.$orphan['bank_id']; + print ''; + print ' | '; + + // Action + print ''; + print ''; + print ' | '; + + print '