'setCausal', 'closeImportModal' => 'closeImportModal', ]; public $sortField = 'date'; public $sortAsc = false; public $typeOUT = "OUT"; public $fromPage = ''; public $multiP = false; public $multiMonthTo = 0; public $multiYearTo = 0; public $multiMonthFrom = 0; public $multiYearFrom = 0; public function sortBy($field) { if ($this->sortField === $field) { $this->sortAsc = ! $this->sortAsc; } else { $this->sortAsc = true; } $this->sortField = $field; } public $records, $dataId, $member_id, $supplier_id, $causal_id, $payment_method_id, $date, $data_pagamento, $month, $year, $type, $amount, $note, $commercial, $update = false, $add = false, $is_paid = false; public $attachment; public $attachment_old; public $filterSupplier = 0, $filterPaymentMethod = 0, $filterCausals = [], $filterFrom = '', $filterTo = '', $filterCommercial = 0; public $hasFilter = false; public $total = 0; public $selectedFilter = ''; public $multipleIds = []; public $multipleAction = ''; public $selectId = 0; public $causals = array(); public $payments = array(); public $suppliers = array(); public $rows = array(); public $receiptFiles = []; public $selectedCausal = ''; public $importCausals = array(); public $vats = array(); public $numero_fattura; protected $rules = [ 'supplier_id' => 'required', 'payment_method_id' => 'required', 'rows.*.causal_id' => 'required', 'rows.*.amount' => 'required' ]; protected $messages = [ 'supplier_id.required' => 'Il fornitore è obbligatorio', 'payment_method_id.required' => 'Il metodo di pagamento è obbligatorio', 'rows.*.amount.required' => 'L\'importo è obbligatorio', 'rows.*.causal_id.required' => 'La causale è obbligatoria' ]; protected $recordFileService; public function boot() { $this->recordFileService = app(RecordFileService::class); } public function getSupplierProperty() { $ret = null; if ($this->supplier_id > 0) { $ret = \App\Models\Supplier::findOrFail($this->supplier_id); } return $ret; } public function getCausalProperty() { $ret = null; if ($this->causal_id > 0) { $ret = \App\Models\Causal::findOrFail($this->causal_id); } return $ret; } public function resetFields() { $this->member_id = null; $this->supplier_id = null; $this->payment_method_id = null; $this->date = date("Y-m-d"); $this->data_pagamento = date("Y-m-d"); $this->attachment = null; $this->attachment_old = null; $this->numero_fattura = null; $this->type = 'OUT'; $this->commercial = 1; $this->rows = array(); $this->rows[] = array( 'causal_id' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')), 'amount' => null, 'imponibile' => null, 'aliquota_iva' => '', 'note' => '', 'commercial' => 0 ); $this->emit('load-data-table'); } public function getCausale($records, $indentation) { foreach ($records as $record) { $this->causals[] = array('id' => $record->id, 'name' => $record->getTree()); if (count($record->childs)) $this->getCausale($record->childs, $indentation + 1); } } public $isImportModalOpen = false; public function openImportModal() { $this->isImportModalOpen = true; } public function closeImportModal() { $this->isImportModalOpen = false; $this->reset(['selectedCausal', 'receiptFiles']); $this->resetValidation(); $this->dispatchBrowserEvent('closeModal'); } public function hydrate() { $this->emit('load-select'); if (empty($this->importCausals)) { $this->loadImportCausals(); } $this->vats = \App\Models\Vat::select('id', 'name', 'value')->orderBy('value')->get(); } public function mount() { if (isset($_GET["from"])) { $this->fromPage = $_GET["from"]; } if (Auth::user()->level != env('LEVEL_ADMIN', 0)) return redirect()->to('/dashboard'); $this->multiMonthFrom = date("n"); $this->multiYearFrom = date("Y"); $this->multiMonthTo = date("n"); $this->multiYearTo = date("Y"); if (isset($_GET["new"])) $this->add(); $this->causals = array(); $this->getCausale(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'OUT')->orderBy('name')->get(), 0); $this->suppliers = \App\Models\Supplier::select('name', 'id')->orderBy('name')->get(); $this->payments = \App\Models\PaymentMethod::select('id', 'name')->whereIn('type', array('ALL', 'OUT'))->where('enabled', true)->orderBy('name')->get(); $this->vats = \App\Models\Vat::select('id', 'name', 'value')->orderBy('value')->get(); $this->importCausals = []; $this->loadImportCausals(); } public function loadImportCausals() { $causals = \App\Models\Causal::select('id', 'name', 'parent_id') ->where('type', 'OUT') ->orderBy('name') ->get(); $this->importCausals = []; $this->buildCausalTree($causals); } private function buildCausalTree($causals, $parentId = null, $level = 0) { foreach ($causals as $causal) { if ($causal->parent_id == $parentId) { $dotString = str_repeat('● ', $level); $this->importCausals[] = [ 'id' => $causal->id, 'name' => $dotString . $causal->name ]; $this->buildCausalTree($causals, $causal->id, $level + 1); } } } private function getCausalLevel($causal, $level = 0) { if ($causal->parent_id == null) { return $level; } $parent = \App\Models\Causal::find($causal->parent_id); if (!$parent) { return $level; } return $this->getCausalLevel($parent, $level + 1); } public function getCausal($causal) { $ret = ''; if ($causal > 0) { $ret = \App\Models\Causal::findOrFail($causal)->getTree(); } return $ret; } public function search() { $this->hasFilter = true; } public function disableSearch() { $this->filterSupplier = 0; $this->filterPaymentMethod = 0; $this->filterCausals = []; $this->filterTo = ''; $this->filterFrom = ''; $this->filterCommercial = 0; $this->hasFilter = false; $this->hasFilter = false; } public function render() { $datas = []; if (false) { if ($this->hasFilter) { $datas = \App\Models\Record::where('type', 'OUT')->with('supplier', 'payment_method', 'is_paid'); if ($this->filterSupplier > 0) { $datas = $datas->where('supplier_id', $this->filterSupplier); } if ($this->filterPaymentMethod > 0) { $datas = $datas->where('payment_method_id', $this->filterPaymentMethod); } if ($this->filterFrom != '') { $datas = $datas->where('date', '>=', $this->filterFrom); } if ($this->filterTo != '') { $datas = $datas->where('date', '<=', $this->filterTo); } $this->records = $datas->get(); $this->total = 0; foreach ($this->records as $r) { foreach ($r->rows as $rr) { $this->total += $rr->amount; } } } else { $fromDate = date("Y-m-d"); $toDate = date("Y-m-d"); if ($this->selectedFilter == '') { $this->records = \App\Models\Record::where('type', 'OUT')->with('supplier', 'payment_method')->limit(20)->orderBy('date', 'DESC')->orderBy('id', 'DESC')->get(); } else { if ($this->selectedFilter == 0) { $fromDate = date("Y-m-d"); $toDate = date("Y-m-d"); } if ($this->selectedFilter == 1) { $fromDate = date("Y-m-01"); $toDate = date("Y-m-t"); } if ($this->selectedFilter == 2) { $fromDate = date("Y-01-01"); $toDate = date("Y-12-31"); } if ($this->selectedFilter == 3) { $fromDate = date("2000-01-01"); $toDate = date("Y-12-31"); } $this->records = \App\Models\Record::where('type', 'OUT')->whereBetween('date', [$fromDate, $toDate])->with('supplier', 'payment_method')->get(); } } foreach ($this->records as $r) { $r->total = $r->getTotal(); $r->supplier = $r->supplier ? $r->supplier->name : ''; $r->payment = $r->payment_method ? $r->payment_method->name : ''; $r->is_paid = $r->is_paid ? 'Pagato' : 'Non Pagato'; } } return view('livewire.records_out'); } public function executeMultipleAction() { if ($this->multipleAction == 'delete') $this->multipleDelete(); } public function add() { $this->emit('load-select'); $this->emit('hide-search'); $this->resetFields(); $this->add = true; $this->update = false; $this->emit('setEdit', true); } public function store() { $this->emit('refresh'); if ($this->numero_fattura == null || $this->numero_fattura == '') { $this->numero_fattura = 'USC-' . date('Ymd'); } if (empty($this->data_pagamento) || $this->data_pagamento == '1970-01-01') { $this->data_pagamento = null; Log::info("Setting data_pagamento to NULL in store"); $is_paid = false; } else { Log::info("Using data_pagamento: " . $this->data_pagamento); $is_paid = true; } $this->validate(); try { $record = \App\Models\Record::create([ 'member_id' => $this->member_id, 'supplier_id' => $this->supplier_id, 'payment_method_id' => $this->payment_method_id, 'date' => $this->date, 'data_pagamento' => $this->data_pagamento, 'attachment' => '', 'type' => $this->type, 'amount' => $this->currencyToDouble($this->amount), 'commercial' => $this->commercial, 'numero_fattura' => $this->numero_fattura, 'is_paid' => $is_paid, ]); Log::info("Record data being inserted: " . json_encode($record)); $this->dataId = $record->id; $this->recordFileService->createRecordFolders($record->id, 'OUT'); if ($this->attachment) { try { $attachmentPath = $this->recordFileService->uploadAttachment($this->attachment, $record->id, 'OUT'); $record->update(['attachment' => $attachmentPath]); Log::info("Attachment uploaded and record updated: " . $attachmentPath); } catch (\Exception $ex) { Log::error("Error uploading attachment: " . $ex->getMessage()); } } $tot = 0; foreach ($this->rows as $row) { foreach ($row["when"] as $x => $y) { $row["when"][$x]['period'] = $row["when"][$x]['month'] . "-" . $row["when"][$x]['year']; } $imponibile = isset($row["imponibile"]) ? $this->currencyToDouble($row["imponibile"]) : null; Log::info("Imponibile store: " . $imponibile); $aliquota_iva = isset($row["aliquota_iva"]) ? floatval(str_replace('%', '', $row["aliquota_iva"])) : null; Log::info("Aliquota IVA store: " . $aliquota_iva); \App\Models\RecordRow::create([ 'record_id' => $this->dataId, 'causal_id' => $row["causal_id"], 'note' => $row["note"], 'amount' => $this->currencyToDouble($row["amount"]), 'imponibile' => $imponibile, 'aliquota_iva' => $aliquota_iva, 'commercial' => $row["commercial"], 'when' => json_encode($row["when"]) ]); $tot += $this->currencyToDouble($row["amount"]); } $record->amount = $tot; $record->save(); session()->flash('success', 'Movimento creato'); $this->resetFields(); $this->add = false; $this->emit('setEdit', false); } catch (\Exception $ex) { $this->emit('flash-error', 'Errore (' . $ex->getMessage() . ')'); } } public function setDataPagamentoAttribute($value) { if (empty($value) || $value == '1970-01-01' || $value == '0000-00-00') { $this->attributes['data_pagamento'] = null; } else { $this->attributes['data_pagamento'] = $value; } } public function getDataPagamentoAttribute($value) { if ($value == '1970-01-01' || $value == '0000-00-00') { return null; } return $value; } public function edit($id) { if (!isset($_GET["from"]) && $this->fromPage == '') $this->fromPage = 'out'; $this->emit('setEdit', true); $this->emit('load-select'); $this->emit('hide-search'); try { $record = \App\Models\Record::findOrFail($id); if (!$record) { $this->emit('flash-error', 'Movimento non trovato'); } else { $this->member_id = $record->member_id; $this->supplier_id = $record->supplier_id; $this->payment_method_id = $record->payment_method_id; $this->date = date("Y-m-d", strtotime($record->date)); $this->data_pagamento = $record->data_pagamento; $this->type = $record->type; $this->numero_fattura = $record->numero_fattura; $this->attachment_old = $record->attachment; $this->commercial = $record->commercial; $this->dataId = $record->id; $this->update = true; $this->add = false; $this->rows = []; $this->recordFileService->createRecordFolders($record->id, 'OUT'); $recordRows = \App\Models\RecordRow::where('record_id', $this->dataId)->get(); foreach ($recordRows as $recordRow) { $rowData = [ 'causal_id' => $recordRow->causal_id, 'note' => $recordRow->note, 'commercial' => $recordRow->commercial, 'when' => json_decode($recordRow->when), 'amount' => formatPrice($recordRow->amount) ]; if (isset($recordRow->imponibile)) { $rowData['imponibile'] = formatPrice($recordRow->imponibile); } $vatLookup = []; foreach ($this->vats as $vat) { $vatLookup[(string)$vat->value] = $vat->value; } if (isset($recordRow->aliquota_iva)) { $dbVatValue = (string)$recordRow->aliquota_iva; if (isset($vatLookup[$dbVatValue])) { $rowData['aliquota_iva'] = (string)$vatLookup[$dbVatValue]; } else { $dbVatValueFloat = floatval($dbVatValue); $closestMatch = null; $minDiff = PHP_FLOAT_MAX; foreach ($vatLookup as $vatValue) { $diff = abs($dbVatValueFloat - floatval($vatValue)); if ($diff < $minDiff && $diff < 0.1) { $minDiff = $diff; $closestMatch = $vatValue; } } if ($closestMatch !== null) { $rowData['aliquota_iva'] = (string)$closestMatch; } else { $rowData['aliquota_iva'] = $dbVatValue; } } } $this->rows[] = $rowData; } } } catch (\Exception $ex) { $this->emit('flash-error', 'Errore (' . $ex->getMessage() . ')'); } } public function update() { $this->emit('refresh'); $this->validate(); if (empty($this->data_pagamento) || $this->data_pagamento == '') { $this->data_pagamento = null; Log::info("Data pagamento vuota, imposto a NULL in update"); $is_paid = false; } else { Log::info("Data pagamento in update: " . $this->data_pagamento); $is_paid = true; } try { $attachmentPath = $this->attachment_old; if ($this->attachment) { try { if ($this->attachment_old) { $this->recordFileService->deleteAttachment($this->attachment_old); } $attachmentPath = $this->recordFileService->uploadAttachment($this->attachment, $this->dataId, 'OUT'); } catch (\Exception $ex) { session()->flash('error', 'Errore caricamento allegato: ' . $ex->getMessage()); return; } } \App\Models\Record::whereId($this->dataId)->update([ 'member_id' => $this->member_id, 'supplier_id' => $this->supplier_id, 'payment_method_id' => $this->payment_method_id, 'date' => $this->date, 'data_pagamento' => $this->data_pagamento, 'type' => $this->type, 'commercial' => $this->commercial, 'is_paid' => $is_paid, 'numero_fattura' => $this->numero_fattura, 'attachment' => $attachmentPath, ]); $tot = 0; $existingRows = \App\Models\RecordRow::where('record_id', $this->dataId) ->select('id', 'quantita', 'numero_linea') ->get() ->keyBy('id') ->toArray(); \App\Models\RecordRow::where('record_id', $this->dataId)->delete(); foreach ($this->rows as $row) { foreach ($row["when"] as $x => $y) { $row["when"][$x]['period'] = $row["when"][$x]['month'] . "-" . $row["when"][$x]['year']; } $imponibile = null; if (isset($row["imponibile"]) && $row["imponibile"] !== null && $row["imponibile"] !== '') { $imponibile = $this->currencyToDouble($row["imponibile"]); Log::info("Imponibile: " . $imponibile); } $aliquota_iva = null; if (isset($row["aliquota_iva"]) && $row["aliquota_iva"] !== null && $row["aliquota_iva"] !== '') { $aliquota_iva = floatval(str_replace('%', '', $row["aliquota_iva"])); Log::info("Aliquota IVA: " . $aliquota_iva); } $amount = $this->currencyToDouble($row["amount"]); $imposta = null; if ($imponibile !== null) { $imposta = $amount - $imponibile; Log::info("Imposta calculated: " . $imposta); } $recordRowData = [ 'record_id' => $this->dataId, 'causal_id' => $row["causal_id"], 'note' => $row["note"], 'amount' => $this->currencyToDouble($row["amount"]), 'commercial' => $row["commercial"], 'when' => json_encode($row["when"]), 'imponibile' => $imponibile, 'aliquota_iva' => $aliquota_iva, 'imposta' => $imposta, 'divisa' => 'EUR', ]; if (isset($row["id"]) && isset($existingRows[$row["id"]])) { $existingRow = $existingRows[$row["id"]]; $recordRowData['quantita'] = $existingRow['quantita']; $recordRowData['numero_linea'] = $existingRow['numero_linea']; } Log::info("RecordRowData: " . json_encode($recordRowData)); \App\Models\RecordRow::create($recordRowData); $tot += $this->currencyToDouble($row["amount"]); } $rec = \App\Models\Record::findOrFail($this->dataId); $rec->amount = $tot; $rec->save(); session()->flash('success', 'Movimento aggiornato'); $this->resetFields(); $this->update = false; $this->emit('setEdit', false); } catch (\Exception $ex) { $this->emit('flash-error', 'Errore (' . $ex->getMessage() . ')'); } } public function cancel() { $this->add = false; $this->update = false; $this->emit('setEdit', false); $this->resetFields(); } public function delete($id) { try { $record = \App\Models\Record::find($id); if ($record->attachment) { $this->recordFileService->deleteAttachment($record->attachment); } $record->delete(); session()->flash('success', "Movimento eliminato"); } catch (\Exception $e) { $this->emit('flash-error', 'Errore (' . $e->getMessage() . ')'); } } public function multipleDelete() { try { foreach ($this->multipleIds as $id) { $record = \App\Models\Record::find($id); if ($record->attachment) { $this->recordFileService->deleteAttachment($record->attachment); } $record->delete(); } } catch (\Exception $e) { $this->emit('flash-error', 'Errore (' . $e->getMessage() . ')'); } $this->multipleAction = ''; } public function getAttachmentUrl($filePath) { if (!$filePath) { return null; } return $this->recordFileService->getAttachmentUrl($filePath); } /** * Remove attachment file */ public function removeAttachment() { if ($this->attachment_old) { $this->recordFileService->deleteAttachment($this->attachment_old); } $this->attachment_old = ''; } function currencyToDouble($val) { $x = str_replace("€", "", $val); $x = str_replace(".", "", $x); $x = str_replace(",", ".", $x); return floatval(trim($x)); } public function addRow() { $this->rows[] = array( 'causal_id' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')), 'amount' => null, 'imponibile' => null, 'aliquota_iva' => null, 'note' => '', 'commercial' => 0 ); $this->emit('load-select'); } public function delRow($idx) { unset($this->rows[$idx]); // $this->emit('load-select'); } public function addPeriod($idx) { $x = sizeof($this->rows[$idx]['when']) - 1; $newDate = \Carbon\Carbon::create($this->rows[$idx]['when'][$x]["year"] . "-" . $this->rows[$idx]['when'][$x]["month"] . '-01')->addMonth(); $this->rows[$idx]['when'][] = array('month' => $newDate->format("n"), 'year' => $newDate->format("Y"), 'period' => ''); //$this->rows[$idx]['when'][] = array('month' => date("n"), 'year' => date("Y"), 'period' => ''); } public function delPeriod($idx, $xxx) { array_splice($this->rows[$idx]['when'], $xxx, 1); // $this->emit('load-select'); } public function getTotal() { $total = 0.00; foreach ($this->rows as $r) { $total += $this->currencyToDouble($r["amount"] != null ? $r["amount"] : 0); } return formatPrice($total); // $this->emit('load-select'); } public function setCausal($id, $idx) { $this->rows[$idx]["causal_id"] = $id; } public function multiPeriod() { $this->multiP = true; } public function multiPeriodCreate($idx) { $period = \Carbon\CarbonPeriod::create($this->multiYearFrom . '-' . $this->multiMonthFrom . '-01', '1 month', $this->multiYearTo . '-' . $this->multiMonthTo . '-01'); $this->rows[$idx]['when'] = []; foreach ($period as $dt) { if (!in_array(array('month' => $dt->format("n"), 'year' => $dt->format("Y"), 'period' => ''), $this->rows[$idx]['when'])) $this->rows[$idx]['when'][] = array('month' => $dt->format("n"), 'year' => $dt->format("Y"), 'period' => ''); } $this->multiP = false; } public function multiPeriodCancel() { $this->multiP = false; } public function importReceipts() { $this->validate([ 'selectedCausal' => 'required|exists:causals,id', ]); Log::info("Importazione ricevute: " . json_encode($this->receiptFiles)); try { $importCount = 0; $updateCount = 0; $errorsCount = 0; $errorMessages = []; $importedFiles = []; $updatedFiles = []; $errorFiles = []; $totalFiles = count($this->receiptFiles); // disabilita select $this->emit('import-started'); Log::info("Import iniziato"); foreach ($this->receiptFiles as $index => $receiptFile) { try { $fileName = $receiptFile->getClientOriginalName(); Log::info("Elaborazione file: " . $fileName); // Carica e analizza il file XML $xmlString = file_get_contents($receiptFile->getRealPath()); $xml = simplexml_load_string($xmlString); if (empty($xmlString)) { throw new \Exception("Il file è vuoto."); } // Verifica se il file sembra essere un XML if (strpos($xmlString, 'extractFatturaData($xml); // Trova o crea il fornitore $supplier = $this->findOrCreateSupplier($fatturaData); // Trova il metodo di pagamento $paymentMethodId = $this->findPaymentMethod($fatturaData['modalitaPagamento']); // Crea il record principale $isUpdate = false; $existingRecord = \App\Models\Record::where('supplier_id', $supplier->id) ->where('numero_fattura', $fatturaData['numeroFattura']) ->first(); if ($existingRecord) { $record = $this->updateRecord($existingRecord, $paymentMethodId, $fatturaData); $isUpdate = true; $updateCount++; $updatedFiles[] = $fileName; Log::info("Fattura aggiornata con successo: {$fatturaData['numeroFattura']}, Fornitore: {$supplier->name}"); } else { // Crea un nuovo record $record = $this->createRecord($supplier->id, $paymentMethodId, $fatturaData); $importCount++; $importedFiles[] = $fileName; Log::info("Fattura importata con successo: {$fatturaData['numeroFattura']}, Fornitore: {$supplier->name}"); } // ← NOW $record exists! Create folder structure for the record $this->recordFileService->createRecordFolders($record->id, 'OUT'); // ← Store the XML file as an attachment to the record try { $xmlAttachmentPath = $this->recordFileService->uploadXmlReceipt($receiptFile, $record->id, 'OUT'); // ← Update the record with the XML attachment path (if you have a field for it) // If you want to store XML separately from regular attachments, you could add a xml_attachment field // Or if you want to overwrite the attachment field: $record->update(['attachment' => $xmlAttachmentPath]); Log::info("XML receipt stored as attachment: " . $xmlAttachmentPath); } catch (\Exception $ex) { Log::warning("Could not store XML as attachment: " . $ex->getMessage()); } // Crea il record row $this->createRecordRow($record->id, $fatturaData); Log::info("Fattura importata con successo: {$fatturaData['numeroFattura']}, Fornitore: {$fatturaData['denominazione']} OR {$fatturaData['cognome']}, {$fatturaData['nome']}"); } catch (\Exception $e) { $errorMsg = $e->getMessage(); $friendlyErrorMsg = $this->getFriendlyErrorMessage($errorMsg); Log::error("Errore originale: " . $errorMsg); $errorMessages[] = $friendlyErrorMsg; $errorFiles[] = $fileName; $errorsCount++; } $progress = ($index + 1) / $totalFiles * 100; $this->emit('update-progress', $progress); } $this->showResultMessages($importCount, $updateCount, $errorsCount, $importedFiles, $updatedFiles, $errorFiles, $errorMessages); } catch (\Exception $e) { $errorMsg = 'Errore durante l\'importazione dei file XML: ' . $e->getMessage(); Log::error($errorMsg); $this->emit('show-import-result', [ 'message' => $errorMsg, 'type' => 'error' ]); } finally { $this->emit('load-data-table'); } } /** * Estrae i dati dalla fattura elettronica XML */ private function extractFatturaData($xml) { $data = []; try { // Stampa la struttura XML per debug Log::info("Nomi dei children dell'elemento radice: " . implode(", ", array_map(function ($node) { return $node->getName(); }, iterator_to_array($xml->children())))); $headerNode = $xml->FatturaElettronicaHeader; $bodyNode = $xml->FatturaElettronicaBody; if (!$headerNode || !$bodyNode) { throw new \Exception("Struttura XML non standard, FatturaElettronicaHeader o FatturaElettronicaBody non trovati"); } // Estrai dati del fornitore (cedente/prestatore) $cedenteNode = $headerNode->CedentePrestatore; $datiAnagrafici = $cedenteNode->DatiAnagrafici; $idFiscaleIVA = $datiAnagrafici->IdFiscaleIVA; $data['idPaese'] = (string)$idFiscaleIVA->IdPaese; $data['idCodice'] = (string)$idFiscaleIVA->IdCodice; $data['partitaIva'] = $data['idPaese'] . $data['idCodice']; $data['codiceFiscale'] = (string)$datiAnagrafici->CodiceFiscale; $data['denominazione'] = (string)$datiAnagrafici->Anagrafica->Denominazione; $data['cognome'] = (string)$datiAnagrafici->Anagrafica->Cognome; $data['nome'] = (string)$datiAnagrafici->Anagrafica->Nome; // Estrai dati della sede $sede = $cedenteNode->Sede; $data['indirizzo'] = (string)$sede->Indirizzo; $data['cap'] = (string)$sede->CAP; $data['comune'] = (string)$sede->Comune; $data['provincia'] = (string)$sede->Provincia; $data['nazione'] = (string)$sede->Nazione ?: 'IT'; // Email, se presente $data['email'] = ''; if (isset($cedenteNode->Contatti) && isset($cedenteNode->Contatti->Email)) { $data['email'] = (string)$cedenteNode->Contatti->Email; } // Dati generali della fattura $datiGeneraliDocumento = $bodyNode->DatiGenerali->DatiGeneraliDocumento; $data['tipoDocumento'] = (string)$datiGeneraliDocumento->TipoDocumento; $data['divisa'] = (string)$datiGeneraliDocumento->Divisa ?: 'EUR'; $data['dataDocumento'] = (string)$datiGeneraliDocumento->Data; $data['numeroFattura'] = (string)$datiGeneraliDocumento->Numero; $data['importoTotale'] = (float)$datiGeneraliDocumento->ImportoTotaleDocumento; // Dati di pagamento $data['condizioniPagamento'] = ''; $data['modalitaPagamento'] = ''; $data['dataScadenza'] = ''; $data['iban'] = ''; $data['bic'] = ''; $data['istitutoFinanziario'] = ''; if (isset($bodyNode->DatiPagamento)) { $datiPagamento = $bodyNode->DatiPagamento; $data['condizioniPagamento'] = (string)$datiPagamento->CondizioniPagamento; if (isset($datiPagamento->DettaglioPagamento)) { $dettaglioPagamento = $datiPagamento->DettaglioPagamento; $data['modalitaPagamento'] = (string)$dettaglioPagamento->ModalitaPagamento; $data['dataScadenza'] = (string)$dettaglioPagamento->DataScadenzaPagamento; $data['iban'] = (string)$dettaglioPagamento->IBAN; $data['bic'] = (string)$dettaglioPagamento->BIC; $data['istitutoFinanziario'] = (string)$dettaglioPagamento->IstitutoFinanziario; } } // Estrai le linee di dettaglio $data['linee'] = []; if (isset($bodyNode->DatiBeniServizi) && isset($bodyNode->DatiBeniServizi->DettaglioLinee)) { foreach ($bodyNode->DatiBeniServizi->DettaglioLinee as $index => $linea) { if (isset($linea->Quantita)) { $lineaData = [ 'numeroLinea' => (int)($linea->NumeroLinea ?? ($index + 1)), 'descrizione' => (string)($linea->Descrizione ?? ''), 'quantita' => (float)($linea->Quantita ?? 1), 'prezzoUnitario' => (float)($linea->PrezzoUnitario ?? 0), 'prezzoTotale' => (float)($linea->PrezzoTotale ?? 0), 'aliquotaIva' => (float)($linea->AliquotaIVA ?? 0), ]; // Calcola il prezzo totale se non presente if ($lineaData['prezzoTotale'] == 0) { $lineaData['prezzoTotale'] = $lineaData['quantita'] * $lineaData['prezzoUnitario']; } $data['linee'][] = $lineaData; } else { // Se la linea ha un campo Quantita, non la consideriamo Log::info("Linea con Quantita non considerata: " . json_encode($linea)); } } } $data['riepilogo'] = null; if (isset($bodyNode->DatiBeniServizi) && isset($bodyNode->DatiBeniServizi->DatiRiepilogo)) { $riepilogoNode = $bodyNode->DatiBeniServizi->DatiRiepilogo; if ($riepilogoNode) { $data['riepilogo'] = [ 'aliquotaIva' => (float)($riepilogoNode->AliquotaIVA ?? 0), 'imponibile' => (float)($riepilogoNode->ImponibileImporto ?? 0), 'imposta' => (float)($riepilogoNode->Imposta ?? 0), ]; } } Log::info("Dati estratti dalla fattura: P.IVA={$data['partitaIva']}, Denominazione={$data['denominazione']},Nome={$data['nome']},Cognome={$data['cognome']} , Numero={$data['numeroFattura']}, Importo={$data['importoTotale']}"); return $data; } catch (\Exception $e) { Log::error("Errore nell'estrazione dei dati XML: " . $e->getMessage()); throw $e; } } /** * Trova o crea un fornitore basato sui dati della fattura */ private function findOrCreateSupplier($fatturaData) { $supplier = \App\Models\Supplier::where('vat', $fatturaData['partitaIva'])->first(); if (!$supplier) { Log::info("Creazione nuovo fornitore con P.IVA: {$fatturaData['partitaIva']} ({$fatturaData['denominazione']}) OR ({$fatturaData['nome']}), ({$fatturaData['cognome']})"); $countryId = $this->getCountryId($fatturaData['nazione']); $provinceId = $this->getProvinceId($fatturaData['provincia']); $cityId = $this->getCityId($fatturaData['comune']); $supplier = new \App\Models\Supplier(); $supplier->name = $fatturaData['denominazione'] ?: $fatturaData['cognome'] . ' ' . $fatturaData['nome']; Log::info("Nome fornitore: " . $supplier->name); $supplier->vat = $fatturaData['partitaIva']; $supplier->fiscal_code = $fatturaData['codiceFiscale']; $supplier->address = $fatturaData['indirizzo']; $supplier->city_id = $cityId; $supplier->zip_code = $fatturaData['cap']; $supplier->province_id = $provinceId; $supplier->nation_id = $countryId; $supplier->email = $fatturaData['email']; $supplier->save(); Log::info("Fornitore creato con ID: " . $supplier->id); } return $supplier; } /** * Trova l'ID della nazione dal codice */ private function getCountryId($nationCode) { $country = DB::table('nations')->where('code', $nationCode)->first(); return $country ? $country->id : null; } /** * Trova l'ID della provincia dal codice */ private function getProvinceId($provinceCode) { $province = DB::table('provinces')->where('code', $provinceCode)->first(); return $province ? $province->id : null; } /** * Trova l'ID della città dal nome */ private function getCityId($cityName) { if (empty($cityName)) return null; $city = DB::table('cities')->where('name', $cityName)->first(); return $city ? $city->id : null; } /** * Trova l'ID del metodo di pagamento dal codice */ private function findPaymentMethod($paymentCode) { if (!empty($paymentCode)) { $paymentMethod = DB::table('payment_methods')->where('code', $paymentCode)->first(); if ($paymentMethod) { Log::info("Metodo di pagamento trovato: $paymentCode (ID: {$paymentMethod->id})"); return $paymentMethod->id; } } // Cerca il metodo di pagamento predefinito - corretto per evitare l'errore della colonna 'default' // Verifica se esiste una colonna 'is_default' o simile $possibleDefaultColumns = ['is_default', 'is_default_method', 'default_method', 'primary']; $defaultPaymentMethod = null; foreach ($possibleDefaultColumns as $column) { if (Schema::hasColumn('payment_methods', $column)) { $defaultPaymentMethod = DB::table('payment_methods')->where($column, 1)->first(); if ($defaultPaymentMethod) { Log::info("Usando metodo di pagamento predefinito (colonna $column) ID: {$defaultPaymentMethod->id}"); break; } } } // Se non è stato trovato un metodo predefinito, prendi il primo disponibile if (!$defaultPaymentMethod) { $defaultPaymentMethod = DB::table('payment_methods')->first(); if ($defaultPaymentMethod) { Log::info("Usando il primo metodo di pagamento disponibile ID: {$defaultPaymentMethod->id}"); } } if ($defaultPaymentMethod) { return $defaultPaymentMethod->id; } throw new \Exception("Nessun metodo di pagamento disponibile nel sistema"); } /** * Crea un record nella tabella records */ private function createRecord($supplierId, $paymentMethodId, $fatturaData) { $record = new \App\Models\Record(); $record->supplier_id = $supplierId; $record->payment_method_id = $paymentMethodId; $record->date = $fatturaData['dataDocumento']; //$record->data_pagamento = $fatturaData['dataDocumento']; $record->numero_fattura = $fatturaData['numeroFattura']; $record->type = 'OUT'; $record->commercial = 1; $record->corrispettivo_fiscale = 0; $record->deleted = 0; $record->financial_movement = 1; $record->amount = $fatturaData['importoTotale']; $record->tipo_documento = $this->mapTipoDocumento($fatturaData['tipoDocumento']); $record->is_ricevuta = true; if ($record->data_pagamento != null) { $record->is_paid = true; } else { $record->is_paid = false; } if (isset($fatturaData['condizioniPagamento']) && !empty($fatturaData['condizioniPagamento'])) { $record->condizioni_pagamento = $this->mapCondizioniPagamento($fatturaData['condizioniPagamento']); } if (isset($fatturaData['iban']) && !empty($fatturaData['iban'])) { $record->IBAN = $fatturaData['iban']; } if (isset($fatturaData['bic']) && !empty($fatturaData['bic'])) { $record->BIC = $fatturaData['bic']; } if (isset($fatturaData['dataScadenza']) && !empty($fatturaData['dataScadenza'])) { $record->data_scadenza = $fatturaData['dataScadenza']; } $record->save(); Log::info("Nuovo record creato con ID: " . $record->id); return $record; } /** * Aggiorna un record esistente nella tabella records */ private function updateRecord($record, $paymentMethodId, $fatturaData) { $record->payment_method_id = $paymentMethodId; $record->date = $fatturaData['dataDocumento']; $record->data_pagamento = $fatturaData['dataDocumento']; $record->type = 'OUT'; $record->commercial = 1; $record->corrispettivo_fiscale = 0; $record->deleted = 0; $record->financial_movement = 1; $record->amount = $fatturaData['importoTotale']; $record->tipo_documento = $this->mapTipoDocumento($fatturaData['tipoDocumento']); $record->is_ricevuta = true; if (isset($fatturaData['condizioniPagamento']) && !empty($fatturaData['condizioniPagamento'])) { $record->condizioni_pagamento = $this->mapCondizioniPagamento($fatturaData['condizioniPagamento']); } if (isset($fatturaData['iban']) && !empty($fatturaData['iban'])) { $record->IBAN = $fatturaData['iban']; } if (isset($fatturaData['bic']) && !empty($fatturaData['bic'])) { $record->BIC = $fatturaData['bic']; } if (isset($fatturaData['dataScadenza']) && !empty($fatturaData['dataScadenza'])) { $record->data_scadenza = $fatturaData['dataScadenza']; } $record->save(); Log::info("Record esistente aggiornato con ID: " . $record->id); return $record; } /** * Crea un record row per il record specificato */ private function createRecordRow($recordId, $fatturaData) { Log::info("Inizio creazione RecordRow per Record ID: " . $recordId); $existingRows = \App\Models\RecordRow::where('record_id', $recordId)->get(); // Delete existing rows if they exist if ($existingRows->count() > 0) { Log::info("Eliminazione di " . $existingRows->count() . " righe record esistenti per Record ID: " . $recordId); foreach ($existingRows as $row) { $row->delete(); } } if (!empty($fatturaData['linee'])) { foreach ($fatturaData['linee'] as $linea) { $this->createSingleRecordRow($recordId, $fatturaData, $linea); } } else if ($fatturaData['riepilogo']) { $this->createRecordRowFromRiepilogo($recordId, $fatturaData); Log::info("Creata una riga di record dai dati di riepilogo"); } else { $this->createDefaultRecordRow($recordId, $fatturaData); Log::info("Creata una riga di record predefinita dall'importo totale"); } } private function createSingleRecordRow($recordId, $fatturaData, $linea) { Log::info("Inizio creazione riga record singola per Record ID: " . $recordId . ", Linea: " . json_encode($linea)); $dataObj = new \DateTime($fatturaData['dataDocumento']); $month = $dataObj->format('n'); $year = $dataObj->format('Y'); $period = "$month-$year"; $whenData = [[ 'month' => $month, 'year' => $year, 'period' => $period ]]; $vatId = null; if (isset($linea['aliquotaIva']) && $linea['aliquotaIva'] > 0) { $vatId = $this->findOrCreateVat($linea['aliquotaIva']); Log::info("Using VAT ID: $vatId for rate: {$linea['aliquotaIva']}%"); } $recordRow = new \App\Models\RecordRow(); $recordRow->record_id = $recordId; $recordRow->causal_id = $this->selectedCausal; $recordRow->amount = $linea['prezzoTotale'] + $linea['prezzoTotale'] * ($linea['aliquotaIva'] / 100); $recordRow->note = $linea['descrizione']; $recordRow->commercial = 1; $recordRow->when = json_encode($whenData); $recordRow->aliquota_iva = $linea['aliquotaIva']; $recordRow->imponibile = $linea['prezzoTotale']; $recordRow->imposta = $linea['prezzoTotale'] * ($linea['aliquotaIva'] / 100); $recordRow->divisa = $fatturaData['divisa']; $recordRow->numero_linea = $linea['numeroLinea']; $recordRow->prezzo_unitario = $linea['prezzoUnitario']; $recordRow->quantita = $linea['quantita']; Log::info("Dati riga record prima del salvataggio: " . json_encode([ 'record_id' => $recordRow->record_id, 'causal_id' => $recordRow->causal_id, 'amount' => $recordRow->amount, 'note' => $recordRow->note, 'aliquota_iva' => $recordRow->aliquota_iva, 'imponibile' => $recordRow->imponibile, 'imposta' => $recordRow->imposta, 'divisa' => $recordRow->divisa, 'numero_linea' => $recordRow->numero_linea, 'prezzo_unitario' => $recordRow->prezzo_unitario, 'quantita' => $recordRow->quantita, ])); $recordRow->save(); Log::info("Riga record creata per linea {$linea['numeroLinea']}: {$linea['descrizione']} (€{$linea['prezzoTotale']})"); } private function createRecordRowFromRiepilogo($recordId, $fatturaData) { Log::info("Inizio creazione riga record da riepilogo per Record ID: " . $recordId . ", Riepilogo: " . json_encode($fatturaData['riepilogo'])); $riepilogo = $fatturaData['riepilogo']; $dataObj = new \DateTime($fatturaData['dataDocumento']); $month = $dataObj->format('n'); $year = $dataObj->format('Y'); $period = "$month-$year"; $whenData = [[ 'month' => $month, 'year' => $year, 'period' => $period ]]; $vatId = null; if (isset($riepilogo['aliquotaIva']) && $riepilogo['aliquotaIva'] > 0) { $vatId = $this->findOrCreateVat($riepilogo['aliquotaIva']); Log::info("Using VAT ID: $vatId for rate: {$riepilogo['aliquotaIva']}%"); } $total_amount = $riepilogo['imponibile'] + $riepilogo['imposta']; $recordRow = new \App\Models\RecordRow(); $recordRow->record_id = $recordId; $recordRow->causal_id = $this->selectedCausal; $recordRow->amount = $total_amount; $recordRow->commercial = 1; $recordRow->when = json_encode($whenData); $recordRow->aliquota_iva = $riepilogo['aliquotaIva']; $recordRow->imponibile = $riepilogo['imponibile']; $recordRow->imposta = $riepilogo['imposta']; $recordRow->divisa = $fatturaData['divisa']; $recordRow->numero_linea = 1; $recordRow->quantita = 1; Log::info("Dati riga record da riepilogo prima del salvataggio: " . json_encode([ 'record_id' => $recordRow->record_id, 'causal_id' => $recordRow->causal_id, 'amount' => $recordRow->amount, 'aliquota_iva' => $recordRow->aliquota_iva, 'imponibile' => $recordRow->imponibile, 'imposta' => $recordRow->imposta, 'divisa' => $recordRow->divisa, ])); $recordRow->save(); Log::info("Riga record creata da riepilogo: Imponibile={$riepilogo['imponibile']}, IVA={$riepilogo['aliquotaIva']}%"); } private function createDefaultRecordRow($recordId, $fatturaData) { Log::info("Inizio creazione riga record predefinita per Record ID: " . $recordId . ", Importo Totale: " . $fatturaData['importoTotale']); $dataObj = new \DateTime($fatturaData['dataDocumento']); $month = $dataObj->format('n'); $year = $dataObj->format('Y'); $period = "$month-$year"; $whenData = [[ 'month' => $month, 'year' => $year, 'period' => $period ]]; $recordRow = new \App\Models\RecordRow(); $recordRow->record_id = $recordId; $recordRow->causal_id = $this->selectedCausal; $recordRow->amount = $fatturaData['importoTotale']; $recordRow->commercial = 1; $recordRow->when = json_encode($whenData); $recordRow->divisa = $fatturaData['divisa']; $recordRow->numero_linea = 1; $recordRow->quantita = 1; Log::info("Dati riga record predefinita prima del salvataggio: " . json_encode([ 'record_id' => $recordRow->record_id, 'causal_id' => $recordRow->causal_id, 'amount' => $recordRow->amount, 'divisa' => $recordRow->divisa, ])); $recordRow->save(); Log::info("Riga record predefinita creata con importo totale: {$fatturaData['importoTotale']}"); } private function mapTipoDocumento($codice) { $tipiDocumento = [ 'TD01' => 'Fattura', 'TD02' => 'Acconto/Anticipo su fattura', 'TD03' => 'Acconto/Anticipo su parcella', 'TD04' => 'Nota di credito', 'TD05' => 'Nota di debito', 'TD06' => 'Parcella', 'TD16' => 'Integrazione fattura reverse charge interno', 'TD17' => 'Integrazione/autofattura per acquisto servizi dall\'estero', 'TD18' => 'Integrazione per acquisto di beni intracomunitari', 'TD19' => 'Integrazione/autofattura per acquisto di beni ex art.17 c.2 DPR 633/72', 'TD20' => 'Autofattura per regolarizzazione e integrazione delle fatture', 'TD21' => 'Autofattura per splafonamento', 'TD22' => 'Estrazione beni da Deposito IVA', 'TD23' => 'Estrazione beni da Deposito IVA con versamento dell\'IVA', 'TD24' => 'Fattura differita di cui all\'art.21, comma 4, lett. a)', 'TD25' => 'Fattura differita di cui all\'art.21, comma 4, terzo periodo lett. b)', 'TD26' => 'Cessione di beni ammortizzabili e per passaggi interni', 'TD27' => 'Fattura per autoconsumo o per cessioni gratuite senza rivalsa' ]; return $tipiDocumento[$codice] ?? $codice; } private function mapCondizioniPagamento($codice) { $condizioniPagamento = [ 'TP01' => 'Pagamento a rate', 'TP02' => 'Pagamento completo', 'TP03' => 'Anticipo' ]; return $condizioniPagamento[$codice] ?? $codice; } private function showResultMessages($importCount, $updateCount, $errorsCount, $importedFiles = [], $updatedFiles = [], $errorFiles = [], $errorMessages = []) { $message = ""; $messageType = "success"; if ($importCount > 0) { $message .= "Importate correttamente n° $importCount nuove fatture.
"; if (!empty($importedFiles)) { $message .= "
File importati (clicca per espandere)

"; } } if ($updateCount > 0) { $message .= "Aggiornate n° $updateCount fatture perchè già presenti.
"; if (!empty($updatedFiles)) { $message .= "
File aggiornati (clicca per espandere)

"; } } if ($errorsCount > 0) { $message .= "Importazione fallita per n° $errorsCount fatture.
"; // Aggiungi i dettagli degli errori se disponibili if (!empty($errorFiles)) { $message .= "
File con errori (clicca per espandere)

"; } if ($importCount == 0 && $updateCount == 0) { $messageType = "error"; } else { $messageType = "warning"; } } if ($importCount > 0 || $updateCount > 0 || $errorsCount > 0) { $this->emit('show-import-result', [ 'message' => $message, 'type' => $messageType ]); } else { $this->emit('show-import-result', [ 'message' => 'Nessuna fattura importata. Controlla i file XML e riprova.', 'type' => 'error' ]); } // Resetta i campi del form dopo l'importazione $this->reset(['receiptFiles']); } private function getFriendlyErrorMessage($errorMessage) { // Errore di parsing XML iniziale if (strpos($errorMessage, "simplexml_load_string(): Entity: line 1: parser error : Start tag expected, '<' not found") !== false) { return "Il file non è in formato valido. Potrebbe essere danneggiato o in un formato diverso."; } // Errore di struttura XML if (strpos($errorMessage, "FatturaElettronicaHeader o FatturaElettronicaBody non trovati") !== false) { return "Il file non sembra essere una fattura elettronica valida. Mancano le sezioni principali."; } // Altri errori comuni di parsing XML if (strpos($errorMessage, "parser error") !== false) { return "Il file XML contiene errori di formattazione e non può essere letto correttamente."; } // Errori di struttura interna if ( strpos($errorMessage, "Undefined index") !== false || strpos($errorMessage, "Trying to get property") !== false ) { return "La fattura è incompleta o non contiene tutti i dati necessari."; } // Per altri errori, conserva il messaggio originale ma semplificalo return $errorMessage; } public function getVats() { $vats = array(); foreach ($this->rows as $r) { if ($r["amount"] != null && $r["amount"] != "" && $r["vat_id"] > 0) { $vat = getVatValue($this->currencyToDouble($r["amount"]), $r["vat_id"]); $vatName = ""; foreach ($this->vats as $v) { if ($v->id == $r["vat_id"]) $vatName = $v->name; } if (isset($vats[$vatName])) $vats[$vatName] += $vat; else $vats[$vatName] = $vat; } } return $vats; } private function findOrCreateVat($vatRate) { $vatRate = (float)$vatRate; Log::info("Searching for VAT rate: $vatRate%"); $existingVat = \App\Models\Vat::where(function ($query) use ($vatRate) { $query->whereRaw('ABS(value - ?) < 0.01', [$vatRate]); })->first(); if ($existingVat) { Log::info("Found existing VAT rate: ID={$existingVat->id}, Name={$existingVat->name}, Value={$existingVat->value}"); return $existingVat->id; } Log::info("Creating new VAT rate: $vatRate%"); $vatName = "IVA $vatRate%"; $newVat = new \App\Models\Vat(); $newVat->name = $vatName; $newVat->value = $vatRate; $newVat->enabled = 1; $newVat->save(); Log::info("Created new VAT rate: ID={$newVat->id}, Name={$newVat->name}, Value={$newVat->value}"); $this->vats = \App\Models\Vat::select('id', 'name', 'value')->orderBy('value')->get(); return $newVat->id; } public function viewData($id) { Log::info("Visualizzazione dati per ID: " . $id); try { $record = \App\Models\Record::with(['supplier', 'payment_method', 'rows.causal'])->findOrFail($id); if (!$record) { $this->emit('flash-error', 'Movimento non trovato'); return; } $record->formatted_date = date("d/m/Y", strtotime($record->date)); $record->formatted_data_pagamento = $record->data_pagamento ? date("d/m/Y", strtotime($record->data_pagamento)) : 'Non impostata'; $record->supplier_name = $record->supplier ? $record->supplier->name : 'N/A'; $record->payment_method_name = $record->payment_method ? $record->payment_method->name : 'N/A'; $record->formatted_amount = formatPrice($record->amount); $record->payment_status = $record->is_paid ? 'Pagato' : 'Da Pagare'; foreach ($record->rows as $row) { $row->causal_name = $row->causal ? $row->causal->getTree() : 'N/A'; $row->formatted_imponibile = $row->imponibile ? formatPrice($row->imponibile) : 'N/A'; $row->iva = $row->aliquota_iva ? formatPrice($row->aliquota_iva) : 'N/A'; $row->formatted_imposta = $row->imposta ? formatPrice($row->imposta) : 'N/A'; $row->formatted_amount = formatPrice($row->amount); } if ($record->attachment) { $record->attachment_url = $this->getAttachmentUrl($record->attachment); } Log::info("Emitting show-record-details event"); $this->dispatchBrowserEvent('show-record-details', ['record' => $record]); } catch (\Exception $e) { Log::error("Errore nel caricamento dei dettagli: " . $e->getMessage()); $this->emit('flash-error', 'Errore nel caricamento dei dettagli: ' . $e->getMessage()); } } }