* * 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/statements.php * \ingroup bankimport * \brief Page to fetch and display bank statements via FinTS */ // 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/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; dol_include_once('/bankimport/class/fints.class.php'); dol_include_once('/bankimport/class/banktransaction.class.php'); dol_include_once('/bankimport/class/bankimportcron.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")); $action = GETPOST('action', 'aZ09'); // Security check if (!$user->hasRight('bankimport', 'write')) { accessforbidden(); } // Date filters $date_fromday = GETPOSTINT('date_fromday'); $date_frommonth = GETPOSTINT('date_frommonth'); $date_fromyear = GETPOSTINT('date_fromyear'); $date_today = GETPOSTINT('date_today'); $date_tomonth = GETPOSTINT('date_tomonth'); $date_toyear = GETPOSTINT('date_toyear'); $dateFrom = 0; $dateTo = 0; if ($date_fromday && $date_frommonth && $date_fromyear) { $dateFrom = dol_mktime(0, 0, 0, $date_frommonth, $date_fromday, $date_fromyear); } if ($date_today && $date_tomonth && $date_toyear) { $dateTo = dol_mktime(23, 59, 59, $date_tomonth, $date_today, $date_toyear); } // Default: last 30 days if (empty($dateFrom)) { $dateFrom = dol_time_plus_duree(dol_now(), -30, 'd'); } if (empty($dateTo)) { $dateTo = dol_now(); } /* * Actions */ $transactions = array(); $balance = array(); $error = 0; $tanRequired = false; $tanChallenge = ''; $importResult = null; // Import transactions to database if ($action == 'import' && !empty($_SESSION['fints_transactions'])) { $fints = new BankImportFinTS($db); $iban = $fints->getIban(); $transImporter = new BankImportTransaction($db); $importResult = $transImporter->importFromFinTS($_SESSION['fints_transactions'], $iban, $user); if ($importResult['imported'] > 0) { setEventMessages($langs->trans("TransactionsImported", $importResult['imported']), null, 'mesgs'); } if ($importResult['skipped'] > 0) { setEventMessages($langs->trans("TransactionsSkipped", $importResult['skipped']), null, 'warnings'); } if (!empty($importResult['errors'])) { setEventMessages(implode('
', $importResult['errors']), null, 'errors'); } // Clear session after import unset($_SESSION['fints_transactions']); unset($_SESSION['fints_balance']); } // Resume pending cron TAN if ($action == 'resumecron') { $cronJob = new BankImportCron($db); $result = $cronJob->resumePendingAction(); if ($result > 0 || $result == 0) { if ($result > 0) { setEventMessages($langs->trans("TANConfirmedImportComplete"), null, 'mesgs'); } else { setEventMessages($langs->trans("WaitingForSecureGoConfirmation"), null, 'warnings'); } } else { setEventMessages($langs->trans("TANCheckFailed").': '.$cronJob->error, null, 'errors'); } } if ($action == 'fetch') { $fints = new BankImportFinTS($db); if (!$fints->isConfigured()) { setEventMessages($langs->trans("FinTSNotConfigured"), null, 'errors'); $error++; } elseif (!$fints->isLibraryAvailable()) { setEventMessages($langs->trans("FinTSLibraryNotFound"), null, 'errors'); $error++; } else { // Login first $loginResult = $fints->login(); if ($loginResult < 0) { setEventMessages($langs->trans("LoginFailed").': '.$fints->error, null, 'errors'); $error++; } elseif ($loginResult == 0) { // TAN required $tanRequired = true; $tanChallenge = $fints->tanChallenge; // Check if decoupled (SecureGo Plus) if ($fints->selectedTanMode && $fints->selectedTanMode->isDecoupled()) { setEventMessages($langs->trans("SecureGoPlusConfirmRequired"), null, 'warnings'); // Store state in session for polling $_SESSION['fints_state'] = $fints->persist(); $_SESSION['fints_action'] = 'login'; } else { setEventMessages($langs->trans("TANRequired").': '.$tanChallenge, null, 'warnings'); } } else { // Login successful - log bank parameters for diagnostics $bankParams = $fints->getBankParameters(); dol_syslog("BankImport: Bank parameters: ".json_encode($bankParams), LOG_DEBUG); // Check what statement methods are supported $hikazsVersions = $bankParams['HIKAZS'] ?? array(); $hicazsVersions = $bankParams['HICAZS'] ?? array(); $hiekaVersions = $bankParams['HIEKAS'] ?? array(); dol_syslog("BankImport: HIKAZS versions: ".implode(',', $hikazsVersions)." | HICAZS: ".implode(',', $hicazsVersions)." | HIEKAS: ".implode(',', $hiekaVersions), LOG_DEBUG); // Fetch statements $result = $fints->fetchStatements($dateFrom, $dateTo); if ($result === 0) { // TAN required for statements $tanRequired = true; $tanChallenge = $fints->tanChallenge; // Store state in session for polling $_SESSION['fints_state'] = $fints->persist(); $_SESSION['fints_pending_action'] = serialize($fints->getPendingAction()); $_SESSION['fints_action'] = 'statements'; $_SESSION['fints_datefrom'] = $dateFrom; $_SESSION['fints_dateto'] = $dateTo; setEventMessages($langs->trans("SecureGoPlusConfirmRequired"), null, 'warnings'); } elseif ($result < 0) { setEventMessages($langs->trans("FetchFailed").': '.$fints->error, null, 'errors'); // Show supported versions for diagnostics if (!empty($hikazsVersions) || !empty($hicazsVersions)) { $diagMsg = 'Bank unterstützt: HIKAZS v'.implode(',v', $hikazsVersions); if (!empty($hicazsVersions)) { $diagMsg .= ' | HICAZS v'.implode(',v', $hicazsVersions); } setEventMessages($diagMsg, null, 'warnings'); } $error++; } elseif (is_array($result)) { $transactions = $result['transactions'] ?? array(); $balance = $result['balance'] ?? array(); $isPartial = $result['partial'] ?? false; $infoMsg = $result['info'] ?? ''; if (empty($transactions)) { setEventMessages($langs->trans("NoTransactionsFound"), null, 'warnings'); } else { setEventMessages($langs->trans("TransactionsFound", count($transactions)), null, 'mesgs'); // Store in session for import $_SESSION['fints_transactions'] = $transactions; $_SESSION['fints_balance'] = $balance; // Show info about partial results (older data not available) if ($isPartial && !empty($infoMsg)) { setEventMessages($infoMsg, null, 'warnings'); } } // Save session state for cronjob before closing $cronState = $fints->persist(); if (!empty($cronState)) { dolibarr_set_const($db, 'BANKIMPORT_CRON_STATE', $cronState, 'chaine', 0, '', $conf->entity); dol_syslog("BankImport: Saved session state for cronjob", LOG_DEBUG); } // Only close on success $fints->close(); } // NOTE: Don't call close() when TAN is required ($result === 0) // The connection must stay open for checkDecoupledTan() } } } // Check decoupled TAN status (SecureGo Plus polling) if ($action == 'checktan') { if (!empty($_SESSION['fints_state']) && !empty($_SESSION['fints_pending_action'])) { $fints = new BankImportFinTS($db); $fints->restore($_SESSION['fints_state']); $fints->setPendingAction(unserialize($_SESSION['fints_pending_action'])); $result = $fints->checkDecoupledTan(); if ($result > 0) { // TAN confirmed $savedAction = $_SESSION['fints_action'] ?? 'statements'; $savedDateFrom = $_SESSION['fints_datefrom'] ?? $dateFrom; $savedDateTo = $_SESSION['fints_dateto'] ?? $dateTo; unset($_SESSION['fints_state']); unset($_SESSION['fints_pending_action']); unset($_SESSION['fints_action']); unset($_SESSION['fints_datefrom']); unset($_SESSION['fints_dateto']); // Fetch statements after TAN confirmation $result = $fints->fetchStatements($savedDateFrom, $savedDateTo); if ($result === 0) { // Another TAN required (unlikely but possible) $_SESSION['fints_state'] = $fints->persist(); $_SESSION['fints_action'] = 'statements'; $_SESSION['fints_datefrom'] = $savedDateFrom; $_SESSION['fints_dateto'] = $savedDateTo; setEventMessages($langs->trans("SecureGoPlusConfirmRequired"), null, 'warnings'); } elseif (is_array($result)) { $transactions = $result['transactions'] ?? array(); $balance = $result['balance'] ?? array(); setEventMessages($langs->trans("TransactionsFound", count($transactions)), null, 'mesgs'); // Store transactions for import $_SESSION['fints_transactions'] = $transactions; $_SESSION['fints_balance'] = $balance; // Save session state for cronjob before closing $cronState = $fints->persist(); if (!empty($cronState)) { dolibarr_set_const($db, 'BANKIMPORT_CRON_STATE', $cronState, 'chaine', 0, '', $conf->entity); dol_syslog("BankImport: Saved session state for cronjob after TAN confirmation", LOG_DEBUG); } $fints->close(); } else { setEventMessages($langs->trans("FetchFailed").': '.$fints->error, null, 'errors'); } } elseif ($result == 0) { // Still waiting setEventMessages($langs->trans("WaitingForSecureGoConfirmation"), null, 'warnings'); $_SESSION['fints_state'] = $fints->persist(); } else { setEventMessages($langs->trans("TANCheckFailed").': '.$fints->error, null, 'errors'); unset($_SESSION['fints_state']); } } else { setEventMessages("Keine aktive Session. Bitte erneut abrufen.", null, 'errors'); unset($_SESSION['fints_state']); unset($_SESSION['fints_pending_action']); } } /* * View */ $form = new Form($db); $title = $langs->trans("BankStatements"); llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-bankimport page-statements'); print load_fiche_titre($title, '', 'bank'); // Check configuration $fints = new BankImportFinTS($db); if (!$fints->isConfigured()) { print '
'; print $langs->trans("FinTSNotConfigured"); print ' '.$langs->trans("GoToSetup").''; print '
'; llxFooter(); $db->close(); exit; } // Check for pending cron TAN notification $cronNotification = BankImportCron::getNotification(); if ($cronNotification !== null) { $notifType = $cronNotification['type']; $notifDate = $cronNotification['date']; if ($notifType === 'tan_required') { print '
'; print ''.$langs->trans("AutoImportTANRequired").'
'; print $langs->trans("AutoImportTANRequiredDesc"); print ' '.$langs->trans("CheckSecureGoStatus").''; print '

'; } elseif (in_array($notifType, array('login_error', 'fetch_error', 'config_error', 'error'))) { print '
'; print ''.$langs->trans("AutoImportError").'
'; print $langs->trans("AutoImportErrorDesc", dol_print_date($notifDate, 'dayhour')); print '

'; } } // Check for old data warning $lastFetch = getDolGlobalInt('BANKIMPORT_LAST_FETCH'); if ($lastFetch > 0 && (time() - $lastFetch) > 14 * 86400) { print '
'; print $langs->trans("LastFetchWarning", dol_print_date($lastFetch, 'day')); print '

'; } if (!$fints->isLibraryAvailable()) { print '
'; print $langs->trans("FinTSLibraryNotFound"); print '
cd '.dirname(__FILE__).' && composer install'; print '
'; llxFooter(); $db->close(); exit; } // Filter form print '
'; print ''; print ''; print '
'; print ''; // IBAN display print ''; print ''; print ''; print ''; // Date from print ''; print ''; print ''; print ''; // Date to print ''; print ''; print ''; print ''; print '
'.$langs->trans("Account").''.dol_escape_htmltag($fints->getIban()).'
'.$langs->trans("DateFrom").''; print $form->selectDate($dateFrom, 'date_from', 0, 0, 0, '', 1, 1); print '
'.$langs->trans("DateTo").''; print $form->selectDate($dateTo, 'date_to', 0, 0, 0, '', 1, 1); print '
'; print '
'; print '
'; print ''; // SecureGo Plus polling button if (!empty($_SESSION['fints_state'])) { print ' '.$langs->trans("CheckSecureGoStatus").''; } print '
'; print '
'; // JavaScript for automatic TAN polling if (!empty($_SESSION['fints_state'])) { print ' '; } print '
'; print '
'; // Display account balance from bank if (!empty($balance)) { print '
'; print '
'; print ''.$langs->trans("AccountBalance").': '; $balColor = $balance['amount'] >= 0 ? 'green' : 'red'; print ''.price($balance['amount'], 0, $langs, 1, -1, 2, $balance['currency']).''; print ' ('.$langs->trans("AsOf").' '.dol_print_date(strtotime($balance['date']), 'day').')'; print '
'; } // Display transactions if (!empty($transactions)) { print '
'; print '

'.$langs->trans("Transactions").' ('.count($transactions).')

'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; $totalCredit = 0; $totalDebit = 0; foreach ($transactions as $trans) { print ''; print ''; print ''; print ''; print ''; print ''; } // Totals print ''; print ''; print ''; print ''; print '
'.$langs->trans("Date").''.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Amount").'
'.dol_print_date($trans['date'], 'day').''.dol_escape_htmltag($trans['name']).''.dol_escape_htmltag(dol_trunc($trans['reference'], 80)).''; if ($trans['amount'] >= 0) { $totalCredit += $trans['amount']; print '+'.price($trans['amount'], 0, $langs, 1, -1, 2, 'EUR').''; } else { $totalDebit += abs($trans['amount']); print ''.price($trans['amount'], 0, $langs, 1, -1, 2, 'EUR').''; } print '
'.$langs->trans("Total").''; print '+'.price($totalCredit, 0, $langs, 1, -1, 2, 'EUR').''; print ' / '; print '-'.price($totalDebit, 0, $langs, 1, -1, 2, 'EUR').''; print ' = '; $balance = $totalCredit - $totalDebit; $balanceColor = $balance >= 0 ? 'green' : 'red'; print ''.price($balance, 0, $langs, 1, -1, 2, 'EUR').''; print '
'; // Import button print '
'; print '
'; print ''; print ''; print ''; print ' '.$langs->trans("ViewImportedTransactions").''; print '
'; print '
'; } print '
'; // End transactions-container llxFooter(); $db->close();