feat: PDF-Kontoauszüge per FinTS (HKEKP) abrufen
- Neue php-fints Segmente: HKEKPv2, HIEKPv2, HIEKPSv2 - Action-Klasse GetStatementPDF mit Pagination-Support - Integration in pdfstatements.php (2-Spalten-Layout) - Cronjob doAutoFetchPdf für automatischen Abruf - Bank-Support-Prüfung via BPD (HIEKPS Parameter) Hinweis: Nicht alle Banken unterstützen HKEKP Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e3687f0d34
commit
fc380892f0
16 changed files with 1018 additions and 7 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -2,6 +2,27 @@
|
||||||
|
|
||||||
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
||||||
|
|
||||||
|
## [3.5] - 2026-03-05
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- **PDF-Kontoauszüge per FinTS (HKEKP)**: Elektronische Kontoauszüge direkt von der Bank abrufen
|
||||||
|
- Neue Segmente für php-fints: HKEKPv2, HIEKPv2, HIEKPSv2, ParameterKontoauszugPdf
|
||||||
|
- Neue Action-Klasse: GetStatementPDF für PDF-Abruf
|
||||||
|
- Integration in bestehende PDF-Kontoauszüge-Seite
|
||||||
|
- Support für Base64-kodierte PDFs (automatische Erkennung aus BPD)
|
||||||
|
- **Hinweis**: Nicht alle Banken unterstützen HKEKP - prüfbar via BPD-Parameter HIEKPS
|
||||||
|
- **Cronjob für automatischen PDF-Abruf**: Neue geplante Aufgabe `doAutoFetchPdf`
|
||||||
|
- Aktivierbar über Konstante `BANKIMPORT_PDF_AUTO_ENABLED`
|
||||||
|
- Ruft automatisch neue PDF-Kontoauszüge ab und speichert sie
|
||||||
|
|
||||||
|
### Geändert
|
||||||
|
- PDF-Kontoauszüge-Seite: Neues Layout mit zwei Spalten (FinTS-Abruf links, Upload rechts)
|
||||||
|
- fints.class.php: Neue Methoden `getStatementPDF()` und `supportsPdfStatements()`
|
||||||
|
|
||||||
|
### Technisch
|
||||||
|
- Erweiterung der php-fints Bibliothek um HKEKP-Unterstützung (Segment/EKP/*)
|
||||||
|
- Neue Action-Klasse mit Pagination-Support für große PDF-Auszüge
|
||||||
|
|
||||||
## [3.1] - 2026-03-05
|
## [3.1] - 2026-03-05
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
|
|
||||||
40
CLAUDE.md
Normal file → Executable file
40
CLAUDE.md
Normal file → Executable file
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Modul-Übersicht
|
## Modul-Übersicht
|
||||||
- **Name**: BankImport
|
- **Name**: BankImport
|
||||||
- **Version**: 3.1
|
- **Version**: 3.5
|
||||||
- **Pfad**: `/srv/http/dolibarr/custom/bankimport/`
|
- **Pfad**: `/srv/http/dolibarr/custom/bankimport/`
|
||||||
- **Funktion**: FinTS/HBCI Kontoauszüge importieren und mit Dolibarr-Rechnungen abgleichen
|
- **Funktion**: FinTS/HBCI Kontoauszüge importieren und mit Dolibarr-Rechnungen abgleichen
|
||||||
|
|
||||||
|
|
@ -16,6 +16,44 @@
|
||||||
| `repair.php` | Admin-Seite für verwaiste Transaktionen |
|
| `repair.php` | Admin-Seite für verwaiste Transaktionen |
|
||||||
| `cron/bankimport.cron.php` | Cronjob für automatischen Import |
|
| `cron/bankimport.cron.php` | Cronjob für automatischen Import |
|
||||||
| `admin/cronmonitor.php` | Cron-Monitoring und Pause/Resume |
|
| `admin/cronmonitor.php` | Cron-Monitoring und Pause/Resume |
|
||||||
|
| `pdfstatements.php` | PDF-Kontoauszüge hochladen und per FinTS abrufen |
|
||||||
|
| `vendor/.../Segment/EKP/*` | HKEKP-Segmente für PDF-Abruf |
|
||||||
|
| `vendor/.../Action/GetStatementPDF.php` | Action-Klasse für PDF-Abruf |
|
||||||
|
|
||||||
|
## PDF-Kontoauszüge per FinTS (HKEKP)
|
||||||
|
|
||||||
|
### Übersicht
|
||||||
|
Seit Version 3.5 können PDF-Kontoauszüge direkt von der Bank abgerufen werden (HKEKP = Elektronischer Kontoauszug PDF).
|
||||||
|
|
||||||
|
### Neue Dateien in php-fints
|
||||||
|
```
|
||||||
|
vendor/nemiah/php-fints/lib/Fhp/
|
||||||
|
├── Action/GetStatementPDF.php # Haupt-Action-Klasse
|
||||||
|
└── Segment/EKP/
|
||||||
|
├── HKEKPv2.php # Request-Segment
|
||||||
|
├── HIEKPv2.php # Response-Segment
|
||||||
|
├── HIEKP.php # Response-Interface
|
||||||
|
├── HIEKPSv2.php # Parameter-Segment
|
||||||
|
├── HIEKPS.php # Parameter-Interface
|
||||||
|
└── ParameterKontoauszugPdf.php # Parameter-Model
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung
|
||||||
|
```php
|
||||||
|
$fints = new BankImportFinTS();
|
||||||
|
if ($fints->supportsPdfStatements()) {
|
||||||
|
$result = $fints->getStatementPDF(0); // Account-Index, optional Nr+Jahr
|
||||||
|
if ($result['success']) {
|
||||||
|
$pdfData = $result['data']['pdf'];
|
||||||
|
$info = $result['data']['info']; // statementNumber, statementYear, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cronjob
|
||||||
|
- Aktivieren: `BANKIMPORT_PDF_AUTO_ENABLED = 1`
|
||||||
|
- Klasse: `BankImportCron::doAutoFetchPdf()`
|
||||||
|
- Frequenz: Wie Transaktions-Import konfigurierbar
|
||||||
|
|
||||||
## Multi-Invoice Matching (Sammelzahlungen)
|
## Multi-Invoice Matching (Sammelzahlungen)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
|
||||||
dol_include_once('/bankimport/class/fints.class.php');
|
dol_include_once('/bankimport/class/fints.class.php');
|
||||||
dol_include_once('/bankimport/class/banktransaction.class.php');
|
dol_include_once('/bankimport/class/banktransaction.class.php');
|
||||||
|
dol_include_once('/bankimport/class/bankstatement.class.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BankImportCron
|
* Class BankImportCron
|
||||||
|
|
@ -732,4 +733,184 @@ class BankImportCron
|
||||||
|
|
||||||
return $this->doAutoImport();
|
return $this->doAutoImport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute automatic PDF statement fetch via HKEKP
|
||||||
|
* Called by Dolibarr's scheduled task system
|
||||||
|
*
|
||||||
|
* @return int 0 if OK, < 0 if error
|
||||||
|
*/
|
||||||
|
public function doAutoFetchPdf()
|
||||||
|
{
|
||||||
|
global $conf, $langs, $user;
|
||||||
|
|
||||||
|
// Initialize timing
|
||||||
|
$this->startTime = microtime(true);
|
||||||
|
|
||||||
|
$langs->load('bankimport@bankimport');
|
||||||
|
|
||||||
|
$this->cronLog("========== PDF FETCH CRON START ==========");
|
||||||
|
$this->recordCronStatus('started', 'PDF fetch cron started');
|
||||||
|
|
||||||
|
// Check if PDF fetch is enabled
|
||||||
|
if (!getDolGlobalInt('BANKIMPORT_PDF_AUTO_ENABLED')) {
|
||||||
|
$this->output = $langs->trans('AutoPdfFetchDisabled');
|
||||||
|
$this->cronLog("Auto PDF fetch is disabled - exiting");
|
||||||
|
$this->recordCronStatus('completed', 'Auto PDF fetch disabled');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cron pause status (shared with main cron)
|
||||||
|
$pausedUntil = getDolGlobalInt('BANKIMPORT_CRON_PAUSED_UNTIL');
|
||||||
|
if ($pausedUntil > 0 && $pausedUntil > time()) {
|
||||||
|
$pauseReason = getDolGlobalString('BANKIMPORT_CRON_PAUSE_REASON');
|
||||||
|
$this->output = "Cron pausiert: {$pauseReason}";
|
||||||
|
$this->cronLog("Cron is PAUSED - skipping PDF fetch", 'WARNING');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cronLog("Initializing FinTS for PDF fetch");
|
||||||
|
|
||||||
|
// Initialize FinTS
|
||||||
|
$fints = new BankImportFinTS($this->db);
|
||||||
|
|
||||||
|
if (!$fints->isConfigured()) {
|
||||||
|
$this->error = $langs->trans('AutoImportNotConfigured');
|
||||||
|
$this->cronLog("FinTS not configured", 'WARNING');
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Login to bank
|
||||||
|
$this->cronLog("Logging in to bank");
|
||||||
|
$loginResult = $fints->login();
|
||||||
|
|
||||||
|
if ($loginResult < 0) {
|
||||||
|
$this->error = $fints->error;
|
||||||
|
$this->cronLog("Login failed: ".$fints->error, 'ERROR');
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginResult == 0) {
|
||||||
|
// TAN required
|
||||||
|
$this->output = $langs->trans('TANRequired');
|
||||||
|
$this->cronLog("TAN required for PDF fetch", 'WARNING');
|
||||||
|
$this->recordCronStatus('completed', 'TAN required');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bank supports PDF statements
|
||||||
|
if (!$fints->supportsPdfStatements()) {
|
||||||
|
$this->output = $langs->trans('ErrorBankDoesNotSupportPdfStatements');
|
||||||
|
$this->cronLog("Bank does not support PDF statements (HKEKP)", 'WARNING');
|
||||||
|
$fints->close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch PDF statement
|
||||||
|
$this->cronLog("Fetching PDF statement via HKEKP");
|
||||||
|
$pdfResult = $fints->getStatementPDF(0);
|
||||||
|
|
||||||
|
if ($pdfResult === 0) {
|
||||||
|
// TAN required
|
||||||
|
$this->output = $langs->trans('TANRequired');
|
||||||
|
$this->cronLog("TAN required for PDF statement", 'WARNING');
|
||||||
|
$fints->close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pdfResult === -1) {
|
||||||
|
$this->error = $fints->error;
|
||||||
|
$this->cronLog("PDF fetch failed: ".$fints->error, 'ERROR');
|
||||||
|
$fints->close();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we got any data
|
||||||
|
if (empty($pdfResult['pdfData'])) {
|
||||||
|
$this->output = $langs->trans('NoPdfStatementsAvailable');
|
||||||
|
$this->cronLog("No new PDF statements available");
|
||||||
|
$fints->close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the PDF
|
||||||
|
$info = $pdfResult['info'];
|
||||||
|
$pdfData = $pdfResult['pdfData'];
|
||||||
|
|
||||||
|
$this->cronLog("Received PDF statement #".$info['statementNumber'].'/'.$info['statementYear']);
|
||||||
|
|
||||||
|
// Check if statement already exists
|
||||||
|
$stmt = new BankImportStatement($this->db);
|
||||||
|
$stmt->statement_number = $info['statementNumber'];
|
||||||
|
$stmt->statement_year = $info['statementYear'];
|
||||||
|
$stmt->iban = $info['iban'] ?: getDolGlobalString('BANKIMPORT_IBAN');
|
||||||
|
|
||||||
|
if ($stmt->exists()) {
|
||||||
|
$this->output = $langs->trans("StatementAlreadyExists").': '.$stmt->statement_number.'/'.$stmt->statement_year;
|
||||||
|
$this->cronLog("Statement already exists - skipping");
|
||||||
|
$fints->close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
$this->error = $langs->trans("ErrorSavingPdfFile");
|
||||||
|
$this->cronLog("Failed to save PDF file", 'ERROR');
|
||||||
|
$fints->close();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create database record
|
||||||
|
$stmt->filename = $filename;
|
||||||
|
$stmt->filepath = $filepath;
|
||||||
|
$stmt->filesize = strlen($pdfData);
|
||||||
|
$stmt->statement_date = $info['creationDate'] ? $info['creationDate']->getTimestamp() : dol_now();
|
||||||
|
$stmt->import_key = 'fints_cron_'.date('YmdHis');
|
||||||
|
|
||||||
|
// Get system user
|
||||||
|
$importUser = new User($this->db);
|
||||||
|
$importUser->fetch(1);
|
||||||
|
|
||||||
|
$result = $stmt->create($importUser);
|
||||||
|
|
||||||
|
if ($result > 0) {
|
||||||
|
$this->output = $langs->trans("PdfStatementFetched", $stmt->statement_number.'/'.$stmt->statement_year);
|
||||||
|
$this->cronLog("PDF statement saved successfully: ".$stmt->statement_number.'/'.$stmt->statement_year);
|
||||||
|
|
||||||
|
// Update last fetch timestamp
|
||||||
|
dolibarr_set_const($this->db, 'BANKIMPORT_PDF_LAST_FETCH', time(), 'chaine', 0, '', $conf->entity);
|
||||||
|
} else {
|
||||||
|
$this->error = $stmt->error;
|
||||||
|
$this->cronLog("Failed to create database record: ".$stmt->error, 'ERROR');
|
||||||
|
@unlink($filepath);
|
||||||
|
$fints->close();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fints->close();
|
||||||
|
|
||||||
|
$duration = round(microtime(true) - $this->startTime, 2);
|
||||||
|
$this->cronLog("PDF fetch completed successfully in {$duration}s");
|
||||||
|
$this->recordCronStatus('completed', "PDF fetched: {$stmt->statement_number}/{$stmt->statement_year}");
|
||||||
|
$this->cronLog("========== PDF FETCH CRON END ==========");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->error = 'Exception: '.$e->getMessage();
|
||||||
|
$this->cronLog("EXCEPTION: ".$e->getMessage(), 'ERROR');
|
||||||
|
$this->recordCronStatus('error', 'Exception: '.$e->getMessage());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ use Fhp\Options\Credentials;
|
||||||
use Fhp\Action\GetSEPAAccounts;
|
use Fhp\Action\GetSEPAAccounts;
|
||||||
use Fhp\Action\GetStatementOfAccount;
|
use Fhp\Action\GetStatementOfAccount;
|
||||||
use Fhp\Action\GetStatementOfAccountXML;
|
use Fhp\Action\GetStatementOfAccountXML;
|
||||||
|
use Fhp\Action\GetStatementPDF;
|
||||||
use Fhp\Model\StatementOfAccount\Statement;
|
use Fhp\Model\StatementOfAccount\Statement;
|
||||||
use Fhp\Model\StatementOfAccount\Transaction;
|
use Fhp\Model\StatementOfAccount\Transaction;
|
||||||
|
|
||||||
|
|
@ -1017,4 +1018,125 @@ class BankImportFinTS
|
||||||
{
|
{
|
||||||
return $this->iban;
|
return $this->iban;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get PDF bank statement via HKEKP
|
||||||
|
*
|
||||||
|
* @param int $accountIndex Index of account to use (default 0)
|
||||||
|
* @param int|null $statementNumber Optional: specific statement number
|
||||||
|
* @param int|null $statementYear Optional: statement year
|
||||||
|
* @return array|int Array with 'pdfData' and 'info', or 0 if TAN required, or -1 on error
|
||||||
|
*/
|
||||||
|
public function getStatementPDF($accountIndex = 0, $statementNumber = null, $statementYear = null)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$this->error = '';
|
||||||
|
|
||||||
|
if (!$this->fints) {
|
||||||
|
$this->error = 'Not connected';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get accounts if not cached
|
||||||
|
if (empty($this->accounts)) {
|
||||||
|
$getAccounts = GetSEPAAccounts::create();
|
||||||
|
$this->fints->execute($getAccounts);
|
||||||
|
|
||||||
|
if ($getAccounts->needsTan()) {
|
||||||
|
$this->pendingAction = $getAccounts;
|
||||||
|
$tanRequest = $getAccounts->getTanRequest();
|
||||||
|
$this->tanChallenge = $tanRequest->getChallenge();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->accounts = $getAccounts->getAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->accounts) || !isset($this->accounts[$accountIndex])) {
|
||||||
|
$this->error = 'No accounts available or invalid account index';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedAccount = $this->accounts[$accountIndex];
|
||||||
|
|
||||||
|
dol_syslog("BankImport: Fetching PDF statement via HKEKP", LOG_DEBUG);
|
||||||
|
|
||||||
|
$getPdf = GetStatementPDF::create(
|
||||||
|
$selectedAccount,
|
||||||
|
$statementNumber,
|
||||||
|
$statementYear
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->fints->execute($getPdf);
|
||||||
|
|
||||||
|
if ($getPdf->needsTan()) {
|
||||||
|
$this->pendingAction = $getPdf;
|
||||||
|
$tanRequest = $getPdf->getTanRequest();
|
||||||
|
$this->tanChallenge = $tanRequest->getChallenge();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdfData = $getPdf->getPdfData();
|
||||||
|
$info = $getPdf->getStatementInfo();
|
||||||
|
|
||||||
|
if (empty($pdfData)) {
|
||||||
|
dol_syslog("BankImport: No PDF data received (no new statements available)", LOG_DEBUG);
|
||||||
|
return array(
|
||||||
|
'pdfData' => '',
|
||||||
|
'info' => array(),
|
||||||
|
'message' => 'No new statements available'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dol_syslog("BankImport: Received PDF statement #".$info['statementNumber'].'/'.$info['statementYear'], LOG_DEBUG);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'pdfData' => $pdfData,
|
||||||
|
'info' => $info
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->error = $e->getMessage();
|
||||||
|
dol_syslog("BankImport: HKEKP failed: ".$this->error, LOG_ERR);
|
||||||
|
|
||||||
|
// Check if bank doesn't support HKEKP
|
||||||
|
if (stripos($this->error, 'HKEKP') !== false || stripos($this->error, 'not support') !== false) {
|
||||||
|
$this->error = 'Bank does not support PDF statements (HKEKP)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if bank supports PDF statements (HKEKP)
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function supportsPdfStatements()
|
||||||
|
{
|
||||||
|
if (!$this->fints) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to get BPD and check for HIEKPS
|
||||||
|
$reflection = new ReflectionClass($this->fints);
|
||||||
|
$bpdProperty = $reflection->getProperty('bpd');
|
||||||
|
$bpdProperty->setAccessible(true);
|
||||||
|
$bpd = $bpdProperty->getValue($this->fints);
|
||||||
|
|
||||||
|
if ($bpd === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hiekps = $bpd->getLatestSupportedParameters('HIEKPS');
|
||||||
|
return $hiekps !== null;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
$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'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '3.4';
|
$this->version = '3.5';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
@ -281,6 +281,20 @@ class modBankImport extends DolibarrModules
|
||||||
'test' => 'isModEnabled("bankimport") && getDolGlobalInt("BANKIMPORT_AUTO_ENABLED")',
|
'test' => 'isModEnabled("bankimport") && getDolGlobalInt("BANKIMPORT_AUTO_ENABLED")',
|
||||||
'priority' => 50,
|
'priority' => 50,
|
||||||
),
|
),
|
||||||
|
1 => array(
|
||||||
|
'label' => 'BankImportPdfFetch',
|
||||||
|
'jobtype' => 'method',
|
||||||
|
'class' => '/bankimport/class/bankimportcron.class.php',
|
||||||
|
'objectname' => 'BankImportCron',
|
||||||
|
'method' => 'doAutoFetchPdf',
|
||||||
|
'parameters' => '',
|
||||||
|
'comment' => 'Automatic PDF statement fetch via HKEKP',
|
||||||
|
'frequency' => 1,
|
||||||
|
'unitfrequency' => 86400, // Daily
|
||||||
|
'status' => 0, // Disabled by default
|
||||||
|
'test' => 'isModEnabled("bankimport") && getDolGlobalInt("BANKIMPORT_PDF_AUTO_ENABLED")',
|
||||||
|
'priority' => 51,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
/* END MODULEBUILDER CRON */
|
/* END MODULEBUILDER CRON */
|
||||||
// Example: $this->cronjobs=array(
|
// Example: $this->cronjobs=array(
|
||||||
|
|
|
||||||
|
|
@ -375,3 +375,28 @@ Open = Öffnen
|
||||||
# Cash Discount / Skonto
|
# Cash Discount / Skonto
|
||||||
#
|
#
|
||||||
CashDiscount = Skonto
|
CashDiscount = Skonto
|
||||||
|
|
||||||
|
#
|
||||||
|
# PDF Statement Fetch (HKEKP)
|
||||||
|
#
|
||||||
|
FetchFromBank = Von Bank abrufen
|
||||||
|
FetchNewStatements = Neue Auszüge abrufen
|
||||||
|
FetchNewStatementsDesc = Ruft alle noch nicht abgerufenen PDF-Kontoauszüge von der Bank ab
|
||||||
|
FetchSpecificStatement = Bestimmten Auszug abrufen
|
||||||
|
Fetch = Abrufen
|
||||||
|
ErrorFinTSNotConfigured = FinTS-Verbindung nicht konfiguriert
|
||||||
|
ErrorFinTSConnection = FinTS-Verbindungsfehler
|
||||||
|
ErrorBankDoesNotSupportPdfStatements = Bank unterstützt keine PDF-Kontoauszüge (HKEKP)
|
||||||
|
ErrorFetchingPdfStatement = Fehler beim Abrufen des PDF-Kontoauszugs
|
||||||
|
NoPdfStatementsAvailable = Keine neuen PDF-Kontoauszüge verfügbar
|
||||||
|
PdfStatementFetched = PDF-Kontoauszug %s erfolgreich abgerufen
|
||||||
|
ErrorSavingPdfFile = Fehler beim Speichern der PDF-Datei
|
||||||
|
FinTSNotConfiguredForPdf = FinTS nicht konfiguriert - PDF-Abruf nicht möglich
|
||||||
|
AutoPdfFetchDisabled = Automatischer PDF-Abruf ist deaktiviert
|
||||||
|
BankImportPdfFetch = PDF-Kontoauszüge abrufen
|
||||||
|
PdfFetchCronDescription = Automatischer Abruf von PDF-Kontoauszügen via FinTS (HKEKP)
|
||||||
|
PdfStatementsFetched = %s PDF-Kontoauszüge abgerufen
|
||||||
|
ManualUpload = Manuell hochladen
|
||||||
|
Source = Quelle
|
||||||
|
SourceUpload = Hochgeladen
|
||||||
|
SourceFinTS = FinTS-Abruf
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ 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/lib/files.lib.php';
|
||||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.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/bankstatement.class.php');
|
||||||
|
dol_include_once('/bankimport/class/fints.class.php');
|
||||||
dol_include_once('/bankimport/lib/bankimport.lib.php');
|
dol_include_once('/bankimport/lib/bankimport.lib.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -72,6 +73,102 @@ if (!$user->hasRight('bankimport', 'read')) {
|
||||||
|
|
||||||
$statement = new BankImportStatement($db);
|
$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 HKEKP
|
||||||
|
if (!$fints->supportsPdfStatements()) {
|
||||||
|
setEventMessages($langs->trans("ErrorBankDoesNotSupportPdfStatements"), null, 'errors');
|
||||||
|
$fints->close();
|
||||||
|
} else {
|
||||||
|
// Fetch PDF
|
||||||
|
$pdfResult = $fints->getStatementPDF(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)
|
// Upload PDF (supports multiple files)
|
||||||
if ($action == 'upload' && !empty($_FILES['pdffile'])) {
|
if ($action == 'upload' && !empty($_FILES['pdffile'])) {
|
||||||
$uploadMode = GETPOST('upload_mode', 'alpha');
|
$uploadMode = GETPOST('upload_mode', 'alpha');
|
||||||
|
|
@ -540,20 +637,76 @@ if ($action == 'delete') {
|
||||||
print $formconfirm;
|
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 '<div class="fichecenter">';
|
||||||
|
|
||||||
|
// Left side: Fetch from bank (if FinTS configured)
|
||||||
|
print '<div class="fichehalfleft">';
|
||||||
|
|
||||||
|
if ($fintsConfigured) {
|
||||||
|
print '<table class="noborder centpercent">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<td colspan="2">'.img_picto('', 'download', 'class="pictofixedwidth"').$langs->trans("FetchFromBank").'</td>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
// Fetch all new statements
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
print '<td colspan="2">';
|
||||||
|
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=fetchpdf&token='.newToken().'">';
|
||||||
|
print img_picto('', 'refresh', 'class="pictofixedwidth"').$langs->trans("FetchNewStatements");
|
||||||
|
print '</a>';
|
||||||
|
print '<br><span class="opacitymedium small">'.$langs->trans("FetchNewStatementsDesc").'</span>';
|
||||||
|
print '</td>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
// Fetch specific statement
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
print '<td colspan="2">';
|
||||||
|
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'" style="display: inline;">';
|
||||||
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
|
print '<input type="hidden" name="action" value="fetchpdf_single">';
|
||||||
|
print $langs->trans("FetchSpecificStatement").': ';
|
||||||
|
print '<input type="text" name="fetch_number" class="flat width50" placeholder="Nr." maxlength="5">';
|
||||||
|
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 ' <input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Fetch").'">';
|
||||||
|
print '</form>';
|
||||||
|
print '</td>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
} else {
|
||||||
|
print '<div class="opacitymedium">';
|
||||||
|
print img_warning().' '.$langs->trans("FinTSNotConfiguredForPdf");
|
||||||
|
print ' <a href="'.dol_buildpath('/bankimport/admin/setup.php', 1).'">'.$langs->trans("GoToSetup").'</a>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</div>'; // fichehalfleft
|
||||||
|
|
||||||
|
// Right side: Manual upload
|
||||||
|
print '<div class="fichehalfright">';
|
||||||
|
|
||||||
// Upload form
|
// Upload form
|
||||||
$defaultMode = getDolGlobalString('BANKIMPORT_UPLOAD_MODE') ?: 'auto';
|
$defaultMode = getDolGlobalString('BANKIMPORT_UPLOAD_MODE') ?: 'auto';
|
||||||
$uploadMode = GETPOST('upload_mode', 'alpha') ?: $defaultMode;
|
$uploadMode = GETPOST('upload_mode', 'alpha') ?: $defaultMode;
|
||||||
|
|
||||||
print '<div class="fichecenter">';
|
|
||||||
print '<div class="fichehalfleft">';
|
|
||||||
|
|
||||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'" enctype="multipart/form-data" id="uploadform">';
|
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'" enctype="multipart/form-data" id="uploadform">';
|
||||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
print '<input type="hidden" name="action" value="upload">';
|
print '<input type="hidden" name="action" value="upload">';
|
||||||
|
|
||||||
print '<table class="noborder centpercent">';
|
print '<table class="noborder centpercent">';
|
||||||
print '<tr class="liste_titre">';
|
print '<tr class="liste_titre">';
|
||||||
print '<td colspan="2">'.$langs->trans("UploadPDFStatement").'</td>';
|
print '<td colspan="2">'.img_picto('', 'upload', 'class="pictofixedwidth"').$langs->trans("UploadPDFStatement").'</td>';
|
||||||
print '</tr>';
|
print '</tr>';
|
||||||
|
|
||||||
// Upload mode selection
|
// Upload mode selection
|
||||||
|
|
@ -678,7 +831,7 @@ function toggleUploadMode() {
|
||||||
document.addEventListener("DOMContentLoaded", function() { toggleUploadMode(); });
|
document.addEventListener("DOMContentLoaded", function() { toggleUploadMode(); });
|
||||||
</script>';
|
</script>';
|
||||||
|
|
||||||
print '</div>'; // fichehalfleft
|
print '</div>'; // fichehalfright (upload form)
|
||||||
print '</div>'; // fichecenter
|
print '</div>'; // fichecenter
|
||||||
|
|
||||||
print '<div class="clearboth"></div><br>';
|
print '<div class="clearboth"></div><br>';
|
||||||
|
|
|
||||||
3
vendor/composer/autoload_classmap.php
vendored
3
vendor/composer/autoload_classmap.php
vendored
|
|
@ -6,6 +6,9 @@ $vendorDir = dirname(__DIR__);
|
||||||
$baseDir = dirname($vendorDir);
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
|
'BankImportCron' => $baseDir . '/class/bankimportcron.class.php',
|
||||||
'BankImportFinTS' => $baseDir . '/class/fints.class.php',
|
'BankImportFinTS' => $baseDir . '/class/fints.class.php',
|
||||||
|
'BankImportStatement' => $baseDir . '/class/bankstatement.class.php',
|
||||||
|
'BankImportTransaction' => $baseDir . '/class/banktransaction.class.php',
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
);
|
);
|
||||||
|
|
|
||||||
3
vendor/composer/autoload_static.php
vendored
3
vendor/composer/autoload_static.php
vendored
|
|
@ -24,7 +24,10 @@ class ComposerStaticInitcfc07b7e6c4a3dcfdcd6e754983b1a9b
|
||||||
);
|
);
|
||||||
|
|
||||||
public static $classMap = array (
|
public static $classMap = array (
|
||||||
|
'BankImportCron' => __DIR__ . '/../..' . '/class/bankimportcron.class.php',
|
||||||
'BankImportFinTS' => __DIR__ . '/../..' . '/class/fints.class.php',
|
'BankImportFinTS' => __DIR__ . '/../..' . '/class/fints.class.php',
|
||||||
|
'BankImportStatement' => __DIR__ . '/../..' . '/class/bankstatement.class.php',
|
||||||
|
'BankImportTransaction' => __DIR__ . '/../..' . '/class/banktransaction.class.php',
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
182
vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementPDF.php
vendored
Normal file
182
vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementPDF.php
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Fhp\Action;
|
||||||
|
|
||||||
|
use Fhp\Model\SEPAAccount;
|
||||||
|
use Fhp\PaginateableAction;
|
||||||
|
use Fhp\Protocol\BPD;
|
||||||
|
use Fhp\Protocol\Message;
|
||||||
|
use Fhp\Protocol\UnexpectedResponseException;
|
||||||
|
use Fhp\Protocol\UPD;
|
||||||
|
use Fhp\Segment\Common\Kti;
|
||||||
|
use Fhp\Segment\EKP\HIEKP;
|
||||||
|
use Fhp\Segment\EKP\HIEKPSv2;
|
||||||
|
use Fhp\Segment\EKP\HKEKPv2;
|
||||||
|
use Fhp\Segment\HIRMS\Rueckmeldungscode;
|
||||||
|
use Fhp\UnsupportedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves PDF bank statements (Elektronischer Kontoauszug) via HKEKP.
|
||||||
|
*/
|
||||||
|
class GetStatementPDF extends PaginateableAction
|
||||||
|
{
|
||||||
|
/** @var SEPAAccount */
|
||||||
|
private $account;
|
||||||
|
|
||||||
|
/** @var int|null Optional: specific statement number */
|
||||||
|
private $statementNumber;
|
||||||
|
|
||||||
|
/** @var int|null Optional: statement year */
|
||||||
|
private $statementYear;
|
||||||
|
|
||||||
|
/** @var bool Whether PDF is base64 encoded (from BPD) */
|
||||||
|
private $isBase64 = false;
|
||||||
|
|
||||||
|
// Response data
|
||||||
|
/** @var string Raw PDF data (may be from multiple pages) */
|
||||||
|
private $pdfData = '';
|
||||||
|
|
||||||
|
/** @var array Statement metadata from response */
|
||||||
|
private $statementInfo = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SEPAAccount $account The account to get statements for
|
||||||
|
* @param int|null $statementNumber Optional: Request specific statement by number
|
||||||
|
* @param int|null $statementYear Optional: Year of the statement (required if number given)
|
||||||
|
* @return GetStatementPDF
|
||||||
|
*/
|
||||||
|
public static function create(
|
||||||
|
SEPAAccount $account,
|
||||||
|
?int $statementNumber = null,
|
||||||
|
?int $statementYear = null
|
||||||
|
): GetStatementPDF {
|
||||||
|
$result = new GetStatementPDF();
|
||||||
|
$result->account = $account;
|
||||||
|
$result->statementNumber = $statementNumber;
|
||||||
|
$result->statementYear = $statementYear;
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __serialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
parent::__serialize(),
|
||||||
|
$this->account,
|
||||||
|
$this->statementNumber,
|
||||||
|
$this->statementYear,
|
||||||
|
$this->isBase64,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __unserialize(array $serialized): void
|
||||||
|
{
|
||||||
|
list(
|
||||||
|
$parentSerialized,
|
||||||
|
$this->account,
|
||||||
|
$this->statementNumber,
|
||||||
|
$this->statementYear,
|
||||||
|
$this->isBase64
|
||||||
|
) = $serialized;
|
||||||
|
|
||||||
|
is_array($parentSerialized)
|
||||||
|
? parent::__unserialize($parentSerialized)
|
||||||
|
: parent::unserialize($parentSerialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The raw PDF data
|
||||||
|
*/
|
||||||
|
public function getPdfData(): string
|
||||||
|
{
|
||||||
|
$this->ensureDone();
|
||||||
|
|
||||||
|
// Decode base64 if needed
|
||||||
|
if ($this->isBase64 && !empty($this->pdfData)) {
|
||||||
|
$decoded = base64_decode($this->pdfData, true);
|
||||||
|
if ($decoded !== false) {
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->pdfData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array Statement metadata (number, year, iban, date, filename)
|
||||||
|
*/
|
||||||
|
public function getStatementInfo(): array
|
||||||
|
{
|
||||||
|
$this->ensureDone();
|
||||||
|
return $this->statementInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool Whether receipt confirmation is needed
|
||||||
|
*/
|
||||||
|
public function needsReceipt(): bool
|
||||||
|
{
|
||||||
|
$this->ensureDone();
|
||||||
|
return !empty($this->statementInfo['receiptCode']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
|
protected function createRequest(BPD $bpd, ?UPD $upd)
|
||||||
|
{
|
||||||
|
/** @var HIEKPSv2|null $hiekps */
|
||||||
|
$hiekps = $bpd->getLatestSupportedParameters('HIEKPS');
|
||||||
|
|
||||||
|
if ($hiekps === null) {
|
||||||
|
throw new UnsupportedException('The bank does not support PDF statements (HKEKP).');
|
||||||
|
}
|
||||||
|
|
||||||
|
$param = $hiekps->getParameter();
|
||||||
|
$this->isBase64 = $param->isBase64Encoded();
|
||||||
|
|
||||||
|
// Check if historical statements are allowed
|
||||||
|
if ($this->statementNumber !== null && !$param->isHistoricalStatementsAllowed()) {
|
||||||
|
throw new UnsupportedException('The bank does not allow requesting historical statements by number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return HKEKPv2::create(
|
||||||
|
Kti::fromAccount($this->account),
|
||||||
|
$this->statementNumber,
|
||||||
|
$this->statementYear
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
|
public function processResponse(Message $response)
|
||||||
|
{
|
||||||
|
parent::processResponse($response);
|
||||||
|
|
||||||
|
// Check if no statements available
|
||||||
|
if ($response->findRueckmeldung(Rueckmeldungscode::NICHT_VERFUEGBAR) !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var HIEKP[] $responseSegments */
|
||||||
|
$responseSegments = $response->findSegments(HIEKP::class);
|
||||||
|
|
||||||
|
if (empty($responseSegments)) {
|
||||||
|
// No segments but also no error = empty response
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($responseSegments as $hiekp) {
|
||||||
|
// Append PDF data (for pagination)
|
||||||
|
$this->pdfData .= $hiekp->getPdfData();
|
||||||
|
|
||||||
|
// Store metadata from first segment
|
||||||
|
if (empty($this->statementInfo)) {
|
||||||
|
$this->statementInfo = [
|
||||||
|
'statementNumber' => $hiekp->getStatementNumber(),
|
||||||
|
'statementYear' => $hiekp->getStatementYear(),
|
||||||
|
'iban' => $hiekp->getIban(),
|
||||||
|
'creationDate' => $hiekp->getCreationDate(),
|
||||||
|
'filename' => $hiekp->getFilename(),
|
||||||
|
'receiptCode' => $hiekp->needsReceipt() ? $hiekp->getReceiptCode() : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKP.php
vendored
Normal file
18
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKP.php
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Fhp\Segment\EKP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface für HIEKP Response-Segmente (alle Versionen)
|
||||||
|
*/
|
||||||
|
interface HIEKP
|
||||||
|
{
|
||||||
|
public function getPdfData(): string;
|
||||||
|
public function getStatementNumber(): ?int;
|
||||||
|
public function getStatementYear(): ?int;
|
||||||
|
public function getIban(): ?string;
|
||||||
|
public function getFilename(): ?string;
|
||||||
|
public function getCreationDate(): ?\DateTime;
|
||||||
|
public function needsReceipt(): bool;
|
||||||
|
public function getReceiptCode(): ?string;
|
||||||
|
}
|
||||||
11
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKPS.php
vendored
Normal file
11
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKPS.php
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Fhp\Segment\EKP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface für HIEKPS Parameter-Segmente (alle Versionen)
|
||||||
|
*/
|
||||||
|
interface HIEKPS
|
||||||
|
{
|
||||||
|
public function getParameter(): ParameterKontoauszugPdf;
|
||||||
|
}
|
||||||
22
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKPSv2.php
vendored
Normal file
22
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKPSv2.php
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
|
||||||
|
namespace Fhp\Segment\EKP;
|
||||||
|
|
||||||
|
use Fhp\Segment\BaseGeschaeftsvorfallparameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Segment: Elektronischer Kontoauszug PDF Parameter (Version 2)
|
||||||
|
*
|
||||||
|
* Bankparameterdaten für den Geschäftsvorfall HKEKP.
|
||||||
|
*/
|
||||||
|
class HIEKPSv2 extends BaseGeschaeftsvorfallparameter implements HIEKPS
|
||||||
|
{
|
||||||
|
/** Parameter für Kontoauszug PDF */
|
||||||
|
public ParameterKontoauszugPdf $parameter;
|
||||||
|
|
||||||
|
public function getParameter(): ParameterKontoauszugPdf
|
||||||
|
{
|
||||||
|
return $this->parameter;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKPv2.php
vendored
Normal file
95
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HIEKPv2.php
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
|
||||||
|
namespace Fhp\Segment\EKP;
|
||||||
|
|
||||||
|
use Fhp\Segment\BaseSegment;
|
||||||
|
use Fhp\Segment\Common\Btg;
|
||||||
|
use Fhp\Syntax\Bin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Segment: Elektronischer Kontoauszug PDF Rückmeldung (Version 2)
|
||||||
|
*
|
||||||
|
* Enthält die PDF-Daten des Kontoauszugs sowie Metadaten.
|
||||||
|
*/
|
||||||
|
class HIEKPv2 extends BaseSegment implements HIEKP
|
||||||
|
{
|
||||||
|
/** Gebuchte Umsätze als PDF (binär oder base64-kodiert je nach BPD) */
|
||||||
|
public Bin $gebuchteUmsaetze;
|
||||||
|
|
||||||
|
/** Berichtszeitraum */
|
||||||
|
public \Fhp\Segment\Common\Tsp $berichtszeitraum;
|
||||||
|
|
||||||
|
/** Erstellungsdatum des Kontoauszugs (JJJJMMTT) */
|
||||||
|
public ?string $erstellungsdatumKontoauszug = null;
|
||||||
|
|
||||||
|
/** Jahr des Kontoauszugs */
|
||||||
|
public ?int $kontoauszugsjahr = null;
|
||||||
|
|
||||||
|
/** Nummer des Kontoauszugs */
|
||||||
|
public ?int $kontoauszugsnummer = null;
|
||||||
|
|
||||||
|
/** IBAN des Kontos */
|
||||||
|
public ?string $ibanKonto = null;
|
||||||
|
|
||||||
|
/** BIC des Kontos */
|
||||||
|
public ?string $bicKonto = null;
|
||||||
|
|
||||||
|
/** Auszugsname Zeile 1 (max 35 Zeichen) */
|
||||||
|
public ?string $auszugsname1 = null;
|
||||||
|
|
||||||
|
/** Auszugsname Zeile 2 (max 35 Zeichen) */
|
||||||
|
public ?string $auszugsname2 = null;
|
||||||
|
|
||||||
|
/** Namenszusatz (max 35 Zeichen) */
|
||||||
|
public ?string $namenszusatz = null;
|
||||||
|
|
||||||
|
/** Dateiname des PDF (max 256 Zeichen) */
|
||||||
|
public ?string $dateiname = null;
|
||||||
|
|
||||||
|
/** Quittungscode (binär) - für Quittierungspflicht */
|
||||||
|
public ?Bin $quittungscode = null;
|
||||||
|
|
||||||
|
public function getPdfData(): string
|
||||||
|
{
|
||||||
|
return $this->gebuchteUmsaetze->getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatementNumber(): ?int
|
||||||
|
{
|
||||||
|
return $this->kontoauszugsnummer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatementYear(): ?int
|
||||||
|
{
|
||||||
|
return $this->kontoauszugsjahr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIban(): ?string
|
||||||
|
{
|
||||||
|
return $this->ibanKonto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilename(): ?string
|
||||||
|
{
|
||||||
|
return $this->dateiname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreationDate(): ?\DateTime
|
||||||
|
{
|
||||||
|
if ($this->erstellungsdatumKontoauszug === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return \DateTime::createFromFormat('Ymd', $this->erstellungsdatumKontoauszug) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function needsReceipt(): bool
|
||||||
|
{
|
||||||
|
return $this->quittungscode !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReceiptCode(): ?string
|
||||||
|
{
|
||||||
|
return $this->quittungscode?->getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
75
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HKEKPv2.php
vendored
Normal file
75
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/HKEKPv2.php
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
|
||||||
|
namespace Fhp\Segment\EKP;
|
||||||
|
|
||||||
|
use Fhp\Segment\BaseSegment;
|
||||||
|
use Fhp\Segment\Paginateable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Segment: Elektronischer Kontoauszug PDF anfordern (Version 2)
|
||||||
|
*
|
||||||
|
* Dieser Geschäftsvorfall dient zum Abruf elektronischer Kontoauszüge im PDF-Format.
|
||||||
|
* Der Kontoauszug enthält alle Umsätze seit dem letzten Ausdruck.
|
||||||
|
*
|
||||||
|
* @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Messages_Geschaeftsvorfaelle_2015-08-07_final_version.pdf
|
||||||
|
*/
|
||||||
|
class HKEKPv2 extends BaseSegment implements Paginateable
|
||||||
|
{
|
||||||
|
/** Kontoverbindung international (IBAN/BIC) */
|
||||||
|
public \Fhp\Segment\Common\Kti $kontoverbindungInternational;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontoauszugsnummer - Optional
|
||||||
|
* Falls das Kreditinstitut den Abruf historischer Kontoauszüge unterstützt,
|
||||||
|
* kann hier die Nummer eines bereits gedruckten Auszugs angegeben werden.
|
||||||
|
* Bleibt leer = alle bislang nicht abgerufenen Kontoauszüge werden geliefert.
|
||||||
|
*/
|
||||||
|
public ?int $kontoauszugsnummer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontoauszugsjahr - Optional
|
||||||
|
* Falls die Bank im Jahresturnus die Kontoauszugsnummer neu zu zählen beginnt,
|
||||||
|
* muss das Jahr angegeben werden um historische Auszüge eindeutig zu kennzeichnen.
|
||||||
|
*/
|
||||||
|
public ?int $kontoauszugsjahr = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximale Anzahl Einträge - Optional
|
||||||
|
* Nur erlaubt wenn BPD dies gestattet.
|
||||||
|
*/
|
||||||
|
public ?int $maximaleAnzahlEintraege = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aufsetzpunkt für Pagination - Max 35 Zeichen
|
||||||
|
*/
|
||||||
|
public ?string $aufsetzpunkt = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt ein HKEKP Segment für den Abruf von PDF-Kontoauszügen
|
||||||
|
*
|
||||||
|
* @param \Fhp\Segment\Common\Kti $kti Kontoverbindung
|
||||||
|
* @param int|null $kontoauszugsnummer Optional: Bestimmte Auszugsnummer
|
||||||
|
* @param int|null $kontoauszugsjahr Optional: Jahr des Auszugs
|
||||||
|
* @param string|null $aufsetzpunkt Optional: Pagination Token
|
||||||
|
* @return HKEKPv2
|
||||||
|
*/
|
||||||
|
public static function create(
|
||||||
|
\Fhp\Segment\Common\Kti $kti,
|
||||||
|
?int $kontoauszugsnummer = null,
|
||||||
|
?int $kontoauszugsjahr = null,
|
||||||
|
?string $aufsetzpunkt = null
|
||||||
|
): HKEKPv2 {
|
||||||
|
$result = HKEKPv2::createEmpty();
|
||||||
|
$result->kontoverbindungInternational = $kti;
|
||||||
|
$result->kontoauszugsnummer = $kontoauszugsnummer;
|
||||||
|
$result->kontoauszugsjahr = $kontoauszugsjahr;
|
||||||
|
$result->aufsetzpunkt = $aufsetzpunkt;
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPaginationToken(string $paginationToken): void
|
||||||
|
{
|
||||||
|
$this->aufsetzpunkt = $paginationToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/ParameterKontoauszugPdf.php
vendored
Normal file
48
vendor/nemiah/php-fints/lib/Fhp/Segment/EKP/ParameterKontoauszugPdf.php
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Fhp\Segment\EKP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auftragsspezifische Bankparameterdaten für Kontoauszug PDF (HKEKP)
|
||||||
|
*/
|
||||||
|
class ParameterKontoauszugPdf
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Kontoauszugsnummer erlaubt
|
||||||
|
* Gibt an, ob der Kunde anhand einer Auszugsnummer historische Kontoauszüge anfordern kann.
|
||||||
|
*/
|
||||||
|
public bool $kontoauszugsnummerErlaubt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quittierung benötigt
|
||||||
|
* Gibt an, ob der Kunde den Erhalt des Auszugs quittieren muss.
|
||||||
|
*/
|
||||||
|
public bool $quittierungBenoetigt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eingabe Anzahl Einträge erlaubt
|
||||||
|
* Gibt an, ob der Kunde die maximale Anzahl Einträge angeben darf.
|
||||||
|
*/
|
||||||
|
public bool $eingabeAnzahlEintraegeErlaubt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 kodiert
|
||||||
|
* Gibt an, ob das PDF base64-kodiert geliefert wird (J) oder als reines Binär (N).
|
||||||
|
*/
|
||||||
|
public bool $base64Kodiert;
|
||||||
|
|
||||||
|
public function isHistoricalStatementsAllowed(): bool
|
||||||
|
{
|
||||||
|
return $this->kontoauszugsnummerErlaubt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function needsReceipt(): bool
|
||||||
|
{
|
||||||
|
return $this->quittierungBenoetigt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isBase64Encoded(): bool
|
||||||
|
{
|
||||||
|
return $this->base64Kodiert;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue