Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 1038
0.00% covered (danger)
0.00%
0 / 24
CRAP
0.00% covered (danger)
0.00%
0 / 1
PresupuestosService
0.00% covered (danger)
0.00%
0 / 1038
0.00% covered (danger)
0.00%
0 / 24
94556
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 / 231
0.00% covered (danger)
0.00%
0 / 1
5700
 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 / 170
0.00% covered (danger)
0.00%
0 / 1
2450
 calculateBudgetMargin
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
380
 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 / 18
0.00% covered (danger)
0.00%
0 / 1
380
 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            Log::channel('allInfoQuotationsG3w')->info("Sincronizando presupuesto {$id}");
213            $g3wWarning = 0;
214            $g3wWarningFields = null;
215            $budget = $this->request('get', "presupuesto/{$id}", $region, []);
216
217            if (!isset($budget["presupuesto"]) || !is_array($budget["presupuesto"])) {
218                Log::channel('allInfoQuotationsG3w')->info("El presupuesto no contiene los datos esperados.");
219                throw new \Exception("El presupuesto no contiene los datos esperados.");
220            }
221
222            $companyId = TblCompanies::where('region', $region)->first()->company_id;
223
224            if (TblQuotations::where("internal_quote_id", $budget["presupuesto"]["cod_presupuesto"])->where("company_id", $companyId)->exists()) {
225                Log::channel('allInfoQuotationsG3w')->info("El presupuesto ya existe, procedemos a modificarlo.");
226                $resultEdit = $this->syncModifiedBudgetById($id, $region);
227                return $resultEdit;
228            }
229
230            $statusList = $this->request('get', "presupuesto/tiposestado", $region, []);
231
232            $collection = collect($statusList);
233
234            Log::channel('allInfoQuotationsG3w')->info("Estado del presupuesto: " . $budget["presupuesto"]["estado"]);
235
236            $nameStatus = $collection->firstWhere('ID', $budget["presupuesto"]["estado"])['nombre'] ?? null;
237
238            Log::channel('allInfoQuotationsG3w')->info("Estado del presupuesto en FST: " . $nameStatus);
239
240            if (!$nameStatus) {
241                $statusID = TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id;
242            } else {
243                $statusID = $this->normalizeStatus($nameStatus);
244
245                if ($statusID === TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id) {
246                    $statusID = TblBudgetStatus::where('name', "Estado no reconocido en FST")->first()->budget_status_id;
247                }
248            }
249
250            Log::channel('allInfoQuotationsG3w')->info("ID del estado del presupuesto en FST: " . $statusID);
251
252            $company = $this->request('get', "servicio/{$budget["presupuesto"]["cod_servicio"]}", $region, []);
253
254            if(!$company || (!isset($company['servicio']) && !isset($company['cliente']))){
255                $company = $this->request('get', "cliente/{$budget["presupuesto"]["cod_cliente"]}", $region, []);
256            }
257
258            $companyName = $company["servicio"]["nombre_servicio"] ?? $company["cliente"]["empresa"] ?? null;
259            $companyTelephone = $company["servicio"]["telefono"] ?? $company["cliente"]["telefono"] ?? null;
260            $companyEmail = $company["servicio"]["email"] ?? $company["cliente"]["email"] ?? null;
261
262            Log::channel('allInfoQuotationsG3w')->info("Nombre de la empresa: " . $companyName);
263
264            if($companyEmail) {
265                $companyEmail = trim($companyEmail);
266
267                if (str_contains($companyEmail, ";")) {
268                    $companyEmail = str_replace(";", ",", $companyEmail);
269                }
270            }
271
272            Log::channel('allInfoQuotationsG3w')->info("Email de la empresa: " . $companyEmail);
273
274            if(!$companyName || ($budget["presupuesto"]["cod_cliente"] === 9818 && in_array($companyId, [18, 22]))){
275                $companyName = $this->getAlternativeClientData($budget["presupuesto"]["datos_cliente_alternativo"])["name"];
276            }
277
278            if(!$companyTelephone || ($budget["presupuesto"]["cod_cliente"] === 9818 && in_array($companyId, [18, 22]))){
279                $companyTelephone = $this->getAlternativeClientData($budget["presupuesto"]["datos_cliente_alternativo"])["number"];
280            }
281
282            if(!$companyEmail || ($budget["presupuesto"]["cod_cliente"] === 9818 && in_array($companyId, [18, 22]))){
283                $companyEmail = $this->getAlternativeClientData($budget["presupuesto"]["datos_cliente_alternativo"])["email"];
284            }
285
286            Log::channel('allInfoQuotationsG3w')->info("Nombre de la empresa tras alternative client data: " . $companyName);
287
288            $segmentID = $this->normalizeSegment($company["servicio"]["tipo_servicio"] ?? "Otro");
289
290            Log::channel('allInfoQuotationsG3w')->info("Segmento de la empresa: " . $segmentID);
291
292            $companyId = TblCompanies::where('region', $region)->first()->company_id;
293
294            if(!isset($budget["presupuesto"]["documento"]) || empty($budget["presupuesto"]["documento"])){
295                Log::channel('allInfoQuotationsG3w')->info("El presupuesto no tiene documento asociado.");
296                throw new \Exception("El presupuesto no tiene documento asociado. Creelo y vuelva a intentarlo.");
297            }
298
299            $companyNameFormatted = "";
300
301            if (!empty($companyName)) {
302                $companyNameFormatted = str_replace(' ', '_', $companyName);
303            }
304
305            $nameDocument = $companyNameFormatted
306                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
307                : $budget["presupuesto"]["cod_presupuesto"];
308
309            Log::channel('allInfoQuotationsG3w')->info("Nombre del documento: " . $nameDocument);
310
311            $typeId = $this->normalizeType($budget["presupuesto"]["origen_presupuesto"] ?? null, $region);
312
313            Log::channel('allInfoQuotationsG3w')->info("Tipo de presupuesto: " . $typeId);
314
315            if(empty($budget["presupuesto"]["fecha_creacion"])){
316                $budget["presupuesto"]["fecha_creacion"] = Carbon::now()->format('Y-m-d H:i:s');
317            }
318
319            $work = $this->request('get', "presupuesto/trabajos/{$id}", $region, []);
320
321            $workIds = [];
322
323            if (is_array($work)) {
324                foreach ($work as $item) {
325                    if (isset($item['ID'])) {
326                        $workIds[] = $item['ID'];
327                    }
328                }
329            }
330
331            $idsConcatenados = implode('/', $workIds);
332
333            Log::channel('allInfoQuotationsG3w')->info("Trabajos: " . $idsConcatenados);
334
335            $createdByUser = TblUserG3wMapping::where("name_g3w", $budget["presupuesto"]["usuario"])->first();
336            $createdBy = null;
337            $comercial = null;
338
339            if(!$createdByUser){
340                TblUserG3wMapping::create(array(
341                    "name_g3w" => $budget["presupuesto"]["usuario"]?? null
342                ));
343                $comercial = $budget["presupuesto"]["usuario"];
344                $g3wWarning = 1;
345            } else {
346                $createdBy = TblUsers::where("id", $createdByUser->id_fst)->value('name')
347                    ?? null;
348
349                $comercial = $createdBy;
350            }
351            
352            Log::channel('allInfoQuotationsG3w')->info("Comercial: " . $comercial);
353
354            $comercialUser = TblUserG3wMapping::where("name_g3w", $budget["presupuesto"]["cod_comercial_presupuesto"])->first();
355
356            Log::channel('allInfoQuotationsG3w')->info("Comercial user: " . $comercialUser);
357
358            if(!$comercialUser && $budget["presupuesto"]["cod_comercial_presupuesto"]){
359                TblUserG3wMapping::create(array(
360                    "name_g3w" => $budget["presupuesto"]["cod_comercial_presupuesto"]?? null
361                ));
362            } else if($comercialUser){
363                $comercial = TblUsers::where("id", $comercialUser->id_fst)->value('name')
364                    ?? null;
365            }
366
367            Log::channel('allInfoQuotationsG3w')->info("Comercial limpio: " . $comercial);
368
369            $source = $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null;
370            $sourceId = ($source) ? $this->normalizeSource($source, $region) : null;
371
372            Log::channel('allInfoQuotationsG3w')->info("Source: " . $source);
373
374            if ($statusID == 12) {
375                $companyLimit = TblCompanies::where('company_id', $companyId)->first();
376                $inProgressCount = TblQuotations::where('budget_status_id', 12)->where('company_id', $companyId)->where('commercial', $comercial)->count();
377                if($companyLimit->process_limit <= $inProgressCount){
378                    Log::channel('allInfoQuotationsG3w')->info("Se ha alcanzado el número máximo de pedidos en curso ({$companyLimit->process_limit}) permitido por usuario para esta empresa.");
379                    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.");
380                }
381            }
382
383            //Jomar
384            if($companyId == 18 && $source == 9){
385                $companyId = 22;
386            }
387
388            $lastFollowUp = TblLastFollowUpDate::where('company_id', $companyId)->where('budget_type_id', $typeId)->first();
389            $workingDays = 10;
390            if($lastFollowUp && $lastFollowUp->last_follow_up_date){
391                $workingDays = $lastFollowUp->last_follow_up_date;
392            }
393
394            $date = Carbon::now();
395
396            $daysAdded = 0;
397            while ($daysAdded < $workingDays) {
398                $date->addDay();
399                if ($date->isWeekday()) {
400                    $daysAdded++;
401                }
402            }
403
404            $lastFollowUpDate = $date;
405
406            if(
407                in_array($statusID, [13, 14]) ||
408                !$sourceId ||
409                (!$typeId || $typeId == 0 || $typeId == 16) ||
410                empty(trim($companyName)) ||
411                empty(trim($companyEmail))
412                // || $this->checkEmailInvalid($companyEmail)
413            ){
414                $g3wWarning = 1;
415            }
416
417            $row = (object) array(
418                'client' => $companyName,
419                'email' => $companyEmail,
420                'budget_status_id' => $statusID,
421                'commercial' => $comercial,
422                'source_id' => $sourceId,
423                'budget_type_id' => $typeId,
424                "amount" => $budget["presupuesto"]["importe"] ?? null
425            );
426
427            $g3wWarningFields = $this->checkRequiredFields($row);
428
429            if($g3wWarningFields != null && $g3wWarningFields != ""){
430                $g3wWarning = 1;
431            }
432
433            if ($g3wWarningFields !== null && strpos($g3wWarningFields, 'Email') !== false) {
434                $statusID = 21;
435            }
436
437            Log::channel('allInfoQuotationsG3w')->info("G3W warning: " . $g3wWarning);
438
439            $reasonForNotFollowingUp = null;
440
441            // iker chacori and juan carlos
442            $excludingUsers = ["igc", "030", "031", "fcl", "018", "021", "jcsp", "026", "029", "EXMOF", "MGF"];
443
444            $facilityUser = false;
445
446            if(
447                (in_array($budget["presupuesto"]["usuario"], $excludingUsers) || in_array($budget["presupuesto"]["cod_comercial_presupuesto"], $excludingUsers))
448                && $region == "Cataluña"
449            ){
450                $reasonForNotFollowingUp = 1;
451                $facilityUser = true;
452            }
453
454            if($segmentID == 3) {
455                $reasonForNotFollowingUp = 2;
456            } elseif ($segmentID == 2) {
457                $reasonForNotFollowingUp = 1;
458            }
459
460            Log::channel('allInfoQuotationsG3w')->info("Reason for not following up: " . $reasonForNotFollowingUp);
461
462            $defaultUser = 0;
463
464            if(!$comercialUser){
465                Log::channel('allInfoQuotationsG3w')->info("Default user");
466                if($companyId == 18){
467                    $defaultUser = 94;
468                }
469
470                if($companyId == 19){
471                    $defaultUser = 68;
472                }
473
474                if($companyId == 22){
475                    $defaultUser = 153;
476                }
477
478                if($companyId == 30){
479                    $defaultUser = 124;
480                }
481                Log::channel('allInfoQuotationsG3w')->info("Default user: " . $defaultUser);
482            }
483
484            if(env("APP_ENV") === "local"){
485                if($defaultUser == 0){
486                    $defaultUser = 92;
487                }
488            }
489
490
491            $generateNumber = $this->generateQuoteId($companyId);
492            Log::channel('allInfoQuotationsG3w')->info("Generate number: " . $generateNumber["id"]);
493
494            $g3wNewId = $generateNumber['id'];
495            $newQuoteId = $generateNumber['number'];
496
497            $forApproval = self::checkAproval($companyId, $typeId, 2, $budget["presupuesto"]["importe"], $newQuoteId, $g3wNewId, $comercialUser->id_fst?? $defaultUser, $comercial);
498
499            Log::channel('allInfoQuotationsG3w')->info("For approval: " . $forApproval);
500
501            $g3wArray = array(
502                'internal_quote_id' => $budget["presupuesto"]["cod_presupuesto"],
503                'quote_id' => $newQuoteId,
504                'company_id' => $companyId,
505                'customer_type_id' => 2,
506                'segment_id' => $segmentID,
507                'budget_type_id' => $typeId == 16 ? null : $typeId,
508                'budget_status_id' => $statusID,
509                'source_id' => $sourceId,
510                'client' => $companyName,
511                'phone_number' => $companyTelephone,
512                'email' => $companyEmail,
513                'issue_date' => ($budget["presupuesto"]["fecha_creacion"] ?? null) === "0000/00/00" ? null : $budget["presupuesto"]["fecha_creacion"],
514                'request_date' => ($budget["presupuesto"]["fecha_creacion"] ?? null) === "0000/00/00" ? null : $budget["presupuesto"]["fecha_creacion"],
515                'acceptance_date' => ($budget["presupuesto"]["aceptacion"] ?? null) === "0000/00/00" ? null : $budget["presupuesto"]["aceptacion"],
516                'amount' => $budget["presupuesto"]["importe"] ?? null,
517                'last_follow_up_date' => $facilityUser ? null : $lastFollowUpDate,
518                'commercial' => $comercial,
519                'created_at' => $budget["presupuesto"]["fecha_creacion"] ?? null,
520                'created_by' => $createdBy,
521                'has_attachment' => 1,
522                'cost_of_labor' => 0,
523                'total_cost_of_job' => 0,
524                'invoice_margin' => 0,
525                'margin_for_the_company' => 0,
526                'revenue_per_date_per_worked' => 0,
527                'gross_margin' => 100,
528                'labor_percentage' => 0,
529                'sync_import' => 1,
530                'box_work_g3w' => $idsConcatenados,
531                'segment_by_g3w' => $company["servicio"]["tipo_servicio"] ?? null,
532                'source_by_g3w' => $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null,
533                'status_by_g3w' => $nameStatus,
534                'type_by_g3w' => $budget["presupuesto"]["origen_presupuesto"]?? null,
535                'user_create_by_g3w' => $budget["presupuesto"]["usuario"],
536                'user_commercial_by_g3w' => !empty($budget["presupuesto"]["cod_comercial_presupuesto"]) ? $budget["presupuesto"]["cod_comercial_presupuesto"] : null,
537                "g3w_warning" => $g3wWarning,
538                "g3w_warning_fields" => $g3wWarningFields,
539                'reason_for_not_following_up_id' => $reasonForNotFollowingUp,
540                'for_add' => 0,
541                'for_approval' => $forApproval,
542            );
543
544            TblQuotations::where('id', $g3wNewId)->update($g3wArray);
545
546            Log::channel('allInfoQuotationsG3w')->info("Pasamos el insert.");
547
548            $companyNameFormatted = "";
549
550            if (!empty($companyName)) {
551                $companyNameFormatted = str_replace(' ', '_', $companyName);
552            }
553
554            $nameDocument = $companyNameFormatted
555                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
556                : $budget["presupuesto"]["cod_presupuesto"];
557            
558            Log::channel('allInfoQuotationsG3w')->info("Nombre del documento: " . $nameDocument);
559
560            $responseSaveDocument = $this->saveDocument($budget["presupuesto"]["documento"], $nameDocument, $g3wNewId, $newQuoteId);
561            $responseDataSaveDocument = $responseSaveDocument->getData();
562
563            if (!isset($responseDataSaveDocument->success) || !$responseDataSaveDocument->success) {
564                Log::channel('allInfoQuotationsG3w')->info("No se pudo guardar el documento del presupuesto {$id} al crearlo. Error: " . ($responseDataSaveDocument->error ?? 'Desconocido'));
565                throw new \Exception("No se pudo guardar el documento del presupuesto {$id} al crearlo. Error: " . ($responseDataSaveDocument->error ?? 'Desconocido'));
566            }
567
568            $documentName = $responseDataSaveDocument->documentName;
569
570            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
571
572            return [
573                'success' => true,
574                'id' => $g3wNewId
575            ];
576
577        } catch (\Exception $e) {
578            $errorMessage = $e->getMessage();
579            return ['success' => false, 'error' => "Error sincronizando el presupuesto {$id}" . $errorMessage];
580        }
581    }
582
583    private function getArrayValue(array $array, string $key, $default = null) {
584        return $array[$key] ?? $default;
585    }
586
587    /**
588     * @param $id
589     * @return array|true[]
590     */
591    public function syncModifiedBudgetById($id, $region = null){
592        try {
593            Log::channel('allInfoQuotationsG3w')->info("Modificando presupuesto: " . $id);
594            $statusID = null;
595            $materialId = [
596                "Cataluña" => [102561, 102562, 102568, 102569],
597                "Madrid" => [562, 100026, 100027],
598                "Comunidad Valenciana" => [562]
599            ];
600
601            $statusToChange = [
602                "Enviado",
603                "Aceptado",
604                "Rechazado"
605            ];
606
607            $g3wWarning = 0;
608            $g3wWarningFields = null;
609            $companyId = TblCompanies::where('region', $region)->first()->company_id;
610
611            $budget = $this->request('get', "presupuesto/{$id}", $region, []);
612
613            if (!TblQuotations::where("internal_quote_id", $budget["presupuesto"]["cod_presupuesto"])->where("company_id", $companyId)->exists()) {
614                Log::channel('allInfoQuotationsG3w')->info("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.");
615                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.");
616            }
617
618            $company = $this->request('get', "servicio/{$budget["presupuesto"]["cod_servicio"]}", $region, []);
619
620            $companyName = $company["servicio"]["nombre_servicio"] ?? $company["cliente"]["empresa"] ?? null;
621
622            //$company = $this->request('get', "servicio/{$budget["presupuesto"]["cod_servicio"]}", $region, []);
623
624            /*if(!$company || !isset($company['servicio'])){
625                $company = $this->request('get', "cliente/{$budget["presupuesto"]["cod_cliente"]}", $region, []);
626            }*/
627
628            //$companyName = (isset($company['servicio'])) ? $company["servicio"]["nombre_servicio"] : $company["cliente"]["empresa"];
629            //$companyTelephone = (isset($company['servicio'])) ? $company["servicio"]["telefono"] : $company["cliente"]["telefono"] ;
630
631            //$segmentID = $this->normalizeSegment($company["servicio"]["tipo_servicio"]?? "Otro");
632
633            if(!isset($budget["presupuesto"]["documento"]) || !$budget["presupuesto"]["documento"]){
634                Log::channel('allInfoQuotationsG3w')->info("El presupuesto no tiene documento asociado. Creelo y vuelva a intentarlo.");
635                throw new \Exception("El presupuesto no tiene documento asociado. Creelo y vuelva a intentarlo.");
636            }
637
638            $typeId = $this->normalizeType($budget["presupuesto"]["origen_presupuesto"], $region);
639
640            Log::channel('allInfoQuotationsG3w')->info("Tipo de presupuesto: " . $typeId);
641
642            $statusList = $this->request('get', "presupuesto/tiposestado", $region, []);
643
644            $collection = collect($statusList);
645
646            $nameStatus = $collection->firstWhere('ID', $budget["presupuesto"]["estado"])['nombre'] ?? null;
647
648            Log::channel('allInfoQuotationsG3w')->info("Estado de presupuesto: " . $nameStatus);
649
650            if (!$nameStatus) {
651                $statusID = TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id;
652            } else {
653                $statusID = $this->normalizeStatus($nameStatus);
654
655                if ($statusID === TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id) {
656                    $statusID = TblBudgetStatus::where('name', "Estado no reconocido en FST")->first()->budget_status_id;
657                }
658            }
659
660            Log::channel('allInfoQuotationsG3w')->info("ID de estado: " . $statusID);
661
662            $row = TblQuotations::where('internal_quote_id', $budget["presupuesto"]["cod_presupuesto"])
663                ->where('company_id', $companyId)
664                ->first();
665
666            if($row){
667                if(
668                    in_array($row->budget_status_id, [13, 14]) ||
669                    !$row->source_id ||
670                    (!$typeId || $typeId == 0 || $typeId == 16) ||
671                    empty(trim($row->client)) || // change for $companyName when we implement
672                    empty(trim($row->email))
673                    // || $this->checkEmailInvalid($row->email)
674                ){
675                    $g3wWarning = 1;
676                }
677
678                $g3wWarningFields = $this->checkRequiredFields($row);
679
680                if($g3wWarningFields != null && $g3wWarningFields != ""){
681                    $g3wWarning = 1;
682                }
683
684                Log::channel('allInfoQuotationsG3w')->info("G3W Warning: " . $g3wWarning);
685
686                if($row->budget_type_id !== $typeId){ $this->quotationsController->addUpdateLog($row->id, "System", "budget_type_id", $row->budget_type_id, $typeId); }
687
688                if(in_array($nameStatus, $statusToChange)){ $this->quotationsController->addUpdateLog($row->id, "System", "budget_status_id", $row->budget_status_id, $statusID?? $row->budget_status_id); }
689
690                if($row->amount !== $budget["presupuesto"]["importe"]){ $this->quotationsController->addUpdateLog($row->id, "System", "amount", $row->amount, $budget["presupuesto"]["importe"]); }
691
692                if($row->updated_by !== "System"){ $this->quotationsController->addUpdateLog($row->id, "System", "updated_by", $row->updated_by, "System"); }
693
694                if($row->has_attachment !== 1){ $this->quotationsController->addUpdateLog($row->id, "System", "has_attachment", $row->has_attachment, 1); }
695
696                if($row->sync_import_edited !== 1){ $this->quotationsController->addUpdateLog($row->id, "System", "sync_import_edited", $row->sync_import_edited, 1); }
697
698                if($nameStatus === "Aceptado"){
699                    $this->quotationsController->addUpdateLog($row->id, "System", null, $row->acceptance_date, Carbon::now()->format('Y-m-d H:i:s'));
700                    $this->quotationsController->addUpdateLog($row->id, "System", null, $row->accepted_at, Carbon::now()->format('Y-m-d H:i:s'));
701                    $this->quotationsController->addUpdateLog($row->id, "System", null, $row->accepted_by, "System");
702                }
703
704                Log::channel('allInfoQuotationsG3w')->info("Pasamos el add update log");
705                
706                $totalCostOfMaterial = 0;
707                $totalLabor = 0;
708                foreach($budget["presupuesto"]["lineas"] as $linea){
709                    if(in_array($linea["cod_material"], $materialId[$region])){
710                        $totalLabor += $linea["precio_compra"] * $linea["unidades"];   
711                    } else {
712                        $totalCostOfMaterial += $linea["precio_compra"] * $linea["unidades"];   
713                    }
714                }
715
716
717                $companyInfo = TblCompanies::where('region', $region)->first();
718
719                $numberOfDays = $totalLabor / ($companyInfo->hours_per_worker_per_day * $companyInfo->cost_of_hour);
720
721                $resultMargin = $this->calculateBudgetMargin(
722                    $budget["presupuesto"]["importe"]?? 0, 
723                    $row->commission_pct?? 0, 
724                    1, 
725                    $numberOfDays?? 0, 
726                    $totalCostOfMaterial?? 0, 
727                    $companyInfo->hours_per_worker_per_day?? 0, 
728                    $companyInfo->cost_of_hour?? 0, 
729                    $row->segment_id?? 0, 
730                    $companyInfo->general_costs?? 0
731                );
732
733                Log::channel('allInfoQuotationsG3w')->info("Pasamos el calculo de margen.");
734
735                $forApproval = $row->for_approval;
736
737                $work = $this->request('get', "presupuesto/trabajos/{$id}", $region, []);
738
739                $isWorkAccepted = false;
740
741                if (!empty($work)) {
742                    $dataToSend = [
743                        'ids' => implode(", ", $work)
744                    ];
745
746                    $worksStatus = $this->request("post", "trabajo/estados", $region, $dataToSend);
747
748                    foreach ($worksStatus as $item) {
749                        if ($item->estado === 'Aceptado') {
750                            $isWorkAccepted = true;
751                            break;
752                        }
753                    }
754                }
755
756                if(
757                    ($row->budget_type_id !== $typeId ||
758                    //$row->customer_type_id !== $customerTypeId ||
759                    $row->amount !== $budget["presupuesto"]["importe"]) &&
760                    !$isWorkAccepted
761                ){
762                    $comercialUser = TblUsers::where('name', $row->commercial)->first();
763                    $forApproval = self::checkAproval($companyId, $typeId, $row->customer_type_id, $budget["presupuesto"]["importe"], $row->quote_id, $row->id, $comercialUser->id, $row->commercial);
764                }
765
766                $row->update(
767                    array(
768                        //'segment_id' => $segmentID,
769                        'budget_type_id' => $typeId == 16 ? null : $typeId,
770                        //'client' => $companyName?? null,
771                        //'phone_number' => $companyTelephone?? null,
772                        'budget_status_id' => in_array($nameStatus, $statusToChange) ? $statusID : $row->budget_status_id,
773                        'acceptance_date' => ($nameStatus === "Aceptado") ? Carbon::now()->format('Y-m-d H:i:s') : $row->acceptance_date,
774                        'accepted_at' => ($nameStatus === "Aceptado") ? Carbon::now()->format('Y-m-d H:i:s') : $row->accepted_at,
775                        'accepted_by' => ($nameStatus === "Aceptado") ? "System" : $row->accepted_by,
776                        'amount' => $budget["presupuesto"]["importe"]?? null,
777                        'updated_by' => "System",
778                        'updated_at' => Carbon::now()->format('Y-m-d H:i:s'),
779                        'has_attachment' => 1,
780                        'sync_import_edited' => 1,
781                        'segment_by_g3w' => $company["servicio"]["tipo_servicio"] ?? null,
782                        'source_by_g3w' => $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null,
783                        'status_by_g3w' => $nameStatus,
784                        'type_by_g3w' => $budget["presupuesto"]["origen_presupuesto"]?? null,
785                        'user_create_by_g3w' => $budget["presupuesto"]["usuario"]?? null,
786                        'user_commercial_by_g3w' => !empty($budget["presupuesto"]["cod_comercial_presupuesto"]) ? $budget["presupuesto"]["cod_comercial_presupuesto"] : null,
787                        "g3w_warning" => $g3wWarning,
788                        "g3w_warning_fields" => $g3wWarningFields,
789                        "cost_of_labor" => $resultMargin['cost_of_labor'],
790                        "total_cost_of_job" => $resultMargin['total_cost_of_job'],
791                        "invoice_margin" => $resultMargin['invoice_margin'],
792                        "margin_for_the_company" => $resultMargin['margin_for_the_company'],
793                        "margin_on_invoice_per_day_per_worker" => $resultMargin['margin_on_invoice_per_day_per_worker'],
794                        "commission_cost" => $resultMargin['commission_cost'],
795                        "revenue_per_date_per_worked" => $resultMargin['revenue_per_date_per_worked'],
796                        "gross_margin" => $resultMargin['gross_margin'],
797                        "labor_percentage" => $resultMargin['labor_percentage'],
798                        "estimated_cost_of_materials" => $totalCostOfMaterial,
799                        "people_assigned_to_the_job" => 1,
800                        "duration_of_job_in_days" => $numberOfDays,
801                        'for_approval' => $forApproval,
802                    )
803                );
804            }
805
806            Log::channel('allInfoQuotationsG3w')->info("Pasamos el update");
807
808            $companyNameFormatted = "";
809
810            if (!empty($companyName)) {
811                $companyNameFormatted = str_replace(' ', '_', $companyName);
812            }
813
814            $nameDocument = $companyNameFormatted
815                ? $budget["presupuesto"]["cod_presupuesto"] . "_" . $companyNameFormatted
816                : $budget["presupuesto"]["cod_presupuesto"];
817
818            Log::channel('allInfoQuotationsG3w')->info("Nombre del documento: " . $nameDocument);
819
820            $quote = TblQuotations::where('internal_quote_id', $budget["presupuesto"]["cod_presupuesto"])
821                ->where('company_id', $companyId)->first();
822
823            if (!$quote) {
824                Log::channel('allInfoQuotationsG3w')->info("No se encontró la cotización con internal_quote_id: " . $budget["presupuesto"]["cod_presupuesto"]);
825                throw new \Exception("No se encontró la cotización con internal_quote_id: {$budget["presupuesto"]["cod_presupuesto"]}");
826            }
827
828            $response = $this->saveDocument($budget["presupuesto"]["documento"], $nameDocument, $row->id, $row->quote_id);
829            $responseData = $response->getData();
830
831            if (!isset($responseData->success) || !$responseData->success) {
832                Log::channel('allInfoQuotationsG3w')->info("No se pudo guardar el documento del presupuesto {$id} al editar. Error: " . ($responseData->error ?? 'Desconocido'));
833                throw new \Exception("No se pudo guardar el documento del presupuesto {$id} al editar. Error: " . ($responseData->error ?? 'Desconocido'));
834            }
835
836            $work = $this->request('get', "presupuesto/trabajos/{$id}", $region, []);
837
838            if (!empty($work) && isset($work[0]['ID'])) {
839                $workId = $work[0]['ID'];
840                $albaran = $this->request('get', "albaran/{$workId}", $region, []);
841
842                if(isset($albaran["albaran"]) && isset($albaran["albaran"]["certificado"])){
843                    $certificado = $albaran["albaran"]["certificado"];
844                    $nameCertificado = $id . "_certificado";
845
846                    $response = $this->saveDocument($certificado, $nameCertificado, $row->id, $row->quote_id);
847                    $responseData = $response->getData();
848
849                    if (!isset($responseData->success) || !$responseData->success) {
850                        Log::channel('allInfoQuotationsG3w')->info("No se pudo guardar el certificado del presupuesto {$id} al editar. Error: " . ($responseData->error ?? 'Desconocido'));
851                        throw new \Exception("No se pudo guardar el certificado del presupuesto {$id} al editar. Error: " . ($responseData->error ?? 'Desconocido'));
852                    }
853
854                }
855            }
856
857            Log::channel('allInfoQuotationsG3w')->info("Pasamos la subida de albaran.");
858
859            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
860
861            return [
862                'success' => true
863            ];
864
865        } catch (\Exception $e) {
866            return ['success' => false, 'error' => "Error actualizando el presupuesto {$id}" . $e->getMessage()];
867        }
868    }
869
870    function calculateBudgetMargin($amount, $commission_pct, $people_assigned_to_the_job, $duration_of_job_in_days, $estimated_cost_of_materials, $hours_per_worker_per_day, $cost_of_hour, $segmentId, $generalCosts) {
871        $results = [
872            'cost_of_labor' => 0,
873            'total_cost_of_job' => 0,
874            'invoice_margin' => null,
875            'margin_for_the_company' => null,
876            'margin_on_invoice_per_day_per_worker' => null,
877            'commission_cost' => null,
878            'revenue_per_date_per_worked' => 0,
879            'gross_margin' => 0,
880            'labor_percentage' => 0
881        ];
882
883        $parseFloat = function($value) {
884            if ($value === null || $value === '') return 0;
885            $cleanValue = str_replace(',', '.', (string)$value);
886            return is_numeric($cleanValue) ? (float)$cleanValue : 0;
887        };
888
889        $amount = $parseFloat($amount ?? 0);
890        $commissionPct = $parseFloat($commission_pct ?? 0);
891        $peopleAssigned = $parseFloat($people_assigned_to_the_job ?? 0);
892        $durationInDays = $parseFloat($duration_of_job_in_days ?? 0);
893        $estimatedMaterials = $parseFloat($estimated_cost_of_materials ?? 0);
894
895        if ($amount >= 0 && $peopleAssigned >= 0 && $durationInDays >= 0) {
896            
897            $costOfLabor = $durationInDays * $peopleAssigned * ($hours_per_worker_per_day ?? 0) * ($cost_of_hour ?? 0);
898            
899            $totalCostOfJob = $costOfLabor + $estimatedMaterials;
900
901            if ($commissionPct > 0 && $amount > 0) {
902                $commissionCost = ($commissionPct / 100) * $amount;
903            } else {
904                $commissionCost = 0;
905            }
906
907            if ($totalCostOfJob > 0 && $amount > 0) {
908                if ($segmentId == 7) {
909                    $invoiceMargin = (($amount - $totalCostOfJob - $commissionCost) / $amount) * 100;
910                } else {
911                    $invoiceMargin = (($amount - $totalCostOfJob) / $amount) * 100;
912                }
913
914                $marginForTheCompany = $invoiceMargin - $generalCosts;
915                
916                $marginOnInvoicePerDayPerWorker = ($amount - $estimatedMaterials - $costOfLabor) / 
917                                                ($durationInDays ?: 1) / 
918                                                ($peopleAssigned ?: 1);
919            } else {
920                $invoiceMargin = 0;
921                $marginForTheCompany = 0;
922                $marginOnInvoicePerDayPerWorker = 0;
923            }
924            
925
926            if ($costOfLabor == 0) {
927                $revenuePerDayWorked = 0;
928                $laborPercentage = 0;
929            } else {
930                $revenuePerDayWorked = ($amount / ($peopleAssigned ?: 1) / ($durationInDays ?: 1));
931                $laborPercentage = ($costOfLabor / $amount) * 100;
932            }
933
934            
935
936            $grossMargin = $amount > 0 ? (($amount - $estimatedMaterials) / $amount) * 100 : 0;
937
938            $results['cost_of_labor'] = $costOfLabor;
939            $results['total_cost_of_job'] = $totalCostOfJob;
940            $results['invoice_margin'] = $invoiceMargin;
941            $results['margin_for_the_company'] = $marginForTheCompany;
942            $results['margin_on_invoice_per_day_per_worker'] = $marginOnInvoicePerDayPerWorker;
943            $results['commission_cost'] = $commissionCost;
944            $results['revenue_per_date_per_worked'] = $revenuePerDayWorked;
945            $results['gross_margin'] = $grossMargin;
946            $results['labor_percentage'] = $laborPercentage;
947
948        } else {
949            foreach ($results as $key => $val) {
950                $results[$key] = null;
951            }
952        }
953        return $results;
954    }
955
956    /**
957     * Synchronize budgets that gave us an error.
958     *
959     * @param string $name Who's launch the function
960     * @return array
961     */
962    public function syncErrorBudgets($name = null, $region = null)
963    {
964        try {
965            if($region === "Catalunya"){
966                $region = "Cataluña";
967            }
968
969            $g3wActive = TblCompanies::where("region", $region)->first()->g3W_active;
970
971            if(!$g3wActive || $g3wActive === 0){
972                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
973            }
974
975            $this->setSyncStatus(1, $region);
976
977            $successfulSyncs = 0;
978            $failedSyncs = [];
979            $successIdSyncs = [];
980
981            $startCronDateTime = date('Y-m-d H:i:s');
982
983            $company = TblCompanies::where("region", $region)->first();
984
985            if (!$company) {
986                throw new \Exception("No company found for region: " . $region);
987            }
988            $company_id = $company->company_id;
989            $logs = TblG3WOrdersUpdateLogs::whereNotNull('sync_error_ids')
990                ->where('company_id', $company_id)
991                ->get(['sync_error_ids']);
992
993            $allSyncErrorIds = [];
994
995            foreach ($logs as $log) {
996                if (is_string($log->sync_error_ids)) {
997                    $log->sync_error_ids = json_decode($log->sync_error_ids, true);
998                    $allSyncErrorIds = array_merge($allSyncErrorIds, $log->sync_error_ids);
999                }
1000            }
1001
1002            $allSyncErrorIds = array_unique($allSyncErrorIds);
1003
1004            foreach ($allSyncErrorIds as $idSyncError) {
1005                $result = \DB::transaction(function () use ($idSyncError, $region) {
1006                    return $this->syncById($idSyncError, $region);
1007                });
1008
1009                if ($result['success']) {
1010                    $successfulSyncs++;
1011                    $successIdSyncs[] = [
1012                        'id' => $idSyncError,
1013                    ];
1014                } else {
1015                    if (strpos($result['error'], 'No se ha encontrado el presupuesto') === false) {
1016                        $failedSyncs[] = [
1017                            'id' => $idSyncError,
1018                            'error' => $result['error'] ?? 'Unknown error',
1019                        ];
1020                    }
1021                }
1022            }
1023
1024            TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
1025
1026            $company = TblCompanies::where("region", $region)->first();
1027            if (!$company) {
1028                throw new \Exception("No company found for region: " . $region);
1029            }
1030            $company_id = $company->company_id;
1031
1032            $logs = TblG3WOrdersUpdateLogs::whereNotNull('sync_error_ids')
1033                ->where('company_id', $company_id)
1034                ->update(['sync_error_ids' => []]);
1035
1036            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region);
1037
1038            return [
1039                'success' => true,
1040                'message' => 'Synchronization of failed budgets completed.'
1041            ];;
1042
1043        } catch (\Exception $e) {
1044            Log::channel('g3w')->error('Error when synchronizing error budgets: ' . $e->getMessage());
1045
1046            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
1047                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
1048            }
1049
1050            return ['success' => false, 'error' => $e->getMessage()];
1051        }
1052    }
1053
1054    public function syncBudgetsWorks($name = null, $region = null){
1055        try {
1056            if($region === "Catalunya"){
1057                $region = "Cataluña";
1058            }
1059
1060            $company = TblCompanies::where("region", $region)->first();
1061
1062            if (!$company) {
1063                throw new \Exception("No se encontró la compañía para la región '$region'.");
1064            }
1065            $g3wActive = $company->g3W_active;
1066
1067
1068            if(!$g3wActive || $g3wActive === 0){
1069                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
1070            }
1071
1072            $this->workSevice->getG3wTasksExecuted($region, 1);
1073
1074            $this->setSyncStatus(1, $region);
1075
1076            $successfulSyncs = 0;
1077            $failedSyncs = [];
1078            $successIdSyncs = [];
1079
1080            $startCronDateTime = date('Y-m-d H:i:s');
1081
1082            $quotesIds = TblQuotations::where(function ($query) {
1083                $query->where('sync_import', 1)
1084                    ->orWhere('sync_import_edited', 1);
1085            })
1086                ->where('budget_type_id', 1)
1087                ->where(function ($query) {
1088                    $query->whereNull('box_work_g3w')
1089                        ->orWhere('box_work_g3w', '0');
1090                })
1091                ->get();
1092
1093            foreach ($quotesIds as $quoteId) {
1094                $work = $this->request('get', "presupuesto/trabajos/{$quoteId->internal_quote_id}", $region, []);
1095
1096                sleep(2);
1097
1098                $workIds = [];
1099
1100                if (is_array($work)) {
1101                    foreach ($work as $item) {
1102                        if (isset($item['ID'])) {
1103                            $workIds[] = $item['ID'];
1104                        }
1105                    }
1106                }
1107
1108                $idsConcatenados = implode('/', $workIds);
1109
1110                $wasUpdated = $quoteId->update(['box_work_g3w' => $idsConcatenados]);
1111
1112                if($wasUpdated) {
1113                    $successfulSyncs++;
1114                    $successIdSyncs[] = [
1115                        'id' => $quoteId->internal_quote_id,
1116                    ];
1117                } else {
1118                    $failedSyncs[] = [
1119                        'id' => $quoteId->internal_quote_id,
1120                        'error' => "Error updating the internal quote id $quoteId, work $idsConcatenados.",
1121                    ];
1122
1123                }
1124            }
1125
1126            $g3wUpdate = TblG3wLastUpdate::where("region", $region)->first();
1127            if ($g3wUpdate) {
1128                $g3wUpdate->update(['updatingNow' => 0]);
1129            }
1130
1131            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
1132
1133            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region, "Orders Works");
1134
1135            return [
1136                'success' => true,
1137                'message' => 'Synchronization of budgets works completed.'
1138            ];
1139
1140        } catch (\Exception $e) {
1141            Log::channel('g3w')->error('Error when synchronizing budgets works: ' . $e->getMessage());
1142
1143            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
1144                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
1145            }
1146
1147            return ['success' => false, 'error' => $e->getMessage()];
1148        }
1149    }
1150
1151    /**
1152     * @param $failedSyncs
1153     * @return void
1154     */
1155    public function notifyErrors($failedSyncs)
1156    {
1157        $errorDetails = array_map(function ($failure) {
1158            return "Budget ID: {$failure['id']}, Error: {$failure['error']}";
1159        }, $failedSyncs);
1160
1161        $message = implode("\n", $errorDetails);
1162
1163        /*Mail::luis, rick & chris, tech@fire.es*/
1164
1165        Log::channel('g3w')->error("Error notification sent to ricardo.alemany@fire.es");
1166    }
1167
1168    /**
1169     * Functuion to generate the next QuoteID
1170     * @param $companyId
1171     * @return int|string
1172     */
1173    /*public function generateQuoteId($companyId)
1174    {
1175        if ($companyId == 0) {
1176            $latestQuoteId = TblQuotations::orderBy('id', 'DESC')->first()->quote_id ?? null;
1177        } else {
1178            $latestQuoteId = TblQuotations::where('company_id', $companyId)
1179                ->orderBy('id', 'DESC')
1180                ->first()
1181                ->quote_id ?? null;
1182        }
1183
1184        if (!$latestQuoteId) {
1185            throw new \Exception("Error generando el # en titan");
1186        }
1187
1188        if (is_numeric($latestQuoteId)) {
1189            return (string)((int)$latestQuoteId + 1);
1190        }
1191
1192        preg_match('/([A-Z]+)(\d+)/', $latestQuoteId, $matches);
1193
1194        if (count($matches) < 3) {
1195            throw new \Exception("El formato del Ãºltimo Quote ID no es válido: {$latestQuoteId}");
1196        }
1197
1198        $prefix = $matches[1];
1199        $numericPart = $matches[2];
1200
1201        $incrementedNumber = str_pad((int)$numericPart + 1, strlen($numericPart), '0', STR_PAD_LEFT);
1202
1203        $newQuoteId = $prefix . $incrementedNumber;
1204
1205        return $newQuoteId;
1206    }*/
1207    public function generateQuoteId($companyId)
1208    {
1209        try {
1210
1211            $companyId = addslashes($companyId);
1212            $latestBudget = array();
1213            $number = 0;
1214            $beforeLastId = null;
1215
1216            $x = true;
1217
1218            if($companyId == 0){
1219                $latestBudget = TblQuotations::orderByRaw('CAST(quote_id AS DOUBLE) DESC')->pluck('quote_id')->first();
1220            }else{
1221                $latestBudget = TblCompanies::where('company_id', $companyId)->pluck('last_id')->first();
1222
1223                if($latestBudget == null){
1224                    $latestBudget = TblQuotations::where('company_id', $companyId)->orderByRaw('id DESC')->pluck('quote_id')->first();
1225                    $beforeLastId = $latestBudget;
1226                }
1227            }
1228
1229            $number = $latestBudget;
1230
1231            while ($x) {
1232
1233                if(is_numeric(substr($number, -1))) {
1234                    $number++;
1235                }else{
1236                    $number .= "1";
1237                }
1238
1239                $check = 0;
1240
1241                if($companyId == 0){
1242                    $check = TblQuotations::where('quote_id', (string) $number)->count();
1243                }else{
1244                    $check = TblQuotations::where('company_id', $companyId)->where('quote_id', (string) $number)->count();
1245                }
1246
1247                if($check == 0){
1248                    $x = false;
1249                }
1250            }
1251
1252            $result = TblQuotations::create(array('quote_id' => $number, 'company_id' => $companyId, 'for_add' => 1));
1253
1254            if($beforeLastId == null){
1255                $beforeLastId = $number;
1256            }
1257
1258            $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}";
1259            DB::select($query);
1260
1261            return array(
1262                'id' => $result->id,
1263                'number' => $number
1264            );
1265
1266        } catch (\Exception $e) {
1267            return response(['message' => 'KO', 'error' => $e->getMessage()]);
1268        }
1269    }
1270
1271    /**
1272     * Function to normalice the status provided by G3W.
1273     * @param $status String Row status of G3W
1274     * @return Int ID normalized in FST
1275     * @throws \Exception
1276     */
1277    private function normalizeStatus($status)
1278    {
1279        if (!$status){
1280            return TblBudgetStatus::where('name', "Sin estado en G3W")->first()->budget_status_id;
1281        }
1282
1283        $statusMapping = TblStatusG3wMapping::where("name_g3w", $status)->first();
1284
1285        if (!$statusMapping) {
1286            TblStatusG3wMapping::create(array(
1287                "name_g3w" => $status,
1288                "budget_status_id" => 0
1289            ));
1290            return TblBudgetStatus::where('name', "Estado no reconocido en FST")->first()->budget_status_id;
1291        }
1292
1293        return $statusMapping->budget_status_id;
1294
1295    }
1296
1297    /**
1298     * Function to normalice the segment provided by G3W.
1299     * @param $status String Row status of G3W
1300     * @return Int ID normalized in FST
1301     * @throws \Exception
1302     */
1303    private function normalizeSegment($segment)
1304    {
1305        $segmentMapping = TblSegmentG3wMapping::where("name_g3w", $segment)->first();
1306
1307        if(!$segmentMapping){
1308            TblSegmentG3wMapping::create(array(
1309                "name_g3w" => $segment,
1310                "segment_id" => 0
1311            ));
1312            return TblSegments::where('name', "Otro")->first()->segment_id?? 9;
1313        }
1314
1315        return $segmentMapping->segment_id;
1316    }
1317
1318    /**
1319     * Function to normalize the budget type provided by G3W.
1320     * @param int $type ID del tipo proporcionado por la API.
1321     * @param string $region Región (Madrid o Cataluña).
1322     * @return int ID normalizado del presupuesto en el sistema.
1323     * @throws \Exception
1324     */
1325    private function normalizeType($type, $region)
1326    {
1327        if($region === "Catalunya"){
1328            $region = "Cataluña";
1329        }
1330
1331        $budgetTypeMapping = TblTypeG3wMapping::where("id_g3w", $type)
1332            ->where("region", $region)
1333            ->first();
1334
1335        if (!$budgetTypeMapping) {
1336            TblTypeG3wMapping::create(array(
1337                "id_g3w" => $type?? null,
1338                "budget_type_name" => null,
1339                "region" => $region
1340            ));
1341            throw new \Exception("El estado '$type' no existe en la base de datos.");
1342        }
1343
1344        $budgetType = TblBudgetTypes::where("name", $budgetTypeMapping->budget_type_name)->first();
1345
1346        if(!$budgetType){
1347            return 16;
1348        }
1349
1350        return $budgetType->budget_type_id;
1351    }
1352
1353    private function normalizeSource($id_call, $region){
1354        $regionTxt = "";
1355        $region = $region == "Catalunya" ? "Cataluña" : $region;
1356
1357        if(!$id_call){
1358            $sourceDefault = TblSources::where('name', 'G3W/Gestiona')->first();
1359
1360            return $sourceDefault->source_id?? 20;
1361        }
1362
1363        $sourceMapping = TblSourceG3wMapping::where("id_g3w", $id_call)
1364            ->where("region", $region)
1365            ->first();
1366
1367        if(!$sourceMapping){
1368            TblSourceG3wMapping::create(array(
1369                "id_g3w" => $id_call,
1370                "source_name" => null,
1371                "region" => $region
1372            ));
1373            return null;
1374        }
1375
1376        $sourceId = TblSources::where("name", $sourceMapping->source_name)->first();
1377
1378        if(!$sourceId){
1379            return null;
1380        }
1381
1382        return $sourceId->source_id;
1383
1384    }
1385
1386    /**
1387     * @param $document
1388     * @return \Illuminate\Http\JsonResponse
1389     */
1390    public function saveDocument($document, $nameDocument = null, $quotationId = null, $quoteId = null, $uploadedBy = null, $isInternal = null)
1391    {
1392
1393        try {
1394
1395            $binaryData = base64_decode($document);
1396
1397            if ($binaryData === false) {
1398                throw new \Exception('Los datos Base64 no son válidos.');
1399            }
1400
1401            $documentName = $nameDocument
1402                ? preg_replace('/[^A-Za-z0-9_\-.]/', '', $nameDocument)
1403                : 'document_' . time() . '.pdf';
1404
1405            if (!preg_match('/\.pdf$/i', $documentName)) {
1406                $documentName .= '.pdf';
1407            }
1408
1409            $filename = pathinfo($documentName, PATHINFO_FILENAME) . '_' . time() . '.pdf';                  
1410
1411            $fileSize = strlen($binaryData);
1412            $mimeType = 'application/pdf';
1413
1414            $fileDataBase = TblFiles::where("quotation_id", $quotationId)->get();
1415
1416            if($fileDataBase){
1417                foreach ($fileDataBase as $fileData) {
1418                    if($fileData->original_name == $documentName){
1419                        $s3FilePath = 'uploads/' . $fileData->filename;
1420            
1421                        if (Storage::disk('s3')->exists($s3FilePath)) {
1422                            //Storage::disk('s3')->delete($s3FilePath);
1423                        }
1424                        
1425                        $fileData->delete();
1426
1427                    }
1428                }
1429            }
1430
1431            $s3path = Storage::disk('s3')->put(
1432                'uploads/' . $filename, 
1433                $binaryData,           
1434                [
1435                    'ContentType' => $mimeType,
1436                ]
1437            );           
1438
1439            $file = TblFiles::create([
1440                'quotation_id'  => $quotationId,
1441                'original_name' => $documentName,
1442                'filename'      => $filename,
1443                'uploaded_by'   => $uploadedBy,
1444                'file_size'     => $fileSize,
1445                'mime_type'     => $mimeType,
1446                'uploaded_at'   => date('Y-m-d H:i:s'),
1447            ]);
1448
1449            return response()->json([
1450                'success' => true,
1451                'message' => 'Documento guardado correctamente en la base de datos.',                
1452                'filename' => $filename,                
1453                'fileId' => $file->file_id,
1454                'documentName' => $documentName                
1455            ], 200);
1456
1457        } catch (\Exception $e) {
1458            return response()->json([
1459                'success' => false,
1460                'error' => $e->getMessage(),
1461            ], 500);
1462        }
1463    }
1464
1465    /**
1466     * Formatear bytes a formato legible
1467     */
1468    private function formatBytes($bytes, $precision = 2)
1469    {
1470        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
1471
1472        $bytes = max($bytes, 0);
1473        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
1474        $pow = min($pow, count($units) - 1);
1475
1476        $bytes /= pow(1024, $pow);
1477
1478        return round($bytes, $precision) . ' ' . $units[$pow];
1479    }
1480
1481    /**
1482     * @param $failedSyncs
1483     * @param $successfulSyncs
1484     * @param $startCronDateTime
1485     * @param $name
1486     * @return void
1487     */
1488    public function updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $name, $region, $process = null) {
1489        Log::channel('g3w')->error($failedSyncs);
1490
1491        if ($region === "Catalunya") {
1492            $region = "Cataluña";
1493        }
1494
1495        $companyId = TblCompanies::where('region', $region)->first()->company_id;
1496        $syncStatus = "Success";
1497        $failedSyncsTxt = "";
1498
1499        if (!empty($failedSyncs)) {
1500            $syncStatus = ($successfulSyncs > 0) ? 'Partially failed' : 'Failed';
1501            $this->notifyErrors($failedSyncs);
1502            $errorsArray = array_column($failedSyncs, 'error');
1503            $failedSyncsTxt = implode(', ', $errorsArray);
1504        }
1505
1506        $idsError = array_map('intval', array_column($failedSyncs, 'id'));
1507        $idsError = array_unique($idsError);
1508        $idsErrorCount = count($idsError);
1509        $idsErrorJson = json_encode($idsError);
1510
1511        $idsSuccess = array_map('intval', array_column($successIdSyncs, 'id'));
1512        $idsSuccess = array_unique($idsSuccess);
1513        $idsSuccessCount = count($idsSuccess);
1514        $idsSuccessJson = json_encode($idsSuccess);
1515
1516        TblG3WOrdersUpdateLogs::create(
1517            array(
1518                "company_id" => $companyId,
1519                "to_process" => $process?? "Orders",
1520                "status" => $syncStatus,
1521                "sync_succesfull" => $idsSuccessCount,
1522                "sync_error" => $idsErrorCount,
1523                "sync_error_message" => $failedSyncsTxt,
1524                "sync_error_ids" => $idsErrorJson,
1525                "sync_success_ids" => $idsSuccessJson,
1526                "processed_by" => $name,
1527                "started_at" => $startCronDateTime,
1528                "ended_at" => TblG3wLastUpdate::where("region", $region)->first()->updated_at->format('Y-m-d H:i:s'),
1529            )
1530        );
1531    }
1532
1533    private function checkEmailInvalid($email)
1534    {
1535        $emailPattern = "/^[\w\.\-]+@([\w\-]+\.)+[a-zA-Z]{2,}$/";
1536
1537        $emails = explode(",", $email);
1538
1539        $emailInvalid = false;
1540
1541        foreach ($emails as $email) {
1542            if (!preg_match($emailPattern, $email)) {
1543                $emailInvalid = true;
1544                break;
1545            }
1546        }
1547
1548        return $emailInvalid;
1549    }
1550
1551    private function checkRequiredFields($data){
1552
1553        $g3wWarningFields = array();
1554
1555        if ($data->budget_status_id == 13 || $data->budget_status_id == 14) {
1556            array_push($g3wWarningFields, "Estado");
1557        }
1558
1559        if ($data->budget_type_id == null || $data->budget_type_id == "" || $data->budget_type_id == 0 || $data->budget_type_id == 16) {
1560            array_push($g3wWarningFields, "Tipo");
1561        }
1562
1563        if ($data->commercial == null || $data->commercial == "") {
1564            array_push($g3wWarningFields, "Comercial");
1565        }
1566
1567        if ($data->source_id == null || $data->source_id == "") {
1568            array_push($g3wWarningFields, "Fuente");
1569        }
1570
1571        if ($data->email == null || $data->email == "" || strpos($data->email, '@no') !== false) {
1572            array_push($g3wWarningFields, "Email");
1573        }
1574
1575        if ($data->client == null || $data->client == "") {
1576            array_push($g3wWarningFields, "Datos cliente");
1577        }
1578
1579        if ($data->amount == null || $data->amount == 0) {
1580            array_push($g3wWarningFields, "Importe");
1581        }
1582
1583        if(!empty($g3wWarningFields)){
1584            return implode(', ', $g3wWarningFields);
1585        }else{
1586            return null;
1587        }
1588    }
1589
1590    function syncExistingDataWithWarnings(){
1591
1592        $budgets = TblQuotations::where('g3w_warning', 1)->get();
1593
1594        if(count($budgets) > 0){
1595            foreach ($budgets as $item) {
1596                $g3wWarning = 0;
1597                $g3wWarningFields = $this->checkRequiredFields($item);
1598
1599                if($g3wWarningFields != null && $g3wWarningFields != ""){
1600                    $g3wWarning = 1;
1601                }
1602
1603                TblQuotations::where('id', $item->id)->update(
1604                    array(
1605                        'g3w_warning' => $g3wWarning,
1606                        'g3w_warning_fields' => $g3wWarningFields
1607                    )
1608                );
1609
1610                $g3wWarningFields = null;
1611            }
1612        }
1613    }
1614
1615    public function syncByIds($ids, $region, $date, $user="System")
1616    {
1617        try {
1618            if (!$ids){
1619                throw new \Exception("No ids provided");
1620            }
1621
1622            $arrayIds = explode(",", $ids);
1623
1624            $g3wActive = TblCompanies::where("region", $region)->first()->g3W_active;
1625
1626            if(!$g3wActive || $g3wActive === 0){
1627                Throw new Exception("La sincronización con G3W debe estar desactivada en la region '$region'.");
1628            }
1629
1630            $this->setSyncStatus(1, $region);
1631            $this->syncExistingDataWithWarnings();
1632
1633            $successfulSyncs = 0;
1634            $failedSyncs = [];
1635            $successIdSyncs = [];
1636
1637            $startCronDateTime = date('Y-m-d H:i:s');
1638
1639            $company = TblCompanies::where("region", $region)->first();
1640
1641            $isNullInDate = TblQuotations::where("company_id", $company->company_id)
1642                ->where(function ($query) {
1643                    $query->where("sync_import", 1)
1644                        ->orWhere("sync_import_edited", 1);
1645                })
1646                ->whereDate("created_at", $date)
1647                ->where("internal_quote_id", null)
1648                ->exists();
1649
1650            foreach ($arrayIds as $id) {
1651                $result['success'] = false;
1652                $result['error'] = "Error en sync by Ids";
1653                if($isNullInDate){
1654                    $result['success'] = \DB::transaction(function () use ($id, $region, $company, $date) {
1655                        return $this->syncNullBudget($id, $region, $company->company_id, $date);
1656                    });
1657                }
1658
1659                if(!$result['success']){
1660                    $result = \DB::transaction(function () use ($id, $region, $isNullInDate) {
1661                        return $this->syncById($id, $region, $isNullInDate);
1662                    });
1663                }
1664
1665                if ($result['success']) {
1666                    $successfulSyncs++;
1667                    $successIdSyncs[] = [
1668                        'id' => $id,
1669                    ];
1670
1671                    $quote = TblQuotations::where("company_id", $company->company_id)
1672                        ->where("internal_quote_id", $id)
1673                        ->first();
1674
1675                    if(!$quote && $company->company_id == 18){
1676                        $quote = TblQuotations::where("company_id", 22)
1677                            ->where("internal_quote_id", $id)
1678                            ->first();
1679                    }
1680
1681                    if(!$quote && $company->company_id == 22){
1682                        $quote = TblQuotations::where("company_id", 18)
1683                            ->where("internal_quote_id", $id)
1684                            ->first();
1685                    }
1686
1687                    if($quote){
1688                        $quote->update(array(
1689                            "created_at" => Carbon::parse($date)
1690                        ));
1691                    }
1692
1693                } else {
1694                    if (strpos($result['error'], 'No se ha encontrado el presupuesto') === false) {
1695                        $failedSyncs[] = [
1696                            'id' => $id,
1697                            'error' => $result['error'] ?? 'Unknown error',
1698                        ];
1699                    }
1700                }
1701            }
1702
1703            $this->setSyncStatus(0, $region);
1704
1705            TblQuotations::where('acceptance_date', '0000-00-00 00:00:00')->update(array("acceptance_date"=>null));
1706
1707            $this->updateLogs($failedSyncs, $successfulSyncs, $successIdSyncs, $startCronDateTime, $user, $region);
1708
1709            return [
1710                'success' => true,
1711                'message' => 'Synchronization completed.'
1712            ];
1713        } catch (\Exception $e) {
1714            Log::channel('g3w')->error('Error sincronizando los presupuestos: ' . $e->getMessage());
1715
1716            if (TblG3wLastUpdate::where("region", $region)->first()->updatingNow === 1) {
1717                TblG3wLastUpdate::where("region", $region)->first()->update(['updatingNow' => 0]);
1718            }
1719
1720            return ['success' => false, 'error' => $e->getMessage()];
1721        }
1722
1723    }
1724
1725    private function syncNullBudget($id, $region, $companyId, $date){
1726        Log::info("entramos");
1727        $budget = $this->request('get', "presupuesto/{$id}", $region, []);
1728
1729        if (!isset($budget["presupuesto"]) || !is_array($budget["presupuesto"])) {
1730            throw new \Exception("El presupuesto no contiene los datos esperados.");
1731        }
1732
1733        $quote = TblQuotations::where("company_id", $companyId)
1734            ->where(function ($query) {
1735                $query->where("sync_import", 1)
1736                    ->orWhere("sync_import_edited", 1);
1737            })
1738            ->whereDate("created_at", $date)
1739            ->where("internal_quote_id", null)
1740            ->where("source_by_g3w", $budget["presupuesto"]["cod_empresa_presupuesto"] ?? null)
1741            ->where("type_by_g3w", $budget["presupuesto"]["origen_presupuesto"]?? null)
1742            ->where("user_create_by_g3w", $budget["presupuesto"]["usuario"])
1743            ->where("user_commercial_by_g3w", !empty($budget["presupuesto"]["cod_comercial_presupuesto"]) ? $budget["presupuesto"]["cod_comercial_presupuesto"] : null)
1744            ->first();
1745
1746        Log::info($quote);
1747
1748        if(!$quote){
1749            return false;
1750        }
1751
1752        $quote->update(array(
1753            "internal_quote_id" => $id
1754        ));
1755
1756        return true;
1757    }
1758
1759    function getAlternativeClientData($texto) {
1760        preg_match('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', $texto, $email);
1761        preg_match('/\b\d{9}\b/', $texto, $number);
1762
1763        $nombre = null;
1764        $lineas = explode("\n", $texto);
1765        foreach ($lineas as $linea) {
1766
1767            $linea = trim($linea);
1768
1769            if (empty($linea) ||
1770                preg_match('/@|\b\d{9}\b|C\/|CALLE|AVENIDA|PLAZA|CARRER|\d{5}/i', $linea)) {
1771                continue;
1772            }
1773
1774            if (preg_match('/^[A-Za-zÁÉÍÓÚáéíóúÑñ\s\.]+$/', $linea) &&
1775                !preg_match('/\d/', $linea) &&
1776                substr_count($linea, ' ') >= 1 &&
1777                strlen($linea) > 5) {
1778
1779                $candidatos[] = $linea;
1780            }
1781        }
1782
1783        if (!empty($candidatos)) {
1784            usort($candidatos, function($a, $b) {
1785                $scoreA = (strpos($a, '.') === false ? 2 : 0) + strlen($a);
1786                $scoreB = (strpos($b, '.') === false ? 2 : 0) + strlen($b);
1787                return $scoreB - $scoreA;
1788            });
1789            $nombre = rtrim($candidatos[0], '.');
1790        }
1791
1792        return [
1793            'email' => $email[0] ?? null,
1794            'number' => $number[0] ?? null,
1795            'name' => $nombre
1796        ];
1797    }
1798
1799    private static function checkAproval($companyId, $budgetTypeId, $customerTypeId, $amount, $quoteId, $id, $commercialId, $comercial){
1800        $forApproval = null;
1801
1802        $company = TblCompanies::where('company_id', $companyId)->first();
1803        $project = TblProjectTypes::where('company_id', $companyId)->where('budget_type_id', $budgetTypeId)->first();
1804        $customerTypeIds = array();
1805
1806        if($project){
1807            if(!empty($project->customer_type_ids)){
1808                $customerTypeIds = array_map('intval', explode(',', $project->customer_type_ids));
1809            }
1810            if($project->minimum_order_size != null && in_array($customerTypeId, $customerTypeIds)){
1811                if($amount >= $project->minimum_order_size){
1812                    $forApproval = 1;
1813                }
1814            }
1815            $minimumOrderSize = $project->minimum_order_size;
1816        }else{
1817            if(!empty($company->customer_type_ids)){
1818                $customerTypeIds = array_map('intval', explode(',', $company->customer_type_ids));
1819            }
1820            if($company->minimum_order_size != null && in_array($customerTypeId, $customerTypeIds)){
1821                if($amount >= $company->minimum_order_size){
1822                    $forApproval = 1;
1823                }
1824            }
1825            $minimumOrderSize = $company->minimum_order_size;
1826        }
1827
1828        if($forApproval === 1){
1829            $quotations = new Quotations();
1830
1831            if(!$commercialId){
1832                $commercialId = TblUsers::where("name", $comercial)->first()->id;
1833            }
1834
1835            $quotations->send_approval_notification(
1836                $amount,
1837                $budgetTypeId,
1838                $customerTypeId,
1839                $minimumOrderSize,
1840                $quoteId,
1841                $id,
1842                $company->name,
1843                'System',
1844                $commercialId,
1845                0,
1846                null,
1847                $company->company_id,
1848                'orders',
1849                0,
1850                0,
1851                null,
1852                "es"
1853            );
1854        }
1855
1856        return $forApproval;
1857    }
1858}