Parcourir la source

prima nota - fix esportazioni e modifiche grafiche

ferrari il y a 1 mois
Parent
commit
6c001c140e

+ 156 - 27
app/Http/Livewire/Record.php

@@ -22,6 +22,8 @@ class Record extends Component
     public $in;
     public $out;
     public $payments = [];
+    public $origins = [];
+    public $destinations = [];
     public $fromDate;
     public $toDate;
     public $appliedFromDate;
@@ -31,6 +33,9 @@ class Record extends Component
     public $isExporting = false;
     public $selectedPeriod = 'OGGI';
     public $filterCausals = null;
+    public $filterPaymentMethods = null;
+    public $filterOrigins = null;
+    public $filterDestinations = null;
     public $filterMember = null;
     public $isFiltering = false;
     public array $recordDatas = [];
@@ -50,6 +55,8 @@ class Record extends Component
         'exportEmailAddress' => 'required_if:sendViaEmail,true|email',
         'exportEmailSubject' => 'required_if:sendViaEmail,true|string|max:255',
     ];
+    public $total_in = 0;
+    public $total_out = 0;
 
     protected $messages = [
         'exportEmailAddress.required_if' => 'L\'indirizzo email è obbligatorio quando si sceglie di inviare via email.',
@@ -83,9 +90,19 @@ class Record extends Component
 
         $this->getCausals(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->get(), 0);
 
-        $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])->orderBy('last_name')->orderBy('first_name')->get();
+        $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])
+            ->where(function ($query) {
+                $query->where('is_archived', false)
+                    ->orWhereNull('is_archived');
+            })
+            ->where(function ($query) {
+                $query->where('is_deleted', false)
+                    ->orWhereNull('is_deleted');
+            })->orderBy('last_name')->orderBy('first_name')->get();
 
-        $this->payments = \App\Models\PaymentMethod::select('id', 'name', 'type')->where('enabled', true)->where('money', false)->get();
+        $this->payments = \App\Models\PaymentMethod::select('id', 'name', 'type')->where('enabled', true)->where('money', false)->orderBy('name', 'asc')->get();
+        $this->origins = \App\Models\Bank::select('id', 'name')->where('enabled', true)->whereIn('visibility', ['OUT', 'ALL'])->orderBy('name', 'asc')->get();
+        $this->destinations = \App\Models\Bank::select('id', 'name')->where('enabled', true)->whereIn('visibility', ['IN', 'ALL'])->orderBy('name', 'asc')->get();
 
         $this->selectedMonth = date('Y-m');
         $this->selectedDay = date('Y-m-d');
@@ -158,6 +175,21 @@ class Record extends Component
             Log::info('generateExportDataAndTotals: Causal filters applied', ['causal_count' => count($causals)]);
         }
 
+        if ($this->filterPaymentMethods != null && sizeof($this->filterPaymentMethods) > 0) {
+            $datas->whereIn('payment_method_id', $this->filterPaymentMethods);
+            Log::info('generateExportDataAndTotals: Payment method filters applied', ['payment_method_count' => count($this->filterPaymentMethods)]);
+        }
+
+        if ($this->filterOrigins != null && sizeof($this->filterOrigins) > 0) {
+            $datas->whereIn('origin_id', $this->filterOrigins);
+            Log::info('generateExportDataAndTotals: Origin filters applied', ['origins_count' => count($this->filterOrigins)]);
+        }
+
+        if ($this->filterDestinations != null && sizeof($this->filterDestinations) > 0) {
+            $datas->whereIn('destination_id', $this->filterDestinations);
+            Log::info('generateExportDataAndTotals: Destination filters applied', ['destinations_count' => count($this->filterDestinations)]);
+        }
+
         if ($this->filterMember != null && $this->filterMember > 0) {
             $datas->where('member_id', $this->filterMember);
             Log::info('generateExportDataAndTotals: Member filter applied', ['member_id' => $this->filterMember]);
@@ -306,7 +338,6 @@ class Record extends Component
 
             if (!$group['deleted'])
                 $exportTotals[$group['payment_method']][$group['transaction_type']] += $group['amount'];
-
         }
 
         $finalTime = microtime(true) - $finalStart;
@@ -382,6 +413,15 @@ class Record extends Component
             }
             $datas->whereIn('causal_id', $causals);
         }
+        if ($this->filterPaymentMethods != null && sizeof($this->filterPaymentMethods) > 0) {
+            $datas->whereIn('payment_method_id', $this->filterPaymentMethods);
+        }
+        if ($this->filterOrigins != null && sizeof($this->filterOrigins) > 0) {
+            $datas->whereIn('origin_id', $this->filterOrigins);
+        }
+        if ($this->filterDestinations != null && sizeof($this->filterDestinations) > 0) {
+            $datas->whereIn('destination_id', $this->filterDestinations);
+        }
         if ($this->filterMember != null && $this->filterMember > 0) {
             $datas->where('member_id', $this->filterMember);
         }
@@ -423,6 +463,9 @@ class Record extends Component
         $this->showMonthPicker = false;
         $this->showDayPicker = false;
         $this->filterCausals = [];
+        $this->filterPaymentMethods = [];
+        $this->filterOrigins = [];
+        $this->filterDestinations = [];
         $this->filterMember = null;
 
         $today = date("Y-m-d");
@@ -621,6 +664,15 @@ class Record extends Component
             }
             $datas->whereIn('causal_id', $causals);
         }
+        if ($this->filterPaymentMethods != null && sizeof($this->filterPaymentMethods) > 0) {
+            $datas->whereIn('payment_method_id', $this->filterPaymentMethods);
+        }
+        if ($this->filterOrigins != null && sizeof($this->filterOrigins) > 0) {
+            $datas->whereIn('origin_id', $this->filterOrigins);
+        }
+        if ($this->filterDestinations != null && sizeof($this->filterDestinations) > 0) {
+            $datas->whereIn('destination_id', $this->filterDestinations);
+        }
         if ($this->filterMember != null && $this->filterMember > 0) {
             $datas->where('member_id', $this->filterMember);
         }
@@ -629,14 +681,30 @@ class Record extends Component
             ->orderBy('records_rows.id', 'ASC')
             ->get();
 
-        foreach($datas as $data)
-        {
+        $ret = array();
+
+        $this->total_in = 0;
+        $this->total_out = 0;
+
+        foreach ($datas as $data) {
             $causal = \App\Models\Causal::findOrFail($data->causal_id);
-            $data->causal_name = $causal->getTree();
+            $paymentCheck = $data->payment_method->money;
+            if (!$paymentCheck && ($causal->no_first == null || !$causal->no_first)) {
+                $data->causal_name = $causal->getTree();
+                $ret[] = $data;
+
+                if ($data->type == 'IN')
+                    $this->total_in += $data->amount;
+                if ($data->type == 'OUT')
+                    $this->total_out += $data->amount;
+            }
+        }
+
+        if ($this->total_in > 0 || $this->total_out > 0) {
+            $ret[] = (object) array("date" => "", "total_in" => $this->total_in, "total_out" => $this->total_out);
         }
-        
-        return $datas;
 
+        return $ret;
     }
 
     public function render()
@@ -946,10 +1014,10 @@ class Record extends Component
             Log::info('Export: Starting COMBINED data generation phase (NO SEPARATE CALLS)');
             $startTime = microtime(true);
 
-            $records = $this->loadData($this->exportFromDate, $this->exportToDate);            
-            
+            $records = $this->loadData($this->exportFromDate, $this->exportToDate);
+
             $dataGenTime = microtime(true) - $startTime;
-            
+
 
 
             if ($this->sendViaEmail) {
@@ -1038,6 +1106,16 @@ class Record extends Component
             $query->whereIn('causal_id', $causals);
         }
 
+        if ($this->filterPaymentMethods != null && sizeof($this->filterPaymentMethods) > 0) {
+            $query->whereIn('payment_method_id', $this->filterPaymentMethods);
+        }
+        if ($this->filterOrigins != null && sizeof($this->filterOrigins) > 0) {
+            $query->whereIn('origin_id', $this->filterOrigins);
+        }
+        if ($this->filterDestinations != null && sizeof($this->filterDestinations) > 0) {
+            $query->whereIn('destination_id', $this->filterDestinations);
+        }
+
         if ($this->filterMember != null && $this->filterMember > 0) {
             $query->where('member_id', $this->filterMember);
         }
@@ -1061,6 +1139,36 @@ class Record extends Component
         return implode(', ', $causals);
     }
 
+    private function getPaymentMethodsNames($paymentMethodIds)
+    {
+        if (!is_array($paymentMethodIds)) {
+            return null;
+        }
+
+        $payment_methods = \App\Models\PaymentMethod::whereIn('id', $paymentMethodIds)->pluck('name')->toArray();
+        return implode(', ', $payment_methods);
+    }
+
+    private function getOriginsNames($originIds)
+    {
+        if (!is_array($originIds)) {
+            return null;
+        }
+
+        $origins = \App\Models\Bank::whereIn('id', $originIds)->pluck('name')->toArray();
+        return implode(', ', $origins);
+    }
+
+    private function getDestinationsNames($destinationIds)
+    {
+        if (!is_array($destinationIds)) {
+            return null;
+        }
+
+        $destinations = \App\Models\Bank::whereIn('id', $destinationIds)->pluck('name')->toArray();
+        return implode(', ', $destinations);
+    }
+
     public function updatedExportFromDate()
     {
         $this->updateEmailSubject();
@@ -1111,6 +1219,9 @@ class Record extends Component
             $filterDescriptions = [
                 'member' => $this->filterMember ? $this->getMemberName($this->filterMember) : null,
                 'causals' => $this->filterCausals ? $this->getCausalsNames($this->filterCausals) : null,
+                'payment_methods' => $this->filterPaymentMethods ? $this->getPaymentMethodsNames($this->filterPaymentMethods) : null,
+                'origins' => $this->filterOrigins ? $this->getOriginsNames($this->filterOrigins) : null,
+                'destinations' => $this->filterDestinations ? $this->getDestinationsNames($this->filterDestinations) : null,
             ];
 
             $paymentsArray = $this->payments->map(function ($payment) {
@@ -1562,7 +1673,13 @@ class Record extends Component
         return $email;
     }
 
-        public function updatedSelectedDay($value)
+    public function updatedSelectedPeriod($value)
+    {
+        $this->setPeriodDates();
+        $this->applyFilters();
+    }
+
+    public function updatedSelectedDay($value)
     {
         if (!empty($value)) {
             $this->selectedPeriod = 'GIORNO_PERSONALIZZATO';
@@ -1623,7 +1740,6 @@ class Record extends Component
 
     private function exportExcel($records)
     {
-        
         $startTime = microtime(true);
 
         $spreadsheet = new Spreadsheet();
@@ -1647,18 +1763,30 @@ class Record extends Component
         $activeWorksheet->getStyle('A1:J1')->getFont()->getColor()->setARGB('FFFFFFFF');
 
         $idx = 2;
-        foreach($records as $record)
-        {
-            $activeWorksheet->setCellValue('A' . $idx, date("d/m/Y", strtotime($record->date)));
-            $activeWorksheet->setCellValue('B' . $idx, $record->commercial ? 'Commerciale' : 'Non commerciale');
-            $activeWorksheet->setCellValue('C' . $idx, $record->causal_name);
-            $activeWorksheet->setCellValue('D' . $idx, $record->type == 'IN' ? ($record->member->first_name . " " . $record->member->last_name) : @$record->supplier->name);
-            $activeWorksheet->setCellValue('E' . $idx, $record->deleted ? 'Annullata' : '');
-            $activeWorksheet->setCellValue('F' . $idx, $record->type == 'IN' ? formatPrice($record->amount) : '');
-            $activeWorksheet->setCellValue('G' . $idx, $record->type == 'OUT' ? formatPrice($record->amount) : '');
-            $activeWorksheet->setCellValue('H' . $idx, $record->type == 'OUT' ? $record->origin : '');
-            $activeWorksheet->setCellValue('I' . $idx, $record->type == 'IN' ? $record->destination : '');
-            $activeWorksheet->setCellValue('J' . $idx, $record->payment_method->name);
+        foreach ($records as $record) {
+            if ($record->date != '') {
+                $activeWorksheet->setCellValue('A' . $idx, date("d/m/Y", strtotime($record->date)));
+                $activeWorksheet->setCellValue('B' . $idx, $record->commercial ? 'Commerciale' : 'Non commerciale');
+                $activeWorksheet->setCellValue('C' . $idx, $record->causal_name);
+                $activeWorksheet->setCellValue('D' . $idx, $record->type == 'IN' ? ($record->member->first_name . " " . $record->member->last_name) : @$record->supplier->name);
+                $activeWorksheet->setCellValue('E' . $idx, $record->deleted ? 'Annullata' : '');
+                $activeWorksheet->setCellValue('F' . $idx, $record->type == 'IN' ? formatPrice($record->amount) : '');
+                $activeWorksheet->setCellValue('G' . $idx, $record->type == 'OUT' ? formatPrice($record->amount) : '');
+                $activeWorksheet->setCellValue('H' . $idx, $record->type == 'OUT' ? $record->origin : '');
+                $activeWorksheet->setCellValue('I' . $idx, $record->type == 'IN' ? $record->destination : '');
+                $activeWorksheet->setCellValue('J' . $idx, $record->payment_method->name);
+            } else {
+                $activeWorksheet->setCellValue('A' . $idx, "Totali");
+                $activeWorksheet->setCellValue('B' . $idx, "");
+                $activeWorksheet->setCellValue('C' . $idx, "");
+                $activeWorksheet->setCellValue('D' . $idx, "");
+                $activeWorksheet->setCellValue('E' . $idx, "");
+                $activeWorksheet->setCellValue('F' . $idx, formatPrice($record->total_in));
+                $activeWorksheet->setCellValue('G' . $idx, formatPrice($record->total_out));
+                $activeWorksheet->setCellValue('H' . $idx, "");
+                $activeWorksheet->setCellValue('I' . $idx, "");
+                $activeWorksheet->setCellValue('J' . $idx, "");
+            }
             $idx++;
         }
 
@@ -1673,6 +1801,8 @@ class Record extends Component
         $activeWorksheet->getColumnDimension('I')->setWidth(20);
         $activeWorksheet->getColumnDimension('J')->setWidth(30);
 
+        $activeWorksheet->getStyle('A' . ($idx - 1) . ':J' . ($idx - 1))->getFont()->setBold(true);
+
         $filename = 'prima_nota_' . date("YmdHis") . '.xlsx';
         Log::info('exportWithData: Preparing to save file', [
             'filename' => $filename,
@@ -1687,7 +1817,7 @@ class Record extends Component
             $writer = new Xlsx($spreadsheet);
 
             $writer->save($tempPath);
-            
+
             unset($spreadsheet, $activeWorksheet, $writer);
             gc_collect_cycles();
 
@@ -1766,5 +1896,4 @@ class Record extends Component
             return response()->download($localPath)->deleteFileAfterSend();
         }
     }
-
 }

+ 147 - 15
app/Jobs/ExportPrimaNota.php

@@ -23,8 +23,9 @@ class ExportPrimaNota implements ShouldQueue
     public $tries = 3;
     public $maxExceptions = 3;
 
-    protected $exportData;
-    protected $exportTotals;
+    //protected $exportData;
+    //protected $exportTotals;
+    protected $records;
     protected $emailAddress;
     protected $emailSubject;
     protected $dateRange;
@@ -35,10 +36,12 @@ class ExportPrimaNota implements ShouldQueue
     /**
      * Create a new job instance.
      */
-    public function __construct($exportData, $exportTotals, $emailAddress, $emailSubject, $dateRange, $userId, $payments, $filters = [])
+    //public function __construct($exportData, $exportTotals, $emailAddress, $emailSubject, $dateRange, $userId, $payments, $filters = [])
+    public function __construct($records, $emailAddress, $emailSubject, $dateRange, $userId, $payments, $filters = [])
     {
-        $this->exportData = $exportData;
-        $this->exportTotals = $exportTotals;
+        //$this->exportData = $exportData;
+        //$this->exportTotals = $exportTotals;
+        $this->records = $records;
         $this->emailAddress = $emailAddress;
         $this->emailSubject = $emailSubject;
         $this->dateRange = $dateRange;
@@ -59,7 +62,7 @@ class ExportPrimaNota implements ShouldQueue
                 'user_id' => $this->userId,
                 'email' => $this->emailAddress,
                 'date_range' => $this->dateRange,
-                'total_records' => count($this->exportData)
+                //'total_records' => count($this->exportData)
             ]);
 
             ini_set('memory_limit', '1024M');
@@ -86,7 +89,8 @@ class ExportPrimaNota implements ShouldQueue
                 'subject' => $this->emailSubject,
                 'from_date' => $this->dateRange['from'],
                 'to_date' => $this->dateRange['to'],
-                'total_records' => count($this->exportData),
+                // 'total_records' => count($this->exportData),
+                'total_records' => count($this->records) - 1,
                 'user_name' => $user ? $user->name : 'Utente',
                 'generated_at' => now()->format('d/m/Y H:i:s'),
                 'filters_applied' => $this->getFiltersDescription(),
@@ -161,11 +165,116 @@ class ExportPrimaNota implements ShouldQueue
     /**
      * Create Excel file with export data
      */
+
     private function createExcelFile($filePath)
+    {
+        $startTime = microtime(true);
+
+        $spreadsheet = new Spreadsheet();
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+
+        $activeWorksheet->setCellValue('A1', "Data");
+        $activeWorksheet->setCellValue('B1', "Tipologia");
+        $activeWorksheet->setCellValue('C1', "Causale");
+        $activeWorksheet->setCellValue('D1', "Nominativo");
+        $activeWorksheet->setCellValue('E1', "Stato");
+        $activeWorksheet->setCellValue('F1', "Entrata");
+        $activeWorksheet->setCellValue('G1', "Uscita");
+        $activeWorksheet->setCellValue('H1', "Origine");
+        $activeWorksheet->setCellValue('I1', "Destinazione");
+        $activeWorksheet->setCellValue('J1', "Metodo di pagamento");
+
+        $activeWorksheet->getStyle('A1:J1')->getFill()
+            ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+            ->getStartColor()->setARGB('FF0C6197');
+
+        $activeWorksheet->getStyle('A1:J1')->getFont()->getColor()->setARGB('FFFFFFFF');
+
+        $idx = 2;
+        foreach($this->records as $record)
+        {
+            if ($record->date != '')
+            {
+                $activeWorksheet->setCellValue('A' . $idx, date("d/m/Y", strtotime($record->date)));
+                $activeWorksheet->setCellValue('B' . $idx, $record->commercial ? 'Commerciale' : 'Non commerciale');
+                $activeWorksheet->setCellValue('C' . $idx, $record->causal_name);
+                $activeWorksheet->setCellValue('D' . $idx, $record->type == 'IN' ? ($record->member->first_name . " " . $record->member->last_name) : @$record->supplier->name);
+                $activeWorksheet->setCellValue('E' . $idx, $record->deleted ? 'Annullata' : '');
+                $activeWorksheet->setCellValue('F' . $idx, $record->type == 'IN' ? formatPrice($record->amount) : '');
+                $activeWorksheet->setCellValue('G' . $idx, $record->type == 'OUT' ? formatPrice($record->amount) : '');
+                $activeWorksheet->setCellValue('H' . $idx, $record->type == 'OUT' ? $record->origin : '');
+                $activeWorksheet->setCellValue('I' . $idx, $record->type == 'IN' ? $record->destination : '');
+                $activeWorksheet->setCellValue('J' . $idx, $record->payment_method->name);
+            }
+            else
+            {
+                $activeWorksheet->setCellValue('A' . $idx, "Totali");
+                $activeWorksheet->setCellValue('B' . $idx, "");
+                $activeWorksheet->setCellValue('C' . $idx, "");
+                $activeWorksheet->setCellValue('D' . $idx, "");
+                $activeWorksheet->setCellValue('E' . $idx, "");
+                $activeWorksheet->setCellValue('F' . $idx, formatPrice($record->total_in));
+                $activeWorksheet->setCellValue('G' . $idx, formatPrice($record->total_out));
+                $activeWorksheet->setCellValue('H' . $idx, "");
+                $activeWorksheet->setCellValue('I' . $idx, "");
+                $activeWorksheet->setCellValue('J' . $idx, "");
+            }
+            $idx++;
+        }
+
+        $activeWorksheet->getColumnDimension('A')->setWidth(10);
+        $activeWorksheet->getColumnDimension('B')->setWidth(30);
+        $activeWorksheet->getColumnDimension('C')->setWidth(30);
+        $activeWorksheet->getColumnDimension('D')->setWidth(30);
+        $activeWorksheet->getColumnDimension('E')->setWidth(10);
+        $activeWorksheet->getColumnDimension('F')->setWidth(10);
+        $activeWorksheet->getColumnDimension('G')->setWidth(10);
+        $activeWorksheet->getColumnDimension('H')->setWidth(20);
+        $activeWorksheet->getColumnDimension('I')->setWidth(20);
+        $activeWorksheet->getColumnDimension('J')->setWidth(30);
+
+        $activeWorksheet->getStyle('A' . ($idx - 1) . ':J' . ($idx - 1))->getFont()->setBold(true);
+
+        try {
+            $writerStart = microtime(true);
+            $writer = new Xlsx($spreadsheet);
+            $writer->save($filePath);
+            $writerTime = microtime(true) - $writerStart;
+
+            Log::info('Job createExcelFile: File saved successfully', [
+                'file_path' => $filePath,
+                'file_exists' => file_exists($filePath),
+                'file_size' => file_exists($filePath) ? filesize($filePath) : 0,
+                'writer_time' => $writerTime,
+                'total_time' => microtime(true) - $startTime,
+                'memory_peak' => memory_get_peak_usage(true)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Job createExcelFile: Error during file save', [
+                'file_path' => $filePath,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+                'memory_usage' => memory_get_usage(true),
+                'time_elapsed' => microtime(true) - $startTime
+            ]);
+            throw $e;
+        }
+
+        Log::info('Job createExcelFile: Cleaning up memory');
+        unset($spreadsheet, $activeWorksheet, $writer);
+        gc_collect_cycles();
+
+        Log::info('Job createExcelFile: Completed successfully', [
+            'total_time' => microtime(true) - $startTime,
+            'memory_after_cleanup' => memory_get_usage(true)
+        ]);
+    }
+
+    private function createExcelFileOLD($filePath)
     {
         Log::info('Job createExcelFile: Starting Excel file creation', [
             'file_path' => $filePath,
-            'export_data_count' => count($this->exportData),
+            //'export_data_count' => count($this->exportData),
             'payments_count' => count($this->payments),
             'memory_before' => memory_get_usage(true),
             'time_limit' => ini_get('max_execution_time')
@@ -192,8 +301,8 @@ class ExportPrimaNota implements ShouldQueue
 
         Log::info('Job createExcelFile: Setting basic headers');
         $activeWorksheet->setCellValue('A1', "Data");
-        $activeWorksheet->setCellValue('B1', "Tipologia");
-        $activeWorksheet->setCellValue('C1', "Causale");
+        $activeWorksheet->setCellValue('B1', "Causale");
+        $activeWorksheet->setCellValue('C1', "Dettaglio Causale");
         $activeWorksheet->setCellValue('D1', "Nominativo");
         $activeWorksheet->setCellValue('E1', "Stato");
 
@@ -270,7 +379,7 @@ class ExportPrimaNota implements ShouldQueue
             ->getFont()->getColor()->setARGB('FFFFFFFF');
 
         Log::info('Job createExcelFile: Starting data row processing', [
-            'total_export_records' => count($this->exportData),
+            //'total_export_records' => count($this->exportData),
             'time_elapsed_so_far' => microtime(true) - $startTime
         ]);
 
@@ -694,15 +803,18 @@ class ExportPrimaNota implements ShouldQueue
         $activeWorksheet->fromArray([$totalsData], null, 'A' . $totalRow, true);
     }
 
+    // Apply styles more efficiently
     private function applyStylesEfficiently($activeWorksheet, $letters)
     {
         $maxCol = min(count($letters) - 1, count($this->payments) * 2 + 4);
         $lastCol = $letters[$maxCol];
-        $totalRows = count($this->exportData) + 4;
+        $totalRows = count($this->exportData) + 4; // +4 for headers, spacing, and totals
 
+        // Apply header styles
         $headerRange = 'A1:' . $lastCol . '2';
         $activeWorksheet->getStyle($headerRange)->getFont()->setBold(true);
 
+        // Apply header background
         $activeWorksheet->getStyle('A1:' . $lastCol . '1')
             ->getFill()
             ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
@@ -711,6 +823,7 @@ class ExportPrimaNota implements ShouldQueue
         $activeWorksheet->getStyle('A1:' . $lastCol . '1')
             ->getFont()->getColor()->setARGB('FFFFFFFF');
 
+        // Apply totals row styles
         $totalsRange = 'A' . $totalRows . ':' . $lastCol . $totalRows;
         $activeWorksheet->getStyle($totalsRange)->getFont()->setBold(true);
         $activeWorksheet->getStyle($totalsRange)
@@ -719,6 +832,7 @@ class ExportPrimaNota implements ShouldQueue
             ->getStartColor()->setARGB('FFF0F0F0');
     }
 
+    // Set column dimensions efficiently
     private function setColumnDimensions($activeWorksheet, $letters)
     {
         $dimensions = [
@@ -733,7 +847,8 @@ class ExportPrimaNota implements ShouldQueue
             $activeWorksheet->getColumnDimension($col)->setWidth($width);
         }
 
-        for ($i = 5; $i < count($letters) && $i < 50; $i++) {
+        // Set payment method column widths
+        for ($i = 5; $i < count($letters) && $i < 50; $i++) { // Limit to prevent excessive loops
             $activeWorksheet->getColumnDimension($letters[$i])->setWidth(15);
         }
     }
@@ -760,6 +875,18 @@ class ExportPrimaNota implements ShouldQueue
             $descriptions[] = "Causali: " . (is_array($this->filters['causals']) ? implode(', ', $this->filters['causals']) : $this->filters['causals']);
         }
 
+        if (!empty($this->filters['payment_methods'])) {
+            $descriptions[] = "Metodi di pagamento: " . (is_array($this->filters['payment_methods']) ? implode(', ', $this->filters['payment_methods']) : $this->filters['payment_methods']);
+        }
+
+        if (!empty($this->filters['origins'])) {
+            $descriptions[] = "Origini: " . (is_array($this->filters['origins']) ? implode(', ', $this->filters['origins']) : $this->filters['origins']);
+        }
+
+        if (!empty($this->filters['destinations'])) {
+            $descriptions[] = "Destinazioni: " . (is_array($this->filters['destinations']) ? implode(', ', $this->filters['destinations']) : $this->filters['destinations']);
+        }
+
         return empty($descriptions) ? 'Nessun filtro applicato' : implode(' | ', $descriptions);
     }
 
@@ -775,12 +902,15 @@ class ExportPrimaNota implements ShouldQueue
         ]);
 
         try {
+            // Execute every 5 seconds to monitor progress
             $lastCheck = $startTime;
             $result = null;
 
+            // For non-blocking operations, we can't easily interrupt,
+            // but we can log progress
             register_tick_function(function () use ($startTime, $maxExecutionTime, $description, &$lastCheck) {
                 $currentTime = microtime(true);
-                if ($currentTime - $lastCheck >= 5) {
+                if ($currentTime - $lastCheck >= 5) { // Log every 5 seconds
                     $elapsed = $currentTime - $startTime;
                     $remaining = $maxExecutionTime > 0 ? $maxExecutionTime - $elapsed : 'unlimited';
 
@@ -833,7 +963,7 @@ class ExportPrimaNota implements ShouldQueue
 
         $maxExecutionTime = ini_get('max_execution_time');
         if ($maxExecutionTime <= 0) {
-            return false;
+            return false; // No limit set
         }
 
         $elapsed = microtime(true) - $startTime;
@@ -859,6 +989,8 @@ class ExportPrimaNota implements ShouldQueue
         return false;
     }
 
+    // SOLUTION 8: Log configuration and environment info at start
+
     public function logEnvironmentInfo()
     {
         Log::info('=== EXPORT ENVIRONMENT INFO ===', [

+ 1 - 1
public/css/style.css

@@ -16733,7 +16733,7 @@ body:has(#filter--section.filterWrapper_open) #card--dashboard {
 }
 
 #resume-table.records-table {
-  height: calc(100dvh - (86px + 195px)) !important;
+  height: calc(100dvh - (86px + 280px)) !important;
 }
 
 #card--dashboard:has(.showFilter.filter_shown) #resume-table.course_list-table {

+ 4 - 4
resources/views/layouts/app.blade.php

@@ -308,12 +308,12 @@
                     </div>
                     <div class="accordion-item">
                         <h2 class="accordion-header linkMenu" id="headingTwo">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="{{Request::is('in') || Request::is('out') || Request::is('receipts') || Request::is('records_in_out') || Request::is('records') || Request::is('records_old') ? 'true' : 'false'}}" aria-controls="collapseTwo">
+                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="{{Request::is('in') || Request::is('out') || Request::is('receipts') || Request::is('records_in_out') || Request::is('records'){{--  || Request::is('records_old') --}} ? 'true' : 'false'}}" aria-controls="collapseTwo">
                                 <i class="fas fa-signal"></i>
                                 <span>Contabilità</span>
                             </button>
                         </h2>
-                        <div id="collapseTwo" class="accordion-collapse collapse {{Request::is('in') || Request::is('out') || Request::is('receipts') || Request::is('records_in_out') || Request::is('records') || Request::is('records_old') ? 'show' : ''}}" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
+                        <div id="collapseTwo" class="accordion-collapse collapse {{Request::is('in') || Request::is('out') || Request::is('receipts') || Request::is('records_in_out') || Request::is('records'){{--  || Request::is('records_old') --}} ? 'show' : ''}}" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
                             <div class="accordion-body">
                                 <ul class="nav nav-pills flex-column align-items-center align-items-sm-start w-100" id="menu-contabilita" style="margin-top:0px;">
                                     <li class="nav-item {{Request::is('in') ? "nav-item-active" : ""}}">
@@ -345,11 +345,11 @@
                                             <span class="ms-3 d-md-inline">Prima Nota</span>
                                         </a>
                                     </li>
-                                    <li class="nav-item {{Request::is('records_old') ? "nav-item-active" : ""}}">
+                                    {{-- <li class="nav-item {{Request::is('records_old') ? "nav-item-active" : ""}}">
                                         <a href="/records_old" class="nav-link d-flex align-items-center linkMenu">
                                             <span class="ms-3 d-md-inline">Prima Nota (OLD)</span>
                                         </a>
-                                    </li>
+                                    </li> --}}
                                 </ul>
                             </div>
                         </div>

+ 190 - 20
resources/views/livewire/records.blade.php

@@ -19,7 +19,7 @@
                     @endforeach
                 </select>
             </div>
-            <div class="col-md-4">
+            <div class="col-md-3">
                 Causale
                 <select name="search_causal_id[]" class="form-select filterCausals me-2" multiple="multiple" wire:model="filterCausals">
                     @foreach($causals as $causal)
@@ -259,7 +259,33 @@
                 @endif
                 @endif
             </div>
-            <div class="col-md-2">
+        </div>
+        <div class="row g-3">
+            <div class="col-md-3">
+                Metodo di pagamento
+                <select name="search_payment_method[]" class="form-select filterPaymentMethods me-2" multiple="multiple" wire:model="filterPaymentMethods">
+                    @foreach($payments as $payment)
+                        <option value="{{$payment["id"]}}">{{$payment["name"]}}
+                    @endforeach
+                </select>
+            </div>
+            <div class="col-md-3">
+                Origine
+                <select name="search_origin[]" class="form-select filterOrigins me-2" multiple="multiple" wire:model="filterOrigins">
+                    @foreach($origins as $origin)
+                        <option value="{{$origin["id"]}}">{{$origin["name"]}}
+                    @endforeach
+                </select>
+            </div>
+            <div class="col-md-3">
+                Destinazione
+                <select name="search_destination[]" class="form-select filterDestinations me-2" multiple="multiple" wire:model="filterDestinations">
+                    @foreach($destinations as $destination)
+                        <option value="{{$destination["id"]}}">{{$destination["name"]}}
+                    @endforeach
+                </select>
+            </div>
+            <div class="col">
                 <div class="prima--nota_buttons ms-auto" style="float:right; margin-top:25px;">
                     <button class="btn--ui primary" wire:click="applyFilters" style="margin-right:5px;" @if($isFiltering) disabled @endif>
                         @if($isFiltering)
@@ -337,9 +363,9 @@
                     <th scope="col">Causale</th>
                     <th scope="col">Nominativo</th>
                     <th scope="col">Stato</th>
-                    <th scope="col">Entrata</th>
-                    <th scope="col">Uscita</th>
-                    <th scope="col">Origine</th>
+                    <th scope="col" class="text-end">Entrata</th>
+                    <th scope="col" class="text-end">Uscita</th>
+                    <th scope="col" style="padding-left: 20px;">Origine</th>
                     <th scope="col">Destinazione</th>
                     <th scope="col">Metodo di pagamento</th>
                 </tr>
@@ -359,21 +385,33 @@
             <tbody id="checkall-target">
                 @php $count = 0; @endphp
                 @foreach($records as $record)
-                    <tr>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{date("d/m/Y", strtotime($record->date))}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->commercial ? 'Commerciale' : 'Non commerciale'}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->causal_name}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->type == 'IN' ? ($record->member->first_name . " " . $record->member->last_name) : @$record->supplier->name}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->deleted ? 'Annullata' : ''}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->type == 'IN' ? formatPrice($record->amount) : ''}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->type == 'OUT' ? formatPrice($record->amount) : ''}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->type == 'OUT' ? $record->origin : ''}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->type == 'IN' ? $record->destination : ''}}</td>
-                        <td style="background-color:{{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->payment_method->name}}</td>
-                    </tr>
-                    @php $count++; @endphp
+                    @if($record->date != '')
+                        <tr>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{date("d/m/Y", strtotime($record->date))}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->commercial ? 'Commerciale' : 'Non commerciale'}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}; width: 22%;white-space: pre-line;">{{$record->causal_name}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}; width: 22%">{{$record->type == 'IN' ? ($record->member->first_name . " " . $record->member->last_name) : @$record->supplier->name}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->deleted ? 'Annullata' : ''}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}; text-align: right; color: green">{{$record->type == 'IN' ? formatPrice($record->amount) : ''}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}; text-align: right; color: red">{{$record->type == 'OUT' ? formatPrice($record->amount) : ''}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}; padding-left: 20px;">{{$record->type == 'OUT' ? $record->origin : ''}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->type == 'IN' ? $record->destination : ''}}</td>
+                            <td style="background-color: {{$count % 2 == 0 ? 'white' : '#f2f4f7'}}">{{$record->payment_method->name}}</td>
+                        </tr>
+                        @php $count++; @endphp
+                    @endif
                 @endforeach
             </tbody>
+            @if($total_in > 0 || $total_out > 0)
+                <tfoot>
+                    <tr>
+                        <td colspan="5"><b>Totale</b></td>
+                        <td style="text-align:right; color: green; padding: 0; border-color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));"><b>{{$total_in > 0 ? formatPrice($total_in) : ''}}</b></td>
+                        <td style="text-align:right; color: red; padding: 0; border-color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));"><b>{{$total_out > 0 ? formatPrice($total_out) : ''}}</b></td>
+                        <td colspan="3"></td>
+                    </tr>
+                </tfoot>
+            @endif
         </table>
         <button type="button" class="btn btn-floating btn-lg" id="btn-back-to-bottom"><i class="fas fa-arrow-down"></i></button>
         <button type="button" class="btn btn-floating btn-lg" id="btn-back-to-top"><i class="fas fa-arrow-up"></i></button>
@@ -382,7 +420,7 @@
     <div class="modal fade" id="causalsModal" tabindex="-1" aria-labelledby="causalsModalLabel" aria-hidden="true">
         <div class="modal-dialog modal-lg">
             <div class="modal-content">
-                <div class="modal-header" style="background-color: #0C6197!important;">
+                <div class="modal-header modal-header-blu">
                     <h5 class="modal-title" id="causalsModalLabel">
                         <i class="me-2"></i>
                         Dettaglio Causali
@@ -419,7 +457,7 @@
     <div class="modal fade" id="exportModal" tabindex="-1" aria-labelledby="exportModalLabel" aria-hidden="true">
     <div class="modal-dialog">
         <div class="modal-content">
-            <div class="modal-header" style="background-color: #0C6197!important;">
+            <div class="modal-header modal-header-blu">
                 <h5 class="modal-title" id="exportModalLabel">Seleziona Periodo per Export</h5>
                 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="CHIUDI"></button>
             </div>
@@ -1324,6 +1362,21 @@
                     $(this).select2('close');
                 }
             });
+            $('.filterPaymentMethods').each(function() {
+                if ($(this).hasClass('select2-hidden-accessible')) {
+                    $(this).select2('close');
+                }
+            });
+            $('.filterOrigins').each(function() {
+                if ($(this).hasClass('select2-hidden-accessible')) {
+                    $(this).select2('close');
+                }
+            });
+            $('.filterDestinations').each(function() {
+                if ($(this).hasClass('select2-hidden-accessible')) {
+                    $(this).select2('close');
+                }
+            });
             $('.filterMember').each(function() {
                 if ($(this).hasClass('select2-hidden-accessible')) {
                     $(this).select2('close');
@@ -1370,6 +1423,117 @@
                     }, 100);
                 });
 
+                $(document).on("keypress", $('.filterPaymentMethods'), function (e) {
+                    setTimeout(() => {
+                        $(".select2-results__option").each(function(){
+                            var txt = $(this).html();
+                            var count = (txt.match(/-/g) || []).length;
+                            $(this).addClass('paddingLeftSelect' + count);
+                        });
+                    }, 100);
+                });
+
+                if (!$('.filterPaymentMethods').hasClass('select2-hidden-accessible')) {
+                    $('.filterPaymentMethods').select2({
+                        "language": {"noResults": function(){return "Nessun risultato";}},
+                        "dropdownParent": $('body'),
+                        "width": "100%"
+                    });
+                }
+
+                $('.filterPaymentMethods').off('change.customHandler').on('change.customHandler', function (e) {
+                    var data = $('.filterPaymentMethods').select2("val");
+                    @this.set('filterPaymentMethods', data);
+                });
+
+                $('.filterPaymentMethods').off('select2:open.customHandler').on('select2:open.customHandler', function (e) {
+                    if ($('#causalsModal').hasClass('show')) {
+                        $('#causalsModal').modal('hide');
+                    }
+
+                    setTimeout(() => {
+                        $(".select2-results__option").each(function(){
+                            var txt = $(this).html();
+                            var count = (txt.match(/-/g) || []).length;
+                            $(this).addClass('paddingLeftSelect' + count);
+                        });
+                    }, 100);
+                });
+
+                $(document).on("keypress", $('.filterOrigins'), function (e) {
+                    setTimeout(() => {
+                        $(".select2-results__option").each(function(){
+                            var txt = $(this).html();
+                            var count = (txt.match(/-/g) || []).length;
+                            $(this).addClass('paddingLeftSelect' + count);
+                        });
+                    }, 100);
+                });
+
+                if (!$('.filterOrigins').hasClass('select2-hidden-accessible')) {
+                    $('.filterOrigins').select2({
+                        "language": {"noResults": function(){return "Nessun risultato";}},
+                        "dropdownParent": $('body'),
+                        "width": "100%"
+                    });
+                }
+
+                $('.filterOrigins').off('change.customHandler').on('change.customHandler', function (e) {
+                    var data = $('.filterOrigins').select2("val");
+                    @this.set('filterOrigins', data);
+                });
+
+                $('.filterOrigins').off('select2:open.customHandler').on('select2:open.customHandler', function (e) {
+                    if ($('#causalsModal').hasClass('show')) {
+                        $('#causalsModal').modal('hide');
+                    }
+
+                    setTimeout(() => {
+                        $(".select2-results__option").each(function(){
+                            var txt = $(this).html();
+                            var count = (txt.match(/-/g) || []).length;
+                            $(this).addClass('paddingLeftSelect' + count);
+                        });
+                    }, 100);
+                });
+
+                $(document).on("keypress", $('.filterDestinations'), function (e) {
+                    setTimeout(() => {
+                        $(".select2-results__option").each(function(){
+                            var txt = $(this).html();
+                            var count = (txt.match(/-/g) || []).length;
+                            $(this).addClass('paddingLeftSelect' + count);
+                        });
+                    }, 100);
+                });
+
+                if (!$('.filterDestinations').hasClass('select2-hidden-accessible')) {
+                    $('.filterDestinations').select2({
+                        "language": {"noResults": function(){return "Nessun risultato";}},
+                        "dropdownParent": $('body'),
+                        "width": "100%"
+                    });
+                }
+
+                $('.filterDestinations').off('change.customHandler').on('change.customHandler', function (e) {
+                    var data = $('.filterDestinations').select2("val");
+                    @this.set('filterDestinations', data);
+                });
+
+                $('.filterDestinations').off('select2:open.customHandler').on('select2:open.customHandler', function (e) {
+                    if ($('#causalsModal').hasClass('show')) {
+                        $('#causalsModal').modal('hide');
+                    }
+
+                    setTimeout(() => {
+                        $(".select2-results__option").each(function(){
+                            var txt = $(this).html();
+                            var count = (txt.match(/-/g) || []).length;
+                            $(this).addClass('paddingLeftSelect' + count);
+                        });
+                    }, 100);
+                });
+
                 if (!$('.filterMember').hasClass('select2-hidden-accessible')) {
                     $('.filterMember').select2({
                         "language": {"noResults": function(){return "Nessun risultato";}},
@@ -1507,6 +1671,9 @@
         Livewire.on('filters-reset', () => {
             $('.filterMember').val('').trigger('change');
             $('.filterCausals').val('').trigger('change');
+            $('.filterPaymentMethods').val('').trigger('change');
+            $('.filterOrigins').val('').trigger('change');
+            $('.filterDestinations').val('').trigger('change');
             load();
         });
 
@@ -1835,6 +2002,9 @@
         function resetFiltersWithMonthPicker() {
             $('.filterMember').val('').trigger('change');
             $('.filterCausals').val('').trigger('change');
+            $('.filterPaymentMethods').val('').trigger('change');
+            $('.filterOrigins').val('').trigger('change');
+            $('.filterDestinations').val('').trigger('change');
 
             if (typeof @this !== 'undefined') {
                 @this.call('resetFilters');