* * 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/pdfstatements.php * \ingroup bankimport * \brief Page to upload and manage PDF bank statements */ // 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/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; dol_include_once('/bankimport/class/bankstatement.class.php'); dol_include_once('/bankimport/class/fints.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", "other")); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); $year = GETPOSTISSET('year') ? GETPOSTINT('year') : (int) date('Y'); // Security check if (!$user->hasRight('bankimport', 'read')) { accessforbidden(); } /* * Actions */ $statement = new BankImportStatement($db); // Fetch PDF statement from bank via HKEKP if ($action == 'fetchpdf' || $action == 'fetchpdf_single') { if (!$user->hasRight('bankimport', 'write')) { accessforbidden(); } $fetchNumber = ($action == 'fetchpdf_single') ? GETPOSTINT('fetch_number') : null; $fetchYear = ($action == 'fetchpdf_single') ? GETPOSTINT('fetch_year') : null; // Check FinTS configuration $fintsUrl = getDolGlobalString('BANKIMPORT_FINTS_URL'); $fintsBlz = getDolGlobalString('BANKIMPORT_FINTS_BLZ'); $fintsUser = getDolGlobalString('BANKIMPORT_FINTS_USERNAME'); $fintsPin = getDolGlobalString('BANKIMPORT_FINTS_PIN'); if (empty($fintsUrl) || empty($fintsBlz) || empty($fintsUser) || empty($fintsPin)) { setEventMessages($langs->trans("ErrorFinTSNotConfigured"), null, 'errors'); } else { $fints = new BankImportFinTS($db); $loginResult = $fints->login(); if ($loginResult < 0) { setEventMessages($langs->trans("ErrorFinTSConnection").': '.$fints->error, null, 'errors'); } elseif ($loginResult === 0) { // TAN required for login setEventMessages($langs->trans("TANRequired").($fints->tanChallenge ? ': '.$fints->tanChallenge : ''), null, 'warnings'); $fints->close(); } else { // Check if bank supports any PDF statement method (HKEKP or HKKAA) $pdfMethod = $fints->getPdfStatementMethod(); if ($pdfMethod === false) { setEventMessages($langs->trans("ErrorBankDoesNotSupportPdfStatements"), null, 'errors'); $fints->close(); } else { dol_syslog("BankImport: Using PDF method: ".$pdfMethod, LOG_DEBUG); // Fetch PDF using auto method (tries HKEKP first, falls back to HKKAA) $pdfResult = $fints->getStatementPDFAuto(0, $fetchNumber, $fetchYear); if ($pdfResult === 0) { // TAN required - save to session and show TAN form $_SESSION['bankimport_pending_action'] = serialize($fints); setEventMessages($langs->trans("TANRequired").': '.$fints->tanChallenge, null, 'warnings'); } elseif ($pdfResult === -1) { setEventMessages($langs->trans("ErrorFetchingPdfStatement").': '.$fints->error, null, 'errors'); } elseif (empty($pdfResult['pdfData'])) { setEventMessages($langs->trans("NoPdfStatementsAvailable"), null, 'warnings'); } else { // Save PDF $info = $pdfResult['info']; $pdfData = $pdfResult['pdfData']; // Check if statement already exists $stmt = new BankImportStatement($db); $stmt->statement_number = $info['statementNumber']; $stmt->statement_year = $info['statementYear']; $stmt->iban = $info['iban'] ?: getDolGlobalString('BANKIMPORT_IBAN'); if ($stmt->exists()) { setEventMessages($langs->trans("StatementAlreadyExists").': '.$stmt->statement_number.'/'.$stmt->statement_year, null, 'warnings'); } else { // Save PDF to file $dir = BankImportStatement::getStorageDir(); $ibanPart = preg_replace('/[^A-Z0-9]/', '', strtoupper($stmt->iban ?: 'KONTO')); $filename = sprintf('Kontoauszug_%s_%d_%s.pdf', $ibanPart, $stmt->statement_year, str_pad($stmt->statement_number, 3, '0', STR_PAD_LEFT) ); $filepath = $dir.'/'.$filename; if (file_put_contents($filepath, $pdfData) !== false) { $stmt->filename = $filename; $stmt->filepath = $filepath; $stmt->filesize = strlen($pdfData); $stmt->statement_date = $info['creationDate'] ? $info['creationDate']->getTimestamp() : dol_now(); $stmt->import_key = 'fints_'.date('YmdHis'); $result = $stmt->create($user); if ($result > 0) { setEventMessages($langs->trans("PdfStatementFetched", $stmt->statement_number.'/'.$stmt->statement_year), null, 'mesgs'); $year = $stmt->statement_year; } else { setEventMessages($stmt->error, null, 'errors'); @unlink($filepath); } } else { setEventMessages($langs->trans("ErrorSavingPdfFile"), null, 'errors'); } } } } $fints->close(); } } $action = ''; } // Upload PDF (supports multiple files) if ($action == 'upload' && !empty($_FILES['pdffile'])) { $uploadMode = GETPOST('upload_mode', 'alpha'); $isAutoMode = ($uploadMode !== 'manual'); // Normalize $_FILES for multi-upload: always work with arrays $fileNames = is_array($_FILES['pdffile']['name']) ? $_FILES['pdffile']['name'] : array($_FILES['pdffile']['name']); $fileTmps = is_array($_FILES['pdffile']['tmp_name']) ? $_FILES['pdffile']['tmp_name'] : array($_FILES['pdffile']['tmp_name']); $fileSizes = is_array($_FILES['pdffile']['size']) ? $_FILES['pdffile']['size'] : array($_FILES['pdffile']['size']); $fileCount = count($fileNames); $uploadedCount = 0; $errorCount = 0; $totalLinked = 0; $lastYear = (int) date('Y'); for ($fi = 0; $fi < $fileCount; $fi++) { $error = 0; // Skip empty file slots if (empty($fileNames[$fi]) || empty($fileTmps[$fi])) { continue; } // Validate uploaded file if (!is_uploaded_file($fileTmps[$fi])) { setEventMessages($langs->trans("ErrorNoFileUploaded").': '.$fileNames[$fi], null, 'errors'); $errorCount++; continue; } // Check MIME type $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $fileTmps[$fi]); finfo_close($finfo); if ($mimeType !== 'application/pdf') { setEventMessages($langs->trans("ErrorOnlyPDFAllowed").': '.$fileNames[$fi], null, 'errors'); $errorCount++; continue; } // Check file size (max 10MB) if ($fileSizes[$fi] > 10 * 1024 * 1024) { setEventMessages($langs->trans("ErrorFileTooLarge").': '.$fileNames[$fi], null, 'errors'); $errorCount++; continue; } // Parse PDF metadata automatically $parsed = BankImportStatement::parsePdfMetadata($fileTmps[$fi]); // Determine values: auto mode uses parsed data, manual mode uses form fields if ($isAutoMode && $parsed) { $statementNumber = $parsed['statement_number']; $statementYear = $parsed['statement_year']; $iban = $parsed['iban']; } else { // Manual mode (only for single file upload) $statementNumber = GETPOST('statement_number', 'alpha'); $statementYear = GETPOSTINT('statement_year'); $iban = GETPOST('iban', 'alpha'); // Auto-fill from parsed data if form fields are empty if ($parsed) { if (empty($statementNumber) && !empty($parsed['statement_number'])) { $statementNumber = $parsed['statement_number']; } if (empty($statementYear) && !empty($parsed['statement_year'])) { $statementYear = $parsed['statement_year']; } if (empty($iban) && !empty($parsed['iban'])) { $iban = $parsed['iban']; } } } // Show auto-detection info if ($parsed) { $autoMsg = $langs->trans("PdfAutoDetected").': '.$fileNames[$fi]; if (!empty($statementNumber)) { $autoMsg .= ' | '.$statementNumber.'/'.$statementYear; } if (!empty($parsed['pdf_number'])) { $autoMsg .= ' (PDF-Nr. '.$parsed['pdf_number'].'/'.$parsed['pdf_year'].')'; } if (!empty($parsed['iban'])) { $autoMsg .= ' | IBAN: '.$parsed['iban']; } if ($parsed['date_from'] && $parsed['date_to']) { $autoMsg .= ' | '.$langs->trans("Period").': '.dol_print_date($parsed['date_from'], 'day').' - '.dol_print_date($parsed['date_to'], 'day'); } setEventMessages($autoMsg, null, 'mesgs'); } // Validate required fields if (empty($statementNumber)) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("StatementNumber")).': '.$fileNames[$fi], null, 'errors'); $errorCount++; continue; } if (empty($statementYear)) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("Year")).': '.$fileNames[$fi], null, 'errors'); $errorCount++; continue; } // Create new statement object for each file $stmt = new BankImportStatement($db); $stmt->iban = $iban; $stmt->statement_number = $statementNumber; $stmt->statement_year = $statementYear; // Date fields if ($isAutoMode && $parsed) { $stmt->statement_date = $parsed['statement_date']; $stmt->date_from = $parsed['date_from']; $stmt->date_to = $parsed['date_to']; $stmt->opening_balance = $parsed['opening_balance']; $stmt->closing_balance = $parsed['closing_balance']; } else { $statementDate = dol_mktime(0, 0, 0, GETPOSTINT('statement_datemonth'), GETPOSTINT('statement_dateday'), GETPOSTINT('statement_dateyear')); $dateFrom = dol_mktime(0, 0, 0, GETPOSTINT('date_frommonth'), GETPOSTINT('date_fromday'), GETPOSTINT('date_fromyear')); $dateTo = dol_mktime(0, 0, 0, GETPOSTINT('date_tomonth'), GETPOSTINT('date_today'), GETPOSTINT('date_toyear')); $stmt->statement_date = $statementDate ?: ($parsed ? $parsed['statement_date'] : null); $stmt->date_from = $dateFrom ?: ($parsed ? $parsed['date_from'] : null); $stmt->date_to = $dateTo ?: ($parsed ? $parsed['date_to'] : null); $openBal = GETPOST('opening_balance', 'alpha'); $closeBal = GETPOST('closing_balance', 'alpha'); $stmt->opening_balance = ($openBal !== '' && $openBal !== null) ? (float) price2num($openBal) : ($parsed ? $parsed['opening_balance'] : null); $stmt->closing_balance = ($closeBal !== '' && $closeBal !== null) ? (float) price2num($closeBal) : ($parsed ? $parsed['closing_balance'] : null); } $stmt->import_key = date('YmdHis').'_'.$user->id; // Check duplicate if ($stmt->exists()) { setEventMessages($langs->trans("StatementAlreadyExists").': '.$statementNumber.'/'.$statementYear, null, 'warnings'); $errorCount++; continue; } // Generate filename and save file $dir = BankImportStatement::getStorageDir(); if ($parsed) { $newFilename = BankImportStatement::generateFilename($parsed); } else { $ibanPart = !empty($stmt->iban) ? preg_replace('/[^A-Z0-9]/', '', strtoupper($stmt->iban)) : 'KONTO'; $newFilename = sprintf('Kontoauszug_%s_%d_%s.pdf', $ibanPart, $stmt->statement_year, str_pad($stmt->statement_number, 3, '0', STR_PAD_LEFT) ); } $stmt->filepath = $dir.'/'.$newFilename; // Avoid overwriting existing files if (file_exists($stmt->filepath)) { $newFilename = pathinfo($newFilename, PATHINFO_FILENAME).'_'.date('His').'.pdf'; $stmt->filepath = $dir.'/'.$newFilename; } $stmt->filename = $newFilename; // Move uploaded file if (!move_uploaded_file($fileTmps[$fi], $stmt->filepath)) { setEventMessages($langs->trans("ErrorFailedToSaveFile").': '.$fileNames[$fi], null, 'errors'); $errorCount++; continue; } $stmt->filesize = filesize($stmt->filepath); // Save to database $result = $stmt->create($user); if ($result > 0) { // Link matching FinTS transactions to this statement $linked = $stmt->linkTransactions(); $totalLinked += max(0, $linked); // Parse PDF transaction lines and save to database $pdfLines = $stmt->parsePdfTransactions(); if (!empty($pdfLines)) { $linesSaved = $stmt->saveStatementLines($pdfLines); if ($linesSaved > 0) { setEventMessages($langs->trans("StatementLinesExtracted", $linesSaved, $stmt->statement_number.'/'.$stmt->statement_year), null, 'mesgs'); } } // Copy PDF to Dolibarr's bank statement document directory $uploadBankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID'); if ($uploadBankAccountId > 0) { $stmt->copyToDolibarrStatementDir($uploadBankAccountId); } // Reconcile bank entries if bank account is configured if ($uploadBankAccountId > 0) { $reconciledCount = $stmt->reconcileBankEntries($user, $uploadBankAccountId); if ($reconciledCount > 0) { setEventMessages($langs->trans("BankEntriesReconciled", $reconciledCount, $stmt->statement_number.'/'.$stmt->statement_year), null, 'mesgs'); } } $uploadedCount++; $lastYear = $stmt->statement_year; } else { setEventMessages($stmt->error, null, 'errors'); $errorCount++; // Clean up file on DB error if (file_exists($stmt->filepath)) { @unlink($stmt->filepath); } } } // Summary message if ($uploadedCount > 0) { if ($uploadedCount == 1) { $msg = $langs->trans("StatementUploaded"); } else { $msg = $langs->trans("StatementsUploaded", $uploadedCount); } if ($totalLinked > 0) { $msg .= ' | '.$langs->trans("TransactionsLinked", $totalLinked); } setEventMessages($msg, null, 'mesgs'); // Redirect: for single upload use the year, for multi-upload show all if ($uploadedCount == 1) { header("Location: ".$_SERVER['PHP_SELF']."?year=".$lastYear); } else { header("Location: ".$_SERVER['PHP_SELF']."?year=0"); } exit; } } // Download PDF if ($action == 'download') { $id = GETPOSTINT('id'); if ($statement->fetch($id) > 0) { $filepath = $statement->getFilePath(); if ($filepath && file_exists($filepath)) { header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="'.basename($statement->filename).'"'); header('Content-Length: '.filesize($filepath)); header('Cache-Control: private'); readfile($filepath); exit; } else { setEventMessages($langs->trans("FileNotFound"), null, 'errors'); } } else { setEventMessages($langs->trans("RecordNotFound"), null, 'errors'); } } // View PDF (inline) if ($action == 'view') { $id = GETPOSTINT('id'); if ($statement->fetch($id) > 0) { $filepath = $statement->getFilePath(); if ($filepath && file_exists($filepath)) { header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename="'.basename($statement->filename).'"'); header('Content-Length: '.filesize($filepath)); header('Cache-Control: private'); readfile($filepath); exit; } else { setEventMessages($langs->trans("FileNotFound"), null, 'errors'); } } else { setEventMessages($langs->trans("RecordNotFound"), null, 'errors'); } } // Reconcile single statement if ($action == 'reconcile') { if (!$user->hasRight('bankimport', 'write')) { accessforbidden(); } $id = GETPOSTINT('id'); $reconcileBankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID'); if (empty($reconcileBankAccountId)) { setEventMessages($langs->trans("ErrorNoBankAccountConfigured"), null, 'errors'); } elseif ($statement->fetch($id) > 0) { // Parse statement lines if not yet done $existingLines = $statement->getStatementLines(); if (is_array($existingLines) && empty($existingLines)) { $pdfLines = $statement->parsePdfTransactions(); if (!empty($pdfLines)) { $statement->saveStatementLines($pdfLines); } } $reconciledCount = $statement->reconcileBankEntries($user, $reconcileBankAccountId); if ($reconciledCount > 0) { setEventMessages($langs->trans("BankEntriesReconciled", $reconciledCount, $statement->statement_number.'/'.$statement->statement_year), null, 'mesgs'); } else { setEventMessages($langs->trans("NoBankEntriesToReconcile"), null, 'warnings'); } } $action = ''; } // Reconcile all statements if ($action == 'reconcileall') { if (!$user->hasRight('bankimport', 'write')) { accessforbidden(); } $reconcileBankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID'); if (empty($reconcileBankAccountId)) { setEventMessages($langs->trans("ErrorNoBankAccountConfigured"), null, 'errors'); } else { $allStatements = $statement->fetchAll('statement_year,statement_number', 'ASC', 0, 0, array()); $totalReconciled = 0; $stmtCount = 0; if (is_array($allStatements)) { foreach ($allStatements as $stmt) { // Parse statement lines if not yet done $existingLines = $stmt->getStatementLines(); if (is_array($existingLines) && empty($existingLines)) { $pdfLines = $stmt->parsePdfTransactions(); if (!empty($pdfLines)) { $stmt->saveStatementLines($pdfLines); } } $count = $stmt->reconcileBankEntries($user, $reconcileBankAccountId); if ($count > 0) { $totalReconciled += $count; $stmtCount++; } } } if ($totalReconciled > 0) { setEventMessages($langs->trans("BankEntriesReconciledTotal", $totalReconciled, $stmtCount), null, 'mesgs'); } else { setEventMessages($langs->trans("NoBankEntriesToReconcile"), null, 'warnings'); } } $action = ''; } // Confirm a pending reconciliation match if ($action == 'confirmreconcile') { if (!$user->hasRight('bankimport', 'write')) { accessforbidden(); } $lineId = GETPOSTINT('lineid'); $bankId = GETPOSTINT('bankid'); $reconcileBankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID'); if ($lineId > 0 && $bankId > 0 && $reconcileBankAccountId > 0) { require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; // Get statement info from line $sqlLine = "SELECT sl.fk_statement, s.statement_number, s.statement_year"; $sqlLine .= " FROM ".MAIN_DB_PREFIX."bankimport_statement_line sl"; $sqlLine .= " JOIN ".MAIN_DB_PREFIX."bankimport_statement s ON s.rowid = sl.fk_statement"; $sqlLine .= " WHERE sl.rowid = ".((int) $lineId); $resLine = $db->query($sqlLine); if ($resLine && $db->num_rows($resLine) > 0) { $lineObj = $db->fetch_object($resLine); $numReleve = $lineObj->statement_number.'/'.$lineObj->statement_year; // Reconcile the bank entry $bankLine = new AccountLine($db); $bankLine->fetch($bankId); $bankLine->num_releve = $numReleve; $result = $bankLine->update_conciliation($user, 0, 1); if ($result >= 0) { // Update statement line status $sqlUpd = "UPDATE ".MAIN_DB_PREFIX."bankimport_statement_line SET"; $sqlUpd .= " match_status = 'reconciled'"; $sqlUpd .= " WHERE rowid = ".((int) $lineId); $db->query($sqlUpd); setEventMessages($langs->trans("ReconciliationConfirmed"), null, 'mesgs'); } else { setEventMessages($langs->trans("Error"), null, 'errors'); } } } $action = ''; } // Delete confirmation if ($action == 'delete' && $confirm == 'yes') { $id = GETPOSTINT('id'); if ($statement->fetch($id) > 0) { $result = $statement->delete($user); if ($result > 0) { setEventMessages($langs->trans("RecordDeleted"), null, 'mesgs'); } else { setEventMessages($statement->error, null, 'errors'); } } $action = ''; } /* * View */ $form = new Form($db); $title = $langs->trans("PDFStatements"); llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-bankimport page-pdfstatements'); print load_fiche_titre($title, '', 'bank'); // Reminder: check if statements are outdated $reminderEnabled = getDolGlobalString('BANKIMPORT_REMINDER_ENABLED', '1'); if ($reminderEnabled) { $reminderMonths = getDolGlobalInt('BANKIMPORT_REMINDER_MONTHS') ?: 3; $lastEndDate = $statement->getLatestStatementEndDate(); $thresholdDate = dol_time_plus_duree(dol_now(), -$reminderMonths, 'm'); if ($lastEndDate === null) { print '
'; print img_warning().' '.$langs->trans("ReminderNoStatements"); print '

'; } elseif ($lastEndDate < $thresholdDate) { $monthsAgo = (int) round((dol_now() - $lastEndDate) / (30 * 24 * 3600)); print '
'; print img_warning().' '.$langs->trans("ReminderOutdatedStatements", dol_print_date($lastEndDate, 'day'), $monthsAgo); print '

'; } } // Info box print '
'; print ''.$langs->trans("PDFStatementsInfo").'
'; print $langs->trans("PDFStatementsInfoDesc"); print '
'; // Delete confirmation dialog if ($action == 'delete') { $id = GETPOSTINT('id'); $stmt = new BankImportStatement($db); $stmt->fetch($id); $formconfirm = $form->formconfirm( $_SERVER["PHP_SELF"].'?id='.$id.'&year='.$year, $langs->trans('DeleteStatement'), $langs->trans('ConfirmDeleteStatement', $stmt->statement_number.'/'.$stmt->statement_year), 'delete', '', 0, 1 ); print $formconfirm; } // Check if FinTS is configured for PDF fetch $fintsConfigured = !empty(getDolGlobalString('BANKIMPORT_FINTS_URL')) && !empty(getDolGlobalString('BANKIMPORT_FINTS_BLZ')) && !empty(getDolGlobalString('BANKIMPORT_FINTS_USERNAME')) && !empty(getDolGlobalString('BANKIMPORT_FINTS_PIN')); print '
'; // Left side: Fetch from bank (if FinTS configured) print '
'; if ($fintsConfigured) { print ''; print ''; print ''; print ''; // Fetch all new statements print ''; print ''; print ''; // Fetch specific statement print ''; print ''; print ''; print '
'.img_picto('', 'download', 'class="pictofixedwidth"').$langs->trans("FetchFromBank").'
'; print ''; print img_picto('', 'refresh', 'class="pictofixedwidth"').$langs->trans("FetchNewStatements"); print ''; print '
'.$langs->trans("FetchNewStatementsDesc").''; print '
'; print '
'; print ''; print ''; print $langs->trans("FetchSpecificStatement").': '; print ''; print ' / '; $years = array(); for ($y = (int) date('Y'); $y >= ((int) date('Y') - 5); $y--) { $years[$y] = $y; } print $form->selectarray('fetch_year', $years, (int) date('Y'), 0, 0, 0, '', 0, 0, 0, '', 'minwidth75'); print ' '; print '
'; print '
'; } else { print '
'; print img_warning().' '.$langs->trans("FinTSNotConfiguredForPdf"); print ' '.$langs->trans("GoToSetup").''; print '
'; } print '
'; // fichehalfleft // Right side: Manual upload print '
'; // Upload form $defaultMode = getDolGlobalString('BANKIMPORT_UPLOAD_MODE') ?: 'auto'; $uploadMode = GETPOST('upload_mode', 'alpha') ?: $defaultMode; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; // Upload mode selection print ''; print ''; print ''; print ''; // PDF file (always visible, multiple in auto mode) print ''; print ''; print ''; print ''; // --- Manual fields (hidden when auto mode) --- // IBAN print ''; print ''; print ''; print ''; // Year print ''; print ''; print ''; print ''; // Statement number print ''; print ''; print ''; print ''; // Statement date print ''; print ''; print ''; print ''; // Period from print ''; print ''; print ''; print ''; // Period to print ''; print ''; print ''; print ''; // Opening balance print ''; print ''; print ''; print ''; // Closing balance print ''; print ''; print ''; print ''; print '
'.img_picto('', 'upload', 'class="pictofixedwidth"').$langs->trans("UploadPDFStatement").'
'.$langs->trans("UploadMode").''; print ''; print ''; print '
'.$langs->trans("File").''; print ''; print '
'.$langs->trans("MultipleFilesHint").''; print '
'.$langs->trans("IBAN").''; print ''; print '
'.$langs->trans("Year").''; $years = array(); for ($y = (int) date('Y'); $y >= ((int) date('Y') - 10); $y--) { $years[$y] = $y; } print $form->selectarray('statement_year', $years, GETPOSTISSET('statement_year') ? GETPOSTINT('statement_year') : $year, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100'); print '
'.$langs->trans("StatementNumber").''; $nextNum = $statement->getNextStatementNumber($year); print ''; print '
'.$langs->trans("StatementDate").''; print $form->selectDate(GETPOSTISSET('statement_dateday') ? dol_mktime(0, 0, 0, GETPOSTINT('statement_datemonth'), GETPOSTINT('statement_dateday'), GETPOSTINT('statement_dateyear')) : -1, 'statement_date', 0, 0, 1, '', 1, 0); print '
'.$langs->trans("DateFrom").''; print $form->selectDate(GETPOSTISSET('date_fromday') ? dol_mktime(0, 0, 0, GETPOSTINT('date_frommonth'), GETPOSTINT('date_fromday'), GETPOSTINT('date_fromyear')) : -1, 'date_from', 0, 0, 1, '', 1, 0); print '
'.$langs->trans("DateTo").''; print $form->selectDate(GETPOSTISSET('date_today') ? dol_mktime(0, 0, 0, GETPOSTINT('date_tomonth'), GETPOSTINT('date_today'), GETPOSTINT('date_toyear')) : -1, 'date_to', 0, 0, 1, '', 1, 0); print '
'.$langs->trans("OpeningBalance").''; print ''; print ' EUR'; print '
'.$langs->trans("ClosingBalance").''; print ''; print ' EUR'; print '
'; print '
'; print ''; print '
'; print '
'; // JavaScript for toggling upload modes print ''; print '
'; // fichehalfright (upload form) print '
'; // fichecenter print '

'; // Year filter for list - only show years that have statements $yearsFilter = array(0 => $langs->trans("AllStatements")); $availableYears = $statement->getAvailableYears(); foreach ($availableYears as $yKey => $yVal) { $yearsFilter[$yKey] = $yVal; } // If current year not in list, add it if (!isset($yearsFilter[(int) date('Y')])) { $yearsFilter[(int) date('Y')] = (string) date('Y'); krsort($yearsFilter); } print '
'; print '
'; print ''.$langs->trans("Year").': '; print $form->selectarray('year', $yearsFilter, $year, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100'); print ' '; print '
'; print '
'; // Reconcile All button $reconcileBankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID'); if (!empty($reconcileBankAccountId)) { print '
'; print ''; print img_picto('', 'bank', 'class="pictofixedwidth"').$langs->trans("ReconcileAllStatements"); print ''; print '
'; } else { print '
'; print img_warning().' '.$langs->trans("NoBankAccountConfigured"); print ' '.$langs->trans("GoToSetup").''; print '
'; } // List of existing PDF statements print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; $filter = array(); if ($year > 0) { $filter['year'] = $year; } $records = $statement->fetchAll('statement_year,statement_number', 'DESC', 100, 0, $filter); if (is_array($records) && count($records) > 0) { foreach ($records as $obj) { print ''; // Statement number print ''; // IBAN print ''; // Statement date print ''; // Period print ''; // Opening balance print ''; // Closing balance print ''; // Size print ''; // Creation date print ''; // Actions print ''; print ''; } } else { print ''; } print '
'.$langs->trans("StatementNumber").''.$langs->trans("IBAN").''.$langs->trans("StatementDate").''.$langs->trans("Period").''.$langs->trans("OpeningBalance").''.$langs->trans("ClosingBalance").''.$langs->trans("Size").''.$langs->trans("DateCreation").''.$langs->trans("Actions").'
'; print ''.dol_escape_htmltag($obj->statement_number).'/'.$obj->statement_year; print ''; if ($obj->iban) { print dol_escape_htmltag($obj->iban); } else { print '-'; } print ''; if ($obj->statement_date) { print dol_print_date($obj->statement_date, 'day'); } else { print '-'; } print ''; if ($obj->date_from && $obj->date_to) { print dol_print_date($obj->date_from, 'day').' - '.dol_print_date($obj->date_to, 'day'); } elseif ($obj->date_from) { print $langs->trans("From").' '.dol_print_date($obj->date_from, 'day'); } elseif ($obj->date_to) { print $langs->trans("To").' '.dol_print_date($obj->date_to, 'day'); } else { print '-'; } print ''; if ($obj->opening_balance !== null) { $color = $obj->opening_balance >= 0 ? '' : 'color: red;'; print ''.price($obj->opening_balance, 0, $langs, 1, -1, 2, 'EUR').''; } else { print '-'; } print ''; if ($obj->closing_balance !== null) { $color = $obj->closing_balance >= 0 ? '' : 'color: red;'; print ''.price($obj->closing_balance, 0, $langs, 1, -1, 2, 'EUR').''; } else { print '-'; } print ''; if ($obj->filesize) { print dol_print_size($obj->filesize, 1); } else { print '-'; } print ''; print dol_print_date($obj->datec, 'day'); print ''; if ($obj->filepath && file_exists($obj->filepath)) { // View (inline) print 'id.'&token='.newToken().'" target="_blank" title="'.$langs->trans("View").'">'; print img_picto($langs->trans("View"), 'eye'); print ''; // Download print 'id.'&token='.newToken().'" title="'.$langs->trans("Download").'">'; print img_picto($langs->trans("Download"), 'download'); print ''; } // Reconcile if (!empty($reconcileBankAccountId) && $obj->date_from && $obj->date_to) { print 'id.'&year='.$year.'&token='.newToken().'" title="'.$langs->trans("ReconcileStatement").'">'; print img_picto($langs->trans("ReconcileStatement"), 'bank'); print ''; } // Delete print 'id.'&year='.$year.'&token='.newToken().'" title="'.$langs->trans("Delete").'">'; print img_picto($langs->trans("Delete"), 'delete'); print ''; print '
'; print $langs->trans("NoPDFStatementsFound"); print '
'; print '
'; // Pending review matches $sqlPending = "SELECT sl.rowid as line_id, sl.fk_statement, sl.line_number, sl.date_booking, sl.amount as stmt_amount,"; $sqlPending .= " sl.name as stmt_name, sl.fk_bank,"; $sqlPending .= " b.rowid as bank_id, b.datev, b.amount as bank_amount, b.label as bank_label,"; $sqlPending .= " s.statement_number, s.statement_year"; $sqlPending .= " FROM ".MAIN_DB_PREFIX."bankimport_statement_line sl"; $sqlPending .= " JOIN ".MAIN_DB_PREFIX."bankimport_statement s ON s.rowid = sl.fk_statement"; $sqlPending .= " JOIN ".MAIN_DB_PREFIX."bank b ON b.rowid = sl.fk_bank"; $sqlPending .= " WHERE sl.match_status = 'pending_review'"; $sqlPending .= " AND sl.entity = ".((int) $conf->entity); $sqlPending .= " ORDER BY s.statement_year, s.statement_number, sl.line_number"; $resPending = $db->query($sqlPending); if ($resPending && $db->num_rows($resPending) > 0) { print '
'; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; while ($pendObj = $db->fetch_object($resPending)) { $diff = abs(abs((float) $pendObj->stmt_amount) - abs((float) $pendObj->bank_amount)); $diffColor = ($diff > 10) ? 'color: red; font-weight: bold;' : 'color: #e68a00;'; print ''; // Statement number print ''; // Booking date print ''; // Name print ''; // Amount from PDF statement print ''; // Amount from Dolibarr bank print ''; // Difference print ''; // Bank entry link print ''; // Action: confirm button print ''; print ''; } print '
'; print img_warning().' '.$langs->trans("PendingReconciliationMatches").''; print ' - '.$langs->trans("PendingReconciliationMatchesDesc"); print '
'.$langs->trans("StatementNumber").''.$langs->trans("BookingDate").''.$langs->trans("Name").''.$langs->trans("AmountStatement").''.$langs->trans("AmountDolibarr").''.$langs->trans("Difference").''.$langs->trans("BankEntry").''.$langs->trans("Action").'
'.$pendObj->statement_number.'/'.$pendObj->statement_year.''.dol_print_date($db->jdate($pendObj->date_booking), 'day').''.dol_escape_htmltag($pendObj->stmt_name).''; $stmtColor = $pendObj->stmt_amount >= 0 ? '' : 'color: red;'; print ''.price($pendObj->stmt_amount, 0, $langs, 1, -1, 2, 'EUR').''; print ''; $bankColor = $pendObj->bank_amount >= 0 ? '' : 'color: red;'; print ''.price($pendObj->bank_amount, 0, $langs, 1, -1, 2, 'EUR').''; print ''; print ''.price($diff, 0, $langs, 1, -1, 2, 'EUR').''; print ''; print '#'.$pendObj->bank_id.''; print ''; print 'line_id.'&bankid='.$pendObj->bank_id.'&year='.$year.'&token='.newToken().'">'; print $langs->trans("Confirm"); print ''; print '
'; print '
'; } $db->free($resPending); // Statistics $totalCount = $statement->fetchAll('', '', 0, 0, array(), 'count'); $yearCount = is_array($records) ? count($records) : 0; print '
'; if ($year > 0) { print $langs->trans("Total").': '.$yearCount.' '.$langs->trans("StatementsInYear", $year); print ' | '.$langs->trans("AllStatements").': '.$totalCount.''; } else { print $langs->trans("Total").': '.$totalCount.' '.$langs->trans("AllStatements"); } print '
'; llxFooter(); $db->close();