File: //tmp/rc_backup.php
<?php
class ReceiptController extends BaseController
{
public function index(): void
{
$this->requireAuth();
$status = $this->input('status');
$search = $this->input('q');
$where = "1=1";
$params = [];
if ($status) {
$where .= " AND r.status = ?";
$params[] = $status;
}
if ($search) {
$where .= " AND (r.riferimento_testo LIKE ? OR r.riferimento_numero LIKE ? OR c.company_name LIKE ?)";
$params[] = "%{$search}%";
$params[] = "%{$search}%";
$params[] = "%{$search}%";
}
$receipts = Database::fetchAll(
"SELECT r.*, c.company_name, c.cust_code
FROM receipts r
LEFT JOIN customers c ON r.customer_id = c.id
WHERE {$where}
ORDER BY r.created_at DESC
LIMIT 100",
$params
);
$this->view('receipts/index', [
'pageTitle' => 'Scontrini / Pratiche',
'receipts' => $receipts,
'status' => $status,
'search' => $search,
]);
}
public function show(string $id): void
{
$this->requireAuth();
$receipt = Database::fetchOne(
"SELECT r.*, c.company_name, c.cust_code, c.tax_id_number, c.fiscal_code, c.address, c.city, c.zip_code, c.county, c.ipa_code, c.ei_certified_email, c.telephone, c.email
FROM receipts r LEFT JOIN customers c ON r.customer_id = c.id WHERE r.id = ?", [$id]
);
if (!$receipt) {
$this->flash('error', 'Pratica non trovata');
$this->redirect('/receipts');
}
$items = Database::fetchAll("SELECT * FROM receipt_items WHERE receipt_id = ? ORDER BY line_number", [$id]);
$installments = Database::fetchAll(
"SELECT * FROM receipt_installments WHERE receipt_id = ? ORDER BY installment_number", [$id]
);
$this->view('receipts/show', [
'pageTitle' => 'Pratica #' . $receipt['riferimento_numero'],
'receipt' => $receipt,
'items' => $items,
'installments' => $installments,
]);
}
public function create(): void
{
$this->requireAuth();
$this->view('receipts/form', [
'pageTitle' => 'Nuova Pratica',
'receipt' => null,
]);
}
public function edit(string $id): void
{
$this->requireAuth();
$receipt = Database::fetchOne("SELECT * FROM receipts WHERE id = ?", [$id]);
if (!$receipt) {
$this->flash('error', 'Pratica non trovata');
$this->redirect('/receipts');
}
$items = Database::fetchAll("SELECT * FROM receipt_items WHERE receipt_id = ? ORDER BY line_number", [$id]);
$this->view('receipts/form', [
'pageTitle' => 'Modifica Pratica',
'receipt' => $receipt,
'items' => $items,
]);
}
public function confirm(string $id): void
{
$this->requireAuth();
$receipt = Database::fetchOne("SELECT * FROM receipts WHERE id = ?", [$id]);
if (!$receipt || $receipt['status'] !== 'draft') {
$this->flash('error', 'Pratica non confermabile');
$this->redirect('/receipts/' . $id);
return;
}
if (empty($receipt['customer_id'])) {
$this->flash('error', 'Associa un cliente prima di confermare');
$this->redirect('/receipts/' . $id);
return;
}
Database::beginTransaction();
try {
// Genera piano rate
$numRate = (int)($receipt['num_installments'] ?? 3);
$advance = (float)($receipt['advance_amount'] ?? 0);
$toFinance = (float)($receipt['total'] ?? 0) - $advance;
$costRetail = (float)($receipt['dilazione_cost'] ?? 0);
$totalRate = round($toFinance, 2);
$instAmount = $numRate > 0 ? round($totalRate / $numRate, 2) : $totalRate;
// Data base = data scontrino
$receipt_dates = Database::fetchOne("SELECT riferimento_data FROM receipts WHERE id = ?", [$id]);
$today = !empty($receipt_dates["riferimento_data"]) ? new \DateTime($receipt_dates["riferimento_data"]) : new \DateTime();
$reminderDays = 15;
try { $reminderDays = (int)(Database::fetchOne("SELECT setting_value FROM settings WHERE setting_key = 'reminder_days_before'")['setting_value'] ?? 15); } catch(\Throwable $e) {}
// Rimuovi rate esistenti
Database::delete('receipt_installments', 'receipt_id = ?', [$id]);
for ($i = 1; $i <= $numRate; $i++) {
$days = ($i - 1) * 30;
$dueDate = (clone $today)->modify("+{$days} days");
$reminderDate = (clone $dueDate)->modify("-{$reminderDays} days");
$amount = $i === $numRate ? round($totalRate - ($instAmount * ($numRate - 1)), 2) : $instAmount;
Database::insert('receipt_installments', [
'receipt_id' => $id,
'installment_number' => $i,
'due_date' => $dueDate->format('Y-m-d'),
'amount' => $amount,
'paid_amount' => 0,
'status' => 'pending',
'reminder_date' => $reminderDate->format('Y-m-d'),
]);
}
Database::update('receipts', [
'status' => 'confirmed',
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$id]);
Database::commit();
$this->log('receipt_confirmed', 'receipts', (int)$id, ['installments' => $numRate]);
$this->flash('success', "Pratica confermata con {$numRate} rate generate");
} catch (\Throwable $e) {
Database::rollBack();
$this->flash('error', 'Errore conferma: ' . $e->getMessage());
}
$this->redirect('/receipts/' . $id);
}
public function assignCustomer(string $id): void
{
$this->requireAuth();
$customerId = (int)$this->input("customer_id");
if (!$customerId) { $this->flash("error", "Cliente non valido"); $this->redirect("/receipts/" . $id); return; }
Database::update("receipts", ["customer_id" => $customerId, "updated_at" => date("Y-m-d H:i:s")], "id = ?", [$id]);
$this->log("customer_assigned", "receipts", (int)$id, ["customer_id" => $customerId]);
$this->flash("success", "Cliente associato alla pratica");
$this->redirect("/receipts/" . $id);
}
public function configure(string $id): void
{
$this->requireAuth();
$receipt = Database::fetchOne("SELECT * FROM receipts WHERE id = ?", [$id]);
if (!$receipt || $receipt["status"] !== "draft") { $this->flash("error", "Pratica non modificabile"); $this->redirect("/receipts/" . $id); return; }
$advMode = $this->input("advance_mode", "percent");
$advValue = (float)$this->input("advance_value", 50);
$numRate = (int)$this->input("num_installments", 3);
$subtotal = (float)$receipt["subtotal"];
if ($advMode === "fixed") {
$advance = min($advValue, $subtotal);
$advPercent = $subtotal > 0 ? round(($advance / $subtotal) * 100, 2) : 0;
} else {
$advPercent = min($advValue, 100);
$advance = round($subtotal * ($advPercent / 100), 2);
}
$toFinance = round($subtotal - $advance, 2);
$condition = Database::fetchOne("SELECT * FROM payment_conditions WHERE amount_min <= ? AND amount_max >= ? AND num_installments = ? AND active = 1", [$subtotal, $subtotal, $numRate]);
$costRetail = $condition ? (float)$condition["cost_retail"] : 0;
$total = round($toFinance + $costRetail, 2);
$instAmount = $numRate > 0 ? round($total / $numRate, 2) : $total;
Database::update("receipts", [
"advance_mode" => $advMode, "advance_percent" => $advPercent, "advance_amount" => $advance,
"num_installments" => $numRate, "dilazione_cost" => $costRetail, "total" => round($subtotal + $costRetail, 2),
"installment_amount" => $instAmount, "payment_code" => $condition ? $condition["code"] : null,
"updated_at" => date("Y-m-d H:i:s"),
], "id = ?", [$id]);
$this->flash("success", "Pagamento configurato: acconto " . number_format($advance, 2) . ", " . $numRate . " rate da " . number_format($instAmount, 2));
$this->redirect("/receipts/" . $id);
}
public function delete(string $id): void
{
$this->requireRole("admin");
$receipt = Database::fetchOne("SELECT * FROM receipts WHERE id = ?", [$id]);
if (!$receipt) {
$this->flash("error", "Pratica non trovata");
$this->redirect("/receipts");
return;
}
Database::beginTransaction();
try {
Database::delete("installment_payments", "installment_id IN (SELECT id FROM receipt_installments WHERE receipt_id = ?)", [$id]);
Database::delete("receipt_installments", "receipt_id = ?", [$id]);
Database::delete("receipt_items", "receipt_id = ?", [$id]);
Database::delete("receipts", "id = ?", [$id]);
Database::commit();
$this->log("receipt_deleted", "receipts", (int)$id);
$this->flash("success", "Pratica #" . $receipt["riferimento_numero"] . " eliminata");
} catch (\Throwable $e) {
Database::rollBack();
$this->flash("error", "Errore: " . $e->getMessage());
}
$this->redirect("/receipts");
}
public function importCsv(): void
{
$this->requireAuth();
if (empty($_FILES['csv_file']) || $_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
$this->flash('error', 'Errore nel caricamento del file CSV');
$this->redirect('/receipts');
return;
}
$file = $_FILES['csv_file']['tmp_name'];
$filename = $_FILES['csv_file']['name'];
try {
$handle = fopen($file, 'r');
if (!$handle) throw new \Exception('Impossibile aprire il file');
// Leggi header
$headerLine = fgets($handle);
if (!$headerLine) throw new \Exception('File vuoto');
// Detect separatore (punto e virgola)
$separator = ';';
// Parse header
$headers = array_map('trim', explode($separator, $headerLine));
// Mappa colonne (case-insensitive)
$colMap = [];
foreach ($headers as $i => $h) {
$h = strtolower(trim($h));
$colMap[$h] = $i;
}
// Verifica colonne necessarie
$requiredFields = ['negozio', 'descrizione', 'num matricola', 'num scontrino', 'data'];
foreach ($requiredFields as $rf) {
if (!isset($colMap[$rf])) {
throw new \Exception("Colonna obbligatoria mancante: '{$rf}'. Colonne trovate: " . implode(', ', $headers));
}
}
// Leggi righe dati
$rows = [];
$lineNum = 1;
while (($line = fgets($handle)) !== false) {
$lineNum++;
$line = trim($line);
if (empty($line)) continue;
$cols = explode($separator, $line);
// Rimuovi ultimo elemento se vuoto (trailing semicolon)
if (end($cols) === '' || end($cols) === false) array_pop($cols);
$row = [];
$row['negozio'] = trim($cols[$colMap['negozio']] ?? '');
$row['barcode'] = trim($cols[$colMap['barcode'] ?? -1] ?? '');
$row['qta'] = (int)trim($cols[$colMap['qta'] ?? -1] ?? '1');
$row['listino'] = (float)str_replace(',', '.', trim($cols[$colMap['listino'] ?? -1] ?? '0'));
$row['sconto_val'] = (float)str_replace(',', '.', trim($cols[$colMap['sconto val'] ?? -1] ?? '0'));
$row['descrizione'] = trim($cols[$colMap['descrizione']] ?? '');
$row['num_matricola'] = trim($cols[$colMap['num matricola']] ?? '');
$row['num_scontrino'] = trim($cols[$colMap['num scontrino']] ?? '');
$row['codice_cliente'] = trim($cols[$colMap['codice cliente'] ?? -1] ?? '');
$row['codice_iva'] = (float)trim($cols[$colMap['codice iva'] ?? -1] ?? '22');
$row['non_riscosso'] = (float)str_replace(',', '.', trim($cols[$colMap['non riscosso'] ?? -1] ?? '0'));
$row['causale'] = trim($cols[$colMap['causale'] ?? -1] ?? 'VE');
$row['data'] = trim($cols[$colMap['data']] ?? '');
if (empty($row['descrizione']) && empty($row['barcode'])) continue;
$rows[] = $row;
}
fclose($handle);
if (empty($rows)) throw new \Exception('Nessuna riga dati valida trovata nel CSV');
// Raggruppa per num_matricola + num_scontrino (= 1 pratica)
$groups = [];
foreach ($rows as $row) {
$key = $row['num_matricola'] . '|' . $row['num_scontrino'];
$groups[$key][] = $row;
}
$importedCount = 0;
Database::beginTransaction();
foreach ($groups as $key => $groupRows) {
$first = $groupRows[0];
// Parse data
$dataStr = $first['data'];
$receiptDate = null;
// Formato: dd/mm/yyyy HH:ii:ss
if (preg_match('#(\d{2})/(\d{2})/(\d{4})#', $dataStr, $m)) {
$receiptDate = "{$m[3]}-{$m[2]}-{$m[1]}";
}
// Verifica se esiste gia
// Deduplicazione per filename + matricola + scontrino
$existing = Database::fetchOne(
"SELECT id FROM receipts WHERE csv_filename = ? AND riferimento_testo = ? AND riferimento_numero = ?",
[$filename, $first['num_matricola'], $first['num_scontrino']]
);
if ($existing) continue; // Salta duplicati
// Cerca cliente per codice
$customer = null;
if (!empty($first['codice_cliente'])) {
$customer = Database::fetchOne(
"SELECT id FROM customers WHERE contact_person = ? OR fiscal_code = ? OR cust_code = ?",
[$first['codice_cliente'], $first['codice_cliente'], $first['codice_cliente']]
);
}
// Calcola totale e non riscosso
$subtotal = 0;
$totalNonRiscosso = 0;
foreach ($groupRows as $row) {
$lineTotal = ($row['listino'] * $row['qta']) - $row['sconto_val'];
$subtotal += $lineTotal;
$totalNonRiscosso += $row['non_riscosso'];
}
$contestuale = round($subtotal - $totalNonRiscosso, 2);
// Crea receipt
$receiptId = Database::insert('receipts', [
'customer_id' => $customer ? $customer['id'] : null,
'riferimento_testo' => $first['num_matricola'],
'riferimento_numero' => $first['num_scontrino'],
'riferimento_data' => $receiptDate,
'store_code' => $first['negozio'],
'client_code_csv' => $first['codice_cliente'],
'subtotal' => $subtotal,
'total' => $subtotal,
'non_riscosso' => $totalNonRiscosso,
'advance_amount' => $contestuale,
'invoice_reason' => 'S-VEND',
'ei_document_type' => 'TD24',
'status' => 'draft',
'csv_filename' => $filename,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// Crea righe
foreach ($groupRows as $lineNum => $row) {
Database::insert('receipt_items', [
'receipt_id' => $receiptId,
'line_number' => $lineNum + 1,
'line_type' => 'Merce',
'barcode' => $row['barcode'],
'description' => $row['descrizione'],
'uom' => 'NR',
'quantity' => $row['qta'],
'unit_value' => $row['listino'],
'discount_formula' => $row['sconto_val'] > 0 ? number_format($row['sconto_val'], 2) : null,
'tax_rate' => $row['codice_iva'],
'line_total' => ($row['listino'] * $row['qta']) - $row['sconto_val'],
]);
}
$this->log('receipt_imported', 'receipts', $receiptId, [
'file' => $filename,
'lines' => count($groupRows),
]);
$importedCount++;
}
Database::commit();
$totalRows = count($rows);
$this->flash('success', "Import completato: {$importedCount} pratiche create da {$totalRows} righe (file: {$filename})");
} catch (\Exception $e) {
if (Database::inTransaction()) Database::rollBack();
$this->flash('error', 'Errore import CSV: ' . $e->getMessage());
}
$this->redirect('/receipts');
}
public function updateCustomer(string $id): void
{
$this->requireAuth();
$receipt = Database::fetchOne("SELECT customer_id FROM receipts WHERE id = ?", [$id]);
if (!$receipt || !$receipt['customer_id']) {
$this->flash('error', 'Nessun cliente associato');
$this->redirect('/receipts/' . $id);
return;
}
$data = [
'company_name' => trim($_POST['company_name'] ?? ''),
'tax_id_number' => trim($_POST['tax_id_number'] ?? ''),
'fiscal_code' => trim($_POST['fiscal_code'] ?? ''),
'address' => trim($_POST['address'] ?? ''),
'city' => trim($_POST['city'] ?? ''),
'zip_code' => trim($_POST['zip_code'] ?? ''),
'county' => trim($_POST['county'] ?? ''),
'ipa_code' => trim($_POST['ipa_code'] ?? ''),
'ei_certified_email' => trim($_POST['ei_certified_email'] ?? ''),
'telephone' => trim($_POST['telephone'] ?? ''),
'email' => trim($_POST['email'] ?? ''),
'updated_at' => date('Y-m-d H:i:s'),
];
Database::update('customers', $data, 'id = ?', [$receipt['customer_id']]);
$this->log('customer_updated', 'customers', $receipt['customer_id'], $data);
$this->flash('success', 'Dati cliente aggiornati');
$this->redirect('/receipts/' . $id);
}
public function scanFolder(): void
{
$this->requireAuth();
header('Content-Type: application/json; charset=utf-8');
$folder = '/mnt/fattmagoel/Fattmagoel';
$files = [];
if (is_dir($folder)) {
foreach (glob($folder . '/fattmagoel_*.csv') as $filepath) {
$filename = basename($filepath);
$already = Database::fetchOne(
"SELECT id FROM csv_imports WHERE filename = ?", [$filename]
);
if (!$already) {
$files[] = [
'filename' => $filename,
'filepath' => $filepath,
'size' => filesize($filepath),
'date' => date('Y-m-d H:i:s', filemtime($filepath)),
];
}
}
}
echo json_encode(['files' => $files, 'count' => count($files)], JSON_UNESCAPED_UNICODE);
exit;
}
public function importFromFolder(): void
{
$this->requireAuth();
$folder = '/mnt/fattmagoel/Fattmagoel';
$filename = $_POST['filename'] ?? '';
if (empty($filename)) {
$this->flash('error', 'Nessun file selezionato');
$this->redirect('/receipts');
return;
}
$filepath = $folder . '/' . basename($filename);
if (!file_exists($filepath)) {
$this->flash('error', 'File non trovato: ' . $filename);
$this->redirect('/receipts');
return;
}
// Simula upload CSV riusando importCsv logic
$_FILES['csv_file'] = [
'tmp_name' => $filepath,
'name' => $filename,
'error' => UPLOAD_ERR_OK,
];
// Prima dell'import, salva in csv_imports
try {
$this->importCsv();
// Se arriviamo qui senza redirect, salva risultato
Database::insert('csv_imports', [
'filename' => $filename,
'filepath' => $filepath,
'file_date' => date('Y-m-d H:i:s', filemtime($filepath)),
'status' => 'imported',
'imported_at' => date('Y-m-d H:i:s'),
]);
} catch (\Exception $e) {
Database::insert('csv_imports', [
'filename' => $filename,
'filepath' => $filepath,
'file_date' => date('Y-m-d H:i:s', filemtime($filepath)),
'status' => 'error',
'error_message' => $e->getMessage(),
'imported_at' => date('Y-m-d H:i:s'),
]);
}
}
public function importAllFromFolder(): void
{
$this->requireAuth();
$folder = '/mnt/fattmagoel/Fattmagoel';
$imported = 0;
$errors = 0;
$errorMsgs = [];
if (is_dir($folder)) {
foreach (glob($folder . '/fattmagoel_*.csv') as $filepath) {
$filename = basename($filepath);
$already = Database::fetchOne(
"SELECT id FROM csv_imports WHERE filename = ?", [$filename]
);
if ($already) continue;
try {
$result = $this->processCSVFile($filepath, $filename);
Database::insert('csv_imports', [
'filename' => $filename,
'filepath' => $filepath,
'file_date' => date('Y-m-d H:i:s', filemtime($filepath)),
'receipts_created' => $result['receipts'],
'rows_total' => $result['rows'],
'status' => 'imported',
'imported_at' => date('Y-m-d H:i:s'),
]);
$imported++;
} catch (\Exception $e) {
Database::insert('csv_imports', [
'filename' => $filename,
'filepath' => $filepath,
'file_date' => date('Y-m-d H:i:s', filemtime($filepath)),
'status' => 'error',
'error_message' => $e->getMessage(),
'imported_at' => date('Y-m-d H:i:s'),
]);
$errors++;
$errorMsgs[] = $filename . ': ' . $e->getMessage();
}
}
}
$msg = "Auto-import: {$imported} file importati";
if ($errors > 0) $msg .= ", {$errors} errori (" . implode('; ', $errorMsgs) . ")";
$this->flash($errors > 0 ? 'warning' : 'success', $msg);
$this->redirect('/receipts');
}
private function processCSVFile(string $filepath, string $filename): array
{
$handle = fopen($filepath, 'r');
if (!$handle) throw new \Exception('Impossibile aprire il file');
$headerLine = fgets($handle);
if (!$headerLine) throw new \Exception('File vuoto');
$separator = ';';
$headers = array_map('trim', explode($separator, $headerLine));
$colMap = [];
foreach ($headers as $i => $h) {
$colMap[strtolower(trim($h))] = $i;
}
$requiredFields = ['negozio', 'descrizione', 'num matricola', 'num scontrino', 'data'];
foreach ($requiredFields as $rf) {
if (!isset($colMap[$rf])) {
throw new \Exception("Colonna obbligatoria mancante: '{$rf}'");
}
}
$rows = [];
while (($line = fgets($handle)) !== false) {
$line = trim($line);
if (empty($line)) continue;
$cols = explode($separator, $line);
if (end($cols) === '' || end($cols) === false) array_pop($cols);
$row = [];
$row['negozio'] = trim($cols[$colMap['negozio']] ?? '');
$row['barcode'] = trim($cols[$colMap['barcode'] ?? -1] ?? '');
$row['qta'] = (int)trim($cols[$colMap['qta'] ?? -1] ?? '1');
$row['listino'] = (float)str_replace(',', '.', trim($cols[$colMap['listino'] ?? -1] ?? '0'));
$row['sconto_val'] = (float)str_replace(',', '.', trim($cols[$colMap['sconto val'] ?? -1] ?? '0'));
$row['descrizione'] = trim($cols[$colMap['descrizione']] ?? '');
$row['num_matricola'] = trim($cols[$colMap['num matricola']] ?? '');
$row['num_scontrino'] = trim($cols[$colMap['num scontrino']] ?? '');
$row['codice_cliente'] = trim($cols[$colMap['codice cliente'] ?? -1] ?? '');
$row['codice_iva'] = (float)trim($cols[$colMap['codice iva'] ?? -1] ?? '22');
$row['non_riscosso'] = (float)str_replace(',', '.', trim($cols[$colMap['non riscosso'] ?? -1] ?? '0'));
$row['causale'] = trim($cols[$colMap['causale'] ?? -1] ?? 'VE');
$row['data'] = trim($cols[$colMap['data']] ?? '');
if (empty($row['descrizione']) && empty($row['barcode'])) continue;
$rows[] = $row;
}
fclose($handle);
if (empty($rows)) throw new \Exception('Nessuna riga dati valida');
$groups = [];
foreach ($rows as $row) {
$key = $row['num_matricola'] . '|' . $row['num_scontrino'];
$groups[$key][] = $row;
}
$importedCount = 0;
Database::beginTransaction();
foreach ($groups as $key => $groupRows) {
$first = $groupRows[0];
$receiptDate = null;
if (preg_match('#(\d{2})/(\d{2})/(\d{4})#', $first['data'], $m)) {
$receiptDate = "{$m[3]}-{$m[2]}-{$m[1]}";
}
// Deduplicazione per filename (ogni file = 1 pratica unica)
$existing = Database::fetchOne(
"SELECT id FROM receipts WHERE csv_filename = ? AND riferimento_testo = ? AND riferimento_numero = ?",
[$filename, $first['num_matricola'], $first['num_scontrino']]
);
if ($existing) continue;
$customer = null;
if (!empty($first['codice_cliente'])) {
$customer = Database::fetchOne(
"SELECT id FROM customers WHERE contact_person = ? OR fiscal_code = ? OR cust_code = ?",
[$first['codice_cliente'], $first['codice_cliente'], $first['codice_cliente']]
);
}
$subtotal = 0;
$totalNonRiscosso = 0;
foreach ($groupRows as $row) {
$lineTotal = ($row['listino'] * $row['qta']) - $row['sconto_val'];
$subtotal += $lineTotal;
$totalNonRiscosso += $row['non_riscosso'];
}
$contestuale = round($subtotal - $totalNonRiscosso, 2);
$receiptId = Database::insert('receipts', [
'customer_id' => $customer ? $customer['id'] : null,
'riferimento_testo' => $first['num_matricola'],
'riferimento_numero' => $first['num_scontrino'],
'riferimento_data' => $receiptDate,
'store_code' => $first['negozio'],
'client_code_csv' => $first['codice_cliente'],
'subtotal' => $subtotal,
'total' => $subtotal,
'non_riscosso' => $totalNonRiscosso,
'advance_amount' => $contestuale,
'invoice_reason' => 'S-VEND',
'ei_document_type' => 'TD24',
'status' => 'draft',
'csv_filename' => $filename,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
foreach ($groupRows as $lineNum => $row) {
Database::insert('receipt_items', [
'receipt_id' => $receiptId,
'line_number' => $lineNum + 1,
'line_type' => 'Merce',
'barcode' => $row['barcode'],
'description' => $row['descrizione'],
'uom' => 'NR',
'quantity' => $row['qta'],
'unit_value' => $row['listino'],
'discount_formula' => $row['sconto_val'] > 0 ? number_format($row['sconto_val'], 2) : null,
'tax_rate' => $row['codice_iva'],
'line_total' => ($row['listino'] * $row['qta']) - $row['sconto_val'],
]);
}
$this->log('receipt_imported', 'receipts', $receiptId, [
'file' => $filename,
'lines' => count($groupRows),
]);
$importedCount++;
}
Database::commit();
return ['receipts' => $importedCount, 'rows' => count($rows)];
}
}