162 lines
5.1 KiB
PHP
162 lines
5.1 KiB
PHP
<?php
|
|
namespace App\Services\Contabilita;
|
|
|
|
use Carbon\Carbon;
|
|
|
|
class MpsQifParser
|
|
{
|
|
/**
|
|
* @return array{rows: array<int, array{data: Carbon, valuta: Carbon|null, descrizione: string, descrizione_estesa?: string|null, importo: float, causale: string|null, raw_line: string}>, meta: array<string,mixed>}
|
|
*/
|
|
public function parse(string $content): array
|
|
{
|
|
$content = str_replace(["\r\n", "\r"], "\n", $content);
|
|
$lines = array_values(array_filter(explode("\n", $content), static fn(string $line): bool => trim($line) !== ''));
|
|
|
|
if ($lines === []) {
|
|
throw new \RuntimeException('File QIF vuoto.');
|
|
}
|
|
|
|
$type = null;
|
|
$rows = [];
|
|
$transaction = [];
|
|
|
|
foreach ($lines as $line) {
|
|
$line = preg_replace('/^\xEF\xBB\xBF/', '', $line) ?? $line;
|
|
$trimmed = trim($line);
|
|
|
|
if ($trimmed === '') {
|
|
continue;
|
|
}
|
|
|
|
if (str_starts_with($trimmed, '!Type:')) {
|
|
$type = substr($trimmed, 6) ?: null;
|
|
continue;
|
|
}
|
|
|
|
if ($trimmed === '^') {
|
|
$row = $this->buildRow($transaction, $type);
|
|
if ($row !== null) {
|
|
$rows[] = $row;
|
|
}
|
|
$transaction = [];
|
|
continue;
|
|
}
|
|
|
|
$code = substr($trimmed, 0, 1);
|
|
$value = trim(substr($trimmed, 1));
|
|
|
|
if (! isset($transaction[$code])) {
|
|
$transaction[$code] = [];
|
|
}
|
|
|
|
$transaction[$code][] = $value;
|
|
}
|
|
|
|
if ($transaction !== []) {
|
|
$row = $this->buildRow($transaction, $type);
|
|
if ($row !== null) {
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
|
|
if ($rows === []) {
|
|
throw new \RuntimeException('Nessun movimento valido trovato nel file QIF.');
|
|
}
|
|
|
|
return [
|
|
'rows' => $rows,
|
|
'meta' => [
|
|
'format' => 'mps_qif',
|
|
'qif_type' => $type,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, array<int, string>> $transaction
|
|
* @return array{data: Carbon, valuta: Carbon|null, descrizione: string, descrizione_estesa?: string|null, importo: float, causale: string|null, raw_line: string}|null
|
|
*/
|
|
private function buildRow(array $transaction, ?string $type): ?array
|
|
{
|
|
$date = $this->parseDate($transaction['D'][0] ?? null);
|
|
$amount = $this->parseMoney($transaction['T'][0] ?? null);
|
|
|
|
if (! $date || $amount === null) {
|
|
return null;
|
|
}
|
|
|
|
$payee = $this->joinParts($transaction['P'] ?? []);
|
|
$memo = $this->joinParts($transaction['M'] ?? []);
|
|
$description = $payee !== '' ? $payee : ($memo !== '' ? mb_substr($memo, 0, 255) : 'Movimento QIF');
|
|
$extended = trim($payee . ($payee !== '' && $memo !== '' ? ' · ' : '') . $memo);
|
|
|
|
$rawLines = [];
|
|
foreach ($transaction as $code => $values) {
|
|
foreach ($values as $value) {
|
|
$rawLines[] = $code . $value;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'data' => $date,
|
|
'valuta' => $date->copy(),
|
|
'descrizione' => $description,
|
|
'descrizione_estesa' => $extended !== '' ? $extended : null,
|
|
'importo' => $amount,
|
|
'causale' => $type ? ('QIF:' . strtoupper($type)) : 'QIF',
|
|
'raw_line' => implode("\n", $rawLines),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $parts
|
|
*/
|
|
private function joinParts(array $parts): string
|
|
{
|
|
$parts = array_map(static fn(string $value): string => trim($value), $parts);
|
|
$parts = array_values(array_filter($parts, static fn(string $value): bool => $value !== ''));
|
|
|
|
return trim(implode(' ', $parts));
|
|
}
|
|
|
|
private function parseDate(?string $value): ?Carbon
|
|
{
|
|
$value = trim((string) $value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (['d/m/Y', 'd/m/y', 'm/d/Y', 'm/d/y'] as $format) {
|
|
try {
|
|
return Carbon::createFromFormat($format, $value)->startOfDay();
|
|
} catch (\Throwable) {
|
|
// Try next format.
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function parseMoney(?string $value): ?float
|
|
{
|
|
$value = trim((string) $value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
$value = str_replace(["\u{00A0}", "\t", ' '], '', $value);
|
|
$value = str_ireplace(['EUR', 'EURO', '€'], '', $value);
|
|
$value = preg_replace('/[^0-9,\.\-\+]/', '', $value) ?? $value;
|
|
|
|
if (str_contains($value, ',') && str_contains($value, '.')) {
|
|
$value = str_replace('.', '', $value);
|
|
$value = str_replace(',', '.', $value);
|
|
} elseif (str_contains($value, ',')) {
|
|
$value = str_replace(',', '.', $value);
|
|
}
|
|
|
|
return is_numeric($value) ? (float) $value : null;
|
|
}
|
|
}
|