Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 873
0.00% covered (danger)
0.00%
0 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
PresupuestosService
0.00% covered (danger)
0.00%
0 / 873
0.00% covered (danger)
0.00%
0 / 23
72630
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 syncByDate
0.00% covered (danger)
0.00%
0 / 91
0.00% covered (danger)
0.00%
0 / 1
702
 syncById
0.00% covered (danger)
0.00%
0 / 202
0.00% covered (danger)
0.00%
0 / 1
5402
 getArrayValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 syncModifiedBudgetById
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 1
1260
 syncErrorBudgets
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
182
 syncBudgetsWorks
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 1
182
 notifyErrors
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 generateQuoteId
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
90
 normalizeStatus
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 normalizeSegment
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 normalizeType
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 normalizeSource
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 saveDocument
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
90
 formatBytes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 updateLogs
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
20
 checkEmailInvalid
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 checkRequiredFields
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
272
 syncExistingDataWithWarnings
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 syncByIds
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
272
 syncNullBudget
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
 getAlternativeClientData
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
132
 checkAproval
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
156
1<?php
2namespace App\Services;
3
4use App\Http\Controllers\Quotations;
5use App\Models\TblBudgetStatus;
6use App\Models\TblBudgetTypes;
7use App\Models\TblCompanies;
8use App\Models\TblFiles;
9use App\Models\TblG3wLastUpdate;
10use App\Models\TblLastFollowUpDate;
11use App\Models\TblG3WOrdersUpdateLogs;
12use App\Models\TblProjectTypes;
13use App\Models\TblQuotations;
14use App\Models\TblSegmentG3wMapping;
15use App\Models\TblSegments;
16use App\Models\TblSourceG3wMapping;
17use App\Models\TblSources;
18use App\Models\TblStatusG3wMapping;
19use App\Models\TblTypeG3wMapping;
20use App\Models\TblUserG3wMapping;
21use App\Models\TblUsers;
22use App\Services\WorkService;
23use Google\Service\AdMob\Date;
24use Illuminate\Support\Carbon;
25use Illuminate\Support\Facades\DB;
26use Illuminate\Support\Facades\Log;
27use Mockery\Exception;
28use SendGrid\Mail\Mail;
29use Illuminate\Support\Facades\Storage;
30
31class PresupuestosService extends GestionaService
32{
33    private $quotationsController;
34    private $workSevice;
35
36    public function __construct(Quotations $quotationsController, WorkService $workSevice)
37    {
38        parent::__construct();
39        $this->quotationsController = $quotationsController;
40        $this->workSevice = $workSevice;
41    }
42
43    /**
44     * Synchronize budgets as of a specific date.
45     * It also manages the last synchronized ID.
46     *
47     * @param string $date Default today's date
48     * @param string $name Who's launch the function
49     * @return array
50     */
51    public function syncByDate($date = null, $name = null, $region = "Cataluña")
52    {
53        try {
54            if($region === "Catalunya"){
55                $region="Cataluña";
56            }
57
58            $g3wActive = TblCompanies::where("region", $region)->first()->g3W_active;
59
60            if(!$g3wActive || $g3wActive === 0){
61                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
62            }
63
64            $this->setSyncStatus(1, $region);
65            $this->syncExistingDataWithWarnings();
66
67            $successfulSyncs = 0;
68            $failedSyncs = [];
69            $successIdSyncs = [];
70
71            $startCronDateTime = date('Y-m-d H:i:s');
72
73            $date = $date ?? date('Y-m-d');
74
75            $budgets = $this->request('get', 'presupuesto?fecha=' . $date, $region, []);
76
77            if (!$budgets) {
78                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
79                return [
80                    'success' => true,
81                    'message' => 'No budgets to upload.',
82                ];
83            }
84
85            if (is_string($budgets)) {
86                $budgets = json_decode($budgets, true);
87            }
88
89            if (!is_array($budgets) || empty($budgets)) {
90                throw new \Exception("No budgets to process.");
91            }
92
93            $company = TblCompanies::where("region", $region)->first();
94
95            foreach ($budgets as $budget) {
96                if(
97                    TblQuotations::where("internal_quote_id", $budget['ID'])
98                    ->where("company_id", $company->company_id)
99                    ->exists()
100                ){
101                    continue;
102                }
103
104                if(
105                    in_array($company->company_id, [18, 22])
106                &&
107                    TblQuotations::where("internal_quote_id", $budget['ID'])
108                        ->whereIn("company_id", [18, 22])
109                        ->exists()
110                ){
111                    continue;
112                }
113
114                $result = \DB::transaction(function () use ($budget, $region) {
115                    return $this->syncById($budget['ID'], $region);
116                });
117
118                if ($result['success']) {
119                    $successfulSyncs++;
120                    $successIdSyncs[] = [
121                        'id' => $budget['ID'],
122                    ];
123                } else {
124                    if (strpos($result['error'], 'No se ha encontrado el presupuesto') === false) {
125                        $failedSyncs[] = [
126                            'id' => $budget['ID'],
127                            'error' => $result['error'] ?? 'Unknown error',
128                        ];
129                    }
130                }
131            }
132
133            $ids = array_column($budgets, 'ID');
134            if (empty($ids)) {
135                throw new \Exception("No valid IDs found in budgets.");
136            }
137            $lastId = max($ids);
138
139            TblG3wLastUpdate::where("region", $region)->first()->update([
140                'g3w_id' => $lastId,
141                'updatingNow' => 0,
142            ]);
143
144            $lastUpdate = TblG3wLastUpdate::where("region", $region)->first();
145
146            $time = "";
147
148            if ($lastUpdate) {
149                $updatedAt = Carbon::parse($lastUpdate->updated_at)->subHour();
150                $time = $updatedAt->format('H:i');
151            }
152
153            $modifiedBudgets = (strpos($time, ':') !== false) ?
154                $this->request('get', 'presupuesto/fechamodificacion/' . $date . "/" . $time, $region, []) :
155                $this->request('get', 'presupuesto/fechamodificacion/' . $date, $region, []);
156
157
158            if (is_array($modifiedBudgets) && !empty($modifiedBudgets)) {
159                foreach ($modifiedBudgets as $budget) {
160                    $result = \DB::transaction(function () use ($budget, $region) {
161                        return $this->syncModifiedBudgetById($budget['ID'], $region);
162                    });
163
164                    if ($result['success']) {
165                        $successfulSyncs++;
166                        $successIdSyncs[] = [
167                            'id' => $budget['ID'],
168                        ];
169                    } else {
170                        if ($result['error']) {
171                            if (strpos($result['error'], 'No se encuentra el presupuesto con ID en G3W') === false &&
172                                strpos($result['error'], 'No se ha encontrado el presupuesto') === false) {
173                                $failedSyncs[] = [
174                                    'id' => $budget['ID'],
175                                    'error' => $result['error'] ?? 'Unknown error',
176                                ];
177                            }
178                        }
179                    }
180                }
181            }
182
183            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
184
185            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region);
186
187            return [
188                'success' => true,
189                'message' => 'Synchronization completed.'
190            ];
191
192        } catch (\Exception $e) {
193            Log::channel('g3w')->error('Error sincronizando los presupuestos: ' . $e->getMessage());
194
195            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
196                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
197            }
198
199            return ['success' => false, 'error' => $e->getMessage()];
200        }
201    }
202
203    /**
204     * Synchronizes a budget by its ID.
205     *
206     * @param $id int ID to search in G3W
207     * @return array
208     */
209    public function syncById($id, $region)
210    {
211        try {
212            $g3wWarning = 0;
213            $g3wWarningFields = null;
214            $budget = $this->request('get', "presupuesto/{$id}", $region, []);
215
216            if (!isset($budget["presupuesto"]) || !is_array($budget["presupuesto"])) {
217                throw new \Exception("El presupuesto no contiene los datos esperados.");
218            }
219
220            $companyId = TblCompanies::where('region', $region)->first()->company_id;
221
222            if (TblQuotations::where("internal_quote_id", $budget["presupuesto"]["cod_presupuesto"])->where("company_id", $companyId)->exists()) {
223                $resultEdit = $this->syncModifiedBudgetById($id, $region);
224                return $resultEdit;
225            }
226
227            $statusList = $this->request('get', "presupuesto/tiposestado", $region, []);
228
229            $collection = collect($statusList);
230
231            $nameStatus = $collection->firstWhere('ID', $budget["presupuesto"]["estado"])['nombre'] ?? null;
232
233            if (!$nameStatus) {
234                $statusID = TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id;
235            } else {
236                $statusID = $this->normalizeStatus($nameStatus);
237
238                if ($statusID === TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id) {
239                    $statusID = TblBudgetStatus::where('name', "Estado no reconocido en FST")->first()->budget_status_id;
240                }
241            }
242
243            $company = $this->request('get', "servicio/{$budget["presupuesto"]["cod_servicio"]}", $region, []);
244
245            if(!$company || (!isset($company['servicio']) && !isset($company['cliente']))){
246                $company = $this->request('get', "cliente/{$budget["presupuesto"]["cod_cliente"]}", $region, []);
247            }
248
249            $companyName = $company["servicio"]["nombre_servicio"] ?? $company["cliente"]["empresa"] ?? null;
250            $companyTelephone = $company["servicio"]["telefono"] ?? $company["cliente"]["telefono"] ?? null;
251            $companyEmail = $company["servicio"]["email"] ?? $company["cliente"]["email"] ?? null;
252
253            if($companyEmail) {
254                $companyEmail = trim($companyEmail);
255
256                if (str_contains($companyEmail, ";")) {
257                    $companyEmail = str_replace(";", ",", $companyEmail);
258                }
259            }
260
261            if(!$companyName || ($budget["presupuesto"]["cod_cliente"] === 9818 && in_array($companyId, [18, 22]))){
262                $companyName = $this->getAlternativeClientData($budget["presupuesto"]["datos_cliente_alternativo"])["name"];
263            }
264
265            if(!$companyTelephone || ($budget["presupuesto"]["cod_cliente"] === 9818 && in_array($companyId, [18, 22]))){
266                $companyTelephone = $this->getAlternativeClientData($budget["presupuesto"]["datos_cliente_alternativo"])["number"];
267            }
268
269            if(!$companyEmail || ($budget["presupuesto"]["cod_cliente"] === 9818 && in_array($companyId, [18, 22]))){
270                $companyEmail = $this->getAlternativeClientData($budget["presupuesto"]["datos_cliente_alternativo"])["email"];
271            }
272
273            $segmentID = $this->normalizeSegment($company["servicio"]["tipo_servicio"] ?? "Otro");
274
275            $companyId = TblCompanies::where('region', $region)->first()->company_id;
276
277            if(!isset($budget["presupuesto"]["documento"]) || empty($budget["presupuesto"]["documento"])){
278                throw new \Exception("El presupuesto no tiene documento asociado. Creelo y vuelva a intentarlo.");
279            }
280
281            $companyNameFormatted = "";
282
283            if (!empty($companyName)) {
284                $companyNameFormatted = str_replace(' ', '_', $companyName);
285            }
286
287            $nameDocument = $companyNameFormatted
288                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
289                : $budget["presupuesto"]["cod_presupuesto"];
290
291            //$document = $this->saveDocument($budget["presupuesto"]["documento"], $nameDocument);
292
293            $typeId = $this->normalizeType($budget["presupuesto"]["origen_presupuesto"] ?? null, $region);
294
295            if(empty($budget["presupuesto"]["fecha_creacion"])){
296                $budget["presupuesto"]["fecha_creacion"] = Carbon::now()->format('Y-m-d H:i:s');
297            }
298
299            $work = $this->request('get', "presupuesto/trabajos/{$id}", $region, []);
300
301            $workIds = [];
302
303            if (is_array($work)) {
304                foreach ($work as $item) {
305                    if (isset($item['ID'])) {
306                        $workIds[] = $item['ID'];
307                    }
308                }
309            }
310
311            $idsConcatenados = implode('/', $workIds);
312
313            $createdByUser = TblUserG3wMapping::where("name_g3w", $budget["presupuesto"]["usuario"])->first();
314            $createdBy = null;
315            $comercial = null;
316
317            if(!$createdByUser){
318                TblUserG3wMapping::create(array(
319                    "name_g3w" => $budget["presupuesto"]["usuario"]?? null
320                ));
321                $comercial = $budget["presupuesto"]["usuario"];
322                $g3wWarning = 1;
323            } else {
324                $createdBy = TblUsers::where("id", $createdByUser->id_fst)->value('name')
325                    ?? null;
326
327                $comercial = $createdBy;
328            }
329
330            $comercialUser = TblUserG3wMapping::where("name_g3w", $budget["presupuesto"]["cod_comercial_presupuesto"])->first();
331
332            if(!$comercialUser && $budget["presupuesto"]["cod_comercial_presupuesto"]){
333                TblUserG3wMapping::create(array(
334                    "name_g3w" => $budget["presupuesto"]["cod_comercial_presupuesto"]?? null
335                ));
336            } else if($comercialUser){
337                $comercial = TblUsers::where("id", $comercialUser->id_fst)->value('name')
338                    ?? null;
339            }
340
341            $source = $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null;
342            $sourceId = ($source) ? $this->normalizeSource($source, $region) : null;
343
344            if ($statusID == 12) {
345                $companyLimit = TblCompanies::where('company_id', $companyId)->first();
346                $inProgressCount = TblQuotations::where('budget_status_id', 12)->where('company_id', $companyId)->where('commercial', $comercial)->count();
347                if($companyLimit->process_limit <= $inProgressCount){
348                    throw new \Exception("No se pudo guardar el documento del presupuesto {$id} al crearlo. Error: Se ha alcanzado el número máximo de pedidos en curso (5) permitido por usuario para esta empresa.");
349                }
350            }
351
352            //Jomar
353            if($companyId == 18 && $source == 9){
354                $companyId = 22;
355            }
356
357            $lastFollowUp = TblLastFollowUpDate::where('company_id', $companyId)->where('budget_type_id', $typeId)->first();
358            $workingDays = 10;
359            if($lastFollowUp && $lastFollowUp->last_follow_up_date){
360                $workingDays = $lastFollowUp->last_follow_up_date;
361            }
362
363            $date = Carbon::now();
364
365            $daysAdded = 0;
366            while ($daysAdded < $workingDays) {
367                $date->addDay();
368                if ($date->isWeekday()) {
369                    $daysAdded++;
370                }
371            }
372
373            $lastFollowUpDate = $date;
374
375            Log::channel('g3w')->info($budget["presupuesto"]["cod_presupuesto"] . " / create -> region -> " .  $region . "company id -> " . $companyId);
376
377            if(
378                in_array($statusID, [13, 14]) ||
379                !$sourceId ||
380                (!$typeId || $typeId == 0 || $typeId == 16) ||
381                empty(trim($companyName)) ||
382                empty(trim($companyEmail))
383                // || $this->checkEmailInvalid($companyEmail)
384            ){
385                $g3wWarning = 1;
386            }
387
388            $row = (object) array(
389                'client' => $companyName,
390                'email' => $companyEmail,
391                'budget_status_id' => $statusID,
392                'commercial' => $comercial,
393                'source_id' => $sourceId,
394                'budget_type_id' => $typeId
395            );
396
397            $g3wWarningFields = $this->checkRequiredFields($row);
398
399            if($g3wWarningFields != null && $g3wWarningFields != ""){
400                $g3wWarning = 1;
401            }
402
403            $reasonForNotFollowingUp = null;
404
405            // iker chacori and juan carlos
406            $excludingUsers = ["igc", "030", "031", "fcl", "018", "021", "jcsp", "026", "029", "EXMOF", "MGF"];
407
408            $facilityUser = false;
409
410            if(
411                (in_array($budget["presupuesto"]["usuario"], $excludingUsers) || in_array($budget["presupuesto"]["cod_comercial_presupuesto"], $excludingUsers))
412                && $region == "Cataluña"
413            ){
414                $reasonForNotFollowingUp = 1;
415                $facilityUser = true;
416            }
417
418            if($segmentID == 3) {
419                $reasonForNotFollowingUp = 2;
420            } elseif ($segmentID == 2) {
421                $reasonForNotFollowingUp = 1;
422            }
423
424
425
426            $defaultUser = 0;
427
428            if(!$comercialUser){
429                if($companyId == 18){
430                    $defaultUser = 94;
431                }
432
433                if($companyId == 19){
434                    $defaultUser = 68;
435                }
436
437                if($companyId == 22){
438                    $defaultUser = 153;
439                }
440
441                if($companyId == 30){
442                    $defaultUser = 124;
443                }
444            }
445
446            if(env("APP_ENV") === "local"){
447                if($defaultUser == 0){
448                    $defaultUser = 92;
449                }
450            }
451
452
453            $generateNumber = $this->generateQuoteId($companyId);
454
455            $g3wNewId = $generateNumber['id'];
456            $newQuoteId = $generateNumber['number'];
457
458            $forApproval = self::checkAproval($companyId, $typeId, 2, $budget["presupuesto"]["importe"], $newQuoteId, $g3wNewId, $comercialUser->id_fst?? $defaultUser, $comercial);
459
460            Log::channel('g3w')->info("id: {$g3wNewId} -> " . $budget["presupuesto"]["cod_presupuesto"] ?? "undefined presupuesto-cod_presupuesto");
461
462            $g3wArray = array(
463                'internal_quote_id' => $budget["presupuesto"]["cod_presupuesto"],
464                'quote_id' => $newQuoteId,
465                'company_id' => $companyId,
466                'customer_type_id' => 2,
467                'segment_id' => $segmentID,
468                'budget_type_id' => $typeId == 16 ? null : $typeId,
469                'budget_status_id' => $statusID,
470                'source_id' => $sourceId,
471                'client' => $companyName,
472                'phone_number' => $companyTelephone,
473                'email' => $companyEmail,
474                'issue_date' => ($budget["presupuesto"]["fecha_creacion"] ?? null) === "0000/00/00" ? null : $budget["presupuesto"]["fecha_creacion"],
475                'request_date' => ($budget["presupuesto"]["fecha_creacion"] ?? null) === "0000/00/00" ? null : $budget["presupuesto"]["fecha_creacion"],
476                'acceptance_date' => ($budget["presupuesto"]["aceptacion"] ?? null) === "0000/00/00" ? null : $budget["presupuesto"]["aceptacion"],
477                'amount' => $budget["presupuesto"]["importe"] ?? null,
478                'last_follow_up_date' => $facilityUser ? null : $lastFollowUpDate,
479                'commercial' => $comercial,
480                'created_at' => $budget["presupuesto"]["fecha_creacion"] ?? null,
481                'created_by' => $createdBy,
482                'has_attachment' => 1,
483                'cost_of_labor' => 0,
484                'total_cost_of_job' => 0,
485                'invoice_margin' => 0,
486                'margin_for_the_company' => 0,
487                'revenue_per_date_per_worked' => 0,
488                'gross_margin' => 100,
489                'labor_percentage' => 0,
490                'sync_import' => 1,
491                'box_work_g3w' => $idsConcatenados,
492                'segment_by_g3w' => $company["servicio"]["tipo_servicio"] ?? null,
493                'source_by_g3w' => $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null,
494                'status_by_g3w' => $nameStatus,
495                'type_by_g3w' => $budget["presupuesto"]["origen_presupuesto"]?? null,
496                'user_create_by_g3w' => $budget["presupuesto"]["usuario"],
497                'user_commercial_by_g3w' => !empty($budget["presupuesto"]["cod_comercial_presupuesto"]) ? $budget["presupuesto"]["cod_comercial_presupuesto"] : null,
498                "g3w_warning" => $g3wWarning,
499                "g3w_warning_fields" => $g3wWarningFields,
500                'reason_for_not_following_up_id' => $reasonForNotFollowingUp,
501                'for_add' => 0,
502                'for_approval' => $forApproval,
503            );
504
505            TblQuotations::where('id', $g3wNewId)->update($g3wArray);
506
507            $companyNameFormatted = "";
508
509            if (!empty($companyName)) {
510                $companyNameFormatted = str_replace(' ', '_', $companyName);
511            }
512
513            $nameDocument = $companyNameFormatted
514                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
515                : $budget["presupuesto"]["cod_presupuesto"];
516
517            $responseSaveDocument = $this->saveDocument($budget["presupuesto"]["documento"], $nameDocument, $g3wNewId, $newQuoteId);
518            $responseDataSaveDocument = $responseSaveDocument->getData();
519
520            if (!isset($responseDataSaveDocument->success) || !$responseDataSaveDocument->success) {
521                throw new \Exception("No se pudo guardar el documento del presupuesto {$id} al crearlo. Error: " . ($responseDataSaveDocument->error ?? 'Desconocido'));
522            }
523
524            $documentName = $responseDataSaveDocument->documentName;
525
526            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
527
528            return [
529                'success' => true,
530                'id' => $g3wNewId
531            ];
532
533        } catch (\Exception $e) {
534            $errorMessage = $e->getMessage();
535            return ['success' => false, 'error' => "Error sincronizando el presupuesto {$id}" . $errorMessage];
536        }
537    }
538
539    private function getArrayValue(array $array, string $key, $default = null) {
540        return $array[$key] ?? $default;
541    }
542
543    /**
544     * @param $id
545     * @return array|true[]
546     */
547    public function syncModifiedBudgetById($id, $region = null){
548        try {
549            $statusID = null;
550
551            $statusToChange = [
552                "Enviado",
553                "Aceptado",
554                "Rechazado"
555            ];
556
557            $g3wWarning = 0;
558            $g3wWarningFields = null;
559            $companyId = TblCompanies::where('region', $region)->first()->company_id;
560
561            $budget = $this->request('get', "presupuesto/{$id}", $region, []);
562
563            $companyId = TblCompanies::where('region', $region)->first()->company_id;
564
565            if (!TblQuotations::where("internal_quote_id", $budget["presupuesto"]["cod_presupuesto"])->where("company_id", $companyId)->exists()) {
566                throw new \Exception("No se encuentra el presupuesto con ID en G3W $id. Es posible que sea debido a que se creo antes de la integracion de FST con G3W.");
567            }
568
569            //$company = $this->request('get', "servicio/{$budget["presupuesto"]["cod_servicio"]}", $region, []);
570
571            /*if(!$company || !isset($company['servicio'])){
572                $company = $this->request('get', "cliente/{$budget["presupuesto"]["cod_cliente"]}", $region, []);
573            }*/
574
575            //$companyName = (isset($company['servicio'])) ? $company["servicio"]["nombre_servicio"] : $company["cliente"]["empresa"];
576            //$companyTelephone = (isset($company['servicio'])) ? $company["servicio"]["telefono"] : $company["cliente"]["telefono"] ;
577
578            //$segmentID = $this->normalizeSegment($company["servicio"]["tipo_servicio"]?? "Otro");
579
580            if(!isset($budget["presupuesto"]["documento"]) || !$budget["presupuesto"]["documento"]){
581                throw new \Exception("El presupuesto no tiene documento asociado. Creelo y vuelva a intentarlo.");
582            }
583
584            //$companyNameFormatted = "";
585
586            /*if (!empty($companyName)) {
587                $companyNameFormatted = str_replace(' ', '_', $companyName);
588            }*/
589
590            /*$nameDocument = $companyNameFormatted
591                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
592                : $budget["presupuesto"]["cod_presupuesto"];*/
593
594            //$document = $this->saveDocument($budget["presupuesto"]["documento"], $nameDocument);
595
596            $typeId = $this->normalizeType($budget["presupuesto"]["origen_presupuesto"], $region);
597
598            $statusList = $this->request('get', "presupuesto/tiposestado", $region, []);
599
600            $collection = collect($statusList);
601
602            $nameStatus = $collection->firstWhere('ID', $budget["presupuesto"]["estado"])['nombre'] ?? null;
603
604            if (!$nameStatus) {
605                $statusID = TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id;
606            } else {
607                $statusID = $this->normalizeStatus($nameStatus);
608
609                if ($statusID === TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id) {
610                    $statusID = TblBudgetStatus::where('name', "Estado no reconocido en FST")->first()->budget_status_id;
611                }
612            }
613
614            Log::channel('g3w')->info($budget["presupuesto"]["cod_presupuesto"] . " / modified -> region -> " .  $region);
615
616            $row = TblQuotations::where('internal_quote_id', $budget["presupuesto"]["cod_presupuesto"])
617                ->where('company_id', $companyId)
618                ->first();
619
620            if($row){
621                if(
622                    in_array($row->budget_status_id, [13, 14]) ||
623                    !$row->source_id ||
624                    (!$typeId || $typeId == 0 || $typeId == 16) ||
625                    empty(trim($row->client)) || // change for $companyName when we implement
626                    empty(trim($row->email))
627                    // || $this->checkEmailInvalid($row->email)
628                ){
629                    $g3wWarning = 1;
630                }
631
632                $g3wWarningFields = $this->checkRequiredFields($row);
633
634                if($g3wWarningFields != null && $g3wWarningFields != ""){
635                    $g3wWarning = 1;
636                }
637
638                if($row->budget_type_id !== $typeId){ $this->quotationsController->addUpdateLog($row->id, "System", "budget_type_id", $row->budget_type_id, $typeId); }
639
640                if(in_array($nameStatus, $statusToChange)){ $this->quotationsController->addUpdateLog($row->id, "System", "budget_status_id", $row->budget_status_id, $statusID?? $row->budget_status_id); }
641
642                if($row->amount !== $budget["presupuesto"]["importe"]){ $this->quotationsController->addUpdateLog($row->id, "System", "amount", $row->amount, $budget["presupuesto"]["importe"]); }
643
644                if($row->updated_by !== "System"){ $this->quotationsController->addUpdateLog($row->id, "System", "updated_by", $row->updated_by, "System"); }
645
646                if($row->has_attachment !== 1){ $this->quotationsController->addUpdateLog($row->id, "System", "has_attachment", $row->has_attachment, 1); }
647
648                if($row->sync_import_edited !== 1){ $this->quotationsController->addUpdateLog($row->id, "System", "sync_import_edited", $row->sync_import_edited, 1); }
649
650                if($nameStatus === "Aceptado"){
651                    $this->quotationsController->addUpdateLog($row->id, "System", null, $row->acceptance_date, Carbon::now()->format('Y-m-d H:i:s'));
652                    $this->quotationsController->addUpdateLog($row->id, "System", null, $row->accepted_at, Carbon::now()->format('Y-m-d H:i:s'));
653                    $this->quotationsController->addUpdateLog($row->id, "System", null, $row->accepted_by, "System");
654                }
655
656                $row->update(
657                    array(
658                        //'segment_id' => $segmentID,
659                        'budget_type_id' => $typeId == 16 ? null : $typeId,
660                        //'client' => $companyName?? null,
661                        //'phone_number' => $companyTelephone?? null,
662                        'budget_status_id' => in_array($nameStatus, $statusToChange) ? $statusID : $row->budget_status_id,
663                        'acceptance_date' => ($nameStatus === "Aceptado") ? Carbon::now()->format('Y-m-d H:i:s') : $row->acceptance_date,
664                        'accepted_at' => ($nameStatus === "Aceptado") ? Carbon::now()->format('Y-m-d H:i:s') : $row->accepted_at,
665                        'accepted_by' => ($nameStatus === "Aceptado") ? "System" : $row->accepted_by,
666                        'amount' => $budget["presupuesto"]["importe"]?? null,
667                        'updated_by' => "System",
668                        'updated_at' => Carbon::now()->format('Y-m-d H:i:s'),
669                        'has_attachment' => 1,
670                        'sync_import_edited' => 1,
671                        'segment_by_g3w' => $company["servicio"]["tipo_servicio"] ?? null,
672                        'source_by_g3w' => $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null,
673                        'status_by_g3w' => $nameStatus,
674                        'type_by_g3w' => $budget["presupuesto"]["origen_presupuesto"]?? null,
675                        'user_create_by_g3w' => $budget["presupuesto"]["usuario"]?? null,
676                        'user_commercial_by_g3w' => !empty($budget["presupuesto"]["cod_comercial_presupuesto"]) ? $budget["presupuesto"]["cod_comercial_presupuesto"] : null,
677                        "g3w_warning" => $g3wWarning,
678                        "g3w_warning_fields" => $g3wWarningFields,
679                    )
680                );
681            }
682
683            $companyNameFormatted = "";
684
685            if (!empty($companyName)) {
686                $companyNameFormatted = str_replace(' ', '_', $companyName);
687            }
688
689            $nameDocument = $companyNameFormatted
690                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
691                : $budget["presupuesto"]["cod_presupuesto"];
692
693            $quote = TblQuotations::where('internal_quote_id', $budget["presupuesto"]["cod_presupuesto"])
694                ->where('company_id', $companyId)->first();
695
696            if (!$quote) {
697                throw new \Exception("No se encontró la cotización con internal_quote_id: {$budget["presupuesto"]["cod_presupuesto"]}");
698            }
699
700            /*$tblFile = TblFiles::where('quotation_id', $quote->id)->first();
701
702            Log::info($nameDocument);*/
703
704            /*if ($tblFile) {
705                $tblFile->update([
706                    "original_name" => $documentName,
707                    "filename" => $documentName,
708                ]);
709            }*/
710
711            $response = $this->saveDocument($budget["presupuesto"]["documento"], $nameDocument, $row->id, $row->quote_id);
712            $responseData = $response->getData();
713
714            if (!isset($responseData->success) || !$responseData->success) {
715                throw new \Exception("No se pudo guardar el documento del presupuesto {$id} al editar. Error: " . ($responseData->error ?? 'Desconocido'));
716            }
717
718            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
719
720            return [
721                'success' => true
722            ];
723
724        } catch (\Exception $e) {
725            return ['success' => false, 'error' => "Error actualizando el presupuesto {$id}" . $e->getMessage()];
726        }
727    }
728
729    /**
730     * Synchronize budgets that gave us an error.
731     *
732     * @param string $name Who's launch the function
733     * @return array
734     */
735    public function syncErrorBudgets($name = null, $region = null)
736    {
737        try {
738            if($region === "Catalunya"){
739                $region = "Cataluña";
740            }
741
742            $g3wActive = TblCompanies::where("region", $region)->first()->g3W_active;
743
744            if(!$g3wActive || $g3wActive === 0){
745                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
746            }
747
748            $this->setSyncStatus(1, $region);
749
750            $successfulSyncs = 0;
751            $failedSyncs = [];
752            $successIdSyncs = [];
753
754            $startCronDateTime = date('Y-m-d H:i:s');
755
756            $company = TblCompanies::where("region", $region)->first();
757
758            if (!$company) {
759                throw new \Exception("No company found for region: " . $region);
760            }
761            $company_id = $company->company_id;
762            $logs = TblG3WOrdersUpdateLogs::whereNotNull('sync_error_ids')
763                ->where('company_id', $company_id)
764                ->get(['sync_error_ids']);
765
766            $allSyncErrorIds = [];
767
768            foreach ($logs as $log) {
769                if (is_string($log->sync_error_ids)) {
770                    $log->sync_error_ids = json_decode($log->sync_error_ids, true);
771                    $allSyncErrorIds = array_merge($allSyncErrorIds, $log->sync_error_ids);
772                }
773            }
774
775            $allSyncErrorIds = array_unique($allSyncErrorIds);
776
777            foreach ($allSyncErrorIds as $idSyncError) {
778                $result = \DB::transaction(function () use ($idSyncError, $region) {
779                    return $this->syncById($idSyncError, $region);
780                });
781
782                if ($result['success']) {
783                    $successfulSyncs++;
784                    $successIdSyncs[] = [
785                        'id' => $idSyncError,
786                    ];
787                } else {
788                    if (strpos($result['error'], 'No se ha encontrado el presupuesto') === false) {
789                        $failedSyncs[] = [
790                            'id' => $idSyncError,
791                            'error' => $result['error'] ?? 'Unknown error',
792                        ];
793                    }
794                }
795            }
796
797            TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
798
799            $company = TblCompanies::where("region", $region)->first();
800            if (!$company) {
801                throw new \Exception("No company found for region: " . $region);
802            }
803            $company_id = $company->company_id;
804
805            $logs = TblG3WOrdersUpdateLogs::whereNotNull('sync_error_ids')
806                ->where('company_id', $company_id)
807                ->update(['sync_error_ids' => []]);
808
809            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region);
810
811            return [
812                'success' => true,
813                'message' => 'Synchronization of failed budgets completed.'
814            ];;
815
816        } catch (\Exception $e) {
817            Log::channel('g3w')->error('Error when synchronizing error budgets: ' . $e->getMessage());
818
819            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
820                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
821            }
822
823            return ['success' => false, 'error' => $e->getMessage()];
824        }
825    }
826
827    public function syncBudgetsWorks($name = null, $region = null){
828        try {
829            if($region === "Catalunya"){
830                $region = "Cataluña";
831            }
832
833            $company = TblCompanies::where("region", $region)->first();
834
835            if (!$company) {
836                throw new \Exception("No se encontró la compañía para la región '$region'.");
837            }
838            $g3wActive = $company->g3W_active;
839
840
841            if(!$g3wActive || $g3wActive === 0){
842                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
843            }
844
845            $this->workSevice->getG3wTasksExecuted($region, 1);
846
847            $this->setSyncStatus(1, $region);
848
849            $successfulSyncs = 0;
850            $failedSyncs = [];
851            $successIdSyncs = [];
852
853            $startCronDateTime = date('Y-m-d H:i:s');
854
855            $quotesIds = TblQuotations::where(function ($query) {
856                $query->where('sync_import', 1)
857                    ->orWhere('sync_import_edited', 1);
858            })
859                ->where('budget_type_id', 1)
860                ->where(function ($query) {
861                    $query->whereNull('box_work_g3w')
862                        ->orWhere('box_work_g3w', '0');
863                })
864                ->get();
865
866            foreach ($quotesIds as $quoteId) {
867                $work = $this->request('get', "presupuesto/trabajos/{$quoteId->internal_quote_id}", $region, []);
868
869                sleep(2);
870
871                $workIds = [];
872
873                if (is_array($work)) {
874                    foreach ($work as $item) {
875                        if (isset($item['ID'])) {
876                            $workIds[] = $item['ID'];
877                        }
878                    }
879                }
880
881                $idsConcatenados = implode('/', $workIds);
882
883                $wasUpdated = $quoteId->update(['box_work_g3w' => $idsConcatenados]);
884
885                if($wasUpdated) {
886                    $successfulSyncs++;
887                    $successIdSyncs[] = [
888                        'id' => $quoteId->internal_quote_id,
889                    ];
890                } else {
891                    $failedSyncs[] = [
892                        'id' => $quoteId->internal_quote_id,
893                        'error' => "Error updating the internal quote id $quoteId, work $idsConcatenados.",
894                    ];
895
896                }
897            }
898
899            $g3wUpdate = TblG3wLastUpdate::where("region", $region)->first();
900            if ($g3wUpdate) {
901                $g3wUpdate->update(['updatingNow' => 0]);
902            }
903
904            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
905
906            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region, "Orders Works");
907
908            return [
909                'success' => true,
910                'message' => 'Synchronization of budgets works completed.'
911            ];
912
913        } catch (\Exception $e) {
914            Log::channel('g3w')->error('Error when synchronizing budgets works: ' . $e->getMessage());
915
916            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
917                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
918            }
919
920            return ['success' => false, 'error' => $e->getMessage()];
921        }
922    }
923
924    /**
925     * @param $failedSyncs
926     * @return void
927     */
928    public function notifyErrors($failedSyncs)
929    {
930        $errorDetails = array_map(function ($failure) {
931            return "Budget ID: {$failure['id']}, Error: {$failure['error']}";
932        }, $failedSyncs);
933
934        $message = implode("\n", $errorDetails);
935
936        /*Mail::luis, rick & chris, tech@fire.es*/
937
938        Log::channel('g3w')->error("Error notification sent to ricardo.alemany@fire.es");
939    }
940
941    /**
942     * Functuion to generate the next QuoteID
943     * @param $companyId
944     * @return int|string
945     */
946    /*public function generateQuoteId($companyId)
947    {
948        if ($companyId == 0) {
949            $latestQuoteId = TblQuotations::orderBy('id', 'DESC')->first()->quote_id ?? null;
950        } else {
951            $latestQuoteId = TblQuotations::where('company_id', $companyId)
952                ->orderBy('id', 'DESC')
953                ->first()
954                ->quote_id ?? null;
955        }
956
957        if (!$latestQuoteId) {
958            throw new \Exception("Error generando el # en titan");
959        }
960
961        if (is_numeric($latestQuoteId)) {
962            return (string)((int)$latestQuoteId + 1);
963        }
964
965        preg_match('/([A-Z]+)(\d+)/', $latestQuoteId, $matches);
966
967        if (count($matches) < 3) {
968            throw new \Exception("El formato del último Quote ID no es válido: {$latestQuoteId}");
969        }
970
971        $prefix = $matches[1];
972        $numericPart = $matches[2];
973
974        $incrementedNumber = str_pad((int)$numericPart + 1, strlen($numericPart), '0', STR_PAD_LEFT);
975
976        $newQuoteId = $prefix . $incrementedNumber;
977
978        return $newQuoteId;
979    }*/
980    public function generateQuoteId($companyId)
981    {
982        try {
983
984            $companyId = addslashes($companyId);
985            $latestBudget = array();
986            $number = 0;
987            $beforeLastId = null;
988
989            $x = true;
990
991            if($companyId == 0){
992                $latestBudget = TblQuotations::orderByRaw('CAST(quote_id AS DOUBLE) DESC')->pluck('quote_id')->first();
993            }else{
994                $latestBudget = TblCompanies::where('company_id', $companyId)->pluck('last_id')->first();
995
996                if($latestBudget == null){
997                    $latestBudget = TblQuotations::where('company_id', $companyId)->orderByRaw('id DESC')->pluck('quote_id')->first();
998                    $beforeLastId = $latestBudget;
999                }
1000            }
1001
1002            $number = $latestBudget;
1003
1004            while ($x) {
1005
1006                if(is_numeric(substr($number, -1))) {
1007                    $number++;
1008                }else{
1009                    $number .= "1";
1010                }
1011
1012                $check = 0;
1013
1014                if($companyId == 0){
1015                    $check = TblQuotations::where('quote_id', (string) $number)->count();
1016                }else{
1017                    $check = TblQuotations::where('company_id', $companyId)->where('quote_id', (string) $number)->count();
1018                }
1019
1020                if($check == 0){
1021                    $x = false;
1022                }
1023            }
1024
1025            $result = TblQuotations::create(array('quote_id' => $number, 'company_id' => $companyId, 'for_add' => 1));
1026
1027            if($beforeLastId == null){
1028                $beforeLastId = $number;
1029            }
1030
1031            $query = "UPDATE tbl_companies SET last_id = '{$number}', before_last_id = CASE WHEN before_last_id IS NULL THEN '{$beforeLastId}' ELSE before_last_id END WHERE company_id = {$companyId}";
1032            DB::select($query);
1033
1034            return array(
1035                'id' => $result->id,
1036                'number' => $number
1037            );
1038
1039        } catch (\Exception $e) {
1040            return response(['message' => 'KO', 'error' => $e->getMessage()]);
1041        }
1042    }
1043
1044    /**
1045     * Function to normalice the status provided by G3W.
1046     * @param $status String Row status of G3W
1047     * @return Int ID normalized in FST
1048     * @throws \Exception
1049     */
1050    private function normalizeStatus($status)
1051    {
1052        if (!$status){
1053            return TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id;
1054        }
1055
1056        $statusMapping = TblStatusG3wMapping::where("name_g3w", $status)->first();
1057
1058        if (!$statusMapping) {
1059            TblStatusG3wMapping::create(array(
1060                "name_g3w" => $status,
1061                "budget_status_id" => 0
1062            ));
1063            return TblBudgetStatus::where('name', "Estado no reconocido en FST")->first()->budget_status_id;
1064        }
1065
1066        return $statusMapping->budget_status_id;
1067
1068    }
1069
1070    /**
1071     * Function to normalice the segment provided by G3W.
1072     * @param $status String Row status of G3W
1073     * @return Int ID normalized in FST
1074     * @throws \Exception
1075     */
1076    private function normalizeSegment($segment)
1077    {
1078        $segmentMapping = TblSegmentG3wMapping::where("name_g3w", $segment)->first();
1079
1080        if(!$segmentMapping){
1081            TblSegmentG3wMapping::create(array(
1082                "name_g3w" => $segment,
1083                "segment_id" => 0
1084            ));
1085            return TblSegments::where('name', "Otro")->first()->segment_id?? 9;
1086        }
1087
1088        return $segmentMapping->segment_id;
1089    }
1090
1091    /**
1092     * Function to normalize the budget type provided by G3W.
1093     * @param int $type ID del tipo proporcionado por la API.
1094     * @param string $region Región (Madrid o Cataluña).
1095     * @return int ID normalizado del presupuesto en el sistema.
1096     * @throws \Exception
1097     */
1098    private function normalizeType($type, $region)
1099    {
1100        if($region === "Catalunya"){
1101            $region = "Cataluña";
1102        }
1103
1104        $budgetTypeMapping = TblTypeG3wMapping::where("id_g3w", $type)
1105            ->where("region", $region)
1106            ->first();
1107
1108        if (!$budgetTypeMapping) {
1109            TblTypeG3wMapping::create(array(
1110                "id_g3w" => $type?? null,
1111                "budget_type_name" => null,
1112                "region" => $region
1113            ));
1114            throw new \Exception("El estado '$type' no existe en la base de datos.");
1115        }
1116
1117        $budgetType = TblBudgetTypes::where("name", $budgetTypeMapping->budget_type_name)->first();
1118
1119        if(!$budgetType){
1120            return 16;
1121        }
1122
1123        return $budgetType->budget_type_id;
1124    }
1125
1126    private function normalizeSource($id_call, $region){
1127        $regionTxt = "";
1128        $region = $region == "Catalunya" ? "Cataluña" : $region;
1129
1130        if(!$id_call){
1131            $sourceDefault = TblSources::where('name', 'G3W/Gestiona')->first();
1132
1133            return $sourceDefault->source_id?? 20;
1134        }
1135
1136        $sourceMapping = TblSourceG3wMapping::where("id_g3w", $id_call)
1137            ->where("region", $region)
1138            ->first();
1139
1140        if(!$sourceMapping){
1141            TblSourceG3wMapping::create(array(
1142                "id_g3w" => $id_call,
1143                "source_name" => null,
1144                "region" => $region
1145            ));
1146            return null;
1147        }
1148
1149        $sourceId = TblSources::where("name", $sourceMapping->source_name)->first();
1150
1151        if(!$sourceId){
1152            return null;
1153        }
1154
1155        return $sourceId->source_id;
1156
1157    }
1158
1159    /**
1160     * @param $document
1161     * @return \Illuminate\Http\JsonResponse
1162     */
1163    public function saveDocument($document, $nameDocument = null, $quotationId = null, $quoteId = null, $uploadedBy = null, $isInternal = null)
1164    {
1165
1166        try {
1167
1168            $binaryData = base64_decode($document);
1169
1170            if ($binaryData === false) {
1171                throw new \Exception('Los datos Base64 no son válidos.');
1172            }
1173
1174            $documentName = $nameDocument
1175                ? preg_replace('/[^A-Za-z0-9_\-.]/', '', $nameDocument)
1176                : 'document_' . time() . '.pdf';
1177
1178            if (!preg_match('/\.pdf$/i', $documentName)) {
1179                $documentName .= '.pdf';
1180            }
1181
1182            $filename = pathinfo($documentName, PATHINFO_FILENAME) . '_' . time() . '.pdf';                  
1183
1184            $fileSize = strlen($binaryData);
1185            $mimeType = 'application/pdf';
1186
1187            $fileDataBase = TblFiles::where("quotation_id", $quotationId)->get();
1188
1189            if($fileDataBase){
1190                foreach ($fileDataBase as $fileData) {
1191                    if($fileData->original_name == $documentName){
1192                        $s3FilePath = 'uploads/' . $fileData->filename;
1193            
1194                        if (Storage::disk('s3')->exists($s3FilePath)) {
1195                            //Storage::disk('s3')->delete($s3FilePath);
1196                        }
1197                        
1198                        $fileData->delete();
1199
1200                    }
1201                }
1202            }
1203
1204            $s3path = Storage::disk('s3')->put(
1205                'uploads/' . $filename, 
1206                $binaryData,           
1207                [
1208                    'ContentType' => $mimeType,
1209                ]
1210            );           
1211
1212            $file = TblFiles::create([
1213                'quotation_id'  => $quotationId,
1214                'original_name' => $documentName,
1215                'filename'      => $filename,
1216                'uploaded_by'   => $uploadedBy,
1217                'file_size'     => $fileSize,
1218                'mime_type'     => $mimeType,
1219                'uploaded_at'   => date('Y-m-d H:i:s'),
1220            ]);
1221
1222            return response()->json([
1223                'success' => true,
1224                'message' => 'Documento guardado correctamente en la base de datos.',                
1225                'filename' => $filename,                
1226                'fileId' => $file->file_id,
1227                'documentName' => $documentName                
1228            ], 200);
1229
1230        } catch (\Exception $e) {
1231            return response()->json([
1232                'success' => false,
1233                'error' => $e->getMessage(),
1234            ], 500);
1235        }
1236    }
1237
1238    /**
1239     * Formatear bytes a formato legible
1240     */
1241    private function formatBytes($bytes, $precision = 2)
1242    {
1243        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
1244
1245        $bytes = max($bytes, 0);
1246        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
1247        $pow = min($pow, count($units) - 1);
1248
1249        $bytes /= pow(1024, $pow);
1250
1251        return round($bytes, $precision) . ' ' . $units[$pow];
1252    }
1253
1254    /**
1255     * @param $failedSyncs
1256     * @param $successfulSyncs
1257     * @param $startCronDateTime
1258     * @param $name
1259     * @return void
1260     */
1261    public function updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region, $process = null) {
1262        Log::channel('g3w')->error($failedSyncs);
1263
1264        if ($region === "Catalunya") {
1265            $region = "Cataluña";
1266        }
1267
1268        $companyId = TblCompanies::where('region', $region)->first()->company_id;
1269        $syncStatus = "Success";
1270        $failedSyncsTxt = "";
1271
1272        if (!empty($failedSyncs)) {
1273            $syncStatus = ($successfulSyncs > 0) ? 'Partially failed' : 'Failed';
1274            $this->notifyErrors($failedSyncs);
1275            $errorsArray = array_column($failedSyncs, 'error');
1276            $failedSyncsTxt = implode(', ', $errorsArray);
1277        }
1278
1279        $idsError = array_map('intval', array_column($failedSyncs, 'id'));
1280        $idsError = array_unique($idsError);
1281        $idsErrorCount = count($idsError);
1282        $idsErrorJson = json_encode($idsError);
1283
1284        $idsSuccess = array_map('intval', array_column($successIdSyncs, 'id'));
1285        $idsSuccess = array_unique($idsSuccess);
1286        $idsSuccessCount = count($idsSuccess);
1287        $idsSuccessJson = json_encode($idsSuccess);
1288
1289        TblG3WOrdersUpdateLogs::create(
1290            array(
1291                "company_id" => $companyId,
1292                "to_process" => $process?? "Orders",
1293                "status" => $syncStatus,
1294                "sync_succesfull" => $idsSuccessCount,
1295                "sync_error" => $idsErrorCount,
1296                "sync_error_message" => $failedSyncsTxt,
1297                "sync_error_ids" => $idsErrorJson,
1298                "sync_success_ids" => $idsSuccessJson,
1299                "processed_by" => $name,
1300                "started_at" => $startCronDateTime,
1301                "ended_at" => TblG3wLastUpdate::where("region", $region)->first()->updated_at->format('Y-m-d H:i:s'),
1302            )
1303        );
1304    }
1305
1306    private function checkEmailInvalid($email)
1307    {
1308        $emailPattern = "/^[\w\.\-]+@([\w\-]+\.)+[a-zA-Z]{2,}$/";
1309
1310        $emails = explode(",", $email);
1311
1312        $emailInvalid = false;
1313
1314        foreach ($emails as $email) {
1315            if (!preg_match($emailPattern, $email)) {
1316                $emailInvalid = true;
1317                break;
1318            }
1319        }
1320
1321        return $emailInvalid;
1322    }
1323
1324    private function checkRequiredFields($data){
1325
1326        $g3wWarningFields = array();
1327
1328        if ($data->budget_status_id == 13 || $data->budget_status_id == 14) {
1329            array_push($g3wWarningFields, "Estado");
1330        }
1331
1332        if ($data->budget_type_id == null || $data->budget_type_id == "" || $data->budget_type_id == 0 || $data->budget_type_id == 16) {
1333            array_push($g3wWarningFields, "Tipo");
1334        }
1335
1336        if ($data->commercial == null || $data->commercial == "") {
1337            array_push($g3wWarningFields, "Comercial");
1338        }
1339
1340        if ($data->source_id == null || $data->source_id == "") {
1341            array_push($g3wWarningFields, "Fuente");
1342        }
1343
1344        if ($data->email == null || $data->email == "") {
1345            array_push($g3wWarningFields, "Email");
1346        }
1347
1348        if ($data->client == null || $data->client == "") {
1349            array_push($g3wWarningFields, "Datos cliente");
1350        }
1351
1352        if(!empty($g3wWarningFields)){
1353            return implode(', ', $g3wWarningFields);
1354        }else{
1355            return null;
1356        }
1357    }
1358
1359    function syncExistingDataWithWarnings(){
1360
1361        $budgets = TblQuotations::where('g3w_warning', 1)->get();
1362
1363        if(count($budgets) > 0){
1364            foreach ($budgets as $item) {
1365                $g3wWarning = 0;
1366                $g3wWarningFields = $this->checkRequiredFields($item);
1367
1368                if($g3wWarningFields != null && $g3wWarningFields != ""){
1369                    $g3wWarning = 1;
1370                }
1371
1372                TblQuotations::where('id', $item->id)->update(
1373                    array(
1374                        'g3w_warning' => $g3wWarning,
1375                        'g3w_warning_fields' => $g3wWarningFields
1376                    )
1377                );
1378
1379                $g3wWarningFields = null;
1380            }
1381        }
1382    }
1383
1384    public function syncByIds($ids, $region, $date, $user="System")
1385    {
1386        try {
1387            if (!$ids){
1388                throw new \Exception("No ids provided");
1389            }
1390
1391            $arrayIds = explode(",", $ids);
1392
1393            $g3wActive = TblCompanies::where("region", $region)->first()->g3W_active;
1394
1395            if(!$g3wActive || $g3wActive === 0){
1396                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
1397            }
1398
1399            $this->setSyncStatus(1, $region);
1400            $this->syncExistingDataWithWarnings();
1401
1402            $successfulSyncs = 0;
1403            $failedSyncs = [];
1404            $successIdSyncs = [];
1405
1406            $startCronDateTime = date('Y-m-d H:i:s');
1407
1408            $company = TblCompanies::where("region", $region)->first();
1409
1410            $isNullInDate = TblQuotations::where("company_id", $company->company_id)
1411                ->where(function ($query) {
1412                    $query->where("sync_import", 1)
1413                        ->orWhere("sync_import_edited", 1);
1414                })
1415                ->whereDate("created_at", $date)
1416                ->where("internal_quote_id", null)
1417                ->exists();
1418
1419            foreach ($arrayIds as $id) {
1420                $result['success'] = false;
1421                $result['error'] = "Error en sync by Ids";
1422                if($isNullInDate){
1423                    $result['success'] = \DB::transaction(function () use ($id, $region, $company, $date) {
1424                        return $this->syncNullBudget($id, $region, $company->company_id, $date);
1425                    });
1426                }
1427
1428                if(!$result['success']){
1429                    $result = \DB::transaction(function () use ($id, $region, $isNullInDate) {
1430                        return $this->syncById($id, $region, $isNullInDate);
1431                    });
1432                }
1433
1434                if ($result['success']) {
1435                    $successfulSyncs++;
1436                    $successIdSyncs[] = [
1437                        'id' => $id,
1438                    ];
1439
1440                    $quote = TblQuotations::where("company_id", $company->company_id)
1441                        ->where("internal_quote_id", $id)
1442                        ->first();
1443
1444                    if(!$quote && $company->company_id == 18){
1445                        $quote = TblQuotations::where("company_id", 22)
1446                            ->where("internal_quote_id", $id)
1447                            ->first();
1448                    }
1449
1450                    if(!$quote && $company->company_id == 22){
1451                        $quote = TblQuotations::where("company_id", 18)
1452                            ->where("internal_quote_id", $id)
1453                            ->first();
1454                    }
1455
1456                    if($quote){
1457                        $quote->update(array(
1458                            "created_at" => Carbon::parse($date)
1459                        ));
1460                    }
1461
1462                } else {
1463                    if (strpos($result['error'], 'No se ha encontrado el presupuesto') === false) {
1464                        $failedSyncs[] = [
1465                            'id' => $id,
1466                            'error' => $result['error'] ?? 'Unknown error',
1467                        ];
1468                    }
1469                }
1470            }
1471
1472            $this->setSyncStatus(0, $region);
1473
1474            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
1475
1476            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $user, $region);
1477
1478            return [
1479                'success' => true,
1480                'message' => 'Synchronization completed.'
1481            ];
1482        } catch (\Exception $e) {
1483            Log::channel('g3w')->error('Error sincronizando los presupuestos: ' . $e->getMessage());
1484
1485            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
1486                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
1487            }
1488
1489            return ['success' => false, 'error' => $e->getMessage()];
1490        }
1491
1492    }
1493
1494    private function syncNullBudget($id, $region, $companyId, $date){
1495        Log::info("entramos");
1496        $budget = $this->request('get', "presupuesto/{$id}", $region, []);
1497
1498        if (!isset($budget["presupuesto"]) || !is_array($budget["presupuesto"])) {
1499            throw new \Exception("El presupuesto no contiene los datos esperados.");
1500        }
1501
1502        $quote = TblQuotations::where("company_id", $companyId)
1503            ->where(function ($query) {
1504                $query->where("sync_import", 1)
1505                    ->orWhere("sync_import_edited", 1);
1506            })
1507            ->whereDate("created_at", $date)
1508            ->where("internal_quote_id", null)
1509            ->where("source_by_g3w", $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null)
1510            ->where("type_by_g3w", $budget["presupuesto"]["origen_presupuesto"]?? null)
1511            ->where("user_create_by_g3w", $budget["presupuesto"]["usuario"])
1512            ->where("user_commercial_by_g3w", !empty($budget["presupuesto"]["cod_comercial_presupuesto"]) ? $budget["presupuesto"]["cod_comercial_presupuesto"] : null)
1513            ->first();
1514
1515        Log::info($quote);
1516
1517        if(!$quote){
1518            return false;
1519        }
1520
1521        $quote->update(array(
1522            "internal_quote_id" => $id
1523        ));
1524
1525        return true;
1526    }
1527
1528    function getAlternativeClientData($texto) {
1529        preg_match('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', $texto, $email);
1530        preg_match('/\b\d{9}\b/', $texto, $number);
1531
1532        $nombre = null;
1533        $lineas = explode("\n", $texto);
1534        foreach ($lineas as $linea) {
1535
1536            $linea = trim($linea);
1537
1538            if (empty($linea) ||
1539                preg_match('/@|\b\d{9}\b|C\/|CALLE|AVENIDA|PLAZA|CARRER|\d{5}/i', $linea)) {
1540                continue;
1541            }
1542
1543            if (preg_match('/^[A-Za-zÁÉÍÓÚáéíóúÑñ\s\.]+$/', $linea) &&
1544                !preg_match('/\d/', $linea) &&
1545                substr_count($linea, ' ') >= 1 &&
1546                strlen($linea) > 5) {
1547
1548                $candidatos[] = $linea;
1549            }
1550        }
1551
1552        if (!empty($candidatos)) {
1553            usort($candidatos, function($a, $b) {
1554                $scoreA = (strpos($a, '.') === false ? 2 : 0) + strlen($a);
1555                $scoreB = (strpos($b, '.') === false ? 2 : 0) + strlen($b);
1556                return $scoreB - $scoreA;
1557            });
1558            $nombre = rtrim($candidatos[0], '.');
1559        }
1560
1561        return [
1562            'email' => $email[0] ?? null,
1563            'number' => $number[0] ?? null,
1564            'name' => $nombre
1565        ];
1566    }
1567
1568    private static function checkAproval($companyId, $budgetTypeId, $customerTypeId, $amount, $quoteId, $id, $commercialId, $comercial){
1569        $forApproval = null;
1570
1571        $company = TblCompanies::where('company_id', $companyId)->first();
1572        $project = TblProjectTypes::where('company_id', $companyId)->where('budget_type_id', $budgetTypeId)->first();
1573        $customerTypeIds = array();
1574
1575        if($project){
1576            if(!empty($project->customer_type_ids)){
1577                $customerTypeIds = array_map('intval', explode(',', $project->customer_type_ids));
1578            }
1579            if($project->minimum_order_size != null && in_array($customerTypeId, $customerTypeIds)){
1580                if($amount >= $project->minimum_order_size){
1581                    $forApproval = 1;
1582                }
1583            }
1584            $minimumOrderSize = $project->minimum_order_size;
1585        }else{
1586            if(!empty($company->customer_type_ids)){
1587                $customerTypeIds = array_map('intval', explode(',', $company->customer_type_ids));
1588            }
1589            if($company->minimum_order_size != null && in_array($customerTypeId, $customerTypeIds)){
1590                if($amount >= $company->minimum_order_size){
1591                    $forApproval = 1;
1592                }
1593            }
1594            $minimumOrderSize = $company->minimum_order_size;
1595        }
1596
1597        if($forApproval === 1){
1598            $quotations = new Quotations();
1599
1600            if(!$commercialId){
1601                $commercialId = TblUsers::where("name", $comercial)->first()->id;
1602            }
1603
1604            $quotations->send_approval_notification(
1605                $amount,
1606                $budgetTypeId,
1607                $customerTypeId,
1608                $minimumOrderSize,
1609                $quoteId,
1610                $id,
1611                $company->name,
1612                'System',
1613                $commercialId,
1614                0,
1615                null,
1616                $company->company_id,
1617                'orders',
1618                0,
1619                0,
1620                null,
1621                "es"
1622            );
1623        }
1624
1625        return $forApproval;
1626    }
1627}
1628