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