*
* 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 '
cd '.dirname(__FILE__).' && composer install';
print '| '.$langs->trans("Date").' | '; print ''.$langs->trans("Name").' | '; print ''.$langs->trans("Description").' | '; print ''.$langs->trans("Amount").' | '; print '
|---|---|---|---|
| '.dol_print_date($trans['date'], 'day').' | '; print ''.dol_escape_htmltag($trans['name']).' | '; print ''.dol_escape_htmltag(dol_trunc($trans['reference'], 80)).' | '; print ''; 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 ' | '; print '
| '.$langs->trans("Total").' | '; print ''; 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 ' | '; print '||