Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportFinanceFromDrive
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 2
182
0.00% covered (danger)
0.00%
0 / 1
 handle
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
 processFile
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2
3namespace App\Console\Commands;
4
5use App\Models\TblFinanceReportSemanal;
6use App\Models\TblFinanceSedes;
7use Carbon\Carbon;
8use Illuminate\Console\Command;
9use Illuminate\Support\Facades\Log;
10use Illuminate\Support\Facades\Storage;
11use PhpOffice\PhpSpreadsheet\IOFactory;
12
13class ImportFinanceFromDrive extends Command
14{
15    protected $signature = 'finance:import-drive';
16
17    protected $description = 'Import finance data from Excel files in Google Drive for regions not in Zenital';
18
19    /**
20     * Map of Spanish month names to month numbers.
21     */
22    private const MONTH_MAP = [
23        'enero' => 1,
24        'febrero' => 2,
25        'marzo' => 3,
26        'abril' => 4,
27        'mayo' => 5,
28        'junio' => 6,
29        'julio' => 7,
30        'agosto' => 8,
31        'septiembre' => 9,
32        'octubre' => 10,
33        'noviembre' => 11,
34        'diciembre' => 12,
35    ];
36
37    public function handle(): int
38    {
39        try {
40            $folderId = env('GOOGLE_DRIVE_FINANCE_FOLDER_ID');
41            if ($folderId) {
42                // Override the default folder for the google disk
43                config(['filesystems.disks.google.folderId' => $folderId]);
44            }
45
46            $disk = Storage::disk('google');
47            $files = $disk->files('/');
48
49            $xlsxFiles = array_filter($files, fn ($f) => str_ends_with(strtolower($f), '.xlsx'));
50
51            if (empty($xlsxFiles)) {
52                $this->info('No .xlsx files found in Google Drive folder.');
53
54                return 0;
55            }
56
57            foreach ($xlsxFiles as $file) {
58                $this->info("Processing: {$file}");
59
60                $tempPath = storage_path('app/temp_finance_import.xlsx');
61                file_put_contents($tempPath, $disk->get($file));
62
63                $imported = $this->processFile($tempPath, basename($file));
64
65                unlink($tempPath);
66
67                $this->info("Imported {$imported} rows from {$file}");
68                Log::channel('third-party')->info("finance:import-drive — Imported {$imported} rows from {$file}");
69
70                // Move processed file to 'processed' subfolder
71                $processedName = 'processed/'.date('Y-m-d_His').'_'.basename($file);
72                $disk->move($file, $processedName);
73            }
74
75            return 0;
76        } catch (\Exception $e) {
77            $this->error("Import failed: {$e->getMessage()}");
78            Log::channel('third-party')->error("finance:import-drive — {$e->getMessage()}");
79
80            return 1;
81        }
82    }
83
84    /**
85     * Expected Excel format (per-sede billing file):
86     * Filename: "Facturación <SedeName>.xlsx"
87     * Column A: Rango Inferior (start date, e.g. 01/01/2026)
88     * Column B: Rango Superior (end date, e.g. 31/01/2026)
89     * Column C: Mes (month name in Spanish: Enero, Febrero, etc.)
90     * Column D: Base Imponible (billing amount)
91     *
92     * Each row is stored into tbl_finance_report_semanal using week_date = Rango Inferior.
93     */
94    private function processFile(string $path, string $filename): int
95    {
96        // Parse sede name from filename: "Facturación Cano Lopera.xlsx" -> "Cano Lopera"
97        $sedeName = $filename;
98        $sedeName = preg_replace('/\.xlsx$/i', '', $sedeName);
99        $sedeName = preg_replace('/^Facturación\s+/iu', '', $sedeName);
100        $sedeName = trim($sedeName);
101
102        if (empty($sedeName)) {
103            $this->warn("Could not parse sede name from filename: {$filename}");
104
105            return 0;
106        }
107
108        // Look up sede by name (case-insensitive partial match)
109        $sede = TblFinanceSedes::where('is_active', 1)
110            ->whereRaw('LOWER(name) LIKE ?', ['%'.strtolower($sedeName).'%'])
111            ->first();
112
113        if (! $sede) {
114            $this->warn("Sede not found for name: {$sedeName} (filename: {$filename})");
115
116            return 0;
117        }
118
119        $spreadsheet = IOFactory::load($path);
120        $sheet = $spreadsheet->getActiveSheet();
121        $rows = $sheet->toArray(null, true, true, true);
122
123        // Skip header row
124        array_shift($rows);
125        $imported = 0;
126
127        foreach ($rows as $row) {
128            $rangoInferior = $row['A'] ?? null;
129            $rangoSuperior = $row['B'] ?? null;
130            $mesName = $row['C'] ?? null;
131            $baseImponible = $row['D'] ?? null;
132
133            if (! $rangoInferior || ! $mesName) {
134                continue;
135            }
136
137            // Parse dates (format: dd/mm/yyyy)
138            try {
139                $weekStart = Carbon::createFromFormat('d/m/Y', trim($rangoInferior));
140            } catch (\Exception $e) {
141                $this->warn("Invalid start date: {$rangoInferior}");
142
143                continue;
144            }
145
146            // Determine year from the start date
147            $year = $weekStart->year;
148
149            // Map Spanish month name to number
150            $monthNumber = self::MONTH_MAP[strtolower(trim($mesName))] ?? null;
151            if (! $monthNumber) {
152                $this->warn("Unknown month name: {$mesName}");
153
154                continue;
155            }
156
157            // Use week_date = Rango Inferior (the table has a single week_date column, not separate start/end)
158            TblFinanceReportSemanal::updateOrCreate(
159                [
160                    'company_id' => $sede->company_id,
161                    'sede_id' => $sede->id,
162                    'year' => $year,
163                    'month' => $monthNumber,
164                    'week_date' => $weekStart->toDateString(),
165                ],
166                [
167                    'actuals' => $baseImponible,
168                    'source' => 'google_drive',
169                    'loaded_at' => now(),
170                ]
171            );
172            $imported++;
173        }
174
175        return $imported;
176    }
177}