Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.46% covered (danger)
7.46%
449 / 6020
4.30% covered (danger)
4.30%
4 / 93
CRAP
0.00% covered (danger)
0.00%
0 / 1
Quotations
7.46% covered (danger)
7.46%
449 / 6020
4.30% covered (danger)
4.30%
4 / 93
1847047.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
61.54% covered (warning)
61.54%
8 / 13
0.00% covered (danger)
0.00%
0 / 1
4.91
 create_quotation
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 1
210
 currency
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 send_approval_notification
0.00% covered (danger)
0.00%
0 / 124
0.00% covered (danger)
0.00%
0 / 1
812
 send_approval_margin_notification
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 1
272
 approve_quotation
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
240
 send_approved_notification
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 1
110
 reject_quotation
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
240
 send_rejected_notification
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 1
110
 update_quotation
28.19% covered (danger)
28.19%
139 / 493
0.00% covered (danger)
0.00%
0 / 1
9518.73
 compareArrays
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
4
 convertValue
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
7.05
 callDeleteQuotation
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 get_quotation
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 get_quotation_log
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 send_notification
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
42
 delete_quotation
87.23% covered (warning)
87.23%
41 / 47
0.00% covered (danger)
0.00%
0 / 1
9.17
 getBlacklistEmails
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 validate_email
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 isBlacklistedEmail
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 isInvalidEmail
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
6.97
 list_quotations
24.62% covered (danger)
24.62%
97 / 394
0.00% covered (danger)
0.00%
0 / 1
7825.13
 get_dates
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 list_quotation_analytics_by_source
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 1
1722
 list_quotation_analytics_send_budgets
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
380
 list_quotation_analytics_track_budgets
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 1
1332
 list_quotation_analytics_types_budgets
0.00% covered (danger)
0.00%
0 / 121
0.00% covered (danger)
0.00%
0 / 1
1056
 download_quotations
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
2
 download_quotations_csv
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bulk_upload
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
30
 list_bulk_upload
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 delete_number
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 get_number
78.95% covered (warning)
78.95%
30 / 38
0.00% covered (danger)
0.00%
0 / 1
12.13
 get_years
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
12
 human_filesize
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get_files
0.00% covered (danger)
0.00%
0 / 132
0.00% covered (danger)
0.00%
0 / 1
1892
 download_file
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
56
 delete_file
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 send_email_to_client
0.00% covered (danger)
0.00%
0 / 322
0.00% covered (danger)
0.00%
0 / 1
7140
 send_email_follow_ups
0.00% covered (danger)
0.00%
0 / 422
0.00% covered (danger)
0.00%
0 / 1
9506
 create_sender_identity
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
56
 get_sender_identity
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 get_all_sender_identity
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 delete_sender_identity
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 create_template
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
72
 get_email_files
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 download_email_template_file
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 delete_email_template_file
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 update_email_template_order
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 update_email_template
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
90
 delete_template
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 get_email_template
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 update_sender_identity
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
20
 resend_verification
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 list_quotation_analytics_by_performance
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
30
 list_orders_update_logs
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 list_g3w_orders_update_logs
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 list_g3w_orders_failed
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
 update_budget_status_rejected_manual
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 update_budget_status_rejected
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
110
 bulk_update_quotation
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
30
 move_budget_and_job
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 1
30
 list_quotation_analytics_by_types_of_budgets_created_per_week
0.00% covered (danger)
0.00%
0 / 193
0.00% covered (danger)
0.00%
0 / 1
3782
 preview_file
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
20
 get_past_added_quotation
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
30
 send_acceptance_notification
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 1
156
 get_total_quotations_by_budget_status
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 1
90
 sendgrid_webhook_receiver
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
210
 isEmailValid
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 list_email_status
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 list_quotation_analytics_commercial
0.00% covered (danger)
0.00%
0 / 186
0.00% covered (danger)
0.00%
0 / 1
2970
 clear_open_data
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
 list_quotation_analytics_order_size
0.00% covered (danger)
0.00%
0 / 194
0.00% covered (danger)
0.00%
0 / 1
4422
 send_email_template_preview
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 1
272
 list_quotation_analytics_by_types_of_budgets_company_per_week
0.00% covered (danger)
0.00%
0 / 190
0.00% covered (danger)
0.00%
0 / 1
3782
 request_permission_commercial
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 1
72
 confirm_update_commercial
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 calculateEmailRequestSize
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
 list_quotation_analytics_commercial_productivity
0.00% covered (danger)
0.00%
0 / 234
0.00% covered (danger)
0.00%
0 / 1
4422
 list_quotations_deleted
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 delete_sengrid
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 download_productivity_commercial
0.00% covered (danger)
0.00%
0 / 378
0.00% covered (danger)
0.00%
0 / 1
2450
 update_commercial_numbers
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 list_quotation_analytics_by_service_type
0.00% covered (danger)
0.00%
0 / 156
0.00% covered (danger)
0.00%
0 / 1
1260
 getIdsFromInternalQuoteIds
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 checkQuotationExistByInternalQuoteId
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
 addUpdateLog
95.12% covered (success)
95.12%
78 / 82
0.00% covered (danger)
0.00%
0 / 1
45
 setSolicitudDuplicity
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
20
 getQuoteIdOfDuplicityById
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 download_s3_files
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
72
 getQuotationStatusByInternalId
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
110
 findQuotationByInternalId
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Models\StructureData;
6use App\Models\TblBlockedDomains;
7use App\Models\TblBudgetStatus;
8use App\Models\TblBudgetTypeGroups;
9use App\Models\TblBudgetTypes;
10use App\Models\TblBulkUpload;
11use App\Models\TblBusinessGoals;
12use App\Models\TblCcAcceptanceNotifications;
13use App\Models\TblCcBcc;
14use App\Models\TblCompanies;
15use App\Models\TblCompanyEmails;
16use App\Models\TblCompanyUsers;
17use App\Models\TblCustomerTypes;
18use App\Models\TblEmailConfiguration;
19use App\Models\TblEmailFiles;
20use App\Models\TblFiles;
21use App\Models\TblFollowUpLogs;
22use App\Models\TblG3WOrdersUpdateLogs;
23use App\Models\TblLastFollowUpDate;
24use App\Models\TblNotifications;
25use App\Models\Client;
26use App\Models\TblOngoingJobs;
27use App\Models\TblOrdersUpdateLogs;
28use App\Models\TblProjectTypes;
29use App\Models\TblQuotations;
30use App\Models\TblQuotationsLog;
31use App\Models\TblSegments;
32use App\Models\TblSendgridWebhook;
33use App\Models\TblSources;
34use App\Models\TblToAcceptanceNotifications;
35use App\Models\TblUsers;
36use App\Models\TblVisitTypeGroups;
37use App\Models\TblWorkflowQuestions;
38use Illuminate\Http\Request;
39use Illuminate\Support\Facades\App;
40use Illuminate\Support\Facades\Cache;
41use Illuminate\Support\Facades\DB;
42use Illuminate\Support\Facades\File;
43use Illuminate\Support\Facades\Log;
44use Illuminate\Support\Facades\Response;
45use Illuminate\Support\Facades\Storage;
46use PhpOffice\PhpSpreadsheet\IOFactory;
47use PhpOffice\PhpSpreadsheet\Spreadsheet;
48use SendGrid\Mail\Mail;
49use ZipArchive;
50use Illuminate\Contracts\Routing\ResponseFactory;
51use Illuminate\Http\Response as HttpResponse;
52use function PHPUnit\Framework\isEmpty;
53use function PHPUnit\Framework\isNull;
54use App\Exceptions\AppException;
55
56class Quotations extends Controller
57{
58    private $locale;
59
60    private $userId;
61
62    private $region;
63
64    private $companyIds;
65    private readonly string $companyId;
66
67    public function __construct(){
68        $this->locale = request()->header('Locale-Id');
69        $this->userId = request()->header('User-Id');
70        $this->region = request()->header('Region');
71
72        App::setLocale($this->locale);
73
74        $this->companyIds = [];
75
76        if($this->region != null && $this->region != "" && $this->region != "All"){
77            $this->region = urldecode((string) $this->region);
78
79            $query = 'SELECT
80                        b.company_id
81                    FROM
82                        tbl_company_users a
83                        LEFT JOIN tbl_companies b ON a.company_id = b.company_id
84                    WHERE
85                        a.user_id = ?
86                        AND b.region = ?';
87
88            $this->companyIds = DB::select($query, [intval($this->userId), $this->region]);
89
90            $this->companyIds = collect($this->companyIds)->pluck('company_id')->toArray();
91        } else {
92            $this->companyIds = TblCompanyUsers::where('user_id', $this->userId)->pluck('company_id')->all();
93        }
94
95        $this->companyId = implode(',', $this->companyIds);
96    }
97
98    public function create_quotation(Request $request): ResponseFactory|HttpResponse{
99
100        try {
101
102            $data = $request->all();
103            $data['updated_at'] = date('Y-m-d H:i:s');
104
105            if (isset($data['request_date']) && isset($data['issue_date'])) {
106                $requestDate = strtotime($data['request_date']);
107                $issueDate = strtotime($data['issue_date']);
108                $dateDiff = $issueDate - $requestDate;
109                $data['duration'] = round($dateDiff / (60 * 60 * 24));
110            }
111
112            $r = new Request([
113                'created_by' => $data['created_by'],
114            ]);
115
116            $result = $this->get_number($r, @$data['company_id']);
117            $id = $result->original['id'];
118
119            $files = $request->file('files');
120            if ($files) {
121                $uploadedFiles = [];
122
123                foreach ($files as $file) {
124                    $filename = time().'_'.$file->getClientOriginalName();
125                    $fileSize = $file->getSize();
126                    // $fileContent = file_get_contents($file->getRealPath());
127                    // $fileHash = hash('sha256', $fileContent);
128
129                    $s3path = Storage::disk('s3')->putFileAs(
130                        'uploads',
131                        $file,
132                        $filename,
133                        [
134                            'ContentType' => $file->getMimeType(),
135                        ]
136                    );
137
138                    TblFiles::create(
139                        [
140                            'quotation_id' => $id,
141                            'original_name' => $file->getClientOriginalName(),
142                            'filename' => $filename,
143                            'uploaded_by' => $data['updated_by'],
144                            // 'file' => $fileContent,
145                            'file_size' => $file->getSize(),
146                            // 'file_hash' => $fileHash,
147                            'mime_type' => $file->getMimeType(),
148                            'uploaded_at' => now(),
149                        ]
150                    );
151
152                    // $this->addUpdateLog($id, $data['updated_by'], "upload_attachment", null, $filename, 4);
153                }
154            }
155
156            $query = 'SELECT COUNT(*) as count FROM tbl_files WHERE quotation_id = ?';
157            $fileCount = DB::select($query, [$id])[0]->count;
158
159            $data['has_attachment'] = $fileCount > 0 ? 1 : 0;
160
161            $data = array_diff_key($data, array_flip(['files', '_token', 'otros_campos_no_necesarios']));
162
163            $data['for_add'] = 0;
164
165            // FIRE-864: if email is invalid (blacklisted, malformed, or
166            // empty) and status is sendable, set to 22 (Correo erroneo).
167            // Pre-fix this only checked `isBlacklistedEmail()`, so
168            // malformed emails like "no@" or "ricardo" never got
169            // reclassified.
170            if (isset($data['email']) && $this->isInvalidEmail($data['email'])
171                && isset($data['budget_status_id']) && in_array($data['budget_status_id'], [1, 2, 11, 17])) {
172                $data['budget_status_id'] = 22;
173            }
174
175            TblQuotations::where('id', $id)->update($data);
176
177            $result = TblQuotations::where('id', $id)->first();
178
179            if ($result->budget_status_id == 6) {
180                $data = [
181                    'id' => $result->id ?? null,
182                    'client' => $result->client ?? null,
183                    'email' => $result->email ?? null,
184                    'phone_number' => $result->phone_number ?? null,
185                    'last_follow_up_comment' => $result->last_follow_up_comment ?? null,
186                    'quote_id' => $result->quote_id ?? null,
187                    'request_date' => date('Y-m-d'),
188                    'updated_by' => 'IA',
189                    'user_id' => $result->getAttribute('user_id') ?? null,
190                    'commercial' => $result->commercial ?? null,
191                    'budget_status_id' => $result->budget_status_id ?? null,
192                    'internal_quote_id' => $result->internal_quote_id ?? null,
193                ];
194
195                $ch = curl_init('https://2lsarnb35o6evhgwmfedrsxk3i0lqzzq.lambda-url.eu-west-2.on.aws/checkduplicate');
196                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
197                curl_setopt($ch, CURLOPT_POST, true);
198                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
199                curl_setopt($ch, CURLOPT_HTTPHEADER, [
200                    'Content-Type: application/json',
201                ]);
202
203                $response = curl_exec($ch);
204                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
205
206                if (curl_errno($ch)) {
207                    error_log('Error en cURL: '.curl_error($ch));
208                }
209
210                curl_close($ch);
211            }
212
213            $logCategory = $result->budget_status_id == 6 ? 0 : 2;
214            $this->addUpdateLog($result->id, $result->getAttribute('user_id'), 'create_quotation', null, null, $logCategory);
215            Cache::flush();
216
217            return response(['message' => 'OK', 'data' => $result]);
218
219        } catch (\Exception $e) {
220            report(AppException::fromException($e, 'CREATE_QUOTATION_EXCEPTION'));
221            return response(['message' => 'KO', 'error' => $e->getMessage()]);
222        }
223
224    }
225
226    public function currency($amount, $withEuro = '')
227    {
228
229        if ($withEuro != null) {
230            $withEuro = ' €';
231        }
232
233        return number_format($amount, 2, ',', '.').$withEuro;
234    }
235
236    function send_approval_notification($amount, $budgetTypeId, $customerTypeId, $minimumOrderSize, string $quoteId, $id, $companyName, $createdBy, $userId, $action, $commercialEmail, $companyId, $endpoint, $isQuestion, $questionIdsNo, $n, $locale = null): void{
237
238        if(!is_null($locale)){
239            $this->locale = $locale;
240        }
241
242        if ($action != 1) {
243            if ($this->locale == 'es') {
244                $action = 'actualizado';
245            } else {
246                $action = 'updated';
247            }
248
249        } else {
250            if ($this->locale == 'es') {
251                $action = 'creado';
252            } else {
253                $action = 'created';
254            }
255        }
256
257        $fendpoint = '';
258
259        if ($endpoint == 'orders') {
260            if ($this->locale == 'es') {
261                $fendpoint = 'presupuesto';
262            } else {
263                $fendpoint = 'budget';
264            }
265
266        } else {
267            if ($this->locale == 'es') {
268                $fendpoint = 'trabajo';
269            } else {
270                $fendpoint = 'job';
271            }
272        }
273
274        $user = TblUsers::where('id', $userId)->first();
275
276        $query = "SELECT
277                    a.approver_id,
278                    a.user_id,
279                    b.name,
280                    b.email
281                FROM
282                    tbl_approvers a
283                    INNER JOIN tbl_users b ON a.user_id = b.id
284                WHERE a.company_id = {$companyId}
285                ";
286
287        if ($n == 3) {
288            $query = "SELECT
289                        a.approver_id,
290                        a.user_id,
291                        u.name,
292                        u.email
293                    FROM tbl_approvers a
294                    INNER JOIN tbl_users u ON a.user_id = u.id
295                    WHERE a.company_id = {$companyId}
296
297                    UNION ALL
298
299                    SELECT
300                        c.approver_id,
301                        c.user_id,
302                        u2.name,
303                        u2.email
304                    FROM tbl_approvers_v2 c
305                    INNER JOIN tbl_users u2 ON c.user_id = u2.id
306                    WHERE c.company_id = {$companyId}";
307        }
308
309        $approvers = DB::select($query);
310
311        if (count($approvers) > 0) {
312            $amount = $this->currency($amount, 1);
313            $minimumOrderSize = $this->currency($minimumOrderSize, 1);
314
315            $budgetType = TblBudgetTypes::where('budget_type_id', $budgetTypeId)->first();
316            $clientType = TblCustomerTypes::where('customer_type_id', $customerTypeId)->first();
317
318            $imgpath = file_get_contents(public_path('fireservicetitan.png'));
319            $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
320
321            $subject = __('language.send_approval_notification.subject');
322            $subject = str_replace('{{type}}', $budgetType->name, $subject);
323            $subject = str_replace('{{amount}}', $amount, $subject);
324            $subject = str_replace('{{endpoint}}', ucfirst($fendpoint), $subject);
325
326            $url = config('app.frontend_url') . "{$endpoint}/{$id}?company_id={$companyId}";
327            $href = "<a href='{$url}'>{$quoteId}</a>";
328            $cc = false;
329            foreach ($approvers as $item) {
330
331                $toEmail = $item->email;
332
333                $body = __('language.send_approval_notification.body_hello');
334                $body = str_replace('{{approver}}', $item->name, $body);
335
336                $body .= __('language.send_approval_notification.body_message');
337                $body = str_replace('{{endpoint}}', $fendpoint, $body);
338                $body = str_replace('{{creator}}', $createdBy, $body);
339                $body = str_replace('{{action}}', $action, $body);
340                $body = str_replace('{{company}}', $companyName, $body);
341                $body = str_replace('{{type}}', $budgetType->name, $body);
342                $body = str_replace('{{amount}}', $amount, $body);
343                $body = str_replace('{{quote_id}}', $href, $body);
344
345                if ($isQuestion == 1) {
346                    $body .= __('language.send_approval_notification.note_question');
347                } else {
348                    $body .= __('language.send_approval_notification.note');
349                }
350
351                $body = str_replace('{{company}}', $companyName, $body);
352                $body = str_replace('{{client_type}}', $clientType->name, $body);
353                $body = str_replace('{{project_type}}', $budgetType->name, $body);
354                $body = str_replace('{{amount}}', $minimumOrderSize, $body);
355
356                if ($isQuestion == 1) {
357                    if ($questionIdsNo) {
358                        $questions = TblWorkflowQuestions::whereIn('question_id', $questionIdsNo)->where('company_id', $companyId)->get();
359
360                        if ($questions->isNotEmpty()) {
361                            $ul = '<ul>';
362                            $li = '';
363                            foreach ($questions as $item) {
364                                $li .= "<li>{$item->question}</li>";
365                            }
366                            $ul .= $li.'</ul><br><br>';
367                            $body .= $ul;
368                        }
369                    }
370                }
371
372                $content = $body;
373
374                $body .= '<p>Fire Service Titan</p>';
375                $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
376
377                $html = '<!DOCTYPE html>';
378                $html .= '<html>';
379                $html .= '<head>';
380                $html .= '<meta charset="UTF-8">';
381                $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
382                $html .= '</head>';
383                $html .= '<body>';
384                $html .= $body;
385                $html .= '</body>';
386                $html .= '</html>';
387
388                if ($toEmail != null) {
389                    $email = new \SendGrid\Mail\Mail;
390
391                    // $user is the commercial/creator. It can be null when the
392                    // quote came from G3W with a commercial name that doesn't
393                    // map to any tbl_users row. Notification still goes out to
394                    // the technical approver; the BCC to the commercial is
395                    // simply skipped when we don't know who they are.
396                    $userEmail = $user?->email;
397
398                    if (config('services.sendgrid.staging') && $userEmail) {
399                        $toEmail = $userEmail;
400                        $item->user_id = $userId;
401                    }
402
403                    if ($cc == false) {
404                        $cc = true;
405                        if ($userEmail && $userEmail != $toEmail) {
406                            if ($userEmail != $commercialEmail && $commercialEmail != null) {
407                                $email->addBcc($userEmail);
408                                $email->addBcc($commercialEmail);
409                            } else {
410                                $email->addBcc($userEmail);
411                            }
412                        } elseif (! $userEmail && $commercialEmail != null && $commercialEmail != $toEmail) {
413                            // No commercial user in FST but G3W gave us a raw
414                            // email — include it so the (unknown-to-FST) seller
415                            // at least sees the approval request.
416                            $email->addBcc($commercialEmail);
417                        }
418                    }
419
420                    $email->setFrom('fire@fire.es', 'Fire Service Titan');
421                    $email->setSubject($subject);
422                    $email->addTo($toEmail);
423                    $email->addContent('text/html', $html);
424
425                    $email->addAttachment(
426                        $imgpath,
427                        'image/png',
428                        'fireservicetitan.png',
429                        'inline',
430                        'fireservicetitan'
431                    );
432
433                    $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
434
435                    $response = $sendgrid->send($email);
436                    if ($response->statusCode() == 202) {
437                        Log::channel('email_log')->info('ID:'.$quoteId.' : '.$toEmail.' - APPROVAL EMAIL NOTIFICATION SENT');
438
439                        $this->addUpdateLog($id, $userId, 'send_approval_notification', null, null, 5);
440
441                        TblNotifications::create(
442                            [
443                                'user_id' => $item->user_id,
444                                'content' => $content,
445                                'is_open' => 1,
446                                'created_by' => 'System',
447                                'link' => $url
448                            ]
449                        );
450                    } else {
451                        $error = true;
452                        Log::channel('email_log')->error('ID:'.$quoteId.' : '.$toEmail.' - '.$response->body());
453                    }
454                }
455
456            }
457        }
458    }
459
460    function send_approval_margin_notification($amount, $budgetTypeId, $customerTypeId, $minimumMargin, string $quoteId, $id, $companyName, $createdBy, $userId, $action, $commercialEmail, $invoiceMargin, $companyId, $endpoint): void{
461
462        if ($action != 1) {
463            if ($this->locale == 'es') {
464                $action = 'actualizado';
465            } else {
466                $action = 'updated';
467            }
468
469        } else {
470            if ($this->locale == 'es') {
471                $action = 'creado';
472            } else {
473                $action = 'created';
474            }
475        }
476
477        $fendpoint = '';
478
479        if ($endpoint == 'orders') {
480            if ($this->locale == 'es') {
481                $fendpoint = 'presupuesto';
482            } else {
483                $fendpoint = 'budget';
484            }
485
486        } else {
487            if ($this->locale == 'es') {
488                $fendpoint = 'trabajo';
489            } else {
490                $fendpoint = 'job';
491            }
492        }
493
494        $invoiceMargin = number_format($invoiceMargin, 2);
495        $minimumMargin = number_format($minimumMargin, 2);
496
497        $user = TblUsers::where('id', $userId)->first();
498
499        $query = "SELECT
500                    a.approver_id,
501                    a.user_id,
502                    b.name,
503                    b.email
504                FROM
505                    tbl_approvers a
506                    INNER JOIN tbl_users b ON a.user_id = b.id
507                WHERE a.company_id = {$companyId}
508                ";
509
510        $approvers = DB::select($query);
511
512        if (count($approvers) > 0) {
513
514            $amount = $this->currency($amount, 1);
515
516            $budgetType = TblBudgetTypes::where('budget_type_id', $budgetTypeId)->first();
517            $clientType = TblCustomerTypes::where('customer_type_id', $customerTypeId)->first();
518
519            $imgpath = file_get_contents(public_path('fireservicetitan.png'));
520            $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
521
522            $subject = __('language.send_approval_margin_notification.subject');
523            $subject = str_replace('{{type}}', $budgetType->name, $subject);
524            $subject = str_replace('{{amount}}', $amount, $subject);
525            $subject = str_replace('{{margin}}', $invoiceMargin, $subject);
526            $subject = str_replace('{{endpoint}}', ucfirst($fendpoint), $subject);
527
528            $url = config('app.frontend_url') . "{$endpoint}/{$id}?company_id={$companyId}";
529            $href = "<a href='{$url}'>{$quoteId}</a>";
530            $cc = false;
531            foreach ($approvers as $item) {
532
533                $toEmail = $item->email;
534
535                $body = __('language.send_approval_margin_notification.body_hello');
536                $body = str_replace('{{approver}}', $item->name, $body);
537
538                $body .= __('language.send_approval_margin_notification.body_message');
539                $body = str_replace('{{endpoint}}', $fendpoint, $body);
540                $body = str_replace('{{creator}}', $createdBy, $body);
541                $body = str_replace('{{action}}', $action, $body);
542                $body = str_replace('{{company}}', $companyName, $body);
543                $body = str_replace('{{type}}', $budgetType->name, $body);
544                $body = str_replace('{{amount}}', $amount, $body);
545                $body = str_replace('{{quote_id}}', $href, $body);
546                $body = str_replace('{{margin}}', $invoiceMargin, $body);
547
548                $body .= __('language.send_approval_margin_notification.note');
549                $body = str_replace('{{company}}', $companyName, $body);
550                $body = str_replace('{{client_type}}', $clientType->name, $body);
551                $body = str_replace('{{project_type}}', $budgetType->name, $body);
552                $body = str_replace('{{margin}}', $minimumMargin, $body);
553
554                $content = $body;
555
556                $body .= '<p>Fire Service Titan</p>';
557                $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
558
559                $html = '<!DOCTYPE html>';
560                $html .= '<html>';
561                $html .= '<head>';
562                $html .= '<meta charset="UTF-8">';
563                $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
564                $html .= '</head>';
565                $html .= '<body>';
566                $html .= $body;
567                $html .= '</body>';
568                $html .= '</html>';
569
570                if ($toEmail != null) {
571                    $email = new \SendGrid\Mail\Mail;
572
573                    if(config('services.sendgrid.staging')){
574                        $toEmail = $user->email;
575                        $item->user_id = $userId;
576                    }
577
578                    if ($cc == false) {
579                        $cc = true;
580
581                        if ($user->email != $toEmail) {
582                            if ($user->email != $commercialEmail && $commercialEmail != null) {
583                                $email->addBcc($user->email);
584                                $email->addBcc($commercialEmail);
585                            } else {
586                                $email->addBcc($user->email);
587                            }
588                        }
589                    }
590
591                    $email->setFrom('fire@fire.es', 'Fire Service Titan');
592                    $email->setSubject($subject);
593                    $email->addTo($toEmail);
594                    $email->addContent('text/html', $html);
595
596                    $email->addAttachment(
597                        $imgpath,
598                        'image/png',
599                        'fireservicetitan.png',
600                        'inline',
601                        'fireservicetitan'
602                    );
603
604                    $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
605
606                    $response = $sendgrid->send($email);
607                    if ($response->statusCode() == 202) {
608                        Log::channel('email_log')->info('ID:'.$quoteId.' : '.$toEmail.' - MARGIN APPROVAL EMAIL NOTIFICATION SENT');
609
610                        $this->addUpdateLog($quoteId, $userId, 'send_approval_margin_notification', null, null, 5);
611
612                        TblNotifications::create(
613                            [
614                                'user_id' => $item->user_id,
615                                'content' => $content,
616                                'is_open' => 1,
617                                'created_by' => 'System',
618                                'link' => $url
619                            ]
620                        );
621                    } else {
622                        $error = true;
623                        Log::channel('email_log')->error('ID:'.$quoteId.' : '.$toEmail.' - '.$response->body());
624                    }
625                }
626
627            }
628        }
629    }
630
631    function approve_quotation($id): ResponseFactory|HttpResponse{
632
633        try {
634
635            $id = addslashes((string) $id);
636
637            $result = TblQuotations::where('id', $id)->first();
638            $company = TblCompanies::where('company_id', $result->company_id)->first();
639            $budgetType = TblBudgetTypes::where('budget_type_id', $result->budget_type_id)->first();
640
641            if($result->created_by != $result->commercial){
642                $creatorAndCommercial = [$result->created_by, $result->commercial];
643                foreach ($creatorAndCommercial as $name) {
644                    $user = TblUsers::where('name', $name)->first();
645                    if ($user) {
646                        $this->send_approved_notification($user->id, $user->name, $user->email, $company->name, $budgetType->name, $result->amount, $id, $result->quote_id, $company->company_id, 'orders');
647                    }
648                }
649            } else {
650                $user = TblUsers::where('name', $result->created_by)->first();
651                if ($user) {
652                    $this->send_approved_notification($user->id, $user->name, $user->email, $company->name, $budgetType->name, $result->amount, $id, $result->quote_id, $company->company_id, 'orders');
653                }
654            }
655
656            if ($result->for_approval == 3) {
657                $result = TblQuotations::where('id', $id)->first();
658
659                $approved = 0;
660                $rejected = 0;
661
662                if ($result->approved_by !== null) {
663                    $approved++;
664                }
665                if ($result->approved_by_v2 !== null) {
666                    $approved++;
667                }
668
669                if ($result->rejected_by !== null) {
670                    $rejected++;
671                }
672                if ($result->rejected_by_v2 !== null) {
673                    $rejected++;
674                }
675
676                if ($approved === 2) {
677                    TblQuotations::where('id', $id)->update(['for_approval' => null]);
678                } elseif ($rejected >= 1 && ($approved + $rejected) === 2) {
679                    TblQuotations::where('id', $id)->update(['for_approval' => null]);
680                }
681            } elseif ($result->for_approval == 1) {
682                TblQuotations::where('id', $id)->update(['for_approval' => null]);
683            }
684
685            $this->addUpdateLog($id, $user->id, 'approve_quotation', null, null, 5);
686
687            Cache::flush();
688
689            return response(['message' => 'OK']);
690
691        } catch (\Exception $e) {
692            report(AppException::fromException($e, 'APPROVE_QUOTATION_EXCEPTION'));
693            return response(['message' => 'KO', 'error' => $e->getMessage()]);
694        }
695
696    }
697
698    function send_approved_notification($userId, $username, $email, $companyName, $budgetType, $amount, $id, string $quoteId, $companyId, $endpoint): void{
699
700        $fendpoint = '';
701
702        if ($endpoint == 'orders') {
703            if ($this->locale == 'es') {
704                $fendpoint = 'presupuesto';
705            } else {
706                $fendpoint = 'budget';
707            }
708
709        } else {
710            if ($this->locale == 'es') {
711                $fendpoint = 'trabajo';
712            } else {
713                $fendpoint = 'job';
714            }
715        }
716
717        $query = "SELECT
718                    u.id AS user_id,
719                    u.name,
720                    u.email,
721                    u.sender_email,
722                    CASE
723                        WHEN a.user_id IS NOT NULL AND c.user_id IS NOT NULL THEN 'both'
724                        WHEN a.user_id IS NOT NULL THEN 'approvers'
725                        WHEN c.user_id IS NOT NULL THEN 'approvers_v2'
726                        ELSE 'none'
727                    END AS exists_in
728                FROM tbl_users u
729                LEFT JOIN tbl_approvers a
730                    ON u.id = a.user_id AND a.company_id = {$companyId}
731                LEFT JOIN tbl_approvers_v2 c
732                    ON u.id = c.user_id AND c.company_id = {$companyId}
733                WHERE u.id = {$this->userId}";
734
735        $user = DB::select($query);
736
737        $user = $user[0] ?? null;
738
739        $toEmail = $email;
740
741        $amount = $this->currency($amount, 1);
742
743        $imgpath = file_get_contents(public_path('fireservicetitan.png'));
744        $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
745
746        $url = config('app.frontend_url') . "{$endpoint}/{$id}?company_id={$companyId}";
747        $href = "<a href='{$url}'>{$quoteId}</a>";
748
749        $body = __('language.send_approved_notification.body_hello');
750        $body = str_replace('{{creator}}', $username, $body);
751
752        $body .= __('language.send_approved_notification.body_message');
753        $body = str_replace('{{approver}}', $user->name, $body);
754        $body = str_replace('{{company}}', $companyName, $body);
755        $body = str_replace('{{type}}', $budgetType, $body);
756        $body = str_replace('{{amount}}', $amount, $body);
757        $body = str_replace('{{quote_id}}', $href, $body);
758        $body = str_replace('{{endpoint}}', $fendpoint, $body);
759
760        $content = $body;
761
762        $body .= '<p>Fire Service Titan</p>';
763        $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
764
765        $html = '<!DOCTYPE html>';
766        $html .= '<html>';
767        $html .= '<head>';
768        $html .= '<meta charset="UTF-8">';
769        $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
770        $html .= '</head>';
771        $html .= '<body>';
772        $html .= $body;
773        $html .= '</body>';
774        $html .= '</html>';
775
776        $subject = __('language.send_approved_notification.subject');
777        $subject = str_replace('{{quote_id}}', $quoteId, $subject);
778        $subject = str_replace('{{endpoint}}', ucfirst($fendpoint), $subject);
779
780        if ($toEmail != null) {
781            $email = new \SendGrid\Mail\Mail;
782
783            if(config('services.sendgrid.staging')){
784                $toEmail = $user->email;
785                $userId = $this->userId;
786            }
787
788            $email->setFrom('fire@fire.es', 'Fire Service Titan');
789            $email->setSubject($subject);
790            $email->addTo($toEmail);
791            $email->addContent('text/html', $html);
792
793            $email->addAttachment(
794                $imgpath,
795                'image/png',
796                'fireservicetitan.png',
797                'inline',
798                'fireservicetitan'
799            );
800
801            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
802
803            $response = $sendgrid->send($email);
804            if ($response->statusCode() == 202) {
805                Log::channel('email_log')->info('ID:'.$quoteId.' : '.$toEmail.' - APPROVED EMAIL NOTIFICATION SENT');
806                $this->addUpdateLog($id, $userId, 'send_approved_notification', null, null, 5);
807
808                if ($endpoint == 'orders') {
809                    if ($user->exists_in == 'approvers') {
810                        TblQuotations::where('id', $id)->update(
811                            [
812                                'approved_at' => date('Y-m-d H:i:s'),
813                                'approved_by' => $user->name
814                            ]
815                        );
816                    } elseif ($user->exists_in == 'approvers_v2') {
817                        TblQuotations::where('id', $id)->update(
818                            [
819                                'approved_at_v2' => date('Y-m-d H:i:s'),
820                                'approved_by_v2' => $user->name
821                            ]
822                        );
823                    }
824                } else {
825                    TblOngoingJobs::where('id', $id)->update(
826                        [
827                            'approved_at' => date('Y-m-d H:i:s'),
828                            'approved_by' => $user->name
829                        ]
830                    );
831                }
832
833                TblNotifications::create(
834                    [
835                        'user_id' => $userId,
836                        'content' => $content,
837                        'is_open' => 1,
838                        'created_by' => 'System',
839                        'link' => $url
840                    ]
841                );
842            } else {
843                $error = true;
844                Log::channel('email_log')->error('ID:'.$quoteId.' : '.$toEmail.' - '.$response->body());
845            }
846        }
847
848    }
849
850    function reject_quotation($id): ResponseFactory|HttpResponse{
851
852        try {
853
854            $id = addslashes((string) $id);
855
856            $result = TblQuotations::where('id', $id)->first();
857            $company = TblCompanies::where('company_id', $result->company_id)->first();
858            $budgetType = TblBudgetTypes::where('budget_type_id', $result->budget_type_id)->first();
859
860            if($result->created_by != $result->commercial){
861                $creatorAndCommercial = [$result->created_by, $result->commercial];
862                foreach ($creatorAndCommercial as $name) {
863                    $user = TblUsers::where('name', $name)->first();
864                    if ($user) {
865                        $this->send_rejected_notification($user->id, $user->name, $user->email, $company->name, $budgetType->name, $result->amount, $id, $result->quote_id, $company->company_id, 'orders');
866                    }
867                }
868            } else {
869                $user = TblUsers::where('name', $result->created_by)->first();
870                if ($user) {
871                    $this->send_rejected_notification($user->id, $user->name, $user->email, $company->name, $budgetType->name, $result->amount, $id, $result->quote_id, $company->company_id, 'orders');
872                }
873            }
874
875            if ($result->for_approval == 3) {
876                $result = TblQuotations::where('id', $id)->first();
877
878                $approved = 0;
879                $rejected = 0;
880
881                if ($result->approved_by !== null) {
882                    $approved++;
883                }
884                if ($result->approved_by_v2 !== null) {
885                    $approved++;
886                }
887
888                if ($result->rejected_by !== null) {
889                    $rejected++;
890                }
891                if ($result->rejected_by_v2 !== null) {
892                    $rejected++;
893                }
894
895                if ($approved === 2) {
896                    TblQuotations::where('id', $id)->update(['for_approval' => null]);
897                } elseif ($rejected >= 1 && ($approved + $rejected) === 2) {
898                    TblQuotations::where('id', $id)->update(['for_approval' => null]);
899                }
900            } elseif ($result->for_approval == 1) {
901                TblQuotations::where('id', $id)->update(['for_approval' => null]);
902            }
903
904            $this->addUpdateLog($id, $user->id, 'reject_quotation', null, null, 5);
905
906            Cache::flush();
907
908            return response(['message' => 'OK']);
909
910        } catch (\Exception $e) {
911            report(AppException::fromException($e, 'REJECT_QUOTATION_EXCEPTION'));
912            return response(['message' => 'KO', 'error' => $e->getMessage()]);
913        }
914
915    }
916
917    function send_rejected_notification($userId, $username, $email, $companyName, $budgetType, $amount, $id, string $quoteId, $companyId, $endpoint): void{
918
919        $fendpoint = '';
920
921        if ($endpoint == 'orders') {
922            if ($this->locale == 'es') {
923                $fendpoint = 'presupuesto';
924            } else {
925                $fendpoint = 'budget';
926            }
927
928        } else {
929            if ($this->locale == 'es') {
930                $fendpoint = 'trabajo';
931            } else {
932                $fendpoint = 'job';
933            }
934        }
935
936        $query = "SELECT
937                    u.id AS user_id,
938                    u.name,
939                    u.email,
940                    u.sender_email,
941                    CASE
942                        WHEN a.user_id IS NOT NULL AND c.user_id IS NOT NULL THEN 'both'
943                        WHEN a.user_id IS NOT NULL THEN 'approvers'
944                        WHEN c.user_id IS NOT NULL THEN 'approvers_v2'
945                        ELSE 'none'
946                    END AS exists_in
947                FROM tbl_users u
948                LEFT JOIN tbl_approvers a
949                    ON u.id = a.user_id AND a.company_id = {$companyId}
950                LEFT JOIN tbl_approvers_v2 c
951                    ON u.id = c.user_id AND c.company_id = {$companyId}
952                WHERE u.id = {$this->userId}";
953
954        $user = DB::select($query);
955
956        $user = $user[0] ?? null;
957
958        $toEmail = $email;
959        $amount = $this->currency($amount, 1);
960
961        $imgpath = file_get_contents(public_path('fireservicetitan.png'));
962        $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
963
964        $url = config('app.frontend_url') . "{$endpoint}/{$id}?company_id={$companyId}";
965        $href = "<a href='{$url}'>{$quoteId}</a>";
966
967        $body = __('language.send_rejected_notification.body_hello');
968        $body = str_replace('{{creator}}', $username, $body);
969
970        $body .= __('language.send_rejected_notification.body_message');
971        $body = str_replace('{{approver}}', $user->name, $body);
972        $body = str_replace('{{company}}', $companyName, $body);
973        $body = str_replace('{{type}}', $budgetType, $body);
974        $body = str_replace('{{amount}}', $amount, $body);
975        $body = str_replace('{{quote_id}}', $href, $body);
976        $body = str_replace('{{endpoint}}', $fendpoint, $body);
977
978        $content = $body;
979
980        $body .= '<p>Fire Service Titan</p>';
981        $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
982
983        $html = '<!DOCTYPE html>';
984        $html .= '<html>';
985        $html .= '<head>';
986        $html .= '<meta charset="UTF-8">';
987        $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
988        $html .= '</head>';
989        $html .= '<body>';
990        $html .= $body;
991        $html .= '</body>';
992        $html .= '</html>';
993
994        $subject = __('language.send_rejected_notification.subject');
995        $subject = str_replace('{{quote_id}}', $quoteId, $subject);
996        $subject = str_replace('{{endpoint}}', ucfirst($fendpoint), $subject);
997
998        if ($toEmail != null) {
999            $email = new \SendGrid\Mail\Mail;
1000
1001            if(config('services.sendgrid.staging')){
1002                $toEmail = $user->email;
1003                $userId = $this->userId;
1004            }
1005
1006            $email->setFrom('fire@fire.es', 'Fire Service Titan');
1007            $email->setSubject($subject);
1008            $email->addTo($toEmail);
1009            $email->addContent('text/html', $html);
1010
1011            $email->addAttachment(
1012                $imgpath,
1013                'image/png',
1014                'fireservicetitan.png',
1015                'inline',
1016                'fireservicetitan'
1017            );
1018
1019            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
1020
1021            $response = $sendgrid->send($email);
1022            if ($response->statusCode() == 202) {
1023                Log::channel('email_log')->info('ID:'.$quoteId.' : '.$toEmail.' - REJECTED EMAIL NOTIFICATION SENT');
1024                $this->addUpdateLog($id, $userId, 'send_rejected_notification', null, null, 5);
1025
1026                if ($endpoint == 'orders') {
1027
1028                    if ($user->exists_in == 'approvers') {
1029                        TblQuotations::where('id', $id)->update(
1030                            [
1031                                'rejected_at' => date('Y-m-d H:i:s'),
1032                                'rejected_by' => $user->name
1033                            ]
1034                        );
1035                    } elseif ($user->exists_in == 'approvers_v2') {
1036                        TblQuotations::where('id', $id)->update(
1037                            [
1038                                'rejected_at_v2' => date('Y-m-d H:i:s'),
1039                                'rejected_by_v2' => $user->name
1040                            ]
1041                        );
1042                    }
1043                } else {
1044                    TblOngoingJobs::where('id', $id)->update(
1045                        [
1046                            'rejected_at' => date('Y-m-d H:i:s'),
1047                            'rejected_by' => $user->name
1048                        ]
1049                    );
1050                }
1051
1052                TblNotifications::create(
1053                    [
1054                        'user_id' => $userId,
1055                        'content' => $content,
1056                        'is_open' => 1,
1057                        'created_by' => 'System',
1058                        'link' => $url
1059                    ]
1060                );
1061            } else {
1062                $error = true;
1063                Log::channel('email_log')->error('ID:'.$quoteId.' : '.$toEmail.' - '.$response->body());
1064            }
1065        }
1066
1067    }
1068
1069    public function update_quotation(Request $request, $id): ResponseFactory|HttpResponse{
1070        $approvalMinimumOrderSize = null;
1071        $approvalIsQuestion = null;
1072        $approvalQuestionIdsNo = null;
1073        $approvalN = null;
1074        $approvalMinimumMargin = null;
1075        $approvalId = null;
1076        $approvalForAdd = null;
1077        $approvalInvoiceMargin = null;
1078        $sendApprovalNotification = false;
1079        $sendApprovalMarginNotification = false;
1080        $needToSendReminder = false;
1081
1082        // try {
1083
1084        $data = $request->all();
1085        $id = addslashes((string) $id);
1086        $userId = addslashes((string) $data['user_id']);
1087        if (! TblQuotationsLog::where('quotation_id', $id)->exists()) {
1088            $categoryLog = $data['budget_status_id'] == 6 ? 0 : 2;
1089            $this->addUpdateLog($id, $data['user_id'], 'create_quotation', null, null, $categoryLog);
1090        }
1091        $forApproval = null;
1092        unset($data['user_id']);
1093
1094        $r = ['amount', 'order_number', 'budget_type_id', 'acceptance_date'];
1095        $job = [];
1096
1097        foreach ($data as $key => $value) {
1098            if ($value == 'null') {
1099                $data[$key] = null;
1100            }
1101
1102            if (in_array($key, $r)) {
1103                $job[$key] = $value;
1104            }
1105        }
1106
1107        $files = $request->file('files');
1108        unset($data['files']);
1109
1110        $internalFiles = $request->file('internal_files');
1111        unset($data['internal_files']);
1112
1113        $query = '
1114            SELECT
1115                SUM(CASE WHEN is_internal IS NULL THEN 1 ELSE 0 END) as external_count,
1116                SUM(CASE WHEN is_internal = 1 THEN 1 ELSE 0 END) as internal_count
1117            FROM tbl_files
1118            WHERE quotation_id = ?';
1119
1120        $counts = DB::select($query, [$id]);
1121        $fileCount = $counts[0]->external_count;
1122        $internalFileCount = $counts[0]->internal_count;
1123
1124        if ($fileCount > 0 || ! empty($files)) {
1125            $data['has_attachment'] = 1;
1126        }
1127
1128        if ($files) {
1129            $totalFiles = $fileCount + count($files);
1130            if ($totalFiles > 10) {
1131                return response(['message' => 'KO', 'error' => __('language.file_count_exceeded')]);
1132            }
1133        }
1134
1135        if ($internalFileCount > 0 || ! empty($internalFiles)) {
1136            $data['has_attachment'] = 1;
1137        }
1138
1139        if ($internalFiles) {
1140            $totalInternalFileCount = $internalFileCount + count($internalFiles);
1141            if ($totalInternalFileCount > 10) {
1142                return response(['message' => 'KO', 'error' => __('language.file_count_exceeded')]);
1143            }
1144        }
1145
1146        if (isset($data['request_date']) && isset($data['issue_date'])) {
1147            $requestDate = strtotime($data['request_date']);
1148            $issueDate = strtotime($data['issue_date']);
1149            $dateDiff = $issueDate - $requestDate;
1150            $data['duration'] = round($dateDiff / (60 * 60 * 24));
1151        }
1152
1153        $result = TblQuotations::where('id', $id)->first();
1154
1155        // FIRE-982 follow-up: the manual-create flow can leave the
1156        // frontend with a stub id that has already been deleted (either
1157        // by the duplicate-redirect retarget below, or by a separate
1158        // cleanup). Crashing on a null `$result` here surfaces as an
1159        // unhandled "Attempt to read property quote_id on null" instead
1160        // of a recoverable error the UI can show, so guard the lookup.
1161        if (!$result) {
1162            return response([
1163                'message' => 'KO',
1164                'error' => 'quotation_not_found',
1165                'id' => $id,
1166            ], 404);
1167        }
1168
1169        if ($result->quote_id != $data['quote_id']) {
1170
1171            // Look up the OTHER row that owns this quote_id (excluding the
1172            // row being updated).
1173            $existing = TblQuotations::where('quote_id', (string) $data['quote_id'])
1174                ->where('company_id', $result->company_id)
1175                ->where('id', '<>', $id)
1176                ->first();
1177
1178            if ($existing) {
1179
1180                if ($result->for_add == 1) {
1181                    // Manual-create flow: the frontend opened the "Crear
1182                    // manualmente" form and spawned a stub row; the user
1183                    // typed a quote_id that already belongs to another
1184                    // record. Treat the submission as an edit of that
1185                    // existing record — delete the orphan stub and
1186                    // retarget the update so the rest of this function
1187                    // applies the form data to `$existing`.
1188                    //
1189                    // The manual-create form sends `null` (or the literal
1190                    // string "null") for fields it doesn't expose —
1191                    // `internal_quote_id`, `box_work_g3w`, `order_number`,
1192                    // and friends. Mass-assigning those to a G3W-imported
1193                    // existing row would silently nuke its sync metadata,
1194                    // so drop empty submitted values when the existing row
1195                    // already has a value there. Real edits (e.g. user
1196                    // typed a new client name) still go through; only
1197                    // form-blank-vs-existing-populated keys are skipped.
1198                    foreach ($data as $key => $value) {
1199                        $isFormBlank = $value === null
1200                            || $value === ''
1201                            || $value === 'null';
1202                        if ($isFormBlank && !is_null($existing->$key) && $existing->$key !== '') {
1203                            unset($data[$key]);
1204                        }
1205                    }
1206
1207                    TblQuotations::where('id', $id)->delete();
1208                    $id = $existing->id;
1209                    $result = $existing;
1210                    // fall through into the normal update path below
1211                } else {
1212                    // Non-stub edit: a real quotation is trying to rename
1213                    // its quote_id to one already owned by another row.
1214                    // Silently merging here would clobber the other row's
1215                    // data, so we keep the original protection — return an
1216                    // error and suggest the next free number.
1217                    $latestBudget = TblQuotations::where('company_id', $result->company_id)
1218                        ->orderByRaw('CAST(quote_id AS DOUBLE) DESC')
1219                        ->first();
1220
1221                    $number = $latestBudget->quote_id;
1222                    $x = true;
1223
1224                    while ($x) {
1225
1226                        if (is_numeric(substr((string) $number, -1))) {
1227                            $number++;
1228                        } else {
1229                            $number .= "1";
1230                        }
1231
1232                        $check = TblQuotations::where('company_id', $result->company_id)
1233                            ->where('quote_id', (string) $number)
1234                            ->count();
1235
1236                        if ($check == 0) {
1237                            $x = false;
1238                        }
1239                    }
1240
1241                    return response([
1242                        'message' => 'KO',
1243                        'error' => 'quote_exists',
1244                        'number' => $number,
1245                    ]);
1246                }
1247            }
1248        }
1249
1250        $action = 0;
1251        if ($result->created_by == null || $result->for_add == 1) {
1252            $action = 1;
1253            $data['created_by'] = $data['updated_by'];
1254            $data['for_add'] = 0;
1255        }
1256
1257        $company = TblCompanies::where('company_id', $result->company_id)->first();
1258        $commercial = TblUsers::where('name', $data['commercial'])->first();
1259        $status = TblBudgetStatus::where('budget_status_id', $data['budget_status_id'])->first();
1260
1261        // $checkQuotation = TblQuotations::where(function($query) use ($data, $result) {
1262        //     $query->where('internal_quote_id', $data['internal_quote_id'])
1263        //         ->orWhere('internal_quote_id', 'O-25/'.$data['internal_quote_id']);
1264        // })
1265        //     ->where('company_id', $result->company_id)
1266        //     ->first();
1267
1268        // if($checkQuotation) {
1269        //     $url = "orders/" . $checkQuotation->id . "?company_id=" . $checkQuotation->company_id;
1270        //     return response([
1271        //         'message' => 'KO',
1272        //         'error' => "Presupuesto ya creado. Puedes verlo <a href='$url' target='_blank'>aquí</a>."
1273        //     ]);
1274        // }
1275
1276        $limitReminderEmails = $company->limit_reminder_emails ?? 3;
1277
1278        if ($result->total_sent == $limitReminderEmails) {
1279            $data['total_sent'] = 0;
1280        }
1281
1282        if ($result->budget_status_id != $data['budget_status_id'] || $result->commercial != $data['commercial']) {
1283            if ($data['budget_status_id'] == 12) {
1284                if ($company && $commercial) {
1285                    $inProgressCount = TblQuotations::where('budget_status_id', 12)->where('company_id', $result->company_id)->where('commercial', $data['commercial'])->count();
1286                    if ($company->process_limit <= $inProgressCount) {
1287                        return response(['message' => 'KO', 'error' => 'in_progress', 'limit' => $company->process_limit]);
1288                    }
1289                } else {
1290                    return response(['message' => 'KO', 'error' => 'in_progress', 'limit' => 0]);
1291                }
1292            }
1293        }
1294
1295        $sendNotification = false;
1296        if ($result->commercial != $data['commercial']) {
1297            if (! empty($commercial)) {
1298                $createdByX = ($result->created_by == null) ? $data['created_by'] : $result->created_by;
1299                if ($createdByX != $data['commercial']) {
1300                    $action = 0;
1301                    $sendNotification = true;
1302                }
1303            }
1304        }
1305
1306        if (isset($data['amount']) || isset($data['budget_type_id']) || isset($data['customer_type_id']) || isset($data['invoice_margin']) || isset($data['question_enabled'])) {
1307            if ($company) {
1308
1309                $n = 0;
1310                $invoiceMargin = 0;
1311                $minimumMargin = 0;
1312                $minimumOrderSize = 0;
1313                // $project is only assigned in the "financial fields changed"
1314                // branch below. When the user edits e.g. just the email,
1315                // none of those fields move and $project stays unset — PHP 8
1316                // then throws "Undefined variable" on line ~1312. Seed it so
1317                // the later `if ($project)` short-circuits safely.
1318                $project = null;
1319
1320                if($result->amount != $data['amount'] ||
1321                    $result->budget_type_id != $data['budget_type_id'] ||
1322                    $result->customer_type_id != $data['customer_type_id'] ||
1323                    $result->invoice_margin != $data['invoice_margin']
1324                ){
1325                    $project = TblProjectTypes::where('company_id', $company->company_id)->where('budget_type_id', $data['budget_type_id'])->first();
1326                    $customerTypeIds = [];
1327
1328                    if($project){
1329                        if(!empty($project->customer_type_ids)){
1330                            $customerTypeIds = array_map(intval(...), explode(',', (string) $project->customer_type_ids));
1331                        }
1332                        if($project->minimum_order_size != null && in_array($data['customer_type_id'], $customerTypeIds)){
1333                            if($data['amount'] >= $project->minimum_order_size){
1334                                $data['for_approval'] = 1;
1335                                $n = 1;
1336                            }
1337                        }
1338                        $minimumOrderSize = $project->minimum_order_size;
1339
1340                        if ($n == 1 && $project->minimum_order_size_v2 != null && in_array($data['customer_type_id'], $customerTypeIds)) {
1341                            if ($data['amount'] >= $project->minimum_order_size_v2) {
1342                                $data['for_approval'] = 3;
1343                                $n = 3;
1344                            }
1345                        }
1346                    }
1347
1348                } else {
1349                    if (! empty($company->customer_type_ids)) {
1350                        $customerTypeIds = array_map(intval(...), explode(',', (string) $company->customer_type_ids));
1351                    }
1352                    if ($company->minimum_order_size != null && in_array($data['customer_type_id'], $customerTypeIds)) {
1353                        if ($data['amount'] >= $company->minimum_order_size) {
1354                            $data['for_approval'] = 1;
1355                            $n = 1;
1356                        }
1357                        $minimumOrderSize = $company->minimum_order_size;
1358
1359                        if ($n == 1 && $company->minimum_order_size_v2 != null && in_array($data['customer_type_id'], $customerTypeIds)) {
1360                            if ($data['amount'] >= $company->minimum_order_size_v2) {
1361                                $data['for_approval'] = 3;
1362                                $n = 3;
1363                            }
1364                        }
1365                    }
1366                }
1367
1368                if ($data['budget_margin_enabled'] > 0) {
1369                    $costOfLabor = $data['cost_of_labor'];
1370                    $totalCostOfJob = $data['total_cost_of_job'];
1371
1372                    if ($totalCostOfJob > 0) {
1373                        $invoiceMargin = $data['invoice_margin'] ?? 0;
1374                    }
1375
1376                    $minimumMargin = $company->minimum_margin;
1377                    if (! empty($company->customer_type_ids)) {
1378                        $customerTypeIds = array_map(intval(...), explode(',', (string) $company->customer_type_ids));
1379                    }
1380
1381                    if ($project) {
1382                        $minimumMargin = $project->minimum_margin;
1383                        $minimumOrderSize = $project->minimum_order_size;
1384                        if (! empty($project->customer_type_ids)) {
1385                            $customerTypeIds = array_map(intval(...), explode(',', (string) $project->customer_type_ids));
1386                        }
1387                    }
1388
1389                    if ($invoiceMargin < $minimumMargin && $invoiceMargin != null && $invoiceMargin != 0) {
1390                        if (in_array($data['customer_type_id'], $customerTypeIds)) {
1391                            $data['for_approval'] = 1;
1392                            $n = 2;
1393                        }
1394                    }
1395                }
1396
1397                $isQuestion = 0;
1398                $questionIdsNo = [];
1399                if (isset($data['question_enabled'])) {
1400                    if ($data['question_ids_no'] != $result->question_ids_no
1401                        || $data['budget_type_id'] != $result->budget_type_id
1402                        || $data['customer_type_id'] != $result->customer_type_id
1403                        || $data['amount'] != $result->amount) {
1404                        if ($data['question_enabled'] > 0 && $n == 0) {
1405                            if (! empty($data['question_ids_no'])) {
1406                                $questionIdsNo = array_map(intval(...), explode(",", (string) $data['question_ids_no']));
1407
1408                                if ($company->workflow_budget_size != null) {
1409                                    if ($data['amount'] >= $company->workflow_budget_size) {
1410                                        $isQuestion = 1;
1411                                        $data['for_approval'] = 1;
1412                                        $n = 1;
1413                                    }
1414                                }
1415                                $minimumOrderSize = $company->workflow_budget_size;
1416
1417                            }
1418                        }
1419                    }
1420                }
1421
1422                if (($n == 1 || $n == 3) && $result->for_approval != $n) {
1423                    $sendApprovalNotification = true;
1424                    $approvalMinimumOrderSize = $minimumOrderSize;
1425                    $approvalId = $result->id;
1426                    $approvalForAdd = $result->for_add;
1427                    $approvalIsQuestion = $isQuestion;
1428                    $approvalQuestionIdsNo = $questionIdsNo;
1429                    $approvalN = $n;
1430                }
1431
1432                if ($n == 2) {
1433                    $sendApprovalMarginNotification = true;
1434                    $approvalMinimumMargin = $minimumMargin;
1435                    $approvalId = $result->id;
1436                    $approvalForAdd = $result->for_add;
1437                    $approvalInvoiceMargin = $invoiceMargin;
1438                }
1439            }
1440        }
1441
1442        $data['updated_at'] = date('Y-m-d H:i:s');
1443        $job['updated_at'] = $data['updated_at'];
1444
1445        $data['g3w_warning'] = 0;
1446
1447        if ($result->for_add == 1) {
1448            TblCompanies::where('company_id', $result->company_id)->update(['last_id' => $data['quote_id'], 'before_last_id' => $data['quote_id']]);
1449        }
1450
1451        $forApproval = @$data['for_approval'] ?? null;
1452
1453        if ($forApproval != null) {
1454            $data['approved_at'] = null;
1455            $data['approved_by'] = null;
1456            $data['rejected_at'] = null;
1457            $data['rejected_by'] = null;
1458            $data['approved_at_v2'] = null;
1459            $data['approved_by_v2'] = null;
1460            $data['rejected_at_v2'] = null;
1461            $data['rejected_by_v2'] = null;
1462        }
1463
1464        $actualQuotationValue = TblQuotations::where('id', $id)->first();
1465
1466        $differences = $this->compareArrays($data, $actualQuotationValue);
1467        $primaryAprovalsFields = ['budget_type_id', 'customer_type_id', 'budget_margin_enabled', 'amount', 'invoice_margin', 'margin_for_the_company', 'margin_on_invoice_per_day_per_worker', 'gross_margin'];
1468
1469        if (is_null($actualQuotationValue->customer_type_id)) {
1470            $needToSendReminder = true;
1471        }
1472
1473        foreach ($differences as $field => $value) {
1474            if (in_array($field, $primaryAprovalsFields)) {
1475                $needToSendReminder = true;
1476            }
1477            $statusId = $data['budget_status_id'] ?? $actualQuotationValue->budget_status_id;
1478            $categoryLog = $statusId == 6 ? 1 : 4;
1479            $this->addUpdateLog($id, $userId, $field, $value['oldData'], $value['newData'], $categoryLog);
1480        }
1481
1482        // check if the quotation are a solicitud and the user write the internal quote id
1483
1484        $budgetRequest = TblQuotations::where('id', $id)->first();
1485
1486        if (
1487            ($budgetRequest->budget_status_id == 6 || $budgetRequest->budget_status_id == 8)
1488            && (! is_null($data['internal_quote_id'] ?? null) && $data['internal_quote_id'] !== '')
1489            && (TblQuotations::where('internal_quote_id', $data['internal_quote_id'])
1490                ->where('company_id', $company->company_id)
1491                ->whereNotIn('budget_status_id', [6, 8])
1492                ->exists())
1493            && ($id !== '')
1494        ) {
1495            $createdAt = $budgetRequest->created_at;
1496            $lastFollowUpComment = $budgetRequest->last_follow_up_comment;
1497
1498            TblQuotations::where('id', $id)->first()->update(['internal_quote_id', $data['internal_quote_id']]);
1499
1500            $solicitudLogs = TblQuotationsLog::where('quotation_id', $id)->get();
1501
1502            $budget = TblQuotations::where('internal_quote_id', $data['internal_quote_id'])->where('company_id', $company->company_id)->first();
1503            TblFiles::where('quotation_id', $id)->where('is_internal', 1)->update(['quotation_id' => $budget->id]);
1504
1505            $this->callDeleteQuotation($id, $company->company_id, $userId, $data['updated_by']);
1506
1507            $id = $budget->id;
1508
1509            $dataToChange = array_intersect_key($data, array_flip([
1510                'internal_quote_id',
1511                'client',
1512                'phone_number',
1513                'email',
1514                'source_id',
1515                'created_by',
1516                'updated_by',
1517                'updated_at',
1518                'request_date',
1519                'last_follow_up_comment',
1520            ]));
1521
1522            if ($data['phone_number'] === null || $data['phone_number'] === '' || empty($dataToChange['phone_number'])) {
1523                unset($dataToChange['phone_number']);
1524            }
1525
1526            //check if the quotation are a solicitud and the user write the internal quote id
1527
1528            $budgetRequest = TblQuotations::where('id', $id)->first();
1529
1530            if(
1531                ($budgetRequest->budget_status_id == 6 || $budgetRequest->budget_status_id == 8)
1532                && (!is_null($data["internal_quote_id"] ?? null) && $data["internal_quote_id"] !== "")
1533                && TblQuotations::where('internal_quote_id', $data["internal_quote_id"])->where("company_id", $company->company_id)->exists()
1534                && (!is_null($id) && $id !== "")
1535            ){
1536                $createdAt = $budgetRequest->created_at;
1537                $lastFollowUpComment = $budgetRequest->last_follow_up_comment;
1538
1539                TblQuotations::where('id', $id)->first()->update(["internal_quote_id", $data["internal_quote_id"]]);
1540
1541                $solicitudLogs = TblQuotationsLog::where("quotation_id", $id)->get();
1542
1543                $budget = TblQuotations::where('internal_quote_id', $data["internal_quote_id"])->where("company_id", $company->company_id)->first();
1544                TblFiles::where('quotation_id', $id)->where('is_internal', 1)->update(['quotation_id' => $budget->id]);
1545
1546                $this->callDeleteQuotation($id, $company->company_id, $userId, $data['updated_by']);
1547
1548                $id = $budget->id;
1549
1550                $dataToChange = array_intersect_key($data, array_flip([
1551                    'internal_quote_id',
1552                    'client',
1553                    'phone_number',
1554                    'email',
1555                    'source_id',
1556                    'created_by',
1557                    'updated_by',
1558                    'updated_at',
1559                    'request_date',
1560                    'last_follow_up_comment'
1561                ]));
1562
1563                if($data["phone_number"] === null || $data["phone_number"] === "" || empty($dataToChange["phone_number"])){
1564                    unset($dataToChange['phone_number']);
1565                }
1566
1567                $dataToChange['budget_status_id'] = $budget->budget_status_id;
1568                $dataToChange["created_at"] = $createdAt;
1569                $dataToChange["last_follow_up_comment"] = $budget->last_follow_up_comment . "\n" . $lastFollowUpComment;
1570
1571                $data["g3w_warning"] = 0;
1572
1573                if(
1574                    in_array($budget->budget_status_id, [13, 14]) ||
1575                    !$dataToChange["source_id"] ||
1576                    (!$budget->budget_type_id || $budget->budget_type_id == 0 || $budget->budget_type_id == 16) ||
1577                    empty(trim((string) $dataToChange["client"])) ||
1578                    empty(trim((string) $dataToChange["email"]))
1579                ){
1580                    $data["g3w_warning"] = 1;
1581                }
1582
1583                TblQuotations::where('id', $id)->update($dataToChange);
1584
1585                if (!empty($solicitudLogs)) {
1586                    $nuevosLogs = array_map(function(array $log) use ($budget): array {
1587                        unset($log['id']); 
1588                        $log['quotation_id'] = $budget->id; 
1589                        return $log;
1590                    }, $solicitudLogs);
1591
1592                    TblQuotationsLog::insert($nuevosLogs);
1593                }
1594
1595
1596                $data['g3w_warning'] = 0;
1597
1598                if (
1599                    in_array($budget->budget_status_id, [13, 14]) ||
1600                    ! $dataToChange['source_id'] ||
1601                    (! $budget->budget_type_id || $budget->budget_type_id == 16) ||
1602                    empty(trim($dataToChange['client'])) ||
1603                    empty(trim($dataToChange['email']))
1604                ) {
1605                    $data['g3w_warning'] = 1;
1606                }
1607
1608                TblQuotations::where('id', $id)->update($dataToChange);
1609
1610                if ($solicitudLogs->isNotEmpty()) {
1611                    $nuevosLogs = array_map(function ($log) use ($budget) {
1612                        unset($log['id']);
1613                        $log['quotation_id'] = $budget->id;
1614
1615                        return $log;
1616                    }, $solicitudLogs->toArray());
1617
1618                    TblQuotations::where('id', $id)->update(
1619                        [
1620                            'accepted_at' => $data['updated_at'],
1621                            'accepted_by' => $data['updated_by']
1622                        ]
1623                    );
1624                }
1625
1626            }
1627
1628        } else {
1629            // FIRE-864: if email is invalid (blacklisted, malformed, or
1630            // empty) and status is sendable, set to 22 (Correo erroneo).
1631            if (isset($data['email']) && $this->isInvalidEmail($data['email'])
1632                && isset($data['budget_status_id']) && in_array($data['budget_status_id'], [1, 2, 11, 17])) {
1633                $data['budget_status_id'] = 22;
1634            }
1635
1636            TblQuotations::where('id', $id)->update($data);
1637        }
1638
1639        TblOngoingJobs::where('quotation_id', $id)->update($job);
1640
1641        $this->update_commercial_numbers($result->company_id);
1642
1643        if ($result->budget_status_id != $data['budget_status_id']
1644            && $data['budget_status_id'] == 3) {
1645            $this->send_acceptance_notification($result->id, $result->company_id, $userId, $data['updated_by']);
1646
1647            TblQuotations::where('id', $id)->update(
1648                [
1649                    'accepted_at' => $data['updated_at'],
1650                    'accepted_by' => $data['updated_by'],
1651                ]
1652            );
1653
1654            // Promote lead to general when quotation is accepted
1655            $quotation = TblQuotations::find($id);
1656            if ($quotation && $quotation->client_id) {
1657                Client::where('id', $quotation->client_id)
1658                    ->where('client_type_id', Client::TYPE_LEAD)
1659                    ->update(['client_type_id' => Client::TYPE_GENERAL]);
1660            }
1661        }
1662
1663        if ($files) {
1664
1665            $uploadedFiles = [];
1666            $i = 0;
1667
1668            $combinedFilesSize = 0;
1669            foreach ($files as $file) {
1670                $i++;
1671                $origFilename = $file->getClientOriginalName();
1672                $filename = $result->id.'-'.$result->company_id.'-FC'.time().'-'.$origFilename;
1673
1674                $combinedFilesSize = $combinedFilesSize + $file->getSize();
1675
1676                if ($combinedFilesSize > 25000000) {
1677                    return response(['message' => 'KO', 'error' => __('language.file_size_exceeded')]);
1678                }
1679
1680                if (in_array($origFilename, $uploadedFiles)) {
1681                    $origFilename = $origFilename.$i;
1682                }
1683
1684                // $fileContent = file_get_contents($file->getRealPath());
1685                // $fileHash = hash('sha256', $fileContent);
1686
1687                $s3path = Storage::disk('s3')->putFileAs(
1688                    'uploads',
1689                    $file,
1690                    $filename,
1691                    [
1692                        'ContentType' => $file->getMimeType(),
1693                    ]
1694                );
1695
1696                TblFiles::create(
1697                    [
1698                        'quotation_id' => $id,
1699                        'original_name' => $origFilename,
1700                        'filename' => $filename,
1701                        'uploaded_by' => $data['updated_by'],
1702                        // 'file' => $fileContent,
1703                        'file_size' => $file->getSize(),
1704                        // 'file_hash' => $fileHash,
1705                        'mime_type' => $file->getMimeType(),
1706                        'uploaded_at' => now(),
1707                    ]
1708                );
1709                $this->addUpdateLog($id, $data['updated_by'], 'upload_attachment', null, $filename, 4);
1710
1711                $uploadedFiles[] = $file->getClientOriginalName();
1712            }
1713        }
1714
1715        if ($internalFiles) {
1716
1717            $uploadedFiles = [];
1718            $i = 0;
1719
1720            $combinedFilesSize = 0;
1721            foreach ($internalFiles as $file) {
1722                $i++;
1723                $origFilename = $file->getClientOriginalName();
1724                $filename = $result->id.'-'.$result->company_id.'-FI'.time().'-'.$origFilename;
1725
1726                $combinedFilesSize = $combinedFilesSize + $file->getSize();
1727
1728                if ($combinedFilesSize > 25000000) {
1729                    return response(['message' => 'KO', 'error' => __('language.file_size_exceeded')]);
1730                }
1731
1732                if (in_array($origFilename, $uploadedFiles)) {
1733                    $origFilename = $origFilename.$i;
1734                }
1735
1736                // $fileContent = file_get_contents($file->getRealPath());
1737                // $fileHash = hash('sha256', $fileContent);
1738
1739                $s3path = Storage::disk('s3')->putFileAs(
1740                    'uploads',
1741                    $file,
1742                    $filename,
1743                    [
1744                        'ContentType' => $file->getMimeType(),
1745                    ]
1746                );
1747
1748                TblFiles::create(
1749                    [
1750                        'quotation_id' => $id,
1751                        'original_name' => $origFilename,
1752                        'filename' => $filename,
1753                        'uploaded_by' => $data['updated_by'],
1754                        // 'file' => $fileContent,
1755                        'file_size' => $file->getSize(),
1756                        // 'file_hash' => $fileHash,
1757                        'mime_type' => $file->getMimeType(),
1758                        'uploaded_at' => now(),
1759                        'is_internal' => 1,
1760                    ]
1761                );
1762                $this->addUpdateLog($id, $data['updated_by'], 'upload_attachment', null, $filename, 4);
1763
1764                $uploadedFiles[] = $file->getClientOriginalName();
1765            }
1766        }
1767
1768        $query = "SELECT
1769                        a.id,
1770                        a.quote_id,
1771                        a.company_id,
1772                        b.name company_name,
1773                        a.client,
1774                        c.name client_type,
1775                        c.customer_type_id,
1776                        a.request_date,
1777                        a.visit_date,
1778                        a.issue_date,
1779                        a.acceptance_date,
1780                        a.internal_quote_id,
1781                        DATE_FORMAT(a.request_date, '%d/%m/%Y') request_date_translate,
1782                        DATE_FORMAT(a.issue_date, '%d/%m/%Y') issue_date_translate,
1783                        DATE_FORMAT(a.acceptance_date, '%d/%m/%Y') acceptance_date_translate,
1784                        DATE_FORMAT(a.last_follow_up_date, '%d/%m/%Y') last_follow_up_date_translate,
1785                        -- DATEDIFF(a.issue_date, a.request_date) duration,
1786                        a.phone_number,
1787                        a.email,
1788                        a.duration,
1789                        a.order_number,
1790                        d.name 'type',
1791                        d.budget_type_id,
1792                        e.name 'status',
1793                        e.budget_status_id,
1794                        f.name source,
1795                        f.source_id,
1796                        a.amount,
1797                        g.name reason_for_not_following_up,
1798                        a.reason_for_not_following_up_id,
1799                        a.reason_for_rejection_id,
1800                        a.last_follow_up_date,
1801                        a.last_follow_up_comment,
1802                        CASE WHEN a.reason_for_rejection_id IS NULL THEN a.reason_for_rejection ELSE h.name END reason_for_rejection,
1803                        a.commercial,
1804                        a.created_by,
1805                        a.created_at,
1806                        a.updated_by,
1807                        a.for_approval,
1808                        a.box_work_g3w,
1809                        a.people_assigned_to_the_job,
1810                        a.duration_of_job_in_days,
1811                        a.estimated_cost_of_materials,
1812                        a.budget_margin_enabled,
1813                        a.question_enabled,
1814                        a.cost_of_labor,
1815                        a.total_cost_of_job,
1816                        CASE WHEN a.budget_margin_enabled > 0 THEN a.invoice_margin ELSE NULL END invoice_margin,
1817                        CASE WHEN a.budget_margin_enabled > 0 THEN a.margin_for_the_company ELSE NULL END margin_for_the_company,
1818                        a.margin_on_invoice_per_day_per_worker,
1819                        a.revenue_per_date_per_worked,
1820                        a.commission_cost,
1821                        a.commission_pct,
1822                        a.gross_margin,
1823                        a.labor_percentage,
1824                        a.question_ids,
1825                        a.question_ids_no,
1826                        a.approved_at,
1827                        a.approved_by,
1828                        a.rejected_at,
1829                        a.rejected_by,
1830                        a.approved_at_v2,
1831                        a.approved_by_v2,
1832                        a.rejected_at_v2,
1833                        a.rejected_by_v2,
1834                        a.accepted_at,
1835                        a.accepted_by,
1836                        a.is_validated,
1837                        a.resource_id,
1838                        a.x_status,
1839                        a.likehood,
1840                        a.updated_at
1841                    FROM
1842                        tbl_quotations a
1843                        LEFT JOIN tbl_companies b ON a.company_id = b.company_id
1844                        LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
1845                        LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
1846                        LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
1847                        LEFT JOIN tbl_sources f ON a.source_id = f.source_id
1848                        LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
1849                        LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
1850                    WHERE a.id = {$id}";
1851
1852        $result = DB::select($query);
1853
1854        if ($sendNotification) {
1855            $this->send_notification(
1856                $commercial->email,
1857                $userId,
1858                $data['quote_id'],
1859                $id,
1860                $status->name,
1861                $commercial->id,
1862                $company->name,
1863                0,
1864                $company->company_id,
1865                $data['internal_quote_id']
1866            );
1867        }
1868
1869        if ($sendApprovalNotification && ($needToSendReminder || is_null($result[0]->for_approval ?? null))) {
1870            $this->send_approval_notification(
1871                $data['amount'],
1872                $data['budget_type_id'],
1873                $data['customer_type_id'],
1874                $approvalMinimumOrderSize,
1875                $data['quote_id'],
1876                $approvalId,
1877                $company->name,
1878                @$data['created_by'] ?? @$data['updated_by'],
1879                $userId,
1880                $approvalForAdd,
1881                $commercial ? $commercial->email : null,
1882                $company->company_id,
1883                'orders',
1884                $approvalIsQuestion,
1885                $approvalQuestionIdsNo,
1886                $approvalN
1887            );
1888        }
1889
1890        if ($sendApprovalMarginNotification && $needToSendReminder) {
1891            $this->send_approval_margin_notification(
1892                $data['amount'],
1893                $data['budget_type_id'],
1894                $data['customer_type_id'],
1895                $approvalMinimumMargin,
1896                $data['quote_id'],
1897                $approvalId,
1898                $company->name,
1899                @$data['created_by'] ?? @$data['updated_by'],
1900                $userId,
1901                $approvalForAdd,
1902                $commercial ? $commercial->email : null,
1903                $approvalInvoiceMargin,
1904                $company->company_id,
1905                'orders'
1906            );
1907        }
1908
1909        if (isset($result['budget_status_id']) && $result['budget_status_id'] == 6) {
1910            $data = [
1911                'id' => $result['id'] ?? null,
1912                'client' => $result['client'] ?? null,
1913                'email' => $result['email'] ?? null,
1914                'phone_number' => $result['phone_number'] ?? null,
1915                'last_follow_up_comment' => $result['last_follow_up_comment'] ?? null,
1916                'quote_id' => $result['quote_id'] ?? null,
1917                'request_date' => date('Y-m-d'),
1918                'updated_by' => 'IA',
1919                'user_id' => $result['user_id'] ?? null,
1920                'commercial' => $result['commercial'] ?? null,
1921                'budget_status_id' => $result['budget_status_id'],
1922                'internal_quote_id' => $result['internal_quote_id'] ?? null,
1923            ];
1924
1925            $ch = curl_init('https://2lsarnb35o6evhgwmfedrsxk3i0lqzzq.lambda-url.eu-west-2.on.aws/checkduplicate');
1926            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1927            curl_setopt($ch, CURLOPT_POST, true);
1928            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
1929            curl_setopt($ch, CURLOPT_HTTPHEADER, [
1930                'Content-Type: application/json',
1931            ]);
1932
1933            $response = curl_exec($ch);
1934            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1935
1936            if (curl_errno($ch)) {
1937                error_log('Error en cURL: '.curl_error($ch));
1938            }
1939
1940            curl_close($ch);
1941        }
1942
1943        Cache::flush();
1944
1945        return response(['message' => 'OK', 'data' => $result, 'for_approval' => $forApproval]);
1946
1947        // } catch (\Exception $e) {
1948        //     return response(['message' => 'KO', 'error' => $e->getMessage()]);
1949        // }
1950
1951    }
1952
1953    /**
1954     * @return array{newData: mixed, oldData: mixed}[]
1955     */
1956    private function compareArrays($data, $quotations): array {
1957        $differences = [];
1958        $attributes = $quotations->getAttributes();
1959
1960        foreach ($data as $field => $valueData) {
1961            if (array_key_exists($field, $attributes)) {
1962                $valueQuotations = $quotations->$field;
1963
1964                $valueData = $this->convertValue($valueData);
1965                $valueQuotations = $this->convertValue($valueQuotations);
1966
1967                if ($valueData !== $valueQuotations) {
1968                    if ($this->isEmpty($valueData) && $this->isEmpty($valueQuotations)) {
1969                        continue;
1970                    }
1971
1972                    $differences[$field] = [
1973                        'newData' => $valueData,
1974                        'oldData' => $valueQuotations,
1975                    ];
1976                }
1977            }
1978        }
1979
1980        return $differences;
1981    }
1982
1983    private function isEmpty($value): bool
1984    {
1985        return is_null($value) || $value === '' || $value === 0 || $value === '0';
1986    }
1987
1988    private function convertValue($value)
1989    {
1990        if ($value === null) {
1991            return null;
1992        }
1993
1994        if (is_numeric($value)) {
1995            return (int) $value;
1996        }
1997
1998        if ($value === 'NULL' || $value === 'null') {
1999            return null;
2000        }
2001
2002        if (is_string($value)) {
2003            if (preg_match('/^(\d{4}-\d{2}-\d{2})( \d{2}:\d{2}:\d{2})?$/', $value, $matches)) {
2004                return $matches[1];
2005            }
2006        }
2007
2008        return $value;
2009    }
2010
2011    protected function callDeleteQuotation($id, $company_id, $userId, $user)
2012    {
2013        $request = new \Illuminate\Http\Request([
2014            'company_id' => $company_id,
2015            'user_id' => $userId,
2016            'updated_by' => $user,
2017            'ids' => [$id],
2018            'filterModel' => [],
2019            'sortModel' => [],
2020            'searchText' => '',
2021            'ids_not_in' => [],
2022        ]);
2023
2024        return $this->delete_quotation($request, true);
2025    }
2026
2027    public function get_quotation($id): ResponseFactory|HttpResponse{
2028
2029        try {
2030
2031            $id = addslashes((string) $id);
2032
2033            $query = "SELECT
2034                        a.id,
2035                        a.quote_id,
2036                        a.company_id,
2037                        b.name company_name,
2038                        a.client,
2039                        c.name client_type,
2040                        c.customer_type_id,
2041                        s.name segment,
2042                        s.segment_id,
2043                        a.request_date,
2044                        a.visit_date,
2045                        a.issue_date,
2046                        a.acceptance_date,
2047                        a.internal_quote_id,
2048                        DATE_FORMAT(a.request_date, '%d/%m/%Y') request_date_translate,
2049                        DATE_FORMAT(a.issue_date, '%d/%m/%Y') issue_date_translate,
2050                        DATE_FORMAT(a.acceptance_date, '%d/%m/%Y') acceptance_date_translate,
2051                        DATE_FORMAT(a.last_follow_up_date, '%d/%m/%Y') last_follow_up_date_translate,
2052                        a.phone_number,
2053                        a.email,
2054                        a.duration,
2055                        a.order_number,
2056                        d.name 'type',
2057                        d.budget_type_id,
2058                        e.name 'status',
2059                        e.budget_status_id,
2060                        f.name source,
2061                        f.source_id,
2062                        a.amount,
2063                        g.name reason_for_not_following_up,
2064                        a.reason_for_not_following_up_id,
2065                        a.reason_for_rejection_id,
2066                        a.last_follow_up_date,
2067                        a.last_follow_up_comment,
2068                        CASE WHEN a.reason_for_rejection_id IS NULL THEN a.reason_for_rejection ELSE h.name END reason_for_rejection,
2069                        a.commercial,
2070                        a.created_by,
2071                        a.created_at,
2072                        a.updated_by,
2073                        a.updated_at,
2074                        a.for_approval,
2075                        a.box_work_g3w,
2076                        a.people_assigned_to_the_job,
2077                        a.duration_of_job_in_days,
2078                        a.estimated_cost_of_materials,
2079                        a.budget_margin_enabled,
2080                        a.question_enabled,
2081                        a.cost_of_labor,
2082                        a.total_cost_of_job,
2083                        CASE WHEN a.budget_margin_enabled > 0 THEN a.invoice_margin ELSE NULL END invoice_margin,
2084                        CASE WHEN a.budget_margin_enabled > 0 THEN a.margin_for_the_company ELSE NULL END margin_for_the_company,
2085                        a.margin_on_invoice_per_day_per_worker,
2086                        a.revenue_per_date_per_worked,
2087                        a.commission_cost,
2088                        a.commission_pct,
2089                        a.gross_margin,
2090                        a.labor_percentage,
2091                        a.question_ids,
2092                        a.question_ids_no,
2093                        a.approved_at,
2094                        a.approved_by,
2095                        a.rejected_at,
2096                        a.rejected_by,
2097                        a.approved_at_v2,
2098                        a.approved_by_v2,
2099                        a.rejected_at_v2,
2100                        a.rejected_by_v2,
2101                        a.accepted_at,
2102                        a.accepted_by,
2103                        a.is_validated,
2104                        a.resource_id,
2105                        a.sync_import,
2106                        a.sync_import_edited,
2107                        a.g3w_warning,
2108                        a.g3w_warning_fields,
2109                        a.id_solicitud_duplicity,
2110                        a.x_status
2111                    FROM
2112                        tbl_quotations a
2113                        LEFT JOIN tbl_companies b ON a.company_id = b.company_id
2114                        LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
2115                        LEFT JOIN tbl_segments s ON a.segment_id = s.segment_id
2116                        LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
2117                        LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
2118                        LEFT JOIN tbl_sources f ON a.source_id = f.source_id
2119                        LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
2120                        LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
2121                    WHERE a.id = {$id}";
2122
2123            $result = DB::select($query);
2124
2125            Cache::flush();
2126
2127            return response(['message' => 'OK', 'data' => $result]);
2128
2129        } catch (\Exception $e) {
2130            report(AppException::fromException($e, 'GET_QUOTATION_EXCEPTION'));
2131            return response(['message' => 'KO', 'error' => $e->getMessage()]);
2132        }
2133    }
2134
2135    public function get_quotation_log($id)
2136    {
2137        return TblQuotationsLog::where('quotation_id', $id)->get();
2138    }
2139
2140    public function send_notification($toEmail = null, $userId = null, $quoteId = null, $id = null, $status = null, $sendUserId = null, $companyName = null, $action = null, $companyId = null, $g3wQuoteId = null): void{
2141
2142        $user = TblUsers::where('id', $userId)->first();
2143
2144        $imgpath = file_get_contents(public_path('fireservicetitan.png'));
2145
2146        $url = config('app.frontend_url') . "orders/{$id}?company_id={$companyId}";
2147        $href = "<a href='{$url}'>{$quoteId}</a>";
2148
2149        $body = '';
2150        $subject = '';
2151
2152        if ($g3wQuoteId) {
2153            $quoteId = $quoteId." - G3W #{$g3wQuoteId}";
2154        }
2155
2156        if ($action == 1) {
2157            $body = __('language.email_notification.body_created');
2158            $body = str_replace('{{creator}}', $user->name, $body);
2159            $subject = str_replace('{{quote_id}}', $quoteId, __('language.email_notification.subject_created'));
2160        } else {
2161            $body = __('language.email_notification.body_assigned');
2162            $body = str_replace('{{assignee}}', $user->name, $body);
2163            $subject = str_replace('{{quote_id}}', $quoteId, __('language.email_notification.subject_assigned'));
2164        }
2165
2166        $body = str_replace('{{quote_id}}', $href, $body);
2167        $body = str_replace('{{company}}', $companyName, $body);
2168        $body = str_replace('{{status}}', $status, $body);
2169
2170        $content = $body;
2171
2172        $body .= '<p>Fire Service Titan</p>';
2173        $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
2174
2175        $html = '<!DOCTYPE html>';
2176        $html .= '<html>';
2177        $html .= '<head>';
2178        $html .= '<meta charset="UTF-8">';
2179        $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
2180        $html .= '</head>';
2181        $html .= '<body>';
2182        $html .= $body;
2183        $html .= '</body>';
2184        $html .= '</html>';
2185
2186        if ($toEmail != null) {
2187            $email = new \SendGrid\Mail\Mail;
2188
2189            if(config('services.sendgrid.staging')){
2190                $toEmail = $user->email;
2191            }
2192
2193            $email->setFrom('fire@fire.es', 'Fire Service Titan');
2194            $email->setSubject($subject);
2195            $email->addTo($toEmail);
2196            $email->addContent('text/html', $html);
2197
2198            $email->addAttachment(
2199                $imgpath,
2200                'image/png',
2201                'fireservicetitan.png',
2202                'inline',
2203                'fireservicetitan'
2204            );
2205
2206            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
2207
2208            $response = $sendgrid->send($email);
2209            if ($response->statusCode() == 202) {
2210                Log::channel('email_log')->info('ID:'.$quoteId.' : '.$status.' : '.$toEmail.' - EMAIL NOTIFICATION SENT');
2211                $this->addUpdateLog($id, $sendUserId, 'send_notification', null, null, 5);
2212
2213                TblNotifications::create(
2214                    [
2215                        'user_id' => $sendUserId,
2216                        'content' => $content,
2217                        'is_open' => 1,
2218                        'created_by' => 'System',
2219                        'link' => $url
2220                    ]
2221                );
2222            } else {
2223                $error = true;
2224                Log::channel('email_log')->error('ID:'.$quoteId.' : '.$status.' : '.$toEmail.' - '.$response->body());
2225            }
2226        }
2227
2228    }
2229
2230    public function delete_quotation(Request $request, $isFromDeleteCall = false): ResponseFactory|HttpResponse{
2231
2232        try {
2233
2234            $data = $request->all();
2235            $result = [];
2236
2237            if (count($data['ids']) > 1) {
2238                $u = TblUsers::where('id', $data['user_id'])->first();
2239
2240                if ($u->role_id != 1) {
2241                    return response(['message' => 'KO', 'error' => 'more_than_one']);
2242                }
2243            }
2244
2245            $r = new Request([
2246                'filterModel' => $data['filterModel'],
2247                'sortModel' => $data['sortModel'],
2248                'start' => 0,
2249                'end' => 999999999,
2250                'company_id' => $data['company_id'],
2251                'user_id' => $data['user_id'],
2252                'ids' => $data['ids'],
2253                'searchText' => $data['searchText'],
2254                'ids_not_in' => $data['ids_not_in'],
2255            ]);
2256
2257            $d = [];
2258
2259            $result = $this->list_quotations($r);
2260            $result = $result->original['data'];
2261
2262            $outputArray = [];
2263
2264            foreach ($result as $item) {
2265                if (isset($item->id)) {
2266                    $outputArray[] = $item->id;
2267                }
2268            }
2269
2270            $ids = implode(',', $outputArray);
2271
2272            if ($outputArray) {
2273
2274                TblQuotations::whereIn('id', $outputArray)->update(
2275                    [
2276                        'updated_at' => date('Y-m-d H:i:s'),
2277                        'updated_by' => $data['updated_by'],
2278                        'for_add' => $isFromDeleteCall ? 2 : ($data['for_add'] ?? 0),
2279                        'reason_id' => ($data['reason_id'] ?? null),
2280                        'reason_for_deletion' => ($data['reason_for_deletion'] ?? null),
2281                    ]
2282                );
2283
2284                $query = "INSERT INTO tbl_quotations_deleted
2285                        SELECT * FROM tbl_quotations WHERE id IN ({$ids})";
2286
2287                DB::select($query);
2288
2289                TblQuotations::whereIn('id', $outputArray)->delete();
2290                TblFiles::whereIn('quotation_id', $outputArray)->delete();
2291            }
2292
2293            foreach ($outputArray as $id) {
2294                $this->addUpdateLog($id, $data['user_id'], 'delete', null, null, 6);
2295            }
2296
2297            Cache::flush();
2298
2299            return response(['message' => 'OK', 'data' => $result]);
2300
2301        } catch (\Exception $e) {
2302            report(AppException::fromException($e, 'DELETE_QUOTATION_EXCEPTION'));
2303            return response(['message' => 'KO', 'error' => $e->getMessage()]);
2304        }
2305
2306    }
2307
2308    function getBlacklistEmails(): array{
2309        return [
2310            "no\.no",
2311            "tiene\.email",
2312            "test\.com",
2313            "no\.tiene",
2314            "prueba\.com",
2315            "nomail@nomail\.com",
2316            "notiene@notiene\.notiene",
2317        ];
2318    }
2319
2320    public function validate_email(Request $request)
2321    {
2322        $email = $request->input('email');
2323
2324        if (! $email || trim($email) === '') {
2325            return response()->json([
2326                'valid' => false,
2327                'reason' => 'El email está vacío',
2328            ]);
2329        }
2330
2331        $emailPattern = '/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/';
2332        $emails = explode(',', $email);
2333
2334        foreach ($emails as $e) {
2335            $trimmed = trim($e);
2336
2337            if (! preg_match($emailPattern, $trimmed)) {
2338                return response()->json([
2339                    'valid' => false,
2340                    'reason' => "El email '{$trimmed}' tiene un formato incorrecto",
2341                ]);
2342            }
2343
2344            if ($this->isBlacklistedEmail($trimmed)) {
2345                return response()->json([
2346                    'valid' => false,
2347                    'reason' => "El email '{$trimmed}' está en la lista de direcciones no válidas",
2348                ]);
2349            }
2350        }
2351
2352        return response()->json([
2353            'valid' => true,
2354            'reason' => null,
2355        ]);
2356    }
2357
2358    private function isBlacklistedEmail(?string $email): bool
2359    {
2360        if (! $email || trim($email) === '') {
2361            return true;
2362        }
2363
2364        // FIRE-864 (2026-04-30): use `getBlacklistEmails()` as the single
2365        // source of truth. Pre-fix this method had its own hardcoded
2366        // pattern that was inconsistent with the array — missing `no\.no`
2367        // and `no\.tiene`, and `^no@` was over-anchored (missed
2368        // `xxx@no.no`). The `Emails con error` badge filter and the
2369        // backfill migration both already consume `getBlacklistEmails()`,
2370        // so aligning here closes the gap that left rows in `Listo para
2371        // enviar` after Berta's reclassification expected to catch them.
2372        $pattern = '/' . implode('|', $this->getBlacklistEmails()) . '/i';
2373
2374        $emails = explode(',', $email);
2375        foreach ($emails as $e) {
2376            if (preg_match($pattern, trim($e))) {
2377                return true;
2378            }
2379        }
2380
2381        return false;
2382    }
2383
2384    /**
2385     * FIRE-864 (2026-04-30): a row's email is "invalid" when it's empty,
2386     * malformed (failing the standard email regex), OR matches the
2387     * blacklist. The runtime classifier in `update_quotation` was only
2388     * checking `isBlacklistedEmail()`, so malformed emails like
2389     * "ricardo@" or "no email" never got reclassified to budget_status 22
2390     * and stayed in `Listo para enviar` indefinitely.
2391     *
2392     * The `invalid_email=1` filter in `list_quotations` (line 2974) and
2393     * the SQL backfill migration both use the same three-pronged check;
2394     * this mirrors that for in-process classification.
2395     */
2396    private function isInvalidEmail(?string $email): bool
2397    {
2398        if (! $email || trim($email) === '') {
2399            return true;
2400        }
2401
2402        $emailPattern = '/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/';
2403        foreach (explode(',', $email) as $e) {
2404            $trimmed = trim($e);
2405            if ($trimmed === '') {
2406                return true;
2407            }
2408            if (! preg_match($emailPattern, $trimmed)) {
2409                return true;
2410            }
2411        }
2412
2413        return $this->isBlacklistedEmail($email);
2414    }
2415
2416    public function list_quotations(Request $request)
2417    {
2418
2419        // try {
2420
2421        $data = $request->all();
2422        $companyId = addslashes((string) $data['company_id']);
2423        $userId = addslashes((string) $data['user_id']);
2424        $filter = $data['filterModel'];
2425        $sort = $data['sortModel'];
2426        $result = [];
2427        $subquery = "";
2428        $where = "";
2429        $having = "";
2430        $orderBy = "";
2431        $start = addslashes((string) $data['start']);
2432        $end = addslashes((string) $data['end']);
2433        $totalRowCount = 0;
2434        $withFilters = "";
2435        $logFilter = @$data['log_filter'];
2436        $isInvalidEmail = (isset($data['invalid_email']) && $data['invalid_email'] == 1);
2437        $isFollowUp = (isset($data['is_follow_up']) && $data['is_follow_up'] == 1);
2438
2439        $filterType = [
2440            'contains' => "LIKE '%[value]%'",
2441            'notContains' => "NOT LIKE '%[value]%'",
2442            'equals' => "= '[value]'",
2443            'notEqual' => "<> '[value]'",
2444            'startsWith' => "LIKE '[value]%'",
2445            'endsWith' => "LIKE '%[value]'",
2446            'blank' => 'IS NULL',
2447            'notBlank' => 'IS NOT NULL',
2448            'lessThan' => '< [value]',
2449            'lessThanOrEqual' => '<= [value]',
2450            'greaterThan' => '> [value]',
2451            'greaterThanOrEqual' => '>= [value]',
2452            'inRange' => 'BETWEEN [value1] AND [value2]',
2453            'in' => 'IN ([value])',
2454        ];
2455
2456        /*if(isset($data['internal_quote_id']) && count($data['internal_quote_id']) > 0){
2457            $internalIds = implode(",", $data['internal_quote_id']);
2458            $where = " AND a.internal_quote_id IN ({$internalIds}) ";
2459        }*/
2460
2461        if (isset($data['ids']) && count($data['ids']) > 0) {
2462            $quoteIds = implode(',', $data['ids']);
2463            $where .= " AND a.id IN ({$quoteIds}";
2464        }
2465
2466        if (isset($data['ids_not_in']) && count($data['ids_not_in']) > 0) {
2467            $quoteIds = implode(',', $data['ids_not_in']);
2468            $where .= " AND a.id NOT IN ({$quoteIds}";
2469        }
2470
2471        $lasLeftJoin = '';
2472        $whereBlocked = '';
2473
2474        if (isset($data['last_follow_up_date']) && ! empty($data['last_follow_up_date'])) {
2475            if ($data['last_follow_up_date'] == 1) {
2476
2477                $lasLeftJoin = " LEFT JOIN (
2478                        SELECT
2479                          a.id,
2480                          SUBSTRING_INDEX(
2481                            SUBSTRING_INDEX(a.email, ',', n.digit + 1),
2482                            ',',
2483                            -1
2484                          ) AS email_domain
2485                        FROM
2486                          tbl_quotations a
2487                          INNER JOIN (
2488                            SELECT
2489                              0 AS digit
2490                            UNION ALL
2491                            SELECT
2492                              1
2493                            UNION ALL
2494                            SELECT
2495                              2
2496                            UNION ALL
2497                            SELECT
2498                              3
2499                            UNION ALL
2500                            SELECT
2501                              4
2502                            UNION ALL
2503                            SELECT
2504                              5
2505                            UNION ALL
2506                            SELECT
2507                              6
2508                            UNION ALL
2509                            SELECT
2510                              7
2511                            UNION ALL
2512                            SELECT
2513                              8
2514                            UNION ALL
2515                            SELECT
2516                              9
2517                          ) n ON LENGTH(
2518                            REPLACE(a.email, ',', '')
2519                          ) <= LENGTH(a.email)- n.digit
2520                          GROUP BY a.id
2521                      ) temp ON a.id = temp.id ";
2522
2523                $whereBlocked = " AND a.last_follow_up_date < NOW()
2524                            AND a.budget_status_id IN (2)
2525                            AND a.email IS NOT NULL
2526                            AND a.email <> ''
2527                            AND NOT EXISTS (
2528                                SELECT
2529                                1
2530                                FROM
2531                                tbl_blocked_domains bd
2532                                WHERE
2533                                temp.email_domain LIKE CONCAT('%', bd.domain, '%')
2534                                AND bd.company_id = a.company_id
2535                            )
2536                            AND a.last_follow_up_date IS NOT NULL
2537                            AND a.reason_for_not_following_up_id IS NULL
2538                            AND a.last_follow_up_date > 0
2539                            AND a.total_sent < b.limit_reminder_emails
2540                            AND a.for_add = 0 ";
2541            }
2542        }
2543
2544        if (isset($data['visit_date']) && ! empty($data['visit_date'])) {
2545            if ($data['visit_date'] == 1) {
2546                $where = " AND DATE_FORMAT(a.visit_date, '%Y-%m-%d') <= DATE_FORMAT(NOW(), '%Y-%m-%d') ";
2547            }
2548        }
2549
2550        if ($companyId != 0) {
2551            $where .= " AND a.company_id = {$companyId} ";
2552        } elseif ($this->companyId) {
2553            $where .= " AND a.company_id IN ({$this->companyId}";
2554        }
2555
2556        $matchScoreCol = '';
2557        $matchScoreOrderBy = '';
2558
2559        if (isset($data['searchText']) && $data['searchText'] != null) {
2560
2561            $availableParameters = [
2562                'a.quote_id',
2563                'a.internal_quote_id',
2564                'a.box_work_g3w',
2565                's.name',
2566                'b.name',
2567                'a.client',
2568                'c.name',
2569                'a.phone_number',
2570                'a.email',
2571                'a.order_number',
2572                'a.request_date',
2573                'a.issue_date',
2574                'a.acceptance_date',
2575                'a.created_at',
2576                'a.updated_at',
2577                'a.rejected_at',
2578                'a.accepted_at',
2579                'd.name',
2580                'e.name',
2581                'f.name',
2582                'a.amount',
2583                'g.name',
2584                'a.last_follow_up_comment',
2585                'a.x_status',
2586                'h.name',
2587                'a.commercial',
2588                'a.user_commercial_by_g3w',
2589                'a.user_create_by_g3w',
2590                'a.created_by',
2591                'a.updated_by',
2592                'a.approved_by',
2593                'a.rejected_by',
2594                'a.accepted_by',
2595                'a.sync_import',
2596                'a.sync_import_edited',
2597                'a.g3w_warning',
2598            ];
2599
2600            $searchText = addslashes((string) $data['searchText']);
2601            $searchTextArray = explode(" ", $searchText);
2602
2603            $searchArray = [];
2604            $splitSearchArray = [];
2605            $matchScoreArray = [];
2606            $sc = 1;
2607            foreach ($availableParameters as $field) {
2608                if ($field == 'a.client' || $field == 'a.amount' || $field == 'a.created_at') {
2609                    $sc = 3;
2610                } elseif ($field == 'a.acceptance_date') {
2611                    $sc = 2;
2612                } else {
2613                    $sc = 1;
2614                }
2615
2616                $l = "{$field} LIKE '%{$searchText}%'";
2617                if ($field == 'a.last_follow_up_comment') {
2618                    $l = "{$field} = '{$searchText}'";
2619                } else {
2620
2621                    $d = "IFNULL((LENGTH(LOWER({$field})) - LENGTH(REPLACE(LOWER({$field}), LOWER('{$searchText}'), ''))) / LENGTH(LOWER('{$searchText}')), 0) * {$sc}";
2622
2623                    if (count($searchTextArray) > 1) {
2624                        foreach ($searchTextArray as $word) {
2625                            if (! is_numeric($word)) {
2626                                $d .= " + IFNULL((LENGTH(LOWER({$field})) - LENGTH(REPLACE(LOWER({$field}), LOWER('{$word}'), ''))) / LENGTH(LOWER('{$word}')), 0) * {$sc}";
2627                            }
2628                        }
2629                    }
2630
2631                    array_push($matchScoreArray, $d);
2632                }
2633
2634                if (is_numeric($searchText)) {
2635                    array_push($searchArray, "({$l} OR {$field} = CAST('{$searchText}' AS UNSIGNED))");
2636                } else {
2637                    array_push($searchArray, "({$l} OR DATE_FORMAT({$field}, '%d/%m/%Y') = DATE_FORMAT(STR_TO_DATE('{$searchText}', '%d/%m/%Y'), '%d/%m/%Y'))");
2638                }
2639
2640                if (count($searchTextArray) > 1) {
2641                    foreach ($searchTextArray as $word) {
2642
2643                        $l = "{$field} LIKE '%{$word}%'";
2644                        if ($field == 'a.last_follow_up_comment') {
2645                            $l = "{$field} = '{$word}'";
2646                        }
2647
2648                        if (is_numeric($word)) {
2649                            array_push($splitSearchArray, "{$l} OR {$field} = CAST('{$word}' AS UNSIGNED)");
2650                        } else {
2651                            array_push($splitSearchArray, "{$l} OR DATE_FORMAT({$field}, '%d/%m/%Y') = DATE_FORMAT(STR_TO_DATE('{$word}', '%d/%m/%Y'), '%d/%m/%Y')");
2652                        }
2653                    }
2654                }
2655
2656                $sc = 1;
2657            }
2658
2659            if (count($splitSearchArray) > 0) {
2660                $splitSearchArray = implode(' OR ', $splitSearchArray);
2661                $splitSearchArray = " OR ({$splitSearchArray}";
2662            } else {
2663                $splitSearchArray = '';
2664            }
2665
2666            $searchArray = implode(' OR ', $searchArray);
2667            $matchScoreArray = implode(',', $matchScoreArray);
2668            $matchScoreCol = ", GREATEST({$matchScoreArray}) match_score";
2669            $matchScoreOrderBy = 'match_score DESC,';
2670            $where .= " AND ({$searchArray} {$splitSearchArray})";
2671        }
2672
2673        if (count($sort) > 0) {
2674            $field = $sort[0]['colId'];
2675            $sortBy = $sort[0]['sort'];
2676
2677            if (strpos($field, 'translate') !== false) {
2678                $field = str_replace('_translate', '', $field);
2679            } else {
2680                if ($field == 'client_type') {
2681                    $field = 'c.name';
2682                } elseif ($field == 'segment') {
2683                    $field = 's.name';
2684                } elseif ($field == 'type') {
2685                    $field = 'd.name';
2686                } elseif ($field == 'status') {
2687                    $field = 'e.name';
2688                } elseif ($field == 'source') {
2689                    $field = 'g.name';
2690                } elseif ($field == 'reason_for_not_following_up') {
2691                    $field = 'g.name';
2692                } elseif ($field == 'reason_for_rejection') {
2693                    $field = 'h.name';
2694                } elseif ($field == 'amount') {
2695                    $field = 'CAST(a.amount AS DOUBLE)';
2696                } elseif ($field == 'duration') {
2697                    $field = 'CAST(a.duration AS DOUBLE)';
2698                } elseif ($field == 'quote_id' || $field == 'internal_quote_id') {
2699                    $field = "CAST(a.{$field} AS DOUBLE)";
2700                } elseif ($field == 'company_name') {
2701                    $field = 'b.name';
2702                }
2703
2704            }
2705
2706            if ($matchScoreOrderBy) {
2707                $matchScoreOrderBy = ', match_score DESC';
2708            }
2709
2710            $orderBy = " ORDER BY {$field} {$sortBy} {$matchScoreOrderBy}";
2711        } else {
2712            $orderBy = " ORDER BY {$matchScoreOrderBy} a.id DESC";
2713        }
2714
2715        foreach ($filter as $key => $data) {
2716            if (strpos($key, 'translate') !== false) {
2717
2718                $field = str_replace('_translate', '', $key);
2719                if ($field == 'created_at') {
2720                    $field = 'a.created_at';
2721                } elseif ($field == 'last_follow_up_date') {
2722                    $field = 'a.last_follow_up_date';
2723                } elseif ($field == 'issue_date') {
2724                    $field = 'a.issue_date';
2725                } elseif ($field == 'request_date') {
2726                    $field = 'a.request_date';
2727                } elseif ($field == 'acceptance_date') {
2728                    $field = 'a.acceptance_date';
2729                } elseif ($field == 'internal_quote_id') {
2730                    $field = 'a.internal_quote_id';
2731                }
2732
2733                $whereDates = '';
2734                $z = 0;
2735
2736                if (isset($data['filters']) && ! empty($data['filters'])) {
2737                    $yearsMonths = [];
2738                    $yearsWeeks = [];
2739                    $yearsMW = [];
2740                    foreach ($data['filters'] as $yearKey => $yearData) {
2741
2742                        if ($yearData['isChecked']) {
2743
2744                            if ($yearData['isCheckedAllMonths'] && $yearData['isCheckedAllWeeks']) {
2745                                if ($z > 0) {
2746                                    $whereDates .= " OR YEAR($field) = {$yearKey} ";
2747                                } else {
2748                                    $whereDates .= " YEAR($field) = {$yearKey} ";
2749                                }
2750                            } else {
2751
2752                                if ($yearData['isCheckedAllWeeks']) {
2753                                    for ($i = 0; $i < count($yearData['weeks']); $i++) {
2754                                        if ($yearData['weeks'][$i]['isChecked']) {
2755                                            array_push($yearsMW, " YEARWEEK({$field}, 1) = '{$yearKey}{$yearData['weeks'][$i]['value']}");
2756                                        }
2757                                    }
2758                                }
2759
2760                                if ($yearData['isCheckedAllMonths']) {
2761                                    for ($i = 0; $i < count($yearData['months']); $i++) {
2762                                        if ($yearData['months'][$i]['isChecked']) {
2763                                            array_push($yearsMW, " DATE_FORMAT({$field}, '%Y%m') = '{$yearKey}{$yearData['months'][$i]['value']}");
2764                                        }
2765                                    }
2766                                }
2767
2768                                if (! $yearsMW) {
2769                                    if (! $yearData['isCheckedAllMonths']) {
2770                                        if ($z > 0) {
2771                                            $whereDates .= " OR YEAR($field) = {$yearKey} ";
2772                                        } else {
2773                                            $whereDates .= " YEAR($field) = {$yearKey} ";
2774                                        }
2775                                    }
2776                                }
2777                            }
2778
2779                            $z++;
2780                        }
2781
2782                    }
2783
2784                    if ($yearsMW) {
2785                        $whereDates .= implode(' OR ', $yearsMW);
2786                    }
2787                }
2788
2789                $whereDataUptoToday = '';
2790                if (isset($data['isDataUptoToday'])) {
2791                    if ($data['isDataUptoToday']) {
2792                        $whereDates = '';
2793                        $whereDataUptoToday .= " AND {$field} < NOW() AND {$field} > 0 ";
2794                    }
2795                }
2796
2797                $whereBlanks = '';
2798                if (isset($data['isBlanks'])) {
2799                    if ($data['isBlanks']) {
2800                        $conj = 'OR';
2801                        if ($whereDates == '') {
2802                            $conj = '';
2803                        }
2804                        $whereBlanks .= " {$conj} {$field} IS NULL ";
2805                    } else {
2806                        $conj = 'AND';
2807                        if ($whereDates == '') {
2808                            $conj = '';
2809                        }
2810                        $whereBlanks .= " {$conj} {$field} IS NOT NULL ";
2811                    }
2812                }
2813
2814                $where .= " AND ({$whereDates} {$whereBlanks} {$whereDataUptoToday}";
2815            } else {
2816                if ($data['filterType'] == 'number') {
2817                    if (array_key_exists('operator', $data)) {
2818                        if ($data['condition1']['type'] != 'blank' && $data['condition2']['type'] != 'notBlank') {
2819                            $data['condition1']['filter'] = addslashes($data['condition1']['filter']);
2820                            $data['condition2']['filter'] = addslashes($data['condition2']['filter']);
2821
2822                            if ($data['condition1']['type'] == 'inRange') {
2823                                $data['condition1']['filterTo'] = addslashes($data['condition1']['filterTo']);
2824                                $inRange = str_replace('[value1]', $data['condition1']['filter'], $filterType['inRange']);
2825                                $val1 = str_replace('[value2]', $data['condition1']['filterTo'], $inRange);
2826                            } else {
2827                                $val1 = str_replace('[value]', $data['condition1']['filter'], $filterType[$data['condition1']['type']]);
2828                            }
2829
2830                            if ($data['condition2']['type'] == 'inRange') {
2831                                $data['condition2']['filterTo'] = addslashes($data['condition2']['filterTo']);
2832                                $inRange = str_replace('[value1]', $data['condition2']['filter'], $filterType['inRange']);
2833                                $val2 = str_replace('[value2]', $data['condition2']['filterTo'], $inRange);
2834                            } else {
2835                                $val2 = str_replace('[value]', $data['condition2']['filter'], $filterType[$data['condition2']['type']]);
2836                            }
2837
2838                        } else {
2839                            $val1 = $filterType[$data['condition1']['type']];
2840                            $val2 = $filterType[$data['condition2']['type']];
2841                        }
2842
2843                        $where .= " AND a.{$key} {$val1} {$data['operator']} a.{$key} {$val2} ";
2844                    } else {
2845                        if ($data['type'] != 'blank' && $data['type'] != 'notBlank') {
2846                            $data['filter'] = addslashes($data['filter']);
2847
2848                            if ($data['type'] == 'inRange') {
2849                                $data['filterTo'] = addslashes($data['filterTo']);
2850                                $inRange = str_replace('[value1]', $data['filter'], $filterType['inRange']);
2851                                $val = str_replace('[value2]', $data['filterTo'], $inRange);
2852                            } else {
2853                                $val = str_replace('[value]', $data['filter'], $filterType[$data['type']]);
2854                            }
2855                        } else {
2856                            $val = $filterType[$data['type']];
2857                        }
2858
2859                        $where .= " AND a.{$key} {$val} ";
2860                    }
2861                }
2862
2863                if ($data['filterType'] == 'text') {
2864                    if ($key == 'id') {
2865                        continue;
2866                    }
2867
2868                    if (array_key_exists('operator', $data)) {
2869                        $val1 = '';
2870                        $val2 = '';
2871                        if ($data['condition1']['type'] != 'blank' && $data['condition2']['type'] != 'notBlank') {
2872                            $data['condition1']['filter'] = addslashes($data['condition1']['filter']);
2873                            $val1 = str_replace('[value]', $data['condition1']['filter'], $filterType[$data['condition1']['type']]);
2874                        }
2875
2876                        if ($data['condition2']['type'] != 'blank' && $data['condition2']['type'] != 'notBlank') {
2877                            $data['condition2']['filter'] = addslashes($data['condition2']['filter']);
2878                            $val2 = str_replace('[value]', $data['condition2']['filter'], $filterType[$data['condition2']['type']]);
2879                        }
2880
2881                        $where .= " AND {$key} {$val1} {$data['operator']} {$key} {$val2} ";
2882                    } else {
2883
2884                        $type = $data['type'];
2885                        $filter = $data['filter'];
2886
2887                        if (($type === 'in' || $type === 'contains') && strpos($filter, ',') !== false) {
2888                            $values = explode(',', $filter);
2889                            $escaped = array_map('addslashes', $values);
2890                            $val = "IN ('".implode("','", $escaped)."')";
2891                        } elseif ($type !== 'blank' && $type !== 'notBlank') {
2892                            $data['filter'] = addslashes($data['filter']);
2893                            $val = str_replace('[value]', $data['filter'], $filterType[$type]);
2894                        } else {
2895                            $val = $filterType[$type];
2896                        }
2897
2898                        $where .= " AND {$key} {$val} ";
2899
2900                    }
2901                }
2902
2903                if ($data['filterType'] == 'set') {
2904                    $statusName = $key;
2905
2906                    if ($key == 'client_type') {
2907                        $statusName = 'c.name';
2908                    } elseif ($key == 'segment') {
2909                        $statusName = 's.name';
2910                    } elseif ($key == 'type') {
2911                        $statusName = 'd.name';
2912                    } elseif ($key == 'status') {
2913                        $statusName = 'e.name';
2914                    } elseif ($key == 'source') {
2915                        $statusName = 'f.name';
2916                    } elseif ($key == 'reason_for_not_following_up') {
2917                        $statusName = 'g.name';
2918                    } elseif ($key == 'reason_for_rejection') {
2919                        $statusName = 'h.name';
2920                    } elseif ($key == 'created_by') {
2921                        $statusName = 'a.created_by';
2922                    } elseif ($key == 'has_attachment') {
2923                        $statusName = 'a.has_attachment';
2924                        if ($data['values']) {
2925                            foreach ($data['values'] as $k => $v) {
2926                                if ($v == 'No') {
2927                                    $data['values'][$k] = 0;
2928                                } else {
2929                                    $data['values'][$k] = 1;
2930                                }
2931                            }
2932                        }
2933                    } elseif ($key == 'for_approval') {
2934                        $statusName = 'a.for_approval';
2935                        if ($data['values']) {
2936                            foreach ($data['values'] as $k => $v) {
2937                                if ($v == 'No') {
2938                                    $data['values'][$k] = 0;
2939                                } else {
2940                                    $data['values'][$k] = 1;
2941                                }
2942                            }
2943                        }
2944                    } elseif ($key == 'sync_import') {
2945                        $statusName = 'a.sync_import';
2946                        if ($data['values']) {
2947                            foreach ($data['values'] as $k => $v) {
2948                                if ($v == 'Manual') {
2949                                    $data['values'][$k] = 0;
2950                                } else {
2951                                    $data['values'][$k] = 1;
2952                                }
2953                            }
2954                        }
2955                    } elseif ($key == 'g3w_warning') {
2956                        $statusName = 'a.g3w_warning';
2957                        if ($data['values']) {
2958                            foreach ($data['values'] as $k => $v) {
2959                                if ($v == 'No') {
2960                                    $data['values'][$k] = 0;
2961                                } else {
2962                                    $data['values'][$k] = 1;
2963                                }
2964                            }
2965                        }
2966                    } elseif ($key == 'company_name') {
2967                        $statusName = 'b.name';
2968                    }
2969
2970                    $val = implode("','", $data['values']);
2971
2972                    if (in_array(null, $data['values'], true)) {
2973                        $where .= " AND ({$statusName} IN ('{$val}') OR {$statusName} IS NULL) ";
2974                    } else {
2975                        $where .= " AND {$statusName} IN ('{$val}') ";
2976                    }
2977                }
2978            }
2979        }
2980
2981        $whereSendToClient = $where;
2982
2983        $offset = $start;
2984        $limit = $end - $start;
2985
2986        $subquery = ",(SELECT can_write FROM tbl_company_users WHERE company_id = a.company_id AND user_id = {$userId}) can_write";
2987
2988        // Quotations accepted without acceptance_date
2989        // Quotations with state "No encontrado" or "Estado no reconocido en FST"
2990        // Quotations with not comercial in out database
2991        // Phone number on null
2992        // Source on null
2993        // Client name on null
2994        // Budget Type on null
2995        if (isset($data['g3w_warning'])) {
2996            $g3w_warning = $data['g3w_warning'] == 'No' ? 0 : 1;
2997            /*$where .= "
2998            AND a.sync_import = 1
2999                AND (
3000                    a.budget_status_id IN (13, 14)
3001                    OR (
3002                        a.commercial IS NULL
3003                        OR NOT EXISTS (
3004                            SELECT 1 FROM tbl_users u WHERE u.name = a.commercial
3005                        )
3006                        OR a.phone_number IS NULL
3007                        OR a.source_id IS NULL
3008                        OR a.budget_type_id IS NULL
3009                        OR (a.client IS NULL OR TRIM(a.client) = '')
3010                        OR (a.email IS NULL OR TRIM(a.email) = '')
3011                    )
3012                )
3013             ";*/
3014            $where .= '
3015                AND (a.sync_import = 1 OR a.sync_import_edited = 1)
3016                AND a.g3w_warning = '.$g3w_warning;
3017        }
3018
3019        if ($isInvalidEmail) {
3020            $blacklist = implode('|', $this->getBlacklistEmails());
3021
3022            $where .= "
3023                AND
3024                (
3025                    a.x_status IN ('Error','Error - Bounce','Error - Spam')
3026                    OR (
3027                        a.email IS NULL
3028                        OR TRIM(a.email) = ''
3029                        OR a.email NOT REGEXP '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}'
3030                        OR a.email REGEXP '($blacklist)'
3031                    )
3032                )
3033                AND a.budget_status_id IN(1, 2, 11, 17, 21, 22)";
3034        }
3035
3036        if ($isFollowUp) {
3037            $blacklist = implode('|', $this->getBlacklistEmails());
3038
3039            $where .= "
3040                AND (
3041                    a.email IS NOT NULL 
3042                    AND TRIM(a.email) != '' 
3043                    AND a.email REGEXP '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}'
3044                    AND a.email NOT REGEXP '($blacklist)'
3045                )";
3046        }
3047
3048        $query = "SELECT
3049                        a.id,
3050                        a.quote_id,
3051                        a.internal_quote_id,
3052                        a.company_id,
3053                        b.name company_name,
3054                        a.client,
3055                        c.name client_type,
3056                        c.customer_type_id,
3057                        s.name segment,
3058                        s.segment_id,
3059                        a.request_date,
3060                        a.visit_date,
3061                        a.issue_date,
3062                        a.acceptance_date,
3063                        a.internal_quote_id,
3064                        DATE_FORMAT(a.request_date, '%d/%m/%Y') request_date_translate,
3065                        DATE_FORMAT(a.issue_date, '%d/%m/%Y') issue_date_translate,
3066                        DATE_FORMAT(a.acceptance_date, '%d/%m/%Y') acceptance_date_translate,
3067                        DATE_FORMAT(a.last_follow_up_date, '%d/%m/%Y') last_follow_up_date_translate,
3068                        DATE_FORMAT(a.created_at, '%d/%m/%Y') created_at_translate,
3069                        DATE_FORMAT(a.accepted_at, '%d/%m/%Y') accepted_at_translate,
3070                        a.phone_number,
3071                        a.email,
3072                        a.duration,
3073                        a.order_number,
3074                        d.name 'type',
3075                        d.budget_type_id,
3076                        e.name 'status',
3077                        e.budget_status_id,
3078                        f.name as source,
3079                        f.source_id,
3080                        a.amount,
3081                        g.name reason_for_not_following_up,
3082                        a.reason_for_not_following_up_id,
3083                        a.reason_for_rejection_id,
3084                        a.last_follow_up_date,
3085                        a.last_follow_up_comment,
3086                        CASE WHEN a.reason_for_rejection_id IS NULL THEN a.reason_for_rejection ELSE h.name END reason_for_rejection,
3087                        a.commercial,
3088                        a.user_commercial_by_g3w,
3089                        a.user_create_by_g3w,
3090                        a.created_by,
3091                        a.created_at,
3092                        a.updated_by,
3093                        a.updated_at,
3094                        a.total_sent,
3095                        a.has_attachment,
3096                        a.for_approval,
3097                        a.box_work_g3w,
3098                        a.people_assigned_to_the_job,
3099                        a.duration_of_job_in_days,
3100                        a.estimated_cost_of_materials,
3101                        a.budget_margin_enabled,
3102                        a.question_enabled,
3103                        a.cost_of_labor,
3104                        a.total_cost_of_job,
3105                        CASE WHEN a.budget_margin_enabled > 0 THEN a.invoice_margin ELSE NULL END invoice_margin,
3106                        CASE WHEN a.budget_margin_enabled > 0 THEN a.margin_for_the_company ELSE NULL END margin_for_the_company,
3107                        a.margin_on_invoice_per_day_per_worker,
3108                        a.revenue_per_date_per_worked,
3109                        a.commission_cost,
3110                        a.commission_pct,
3111                        a.gross_margin,
3112                        a.labor_percentage,
3113                        a.question_ids,
3114                        a.question_ids_no,
3115                        a.approved_at,
3116                        a.approved_by,
3117                        a.rejected_at,
3118                        a.rejected_by,
3119                        a.approved_at_v2,
3120                        a.approved_by_v2,
3121                        a.rejected_at_v2,
3122                        a.rejected_by_v2,
3123                        a.accepted_at,
3124                        a.accepted_by,
3125                        a.is_validated,
3126                        a.resource_id,
3127                        a.x_status,
3128                        a.likehood,
3129                        a.sync_import,
3130                        a.sync_import_edited,
3131                        a.g3w_warning,
3132                        a.g3w_warning_fields,
3133                        a.id_solicitud_duplicity,
3134                        SUBSTRING_INDEX(a.email, '@', -1) domain
3135                        {$matchScoreCol}
3136                        {$subquery}
3137                    FROM
3138                        tbl_quotations a
3139                        LEFT JOIN tbl_companies b ON a.company_id = b.company_id
3140                        LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
3141                        LEFT JOIN tbl_segments s ON a.segment_id = s.segment_id
3142                        LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
3143                        LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
3144                        LEFT JOIN tbl_sources f ON a.source_id = f.source_id
3145                        LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
3146                        LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
3147                        {$lasLeftJoin}
3148                    WHERE a.for_add = 0 {$where} {$whereBlocked}
3149                    GROUP BY a.id
3150                    {$orderBy}
3151                    LIMIT {$offset}{$limit}
3152                    ";
3153        // return $query;
3154        $value = Cache::get(base64_encode($query));
3155
3156        if (! $value) {
3157            $result = DB::select($query);
3158
3159            Cache::put(base64_encode($query), $result, 600);
3160        } else {
3161            $result = $value;
3162        }
3163
3164        $totalQuery = "SELECT
3165                            COUNT(a.id) totalRowCount,
3166                            SUM(CAST(a.amount AS DECIMAL(10,2))) totalAmount
3167                        FROM
3168                            tbl_quotations a
3169                            LEFT JOIN tbl_companies b ON a.company_id = b.company_id
3170                            LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
3171                            LEFT JOIN tbl_segments s ON a.segment_id = s.segment_id
3172                            LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
3173                            LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
3174                            LEFT JOIN tbl_sources f ON a.source_id = f.source_id
3175                            LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
3176                            LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
3177                            {$lasLeftJoin}
3178                        WHERE a.for_add = 0
3179                        {$where} {$whereBlocked}";
3180
3181        $value = Cache::get(base64_encode($totalQuery));
3182
3183        if (! $value) {
3184            $countQuery = DB::select($totalQuery);
3185
3186            Cache::put(base64_encode($totalQuery), $countQuery, 600);
3187        } else {
3188            $countQuery = $value;
3189        }
3190
3191        $totalToFollowUpQuery = "SELECT
3192                                        COUNT(DISTINCT a.id) totalRowCount
3193                                    FROM
3194                                        tbl_quotations a
3195                                    LEFT JOIN tbl_companies b ON a.company_id = b.company_id
3196                                    LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
3197                                    LEFT JOIN tbl_segments s ON a.segment_id = s.segment_id
3198                                    LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
3199                                    LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
3200                                    LEFT JOIN tbl_sources f ON a.source_id = f.source_id
3201                                    LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
3202                                    LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
3203                                    LEFT JOIN (
3204                                        SELECT
3205                                        a.id,
3206                                        SUBSTRING_INDEX(
3207                                            SUBSTRING_INDEX(a.email, ',', n.digit + 1),
3208                                            ',',
3209                                            -1
3210                                        ) AS email_domain
3211                                        FROM
3212                                        tbl_quotations a
3213                                        INNER JOIN (
3214                                            SELECT
3215                                            0 AS digit
3216                                            UNION ALL
3217                                            SELECT
3218                                            1
3219                                            UNION ALL
3220                                            SELECT
3221                                            2
3222                                            UNION ALL
3223                                            SELECT
3224                                            3
3225                                            UNION ALL
3226                                            SELECT
3227                                            4
3228                                            UNION ALL
3229                                            SELECT
3230                                            5
3231                                            UNION ALL
3232                                            SELECT
3233                                            6
3234                                            UNION ALL
3235                                            SELECT
3236                                            7
3237                                            UNION ALL
3238                                            SELECT
3239                                            8
3240                                            UNION ALL
3241                                            SELECT
3242                                            9
3243                                        ) n ON LENGTH(
3244                                            REPLACE(a.email, ',', '')
3245                                        ) <= LENGTH(a.email)- n.digit
3246                                        GROUP BY a.id
3247                                    ) temp ON a.id = temp.id
3248                                    WHERE
3249                                    a.last_follow_up_date < NOW()
3250                                    AND a.budget_status_id IN (2)
3251                                    AND a.budget_type_id IS NOT NULL
3252                                    AND a.email IS NOT NULL
3253                                    AND a.email <> ''
3254                                    AND NOT EXISTS (
3255                                        SELECT
3256                                        1
3257                                        FROM
3258                                        tbl_blocked_domains bd
3259                                        WHERE
3260                                        temp.email_domain LIKE CONCAT('%', bd.domain, '%')
3261                                        AND bd.company_id = a.company_id
3262                                    )
3263                                    AND a.last_follow_up_date IS NOT NULL
3264                                    AND a.reason_for_not_following_up_id IS NULL
3265                                    AND a.last_follow_up_date > 0
3266                                    AND a.total_sent < b.limit_reminder_emails
3267                                    AND a.for_add = 0
3268                                    {$where}";
3269
3270        $value = Cache::get(base64_encode($totalToFollowUpQuery));
3271
3272        if (! $value) {
3273            $countToFollowUpQuery = DB::select($totalToFollowUpQuery);
3274
3275            Cache::put(base64_encode($totalToFollowUpQuery), $countToFollowUpQuery, 600);
3276        } else {
3277            $countToFollowUpQuery = $value;
3278        }
3279
3280        $query = "SELECT
3281                            COUNT(1) as count,
3282                            SUM(a.amount) as total_amount
3283                        FROM tbl_quotations a
3284                        LEFT JOIN tbl_companies b ON a.company_id = b.company_id
3285                        LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
3286                        LEFT JOIN tbl_segments s ON a.segment_id = s.segment_id
3287                        LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
3288                        LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
3289                        LEFT JOIN tbl_sources f ON a.source_id = f.source_id
3290                        LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
3291                        LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
3292                        WHERE a.budget_status_id = 11
3293                        AND a.email IS NOT NULL
3294                        AND a.budget_type_id IS NOT NULL
3295                        {$whereSendToClient}
3296                        ";
3297
3298        $value = Cache::get(base64_encode($query));
3299
3300        if (! $value) {
3301            $totalSendToClient = DB::select($query);
3302
3303            Cache::put(base64_encode($query), $totalSendToClient, 600);
3304        } else {
3305            $totalSendToClient = $value;
3306        }
3307
3308        return response([
3309            'message' => 'OK',
3310            'data' => $result,
3311            'totalAmount' => $countQuery[0]->totalAmount,
3312            'totalRowCount' => $countQuery[0]->totalRowCount,
3313            'totalToFollowUpRowCount' => $countToFollowUpQuery[0]->totalRowCount,
3314            'totalSendToClient' => $totalSendToClient[0]->count,
3315            'totalSendToClientAmount' => $totalSendToClient[0]->total_amount,
3316        ]);
3317
3318        // } catch (\Exception $e) {
3319        //     return response(['message' => 'KO', 'error' => $e->getMessage()]);
3320        // }
3321
3322    }
3323
3324    public function get_dates(Request $request): ResponseFactory|HttpResponse{
3325
3326        try {
3327
3328            $data = $request->all();
3329            $companyId = addslashes((string) $data['company_id']);
3330
3331            $where = '';
3332            if ($companyId != 0) {
3333                $where = " AND a.company_id = {$companyId} ";
3334            } else {
3335                $where = " AND a.company_id IN ({$this->companyId})";
3336            }
3337
3338            $query = "SELECT
3339                        DATE_FORMAT(a.request_date, '%d/%m/%Y') request_date_translate,
3340                        DATE_FORMAT(a.issue_date, '%d/%m/%Y') issue_date_translate,
3341                        DATE_FORMAT(a.acceptance_date, '%d/%m/%Y') acceptance_date_translate,
3342                        DATE_FORMAT(a.last_follow_up_date, '%d/%m/%Y') last_follow_up_date_translate,
3343                        DATE_FORMAT(a.created_at, '%d/%m/%Y') created_at_translate,
3344                        DATE_FORMAT(a.accepted_at, '%d/%m/%Y') accepted_at_translate
3345                    FROM tbl_quotations a
3346                    WHERE a.for_add = 0 {$where}";
3347
3348            $result = DB::select($query);
3349
3350            return response([
3351                'message' => 'OK',
3352                'data' => $result,
3353            ]);
3354
3355        } catch (\Exception $e) {
3356            report(AppException::fromException($e, 'GET_DATES_EXCEPTION'));
3357            return response(['message' => 'KO', 'error' => $e->getMessage()]);
3358        }
3359
3360    }
3361
3362    public function list_quotation_analytics_by_source(Request $request): ResponseFactory|HttpResponse{
3363
3364        try {
3365
3366            $data = $request->all();
3367            $companyId = addslashes((string) $data['company_id']);
3368
3369            $where = '';
3370            $whereYear = '';
3371
3372            $dateLflArray = [];
3373            $companyIds = $this->companyIds;
3374
3375            if($companyId != 0){
3376                $companyIds = [$companyId];
3377            }
3378
3379            $field = 'issue_date';
3380
3381            if (isset($data['years']) && $data['years'] != null) {
3382
3383                if (count($data['years']) > 0) {
3384                    foreach ($data['years'] as $year) {
3385                        if (isset($data['week']) && $data['week'] != null) {
3386                            $w = sprintf('%02d', $data['week']);
3387                            $whereYear .= " AND YEARWEEK(q.issue_date, 1) = '{$year}{$w}'";
3388                        } else {
3389                            $whereYear .= " AND YEAR(q.issue_date) = {$year}";
3390                        }
3391                    }
3392                }
3393            }
3394
3395            foreach ($companyIds as $v) {
3396
3397                $lflWhere = " AND q.company_id = {$v} ";
3398
3399                $query = "SELECT
3400                            CONCAT(
3401                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
3402                                ' - ',
3403                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
3404                            ) AS date_like,
3405                            YEAR(q.{$field}) 'year',
3406                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
3407                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
3408                            {$v} 'company_id'
3409                        FROM
3410                            tbl_quotations q
3411                        WHERE
3412                            q.{$field} IS NOT NULL
3413                            AND q.for_add = 0
3414                            {$lflWhere}
3415                            {$whereYear}
3416                        GROUP BY YEAR(q.{$field})
3417                        ORDER BY YEAR(q.{$field}) DESC";
3418
3419                $dateLike = DB::select($query);
3420
3421                $dateLflArray[$v] = $dateLike;
3422            }
3423
3424            $whereAcceptanceDate = 'q.acceptance_date IS NOT NULL ';
3425
3426            $isFy = true;
3427
3428            if (isset($data['issue_year_ytd']) && $data['issue_year_ytd'] != null && $data['issue_year_ytd'] == true) {
3429                $isFy = false;
3430                $ytdArray = [];
3431                $ytdAcceptanceArray = [];
3432                $lflCompanyIds = [];
3433                $lflCompanyIdsAcc = [];
3434                foreach ($dateLflArray as $k => $v) {
3435                    foreach ($dateLflArray[$k] as $item) {
3436                        $year = $item->year;
3437                        $now = date('m-d');
3438                        array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}' AND YEAR(q.acceptance_date) = YEAR(issue_date)");
3439                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
3440                    }
3441
3442                    $ytdArray = implode(' OR ', $ytdArray);
3443                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
3444                    $ytdArray = [];
3445
3446                    $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
3447                    array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
3448                    $ytdAcceptanceArray = [];
3449                }
3450
3451                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
3452                $where .= " AND ({$lflCompanyIds}";
3453
3454                $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
3455                $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
3456            }
3457
3458            if (isset($data['issue_year_lfl']) && $data['issue_year_lfl'] != null && $data['issue_year_lfl'] == true) {
3459                $isFy = false;
3460                $lflArray = [];
3461                $ytdAcceptanceArray = [];
3462                $lflCompanyIds = [];
3463                $lflCompanyIdsAcc = [];
3464                foreach ($dateLflArray as $k => $v) {
3465                    foreach ($dateLflArray[$k] as $item) {
3466                        $year = $item->year;
3467                        $min_date_like = $item->min_date_like;
3468                        $max_date_like = $item->max_date_like;
3469                        array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND YEAR(q.acceptance_date) = YEAR(issue_date)");
3470                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
3471                    }
3472
3473                    $lflArray = implode(' OR ', $lflArray);
3474                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
3475                    $lflArray = [];
3476
3477                    $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
3478                    array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
3479                    $ytdAcceptanceArray = [];
3480                }
3481
3482                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
3483                $where .= " AND ({$lflCompanyIds}";
3484
3485                $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
3486                $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
3487            }
3488
3489            if ($isFy) {
3490                if ($companyId != 0) {
3491                    $where .= " AND q.company_id = {$companyId} ";
3492                } else {
3493                    $where .= " AND q.company_id IN ({$this->companyId})";
3494                }
3495            }
3496
3497            if (isset($data['source']) && $data['source'] != null) {
3498                $where .= " AND s.name = '{$data['source']}'";
3499            }
3500
3501            if (isset($data['month']) && $data['month'] != null) {
3502                $where .= " AND MONTH(q.issue_date) = '{$data['month']}'";
3503            }
3504
3505            if (isset($data['commercial']) && $data['commercial'] != null) {
3506                $where .= " AND q.commercial = '{$data['commercial']}'";
3507            }
3508
3509            if (isset($data['created_by']) && $data['created_by'] != null) {
3510                $where .= " AND q.created_by = '{$data['created_by']}'";
3511            }
3512
3513            if (isset($data['budget_type']) && $data['budget_type'] != null) {
3514                $where .= " AND bt.budget_type_id = {$data['budget_type']}";
3515            }
3516
3517            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
3518                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
3519            }
3520
3521            if (isset($data['budget_status']) && $data['budget_status'] != null) {
3522                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
3523            }
3524
3525            if (isset($data['client_type']) && $data['client_type'] != null) {
3526                $where .= " AND ct.customer_type_id = {$data['client_type']}";
3527            }
3528
3529            if (isset($data['segment_id']) && $data['segment_id'] != null) {
3530                $where .= " AND q.segment_id = {$data['segment_id']}";
3531            }
3532
3533            $query = "SELECT
3534                        YEAR(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) AS 'year',
3535                        LPAD(MONTH(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)), 2, 0) AS 'month',
3536                        LPAD(WEEK(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)), 2, 0) AS 'week',
3537                        DATE_FORMAT(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY), '%W, %M %e') issue_date,
3538                        COUNT(
3539                            CASE WHEN q.issue_date IS NOT NULL
3540                            THEN 1 END
3541                        ) AS totalIssue,
3542                        GROUP_CONCAT(
3543                            CASE WHEN q.issue_date IS NOT NULL
3544                            THEN q.id END
3545                        ) AS groupConcatIds,
3546                        SUM(
3547                            CASE WHEN q.issue_date IS NOT NULL THEN q.amount END
3548                        ) AS revenueIssue,
3549                        COUNT(
3550                            CASE WHEN {$whereAcceptanceDate} AND bs.name = 'Aceptado' THEN 1 END
3551                        ) AS totalAcceptance,
3552                        SUM(
3553                            CASE WHEN {$whereAcceptanceDate} AND bs.name = 'Aceptado' THEN q.amount END
3554                        ) AS revenueAcceptance,
3555                        SUM(
3556                            CASE WHEN {$whereAcceptanceDate} AND bs.name = 'Aceptado' THEN q.amount END
3557                        ) / SUM(
3558                            CASE WHEN q.issue_date IS NOT NULL THEN q.amount END
3559                        ) * 100 AS revenueAcceptanceIssuedPercentage,
3560                        COUNT(
3561                            CASE WHEN bs.name = 'Rechazado' THEN 1 END
3562                        ) AS totalRejected,
3563                        SUM(
3564                            CASE WHEN bs.name = 'Rechazado' THEN q.amount END
3565                        ) AS revenueRejected,
3566                        COUNT(
3567                            CASE WHEN bs.name = 'Rechazado - automaticamente' THEN 1 END
3568                        ) AS totalRejectedAutomatic,
3569                        SUM(
3570                            CASE WHEN bs.name = 'Rechazado - automaticamente' THEN q.amount END
3571                        ) AS revenueRejectedAutomatic
3572                    FROM
3573                        tbl_quotations q
3574                        LEFT JOIN tbl_sources s ON s.source_id = q.source_id
3575                        LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
3576                        LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
3577                        LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
3578                    WHERE
3579                        q.issue_date IS NOT NULL
3580                        AND q.budget_type_id != 7
3581                        AND q.budget_type_id IS NOT NULL
3582                        AND q.for_add = 0
3583                        AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
3584                        {$where}
3585                        {$whereYear}                        
3586                    GROUP BY
3587                        YEAR(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)),
3588                        MONTH(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)),
3589                        WEEK(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) WITH ROLLUP
3590                    ORDER BY
3591                        YEAR DESC,
3592                        MONTH ASC,
3593                        WEEK ASC,
3594                        DATE_FORMAT(q.issue_date, '%e') ASC";
3595            // return $query;
3596            $value = Cache::get(base64_encode($query));
3597
3598            if (! $value) {
3599                $result = DB::select($query);
3600
3601                Cache::put(base64_encode($query), $result, 600);
3602            } else {
3603                $result = $value;
3604            }
3605
3606            return response([
3607                'message' => 'OK',
3608                'data' => $result,
3609            ]);
3610
3611        } catch (\Exception $e) {
3612            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_BY_SOURCE_EXCEPTION'));
3613            return response(['message' => 'KO', 'error' => $e->getMessage()]);
3614        }
3615    }
3616
3617    public function list_quotation_analytics_send_budgets(Request $request): ResponseFactory|HttpResponse{
3618
3619        try {
3620
3621            $data = $request->all();
3622            $companyId = addslashes((string) $data['company_id']);
3623
3624            $where = '';
3625            $whereYear = '';
3626
3627            if ($companyId != 0) {
3628                $where = " AND q.company_id = {$companyId} ";
3629            } else {
3630                $where = " AND q.company_id IN ({$this->companyId}";
3631            }
3632
3633            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
3634                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
3635            }
3636
3637            if (isset($data['budget_status']) && $data['budget_status'] != null) {
3638                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
3639            }
3640
3641            if (isset($data['years']) && $data['years'] != null) {
3642                $years = implode(',', $data['years']);
3643                if (count($data['years']) > 0) {
3644                    $whereYear = " AND YEAR(q.issue_date) IN ({$years})";
3645                }
3646            }
3647
3648            $query = "SELECT
3649                            YEAR(q.issue_date) AS 'year',
3650                            MONTH(q.issue_date) AS 'month',
3651                            SUM(
3652                                CASE WHEN MONTH(request_date) = MONTH(issue_date) THEN 1 ELSE 0 END
3653                            ) totalRequest,
3654                            COUNT(
3655                                CASE WHEN q.issue_date IS NOT NULL THEN 1 END
3656                            ) AS totalIssue,
3657                            GROUP_CONCAT(
3658                                CASE WHEN q.issue_date IS NOT NULL
3659                                THEN q.id END
3660                            ) AS groupConcatIds,
3661                            SUM(
3662                                CASE WHEN MONTH(request_date) = MONTH(issue_date) THEN 1 ELSE 0 END
3663                            ) /
3664                            COUNT(
3665                                CASE WHEN q.issue_date IS NOT NULL THEN 1 END
3666                            ) * 100 issuePercentage,
3667                            AVG(
3668                                COALESCE(q.duration, 0)
3669                            ) AS averageDurationIssue,
3670                            COALESCE(
3671                                AVG(
3672                                    CASE WHEN bt.name = 'Mantenimiento' THEN COALESCE(q.duration, 0) ELSE NULL END
3673                                ), 0
3674                            ) AS averageDurationMaintenance,
3675                            COALESCE(
3676                                AVG(
3677                                    CASE WHEN bt.name = 'Nuevos' THEN COALESCE(q.duration, 0) ELSE NULL END
3678                                ), 0
3679                            ) AS averageDurationNew,
3680                            COALESCE(
3681                                AVG(
3682                                    CASE WHEN bt.name = 'Correctivos' THEN COALESCE(q.duration, 0) ELSE NULL END
3683                                ), 0
3684                            ) AS averageDurationCorretive,
3685                            COALESCE(
3686                                AVG(
3687                                    CASE WHEN bt.name = 'Anomalías' THEN COALESCE(q.duration, 0) ELSE NULL END
3688                                ), 0
3689                            ) AS averageDurationAnomalies,
3690                            COALESCE(
3691                                AVG(
3692                                    CASE WHEN bt.name = 'Precios Unitarios' THEN COALESCE(q.duration, 0) ELSE NULL END
3693                                ), 0
3694                            ) AS averageDurationUnitPrice,
3695                            COALESCE(
3696                                AVG(
3697                                    CASE WHEN bt.name = 'Instalaciones' THEN COALESCE(q.duration, 0) ELSE NULL END
3698                                ), 0
3699                            ) AS averageDurationFacilities,
3700                            COALESCE(
3701                                AVG(
3702                                    CASE WHEN bt.name = 'Alquiler' THEN COALESCE(q.duration, 0) ELSE NULL END
3703                                ), 0
3704                            ) AS averageDurationRent,
3705                            COALESCE(
3706                                AVG(
3707                                    CASE WHEN bt.name = 'OCA visita' THEN COALESCE(q.duration, 0) ELSE NULL END
3708                                ), 0
3709                            ) AS averageDurationOCA,
3710                            COALESCE(
3711                                AVG(
3712                                    CASE WHEN bt.name = 'Retirada y gestion de residuos' THEN COALESCE(q.duration, 0) ELSE NULL END
3713                                ), 0
3714                            ) AS averageDurationWRM,
3715                            COALESCE(
3716                                AVG(
3717                                    CASE WHEN bt.name = 'Formación' THEN COALESCE(q.duration, 0) ELSE NULL END
3718                                ), 0
3719                            ) AS averageDurationTraining,
3720                            COALESCE(
3721                                AVG(
3722                                    CASE WHEN bt.name = 'Anomalías de facilities' THEN COALESCE(q.duration, 0) ELSE NULL END
3723                                ), 0
3724                            ) AS averageDurationFacilityAnomalies
3725                        FROM
3726                            tbl_quotations q
3727                            LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
3728                            LEFT JOIN tbl_budget_type_groups btg ON bt.budget_type_id = btg.budget_type_id
3729                        WHERE
3730                            q.issue_date IS NOT NULL
3731                            AND q.budget_type_id != 7
3732                            AND q.budget_type_id IS NOT NULL
3733                            AND q.for_add = 0
3734                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
3735                            {$where}
3736                            {$whereYear}
3737                        GROUP BY
3738                            YEAR(q.issue_date),
3739                            MONTH(q.issue_date) WITH ROLLUP
3740                        ORDER BY
3741                            YEAR(q.issue_date) DESC,
3742                            MONTH(q.issue_date) ASC";
3743
3744            $sendBudgets = [];
3745            $sendBudgetsTotals = [];
3746
3747            $value = Cache::get(base64_encode($query));
3748
3749            if (! $value) {
3750                $result = DB::select($query);
3751
3752                Cache::put(base64_encode($query), $result, 600);
3753            } else {
3754                $result = $value;
3755            }
3756
3757            if (count($result) > 0) {
3758                for ($i = 0; $i < count($result); $i++) {
3759
3760                    if ($result[$i]->year == null && $result[$i]->month == null) {
3761                        $result[$i]->month = 'totalGeneral';
3762                        $sendBudgetsTotals['totalGeneral'] = $result[$i];
3763
3764                        continue;
3765                    } elseif ($result[$i]->month == null && $result[$i]->year != null) {
3766                        if (count($data['years']) > 0 || $whereYear == '') {
3767                            $result[$i]->month = 'totalSub';
3768                        } else {
3769                            continue;
3770                        }
3771                    } else {
3772                        $result[$i]->month = sprintf('%02d', $result[$i]->month);
3773                    }
3774
3775                    $sendBudgets[$result[$i]->year][$result[$i]->month] = $result[$i];
3776                }
3777            }
3778
3779            return response([
3780                'message' => 'OK',
3781                'data' => $sendBudgets,
3782                'totals' => $sendBudgetsTotals,
3783            ]);
3784
3785        } catch (\Exception $e) {
3786            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_EXCEPTION'));
3787            return response(['message' => 'KO', 'error' => $e->getMessage()]);
3788        }
3789    }
3790
3791    public function list_quotation_analytics_track_budgets(Request $request): ResponseFactory|HttpResponse{
3792
3793        try {
3794
3795            $data = $request->all();
3796            $companyId = addslashes((string) $data['company_id']);
3797
3798            $where = '';
3799            $whereYear = '';
3800            $isBetween = false;
3801
3802            $dateLflArray = [];
3803            $companyIds = $this->companyIds;
3804
3805            if($companyId != 0){
3806                $companyIds = [$companyId];
3807            }
3808
3809            if (isset($data['years']) && $data['years'] != null) {
3810                $years = implode(',', $data['years']);
3811                if (count($data['years']) > 0) {
3812                    $whereYear = " AND YEAR(q.issue_date) IN ({$years})";
3813                }
3814            }
3815
3816            $field = 'issue_date';
3817
3818            foreach ($companyIds as $v) {
3819
3820                $lflWhere = " AND q.company_id = {$v} ";
3821
3822                $query = "SELECT
3823                            CONCAT(
3824                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
3825                                ' - ',
3826                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
3827                            ) AS date_like,
3828                            YEAR(q.{$field}) 'year',
3829                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
3830                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
3831                            {$v} 'company_id'
3832                        FROM
3833                            tbl_quotations q
3834                        WHERE
3835                            q.{$field} IS NOT NULL
3836                            AND q.for_add = 0
3837                            {$lflWhere}
3838                            {$whereYear}
3839                        GROUP BY YEAR(q.{$field})
3840                        ORDER BY YEAR(q.{$field}) DESC";
3841
3842                $dateLike = DB::select($query);
3843
3844                $dateLflArray[$v] = $dateLike;
3845            }
3846
3847            $whereAcceptanceDate = 'q.acceptance_date IS NOT NULL ';
3848
3849            $isFy = true;
3850
3851            if (isset($data['issue_year_ytd']) && $data['issue_year_ytd'] != null && $data['issue_year_ytd'] == true) {
3852                $isFy = false;
3853                $ytdArray = [];
3854                $ytdAcceptanceArray = [];
3855                $lflCompanyIds = [];
3856                $lflCompanyIdsAcc = [];
3857                foreach ($dateLflArray as $k => $v) {
3858                    foreach ($dateLflArray[$k] as $item) {
3859                        $year = $item->year;
3860                        $now = date('m-d');
3861                        array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}' AND YEAR(q.acceptance_date) = YEAR(issue_date)");
3862                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
3863                    }
3864
3865                    $ytdArray = implode(' OR ', $ytdArray);
3866                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
3867                    $ytdArray = [];
3868
3869                    $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
3870                    array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
3871                    $ytdAcceptanceArray = [];
3872                }
3873
3874                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
3875                $where .= " AND ({$lflCompanyIds}";
3876
3877                $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
3878                $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
3879            }
3880
3881            if (isset($data['issue_year_lfl']) && $data['issue_year_lfl'] != null && $data['issue_year_lfl'] == true) {
3882                $isFy = false;
3883                $lflArray = [];
3884                $ytdAcceptanceArray = [];
3885                $lflCompanyIds = [];
3886                $lflCompanyIdsAcc = [];
3887                foreach ($dateLflArray as $k => $v) {
3888                    foreach ($dateLflArray[$k] as $item) {
3889                        $year = $item->year;
3890                        $min_date_like = $item->min_date_like;
3891                        $max_date_like = $item->max_date_like;
3892                        array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND YEAR(q.acceptance_date) = YEAR(issue_date)");
3893                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
3894                    }
3895
3896                    $lflArray = implode(' OR ', $lflArray);
3897                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
3898                    $lflArray = [];
3899
3900                    $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
3901                    array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
3902                    $ytdAcceptanceArray = [];
3903                }
3904
3905                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
3906                $where .= " AND ({$lflCompanyIds}";
3907
3908                $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
3909                $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
3910            }
3911
3912            if ($isFy) {
3913                if ($companyId != 0) {
3914                    $where .= " AND q.company_id = {$companyId} ";
3915                } else {
3916                    $where .= " AND q.company_id IN ({$this->companyId})";
3917                }
3918            }
3919
3920            if (isset($data['source']) && $data['source'] != null) {
3921                $where .= " AND s.name = '{$data['source']}'";
3922            }
3923
3924            if (isset($data['commercial']) && $data['commercial'] != null) {
3925                $where .= " AND q.commercial = '{$data['commercial']}'";
3926            }
3927
3928            if (isset($data['created_by']) && $data['created_by'] != null) {
3929                $where .= " AND q.created_by = '{$data['created_by']}'";
3930            }
3931
3932            if (isset($data['budget_type']) && $data['budget_type'] != null) {
3933                $where .= " AND bt.budget_type_id = {$data['budget_type']}";
3934            }
3935
3936            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
3937                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
3938            }
3939
3940            if (isset($data['budget_status']) && $data['budget_status'] != null) {
3941                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
3942            }
3943
3944            if (isset($data['client_type']) && $data['client_type'] != null) {
3945                $where .= " AND ct.customer_type_id = {$data['client_type']}";
3946            }
3947
3948            if (isset($data['segment_id']) && $data['segment_id'] != null) {
3949                $where .= " AND q.segment_id = {$data['segment_id']}";
3950            }
3951
3952            $query = "SELECT
3953                            YEAR(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) AS 'year',
3954                            LPAD(MONTH(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)), 2, 0) AS 'month',
3955                            LPAD(WEEK(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)), 2, 0) AS 'week',
3956                            DATE_FORMAT(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY), '%W, %M %e') issue_date,
3957                            COUNT(
3958                                CASE WHEN q.issue_date IS NOT NULL
3959                                THEN 1 END
3960                            ) AS totalIssue,
3961                            GROUP_CONCAT(
3962                                CASE WHEN q.issue_date IS NOT NULL
3963                                THEN q.id END
3964                            ) AS groupConcatIds,
3965                            COUNT(
3966                                CASE WHEN {$whereAcceptanceDate}
3967                                AND bs.name = 'Aceptado' THEN 1 END) totalAccept,
3968                            COUNT(
3969                                CASE WHEN q.acceptance_date IS NOT NULL
3970                                AND {$whereAcceptanceDate}
3971                                AND bs.name = 'Aceptado'
3972                                AND DATEDIFF(q.acceptance_date, q.issue_date) <= 10 THEN 1 END
3973                            ) / COUNT(
3974                                CASE WHEN q.issue_date IS NOT NULL
3975                                THEN 1 END
3976                            ) * 100 AS percentageOfacceptanceLessThan10,
3977                            COUNT(
3978                                CASE WHEN q.acceptance_date IS NOT NULL
3979                                AND {$whereAcceptanceDate}
3980                                AND bs.name = 'Aceptado'
3981                                AND DATEDIFF(q.acceptance_date, q.issue_date) > 10
3982                                AND DATEDIFF(q.acceptance_date, q.issue_date) < 30 THEN 1 END
3983                            ) / COUNT(
3984                                CASE WHEN q.issue_date IS NOT NULL
3985                                THEN 1 END
3986                            ) * 100 AS percentageOfacceptanceLessThan30,
3987                            COUNT(
3988                                CASE WHEN q.acceptance_date IS NOT NULL
3989                                AND {$whereAcceptanceDate}
3990                                AND bs.name = 'Aceptado'
3991                                AND DATEDIFF(q.acceptance_date, q.issue_date) >= 30 THEN 1 END
3992                            ) / COUNT(
3993                                CASE WHEN q.issue_date IS NOT NULL
3994                                THEN 1 END
3995                            ) * 100 AS percentageOfacceptanceMoreThan30,
3996                            COUNT(CASE WHEN
3997                                {$whereAcceptanceDate} THEN 1 END
3998                            ) / COUNT(
3999                                CASE WHEN q.issue_date IS NOT NULL
4000                                THEN 1 END
4001                            ) * 100 acceptedPercentage,
4002                            COALESCE(
4003                                AVG(
4004                                    CASE WHEN q.issue_date IS NOT NULL
4005                                    AND q.acceptance_date IS NOT NULL
4006                                    AND {$whereAcceptanceDate}
4007                                    AND bs.name = 'Aceptado'
4008                                    THEN DATEDIFF(q.acceptance_date, q.issue_date) ELSE NULL END
4009                                ), 0
4010                            )
4011                            AS averageAcceptedDuration,
4012                            COALESCE(
4013                                AVG(
4014                                    CASE WHEN q.issue_date IS NOT NULL
4015                                    AND q.request_date IS NOT NULL
4016                                    THEN DATEDIFF(q.issue_date, q.request_date) ELSE NULL END
4017                                ), 0
4018                            )
4019                            AS averageRequestedDays
4020                        FROM
4021                            tbl_quotations q
4022                            LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
4023                            LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
4024                            LEFT JOIN tbl_sources s ON s.source_id = q.source_id
4025                            LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
4026                        WHERE
4027                            q.issue_date IS NOT NULL
4028                            AND q.budget_type_id != 7
4029                            AND q.budget_type_id IS NOT NULL
4030                            AND q.for_add = 0
4031                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
4032                            {$where}
4033                            {$whereYear}
4034                        GROUP BY
4035                            YEAR(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)),
4036                            MONTH(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)),
4037                            WEEK(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) WITH ROLLUP
4038                        ORDER BY
4039                            YEAR DESC,
4040                            MONTH ASC,
4041                            WEEK ASC,
4042                            DATE_FORMAT(q.issue_date, '%e') ASC";
4043
4044            $value = Cache::get(base64_encode($query));
4045
4046            if (! $value) {
4047                $result = DB::select($query);
4048
4049                Cache::put(base64_encode($query), $result, 600);
4050            } else {
4051                $result = $value;
4052            }
4053
4054            return response([
4055                'message' => 'OK',
4056                'data' => $result,
4057            ]);
4058
4059        } catch (\Exception $e) {
4060            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_TRACK_BUDGETS_EXCEPTION'));
4061            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4062        }
4063    }
4064
4065    public function list_quotation_analytics_types_budgets(Request $request): ResponseFactory|HttpResponse{
4066
4067        try {
4068
4069            $data = $request->all();
4070            $companyId = addslashes((string) $data['company_id']);
4071
4072            $where = '';
4073            $whereYear = '';
4074            $isBetween = false;
4075
4076            $dateLflArray = [];
4077            $companyIds = $this->companyIds;
4078
4079            if($companyId != 0){
4080                $companyIds = [$companyId];
4081            }
4082
4083            if (isset($data['years']) && $data['years'] != null) {
4084                $years = implode(',', $data['years']);
4085                if (count($data['years']) > 0) {
4086                    $whereYear = " AND YEAR(q.issue_date) IN ({$years})";
4087                }
4088            }
4089
4090            $field = 'issue_date';
4091
4092            foreach ($companyIds as $v) {
4093
4094                $lflWhere = " AND q.company_id = {$v} ";
4095
4096                $query = "SELECT
4097                            CONCAT(
4098                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
4099                                ' - ',
4100                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
4101                            ) AS date_like,
4102                            YEAR(q.{$field}) 'year',
4103                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
4104                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
4105                            {$v} 'company_id'
4106                        FROM
4107                            tbl_quotations q
4108                        WHERE
4109                            q.{$field} IS NOT NULL
4110                            AND q.for_add = 0
4111                            {$lflWhere}
4112                            {$whereYear}
4113                        GROUP BY YEAR(q.{$field})
4114                        ORDER BY YEAR(q.{$field}) DESC";
4115
4116                $dateLike = DB::select($query);
4117
4118                $dateLflArray[$v] = $dateLike;
4119            }
4120
4121            $whereAcceptanceDate = 'q.acceptance_date IS NOT NULL ';
4122
4123            $isFy = true;
4124
4125            if (isset($data['issue_year_ytd']) && $data['issue_year_ytd'] != null && $data['issue_year_ytd'] == true) {
4126                $isFy = false;
4127                $ytdArray = [];
4128                $ytdAcceptanceArray = [];
4129                $lflCompanyIds = [];
4130                $lflCompanyIdsAcc = [];
4131                foreach ($dateLflArray as $k => $v) {
4132                    foreach ($dateLflArray[$k] as $item) {
4133                        $year = $item->year;
4134                        $now = date('m-d');
4135                        array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}' AND YEAR(q.acceptance_date) = YEAR(issue_date)");
4136                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
4137                    }
4138
4139                    $ytdArray = implode(' OR ', $ytdArray);
4140                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
4141                    $ytdArray = [];
4142
4143                    $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
4144                    array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
4145                    $ytdAcceptanceArray = [];
4146                }
4147
4148                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
4149                $where .= " AND ({$lflCompanyIds}";
4150
4151                $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
4152                $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
4153            }
4154
4155            if (isset($data['issue_year_lfl']) && $data['issue_year_lfl'] != null && $data['issue_year_lfl'] == true) {
4156                $isFy = false;
4157                $lflArray = [];
4158                $ytdAcceptanceArray = [];
4159                $lflCompanyIds = [];
4160                $lflCompanyIdsAcc = [];
4161                foreach ($dateLflArray as $k => $v) {
4162                    foreach ($dateLflArray[$k] as $item) {
4163                        $year = $item->year;
4164                        $min_date_like = $item->min_date_like;
4165                        $max_date_like = $item->max_date_like;
4166                        array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND YEAR(q.acceptance_date) = YEAR(issue_date)");
4167                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
4168                    }
4169
4170                    $lflArray = implode(' OR ', $lflArray);
4171                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
4172                    $lflArray = [];
4173
4174                    $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
4175                    array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
4176                    $ytdAcceptanceArray = [];
4177                }
4178
4179                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
4180                $where .= " AND ({$lflCompanyIds}";
4181
4182                $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
4183                $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
4184            }
4185
4186            if ($isFy) {
4187                if ($companyId != 0) {
4188                    $where .= " AND q.company_id = {$companyId} ";
4189                } else {
4190                    $where .= " AND q.company_id IN ({$this->companyId})";
4191                }
4192            }
4193
4194            if (isset($data['budget_status']) && $data['budget_status'] != null) {
4195                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
4196            }
4197
4198            if (isset($data['segment_id']) && $data['segment_id'] != null) {
4199                $where .= " AND q.segment_id = {$data['segment_id']}";
4200            }
4201
4202            $query = "SELECT
4203                            YEAR(q.issue_date) AS 'year',
4204                            bt.name,
4205                            COUNT(
4206                                CASE WHEN q.issue_date IS NOT NULL
4207                                THEN 1 END
4208                            ) AS totalIssue,
4209                            GROUP_CONCAT(
4210                                CASE WHEN q.issue_date IS NOT NULL
4211                                THEN q.id END
4212                            ) AS groupConcatIds,
4213                            COUNT(CASE WHEN {$whereAcceptanceDate} AND bs.name = 'Aceptado' THEN 1 END) totalAccept,
4214                            COUNT(
4215                                CASE WHEN q.acceptance_date IS NOT NULL
4216                                AND {$whereAcceptanceDate}
4217                                AND bs.name = 'Aceptado'
4218                                AND DATEDIFF(q.acceptance_date, q.issue_date) < 10 THEN 1 END
4219                            ) / COUNT(
4220                                CASE WHEN q.issue_date IS NOT NULL
4221                                THEN 1 END
4222                            ) * 100 AS percentageOfacceptanceLessThan10,
4223                            COUNT(
4224                                CASE WHEN q.acceptance_date IS NOT NULL
4225                                AND {$whereAcceptanceDate}
4226                                AND bs.name = 'Aceptado'
4227                                AND DATEDIFF(q.acceptance_date, q.issue_date) > 10
4228                                AND DATEDIFF(q.acceptance_date, q.issue_date) < 30 THEN 1 END
4229                            ) / COUNT(
4230                                CASE WHEN q.issue_date IS NOT NULL
4231                                THEN 1 END
4232                            ) * 100 AS percentageOfacceptanceLessThan30,
4233                            COUNT(
4234                                CASE WHEN q.acceptance_date IS NOT NULL
4235                                AND {$whereAcceptanceDate}
4236                                AND bs.name = 'Aceptado'
4237                                AND DATEDIFF(q.acceptance_date, q.issue_date) > 30 THEN 1 END
4238                            ) / COUNT(
4239                                CASE WHEN q.issue_date IS NOT NULL
4240                                THEN 1 END
4241                            ) * 100 AS percentageOfacceptanceMoreThan30,
4242                            COUNT(CASE WHEN {$whereAcceptanceDate} THEN 1 END) / COUNT(
4243                                CASE WHEN q.issue_date IS NOT NULL
4244                                THEN 1 END
4245                            ) * 100 acceptedPercentage,
4246                            COALESCE(
4247                                AVG(
4248                                    CASE WHEN q.issue_date IS NOT NULL
4249                                    AND q.acceptance_date IS NOT NULL
4250                                    AND {$whereAcceptanceDate}
4251                                    AND bs.name = 'Aceptado'
4252                                    THEN DATEDIFF(q.acceptance_date, q.issue_date) ELSE NULL END
4253                                ), 0
4254                            )
4255                            AS averageAcceptedDuration,
4256                            COALESCE(
4257                                AVG(
4258                                    CASE WHEN q.issue_date IS NOT NULL
4259                                    AND q.request_date IS NOT NULL
4260                                    AND q.issue_date > q.request_date
4261                                    THEN DATEDIFF(q.issue_date, q.request_date) ELSE NULL END
4262                                ), 0
4263                            )
4264                            AS averageRequestedDays
4265                        FROM
4266                            tbl_quotations q
4267                            LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
4268                            LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
4269                        WHERE
4270                            q.issue_date IS NOT NULL
4271                            AND q.budget_type_id != 7
4272                            AND q.budget_type_id IS NOT NULL
4273                            AND q.for_add = 0
4274                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
4275                            {$where}
4276                            {$whereYear}
4277                        GROUP BY
4278                            YEAR(q.issue_date),
4279                            bt.name WITH ROLLUP";
4280
4281            $value = Cache::get(base64_encode($query));
4282
4283            if (! $value) {
4284                $result = DB::select($query);
4285
4286                Cache::put(base64_encode($query), $result, 600);
4287            } else {
4288                $result = $value;
4289            }
4290
4291            $typesBudgets = [];
4292            $typesBudgetsTotals = [];
4293
4294            if (count($result) > 0) {
4295                for ($i = 0; $i < count($result); $i++) {
4296
4297                    if ($result[$i]->year == null && $result[$i]->name == null) {
4298                        $result[$i]->name = 'totalGeneral';
4299                        $typesBudgetsTotals['totalGeneral'] = $result[$i];
4300
4301                        continue;
4302                    } elseif ($result[$i]->name == null && $result[$i]->year != null) {
4303                        if (count($data['years']) > 0 || $whereYear == '') {
4304                            $result[$i]->name = 'totalSub';
4305                        } else {
4306                            continue;
4307                        }
4308                    }
4309
4310                    $typesBudgets[$result[$i]->year][$result[$i]->name] = $result[$i];
4311                }
4312            }
4313
4314            return response([
4315                'message' => 'OK',
4316                'data' => $typesBudgets,
4317                'totals' => $typesBudgetsTotals,
4318            ]);
4319
4320        } catch (\Exception $e) {
4321            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_TYPES_BUDGETS_EXCEPTION'));
4322            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4323        }
4324    }
4325
4326    function download_quotations(Request $request): ResponseFactory|HttpResponse{
4327        ini_set('max_execution_time', 123456);
4328        $data = $request->all();
4329        $companyId = addslashes($data['company_id']);
4330        $userId = addslashes((string) $data['user_id']);
4331
4332        $where = '';
4333
4334        $query = "SELECT
4335                b.name
4336            FROM tbl_users a
4337            LEFT JOIN tbl_roles b
4338                ON a.role_id = b.role_id
4339            WHERE a.id = {$userId}";
4340
4341        $role = DB::select($query);
4342
4343        $r = new Request([
4344            'filterModel' => $data['filterModel'],
4345            'sortModel' => $data['sortModel'],
4346            'start' => 0,
4347            'end' => 999999999,
4348            'company_id' => $data['company_id'],
4349            'user_id' => $data['user_id'],
4350            'ids' => $data['ids'],
4351            'searchText' => $data['searchText'],
4352            'ids_not_in' => $data['ids_not_in'],
4353        ]);
4354
4355        $result = $this->list_quotations($r);
4356
4357        $result = $result->original['data'];
4358
4359        return response(['data' => $result]);
4360    }
4361
4362    public function download_quotations_csv($filename, $data) {}
4363
4364    function bulk_upload(Request $request): ResponseFactory|HttpResponse{
4365
4366        try {
4367
4368            $data = $request->all();
4369            $file = $request->file('file');
4370            $exte = 'Xlsx';
4371            $companyId = $data['company_id'];
4372
4373            if ($file->getMimeType() == 'application/vnd.ms-excel') {
4374                $exte = 'Xls';
4375            }
4376
4377            $destination_path = config('app.bulk_upload_file_destination');
4378            $filename         = $file->getClientOriginalName();
4379
4380            if (file_exists($destination_path.$filename)) {
4381                $filename = pathinfo($filename, PATHINFO_FILENAME).'-'.uniqid().'.'.pathinfo($filename, PATHINFO_EXTENSION);
4382            }
4383
4384            $file->move($destination_path, $filename);
4385
4386            TblBulkUpload::create(
4387                [
4388                    'company_id' => $companyId,
4389                    'filename' => $filename,
4390                    'status' => 'Uploading...',
4391                    'is_running' => 1,
4392                    'uploaded_by' => $data['created_by']
4393                ]
4394            );
4395
4396            $command = "php BulkUploadQuotations.php '{$data['created_by']}' '{$exte}' '{$destination_path}' '{$filename}{$companyId}";
4397            exec($command.' > /dev/null &');
4398
4399            $query = '';
4400            $isRunning = 0;
4401
4402            if ($companyId == 0) {
4403                $query = 'SELECT * FROM tbl_bulk_upload ORDER BY started_at DESC';
4404                $isRunning = TblBulkUpload::where('is_running', 1)->count();
4405            } else {
4406                $query = "SELECT * FROM tbl_bulk_upload WHERE company_id = {$companyId} ORDER BY started_at DESC";
4407                $isRunning = TblBulkUpload::where('is_running', 1)->where('company_id', $companyId)->count();
4408            }
4409
4410            $result = DB::select($query);
4411
4412            return response(['message' => 'OK', 'data' => $result, 'is_running' => $isRunning]);
4413
4414        } catch (\Exception $e) {
4415            report(AppException::fromException($e, 'BULK_UPLOAD_QUOTATIONS_EXCEPTION'));
4416            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4417        }
4418    }
4419
4420    function list_bulk_upload($companyId): ResponseFactory|HttpResponse{
4421
4422        try {
4423
4424            $companyId = addslashes((string) $companyId);
4425            $query = "";
4426            $isRunning = 0;
4427
4428            if ($companyId == 0) {
4429                $query = 'SELECT * FROM tbl_bulk_upload ORDER BY started_at DESC';
4430                $isRunning = TblBulkUpload::where('is_running', 1)->count();
4431            } else {
4432                $query = "SELECT * FROM tbl_bulk_upload WHERE company_id = {$companyId} ORDER BY started_at DESC";
4433                $isRunning = TblBulkUpload::where('is_running', 1)->where('company_id', $companyId)->count();
4434            }
4435
4436            $result = DB::select($query);
4437
4438            return response(['message' => 'OK', 'data' => $result, 'is_running' => $isRunning]);
4439
4440        } catch (\Exception $e) {
4441            report(AppException::fromException($e, 'LIST_BULK_UPLOAD_EXCEPTION'));
4442            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4443        }
4444
4445    }
4446
4447    function delete_number(Request $request, $id): ResponseFactory|HttpResponse{
4448
4449        try {
4450
4451            $id = addslashes((string) $id);
4452            $data = $request->all();
4453
4454            $r = TblQuotations::where('id', $id)->first();
4455            $updatedAt = date('Y-m-d H:i:s');
4456            $query = "INSERT INTO tbl_quotations_deleted (id, quote_id, company_id, for_add, created_by, updated_by, updated_at)
4457                        SELECT id, quote_id, company_id, 1, created_by, '{$data['updated_by']}', '{$updatedAt}' FROM tbl_quotations WHERE id = {$id}";
4458
4459            DB::select($query);
4460
4461            TblQuotations::where('id', $id)->delete();
4462
4463            $latestBudget = TblQuotations::where('company_id', $r->company_id)->orderByRaw('id DESC')->value('quote_id');
4464
4465            $query = "UPDATE tbl_companies SET last_id = '{$latestBudget}' WHERE company_id = {$r->company_id}";
4466            DB::select($query);
4467
4468            return response(['message' => 'OK']);
4469
4470        } catch (\Exception $e) {
4471            report(AppException::fromException($e, 'DELETE_QUOTATION_EXCEPTION'));
4472            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4473        }
4474
4475    }
4476
4477    function get_number(Request $request, $companyId, $n = null): ResponseFactory|HttpResponse{
4478
4479        try {
4480
4481            $companyId = addslashes((string) $companyId);
4482            $data = $request->all();
4483            $latestBudget = [];
4484            $number = 0;
4485            $beforeLastId = null;
4486
4487            $x = true;
4488
4489            if ($companyId == 0) {
4490                $latestBudget = TblQuotations::orderByRaw('CAST(quote_id AS DOUBLE) DESC')->value('quote_id');
4491            } else {
4492                $latestBudget = TblCompanies::where('company_id', $companyId)->value('last_id');
4493
4494                if ($latestBudget == null) {
4495                    $latestBudget = TblQuotations::where('company_id', $companyId)->orderByRaw('id DESC')->value('quote_id');
4496                    $beforeLastId = $latestBudget;
4497                }
4498            }
4499
4500            $number = $latestBudget;
4501
4502            while ($x) {
4503
4504                if(is_numeric(substr((string) $number, -1))) {
4505                    $number++;
4506                } else {
4507                    $number .= '1';
4508                }
4509
4510                $check = 0;
4511
4512                if ($companyId == 0) {
4513                    $check = TblQuotations::where('quote_id', (string) $number)->count();
4514                } else {
4515                    $check = TblQuotations::where('company_id', $companyId)->where('quote_id', (string) $number)->count();
4516                }
4517
4518                if ($check == 0) {
4519                    $x = false;
4520                }
4521            }
4522
4523            $result = null;
4524
4525            if ($n == null) {
4526                $result = TblQuotations::create(['quote_id' => $number, 'company_id' => $companyId, 'for_add' => 1, 'created_by' => $data['created_by']]);
4527            }
4528
4529            if ($beforeLastId == null) {
4530                $beforeLastId = $number;
4531            }
4532
4533            $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}";
4534            DB::select($query);
4535
4536            return response([
4537                'message' => 'OK',
4538                'number' => $number,
4539                'id' => ($result != null) ? $result->id : null,
4540            ]);
4541
4542        } catch (\Exception $e) {
4543            report(AppException::fromException($e, 'GET_NUMBER_EXCEPTION'));
4544            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4545        }
4546
4547    }
4548
4549    function get_years(Request $request): ResponseFactory|HttpResponse{
4550
4551        try {
4552
4553            $data = $request->all();
4554            $companyId = addslashes((string) $data['company_id']);
4555            $where = "";
4556
4557            if ($companyId != 0) {
4558                $where = " AND company_id = {$companyId} ";
4559            } else {
4560                $where = " AND company_id IN ({$this->companyId}";
4561            }
4562
4563            $query = "SELECT
4564                        CONCAT(
4565                            DATE_FORMAT((SELECT MIN(issue_date) FROM tbl_quotations WHERE issue_date IS NOT NULL {$where}), '%c/%e/'), YEAR(issue_date),
4566                            ' - ',
4567                            DATE_FORMAT((SELECT MAX(issue_date) FROM tbl_quotations WHERE issue_date IS NOT NULL {$where}), '%c/%e/'), YEAR(issue_date)
4568                        ) AS date_like,
4569                        YEAR(issue_date) 'year'
4570                        FROM tbl_quotations
4571                        WHERE issue_date IS NOT NULL {$where}
4572                        GROUP BY YEAR(issue_date)
4573                        ORDER BY YEAR(issue_date) DESC";
4574
4575            $issueDate = DB::select($query);
4576
4577            $query = "SELECT
4578                        CONCAT(
4579                            DATE_FORMAT((SELECT MIN(created_at) FROM tbl_quotations WHERE created_at IS NOT NULL {$where}), '%c/%e/'), YEAR(created_at),
4580                            ' - ',
4581                            DATE_FORMAT((SELECT MAX(created_at) FROM tbl_quotations WHERE created_at IS NOT NULL {$where}), '%c/%e/'), YEAR(created_at)
4582                        ) AS date_like,
4583                        YEAR(created_at) 'year'
4584                        FROM tbl_quotations
4585                        WHERE created_at IS NOT NULL {$where}
4586                        GROUP BY YEAR(created_at)
4587                        ORDER BY YEAR(created_at) DESC";
4588
4589            $createdAt = DB::select($query);
4590
4591            $query = "SELECT
4592                        CONCAT(
4593                            DATE_FORMAT((SELECT MIN(acceptance_date) FROM tbl_quotations WHERE acceptance_date IS NOT NULL {$where}), '%c/%e/'), YEAR(acceptance_date),
4594                            ' - ',
4595                            DATE_FORMAT((SELECT MAX(acceptance_date) FROM tbl_quotations WHERE acceptance_date IS NOT NULL {$where}), '%c/%e/'), YEAR(acceptance_date)
4596                        ) AS date_like,
4597                        YEAR(acceptance_date) 'year'
4598                        FROM tbl_quotations
4599                        WHERE acceptance_date IS NOT NULL {$where}
4600                        GROUP BY YEAR(acceptance_date)
4601                        ORDER BY YEAR(acceptance_date) DESC";
4602
4603            $acceptanceDate = DB::select($query);
4604
4605            return response([
4606                'message' => 'OK',
4607                'data' => $issueDate,
4608                'created_at' => $createdAt,
4609                'acceptance_date' => $acceptanceDate,
4610            ]);
4611
4612        } catch (\Exception $e) {
4613            report(AppException::fromException($e, 'GET_YEARS_EXCEPTION'));
4614            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4615        }
4616
4617    }
4618
4619    function human_filesize($bytes, $decimals = 2): string {
4620        $size = ['B', 'KB', 'MB'];
4621
4622        $factor = floor((strlen($bytes) - 1) / 3);
4623        return number_format($bytes / 1024 ** $factor, 2, ',', '.') . $size[$factor];
4624    }
4625
4626    function get_files($quoteId): ResponseFactory|HttpResponse{
4627
4628        try {
4629
4630            $quoteId = addslashes((string) $quoteId);
4631
4632            $quoteId = (int) $quoteId;
4633            $query = '
4634            SELECT
4635                file_id,
4636                quotation_id,
4637                quote_id,
4638                original_name,
4639                filename,
4640                uploaded_by,
4641                uploaded_at,
4642                is_internal,
4643                file_size,
4644                file_hash,
4645                mime_type
4646            FROM tbl_files
4647            WHERE quotation_id = ?
4648            AND is_internal IS NULL';
4649
4650            $result = DB::select($query, [$quoteId]);
4651
4652            foreach ($result as $file) {
4653                if ($file->file_size > 0) {
4654                    $file->filesize = $this->human_filesize($file->file_size);
4655                } else {
4656                    $path = 'uploads/'.$file->filename;
4657
4658                    if (Storage::disk('s3')->exists($path)) {
4659                        $fileSizeBytes = Storage::disk('s3')->size($path);
4660                    } else {
4661                        $fileSizeBytes = 0;
4662                    }
4663
4664                    $file->filesize = $this->human_filesize($fileSizeBytes);
4665                }
4666
4667                $file->original_name = $file->original_name." ({$file->filesize})";
4668            }
4669
4670            $query = '
4671            SELECT
4672                file_id,
4673                quotation_id,
4674                quote_id,
4675                original_name,
4676                filename,
4677                uploaded_by,
4678                uploaded_at,
4679                is_internal,
4680                file_size,
4681                file_hash,
4682                mime_type
4683            FROM tbl_files
4684            WHERE quotation_id = ?
4685            AND is_internal = 1';
4686
4687            $internal = DB::select($query, [$quoteId]);
4688
4689            foreach ($internal as $file) {
4690                if ($file->file_size > 0) {
4691                    $file->filesize = $this->human_filesize($file->file_size);
4692                } else {
4693                    $path = 'uploads/'.$file->filename;
4694
4695                    if (Storage::disk('s3')->exists($path)) {
4696                        $fileSizeBytes = Storage::disk('s3')->size($path);
4697                    } else {
4698                        $fileSizeBytes = 0;
4699                    }
4700
4701                    $file->filesize = $this->human_filesize($fileSizeBytes);
4702                }
4703
4704                $file->original_name = $file->original_name." ({$file->filesize})";
4705            }
4706
4707            $job = TblOngoingJobs::where('quotation_id', $quoteId)->first();
4708            $order = TblQuotations::where('id', $quoteId)->first();
4709
4710            $z = 0;
4711            $status = 2;
4712            $xStatus = 'processed';
4713            $uniqueEvents = [];
4714            $currentEvents = [];
4715            $followUpLogs = [];
4716            $sendgridFollowUpLogs = [];
4717            $projectTypes = [];
4718
4719            if ($order) {
4720                $emails = explode(',', str_replace(' ', '', $order->email));
4721
4722                // FIRE-864 (30/04): pre-fix this used the composite WHERE
4723                // `quotation_id = ? AND x_message_id = ?`. That returned
4724                // null whenever:
4725                //   - tbl_quotations.x_message_id was NULL (status 22 rows
4726                //     where SendGrid never even attempted a send), or
4727                //   - the webhook row's quotation_id was NULL or didn't
4728                //     match (legacy webhook rows from before we started
4729                //     stamping quotation_id consistently).
4730                // The "Titan intentó enviarlo y SendGrid falló" modal then
4731                // showed empty even though webhook events existed. Match
4732                // on quotation_id only, newest first — we still get the
4733                // latest SendGrid event for that quote.
4734                $sendGrid = TblSendgridWebhook::where('quotation_id', $quoteId)
4735                    ->orderByDesc('id')
4736                    ->first();
4737
4738                if ($sendGrid) {
4739                    $emailErrors = ['deferred', 'bounce', 'dropped', 'spamreport', 'invalid'];
4740                    $events = json_decode($sendGrid->json_body, true);
4741                    $xMessageId = $sendGrid->x_message_id;
4742                    $isDelivered = 0;
4743                    $isProcessed = 0;
4744                    $isError = 0;
4745
4746                    foreach ($emails as $email) {
4747
4748                        $emailEvents = array_filter($events, fn(array $event): bool => strtolower((string) $event['email']) === strtolower($email));
4749
4750                        $statuses = array_unique(array_column($emailEvents, 'event'));
4751                        $eventCount = count($statuses);
4752
4753                        if ($eventCount === 1 && in_array('processed', $statuses)) {
4754                            $xStatus = 'processed';
4755                        }
4756
4757                        if ($eventCount == 2) {
4758                            if (in_array('processed', $statuses) && in_array('delivered', $statuses)) {
4759                                $xStatus = 'delivered';
4760                            }
4761
4762                            foreach ($emailErrors as $e) {
4763                                if (in_array('processed', $statuses) && in_array($e, $statuses)) {
4764                                    $xStatus = $e;
4765                                }
4766                            }
4767
4768                        } elseif ($eventCount > 2) {
4769                            if (in_array('processed', $statuses) && in_array('delivered', $statuses)) {
4770                                $xStatus = 'delivered';
4771                            }
4772
4773                            if ($xStatus != 'delivered') {
4774                                foreach ($emailErrors as $e) {
4775                                    if (in_array('processed', $statuses) && in_array($e, $statuses)) {
4776                                        $xStatus = $e;
4777                                    }
4778                                }
4779                            }
4780                        }
4781
4782                        if ($xStatus == 'processed') {
4783                            $isProcessed++;
4784                        } elseif ($xStatus == 'delivered') {
4785                            $isDelivered++;
4786                        } else {
4787                            $isError++;
4788                        }
4789
4790                        foreach ($emailEvents as $event) {
4791                            $key = strtolower($event['email']).'|'.$event['event'];
4792                            if (! isset($uniqueEvents[$key])) {
4793                                $uniqueEvents[$key] = $event;
4794                            }
4795                        }
4796                    }
4797
4798                    if ($uniqueEvents) {
4799                        foreach (array_values($uniqueEvents) as $d) {
4800                            $d['created_at'] = date('Y-m-d H:i:s', $d['timestamp']);
4801
4802                            if (isset($d['response'])) {
4803                                $d['error'] = $d['response'];
4804                            }
4805
4806                            if (isset($d['reason'])) {
4807                                $d['error'] = $d['reason'];
4808                            }
4809
4810                            array_push($currentEvents, $d);
4811                        }
4812                    }
4813
4814                    if (count($emails) == $isDelivered) {
4815                        $status = 1;
4816                        $xStatus = 'Completed';
4817                    } elseif ($isProcessed > 0) {
4818                        $status = 2;
4819                        $xStatus = 'Processing';
4820                    } elseif ($isError > 0) {
4821                        $status = 3;
4822
4823                        if ($xStatus == 'bounce') {
4824                            $xStatus = 'Error - Bounce';
4825                        } else {
4826                            $xStatus = 'Error';
4827                        }
4828                    }
4829                }
4830
4831                if ($order->x_status != $xStatus) {
4832                    TblQuotations::where('id', $order->id)->update(
4833                        [
4834                            'x_status' => $xStatus,
4835                        ]
4836                    );
4837                    $z = 1;
4838                    Cache::flush();
4839                }
4840
4841                if (empty($currentEvents)) {
4842                    $status = 0;
4843                }
4844
4845                $followUpLogs = TblFollowUpLogs::where('quotation_id', $quoteId)->orderBy('created_at', 'desc')->get();
4846
4847                $projectTypes = TblProjectTypes::where('company_id', $order->company_id)->get();
4848
4849                if ($order->y_message_id) {
4850                    $sendgridFollowUpLogs = TblSendgridWebhook::where('x_message_id', $order->y_message_id)->first();
4851
4852                    if ($sendgridFollowUpLogs) {
4853                        $sendgridFollowUpLogs = json_decode($sendgridFollowUpLogs->json_body, true);
4854                        foreach ($sendgridFollowUpLogs as &$item) {
4855                            $item['created_at'] = date('Y-m-d H:i:s', $item['timestamp']);
4856                        }
4857                    }
4858                }
4859            }
4860
4861            return response([
4862                'message' => 'OK',
4863                'data' => $result,
4864                'followUpLogs' => $followUpLogs,
4865                'sendgridFollowUpLogs' => $sendgridFollowUpLogs,
4866                'internal' => $internal,
4867                'projectTypes' => $projectTypes,
4868                'currentEvents' => [
4869                    'status' => $status,
4870                    'email' => $currentEvents,
4871                    $uniqueEvents,
4872                    $xStatus
4873                ],
4874                'job' => $job,
4875                'isUpdated' => $z,
4876            ]);
4877
4878        } catch (\Exception $e) {
4879            report(AppException::fromException($e, 'GET_FILES_EXCEPTION'));
4880            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4881        }
4882
4883    }
4884
4885    public function download_file($fileId)
4886    {
4887        try {
4888            $fileId = addslashes((string) $fileId);
4889            $file = TblFiles::where('file_id', $fileId)->first();
4890
4891            if (! $file) {
4892                return response()->json([
4893                    'message' => 'KO',
4894                    'error' => 'Archivo no encontrado',
4895                ], 404);
4896            }
4897
4898            if (! is_null($file->file_hash) && ! empty($file->file)) {
4899                $fileContent = $file->file;
4900                $mimeType = $file->mime_type ?? 'application/octet-stream';
4901                $filename = $file->original_name ?? $file->filename ?? 'download';
4902
4903                return response($fileContent)
4904                    ->header('Content-Type', $mimeType)
4905                    ->header('Content-Disposition', 'attachment; filename="' . $filename . '"')
4906                    ->header('Content-Length', strlen((string) $fileContent))
4907                    ->header('Cache-Control', 'no-cache, no-store, must-revalidate');
4908
4909            } else {
4910                $filePath = Storage::disk('s3')->get('uploads/'.$file->filename);
4911                // $filePath = storage_path('app/public/uploads/' . $file->filename);
4912
4913                if (! Storage::disk('s3')->exists('uploads/'.$file->filename)) {
4914                    return response()->json([
4915                        'message' => 'KO',
4916                        'error' => 'Archivo físico no encontrado: '.$file->filename,
4917                    ], 404);
4918                }
4919
4920                $fileSize = filesize($filePath);
4921                $mimeType = mime_content_type($filePath) ?: 'application/octet-stream';
4922                $filename = $file->original_name ?? $file->filename;
4923
4924                $fileContent = file_get_contents($filePath);
4925
4926                return response($fileContent)
4927                    ->header('Content-Type', $mimeType)
4928                    ->header('Content-Disposition', 'attachment; filename="'.$filename.'"')
4929                    ->header('Content-Length', $fileSize)
4930                    ->header('Cache-Control', 'no-cache, no-store, must-revalidate');
4931            }
4932
4933        } catch (\Exception $e) {
4934            report(AppException::fromException($e, 'DOWNLOAD_FILE_EXCEPTION'));
4935            Log::error('Error downloading file: ' . $e->getMessage());
4936            return response()->json([
4937                'message' => 'KO',
4938                'error' => 'Error interno del servidor: '.$e->getMessage(),
4939            ], 500);
4940        }
4941    }
4942
4943    function delete_file($fileId): ResponseFactory|HttpResponse{
4944
4945        try {
4946
4947            $fileId = addslashes((string) $fileId);
4948            $file = TblFiles::where('file_id', $fileId)->first();
4949            $result = TblFiles::where('file_id', $fileId)->first();
4950
4951            if ($result) {
4952                TblFiles::where('file_id', $fileId)->delete();
4953                $path = storage_path('app/public/uploads/'.$result->filename);
4954                Storage::disk('public')->delete('uploads/'.$result->filename);
4955
4956                if(!config('services.sendgrid.staging')){
4957                    Storage::disk('s3')->delete('uploads/' . $result->filename);
4958                }
4959            }
4960
4961            $fileCount = TblFiles::where('quotation_id', $file->quotation_id)->count();
4962            $data = [];
4963            if($fileCount > 0){
4964                $data['has_attachment'] = 1;
4965            } else {
4966                $data['has_attachment'] = 0;
4967            }
4968
4969            TblQuotations::where('quote_id', $file->quote_id)->update($data);
4970
4971            $this->addUpdateLog($file->quote_id, 'user', 'delete_attachment', $file->filename, null, 4);
4972
4973            return response(['message' => 'OK']);
4974
4975        } catch (\Exception $e) {
4976            report(AppException::fromException($e, 'DELETE_FILE_EXCEPTION'));
4977            return response(['message' => 'KO', 'error' => $e->getMessage()]);
4978        }
4979    }
4980
4981    function send_email_to_client(Request $request): ResponseFactory|HttpResponse{
4982
4983        ini_set('max_execution_time', 0);
4984
4985        try {
4986
4987            $data = $request->all();
4988
4989            // $id = addslashes($data['id']);
4990            $userId = addslashes((string) $data['user_id']);
4991            $companyId = addslashes((string) $data['company_id']);
4992
4993            $isResend = null;
4994
4995            if (isset($data['is_resend'])) {
4996                $isResend = 1;
4997                unset($data['is_resend']);
4998            }
4999
5000            $where = '';
5001            $emailTemplateId = $data['email_template_id'];
5002            unset($data['email_template_id']);
5003
5004            $emailTemplates = [];
5005
5006            if (isset($data['html'])) {
5007                $emailTemplates = array_column($data['html'], null, 'id');
5008            }
5009
5010            $emailTemplate = TblEmailConfiguration::whereIn('id', $emailTemplateId)->get()->keyBy('company_id');
5011
5012            $emailCompany = [];
5013            
5014            $result = [];
5015
5016            $result = [];
5017
5018            if ($companyId == 0) {
5019                $indexes = $emailTemplate->keys();
5020
5021                $emailCompany = TblCompanies::whereIn('company_id', $indexes)->get()->keyBy('company_id');
5022                $limit = 21;
5023
5024                foreach ($emailCompany as $item) {
5025                    $r = new Request([
5026                        'filterModel' => $data['filterModel'],
5027                        'sortModel' => $data['sortModel'],
5028                        'start' => 0,
5029                        'end' => 999999999,
5030                        'company_id' => $data['company_id'],
5031                        'user_id' => $data['user_id'],
5032                        'ids' => $data['ids'],
5033                        'searchText' => $data['searchText'],
5034                        'ids_not_in' => $data['ids_not_in'],
5035                    ]);
5036
5037                    $listQuotations = $this->list_quotations($r);
5038                    $d = $listQuotations->original['data'];
5039
5040                    if ($item->limit_send != null) {
5041                        $limit = $item->limit_send;
5042                    }
5043                    $l = 0;
5044                    for ($i = 0; $i < count($d); $i++) {
5045                        if ($d[$i]->email != null
5046                            && ! $this->isBlacklistedEmail($d[$i]->email)
5047                            && $d[$i]->budget_type_id != null
5048                            && ($d[$i]->budget_status_id == 11 || $isResend == 1)
5049                            && $item->company_id == $d[$i]->company_id
5050                        ) {
5051                            if ($l == $limit) {
5052                                break;
5053                            }
5054                            array_push($result, $d[$i]);
5055                            $l++;
5056                        } elseif ($d[$i]->email != null && $this->isBlacklistedEmail($d[$i]->email)) {
5057                            TblQuotations::where('id', $d[$i]->id)->update(['budget_status_id' => 22]);
5058                        }
5059                    }
5060                }
5061            } else {
5062
5063                $emailCompany = TblCompanies::where('company_id', $companyId)->get()->keyBy('company_id');
5064
5065                $limit = 21;
5066
5067                if ($emailCompany[$companyId]->limit_send != null) {
5068                    $limit = $emailCompany[$companyId]->limit_send;
5069                }
5070
5071                $r = new Request([
5072                    'filterModel' => $data['filterModel'],
5073                    'sortModel' => $data['sortModel'],
5074                    'start' => 0,
5075                    'end' => 999999999,
5076                    'company_id' => $data['company_id'],
5077                    'user_id' => $data['user_id'],
5078                    'ids' => $data['ids'],
5079                    'searchText' => $data['searchText'],
5080                    'ids_not_in' => $data['ids_not_in'],
5081                ]);
5082
5083                $listQuotations = $this->list_quotations($r);
5084                $d = $listQuotations->original['data'];
5085                $l = 0;
5086                for ($i = 0; $i < count($d); $i++) {
5087                    if ($d[$i]->email != null
5088                        && ! $this->isBlacklistedEmail($d[$i]->email)
5089                        && ($d[$i]->budget_status_id == 11 || $isResend == 1)
5090                    ) {
5091                        if ($l == $limit) {
5092                            break;
5093                        }
5094                        array_push($result, $d[$i]);
5095                        $l++;
5096                    } elseif ($d[$i]->email != null && $this->isBlacklistedEmail($d[$i]->email)) {
5097                        TblQuotations::where('id', $d[$i]->id)->update(['budget_status_id' => 21]);
5098                    }
5099                }
5100            }
5101
5102            if (count($result) == 0) {
5103                return response(['message' => 'OK', $d ?? []]);
5104            }
5105
5106            $user = TblUsers::where('id', $userId)->first();
5107            $error = false;
5108
5109            $availableParameters = [
5110                'quote_id',
5111                'company_id',
5112                'client',
5113                'client_type',
5114                'phone_number',
5115                'email',
5116                'issue_date',
5117                'request_date',
5118                'duration',
5119                'invoice_number',
5120                'type',
5121                'acceptance_date',
5122                'status',
5123                'source',
5124                'amount',
5125                'reason_for_not_following_up',
5126                'last_follow_up_date',
5127                'last_follow_up_comment',
5128                'reason_for_rejection_id',
5129                'reason_for_rejection',
5130                'commercial',
5131                'created_at',
5132                'created_by',
5133                'updated_at',
5134                'updated_by',
5135            ];
5136
5137            $dateParameters = [
5138                'issue_date',
5139                'request_date',
5140                'acceptance_date',
5141                'last_follow_up_date',
5142                'created_at',
5143                'updated_at',
5144            ];
5145
5146            if($this->locale == 'es'){
5147                setlocale(LC_ALL, "es_ES", 'Spanish_Spain', 'Spanish');
5148                setlocale(LC_ALL, "es_ES", 'Spanish_Spain', 'Spanish');
5149            }
5150
5151            $sentItems = [];
5152            
5153            for ($i = 0; $i < count($result); $i++) {
5154
5155                $body = $emailTemplate[$result[$i]->company_id]->html;
5156                if (isset($data['html']) && ! empty($data['html'])) {
5157                    $body = $emailTemplates[$result[$i]->company_id]['html'];
5158                }
5159                $subject = $emailTemplate[$result[$i]->company_id]->subject;
5160                $commercialUser = $result[$i]->commercial;
5161
5162                if ($result[$i]->email != null) {
5163
5164                    if(config('services.sendgrid.staging')){
5165                        $toEmail = $user->email;
5166                    } else {
5167                        $toEmail = $result[$i]->email;
5168                    }
5169
5170                    preg_match_all('/{{(.*?)}}/', (string) $body, $matches);
5171
5172                    $parameters = $matches[1];
5173
5174                    foreach ($parameters as $parameter) {
5175
5176                        if(in_array($parameter, $dateParameters)){
5177                            if($result[$i]->{$parameter}){
5178                                $result[$i]->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result[$i]->{$parameter})));
5179                            }
5180                        }
5181
5182                        if (in_array($parameter, $availableParameters)) {
5183                            $body = str_replace('{{'.$parameter.'}}', $result[$i]->{$parameter}, $body);
5184                        }
5185                    }
5186
5187                    preg_match_all('/{{(.*?)}}/', (string) $subject, $matches);
5188
5189                    $parameters = $matches[1];
5190
5191                    foreach ($parameters as $parameter) {
5192
5193                        if(in_array($parameter, $dateParameters)){
5194                            if($result[$i]->{$parameter}){
5195                                $result[$i]->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result[$i]->{$parameter})));
5196                            }
5197                        }
5198
5199                        if (in_array($parameter, $availableParameters)) {
5200                            $subject = str_replace('{{'.$parameter.'}}', $result[$i]->{$parameter}, $subject);
5201                        }
5202                    }
5203
5204                    $email = new \SendGrid\Mail\Mail;
5205                    $templateFiles = TblEmailFiles::where('email_template_id', $emailTemplate[$result[$i]->company_id]->id)->orderBy('order', 'asc')->get();
5206
5207                    foreach ($templateFiles as $item) {
5208                        $f = storage_path('app/public/uploads/'.$item->filename);
5209
5210                        if (file_exists($f)) {
5211                            $imgpath = file_get_contents($f);
5212                            $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
5213                            $mimeType = mime_content_type($f);
5214
5215                            $email->addAttachment(
5216                                $imgpath,
5217                                $mimeType,
5218                                str_replace(' ', '', $item->original_name),
5219                                'inline',
5220                                str_replace(' ', '', $item->original_name),
5221                            );
5222
5223                            $body .= "<img src='cid:{$item->original_name}' style='height: 45px; padding-right: 6px' />";
5224                        } else {
5225                            Log::channel('email_failed_log')->error('File not found: '.$f);
5226                        }
5227                    }
5228
5229                    $html = '<!DOCTYPE html>';
5230                    $html .= '<html>';
5231                    $html .= '<head>';
5232                    $html .= '<meta charset="UTF-8">';
5233                    $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
5234                    $html .= '</head>';
5235                    $html .= '<body>';
5236                    $html .= $body;
5237                    $html .= '</body>';
5238                    $html .= '</html>';
5239
5240                    if ($toEmail != null) {
5241
5242                        $toEmail = explode(",", (string) $toEmail);
5243                        $toEmail = array_map(trim(...), $toEmail);
5244
5245                        $companyEmail = null;
5246
5247                        $queryUsers = "SELECT sender_email AS from_email, `name` AS from_name FROM tbl_users WHERE sender_enabled = 1 AND response_id IS NOT NULL AND verified = 1 AND `name` = '{$commercialUser}'";
5248                        $commercialEmail = DB::select($queryUsers);
5249
5250                        if (count($commercialEmail) > 0) {
5251                            $companyEmail = $commercialEmail[0];
5252                        } else {
5253                            if ($emailTemplate[$result[$i]->company_id]->from_id != null) {
5254                                $companyEmail = TblCompanyEmails::where('id', $emailTemplate[$result[$i]->company_id]->from_id)->first();
5255                            } else {
5256                                $companyEmail = TblCompanyEmails::where('is_active', 1)->where('verified', 1)->where('company_id', $result[$i]->company_id)->first();
5257                            }
5258                        }
5259
5260                        if (! $companyEmail) {
5261                            return response(['message' => 'KO', 'error' => __('language.no_active_verified_sender')]);
5262                        }
5263
5264                        $ccBcc = TblCcBcc::where('company_id', $result[$i]->company_id)->get();
5265
5266                        $email->setFrom($companyEmail->from_email, $companyEmail->from_name);
5267                        $email->setSubject($subject);
5268
5269                        foreach ($toEmail as $clientEmail) {
5270                            $isValid = $this->isEmailValid($clientEmail);
5271                            if ($isValid) {
5272                                $email->addTo($clientEmail);
5273                            }
5274                        }
5275
5276                        if (!config('services.sendgrid.staging')) {
5277                            if(!in_array($user->email, $toEmail)){
5278                                $email->addCc($user->email);
5279                            }
5280                            if(count($ccBcc) > 0){
5281                                foreach ($ccBcc as $data) {
5282                                    if (! in_array($data->email, $toEmail) && $user->email != $data->email) {
5283                                        $email->addBcc($data->email);
5284                                    }
5285                                }
5286                            }
5287                        }
5288
5289                        $email->addContent("text/html", $html);
5290                        $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
5291
5292                        $files = TblFiles::where('quotation_id', $result[$i]->id)->where('is_internal', null)->get();
5293                        $requestSize = $this->calculateEmailRequestSize($email);
5294
5295                        foreach ($files as $key => $value) {
5296                            $fileContent = null;
5297                            $fileSize = 0;
5298                            $fileName = null;
5299
5300                            if (Storage::disk('s3')->exists("uploads/" . $files[$key]->filename)) {                                
5301                                $fileContent = Storage::disk('s3')->get("uploads/" . $files[$key]->filename);                                
5302                                $fileSize = (int) ceil(strlen((string) $fileContent) / 1048576);                                
5303                                $fileName = $files[$key]->original_name;                                                            
5304                            }
5305                            else if ($files[$key]->file) {
5306                                $fileContent = $files[$key]->file;
5307
5308                                if (is_string($fileContent) && base64_decode($fileContent, true)) {
5309                                    $fileContent = base64_decode($fileContent);
5310                                }
5311
5312                                $fileSize = strlen((string) $fileContent) / 1048576;
5313                                $fileSize = (int) ceil($fileSize);
5314                                $fileName = $files[$key]->original_name ?: $files[$key]->getAttribute('file_name') ?: 'file';
5315                            }
5316
5317                            if ($fileContent && $fileSize > 0) {
5318                                if ($requestSize + $fileSize < 25) {
5319                                    $attachment = new \SendGrid\Mail\Attachment;
5320                                    $attachment->setFilename($fileName);
5321                                    $attachment->setDisposition('attachment');
5322
5323                                    $attachment->setContent(base64_encode((string) $fileContent));
5324
5325                                    $email->addAttachment($attachment);
5326                                    $requestSize = $this->calculateEmailRequestSize($email);
5327                                }
5328                            }
5329                        }
5330
5331                        $sentWithBackup = false;
5332                        $response = $sendgrid->send($email);
5333
5334                        if (config('services.sendgrid.block') === true) {
5335    
5336                            Log::warning("SendGrid failed with status {$response->statusCode()}. Initiating Failover...");
5337                            try {
5338                                \Illuminate\Support\Facades\Mail::mailer('resend')->send([], [], function ($message) use ($html, $companyEmail, $toEmail, $subject, $files, $user, $ccBcc): void {
5339                                    $message->from($companyEmail->from_email, $companyEmail->from_name)
5340                                        ->to($toEmail)
5341                                        ->subject($subject);
5342
5343                                    $message->html($html);
5344
5345                                    $message->cc($user->email);
5346
5347                                    foreach ($ccBcc as $bcc) {
5348                                        $message->bcc($bcc->email);
5349                                    }
5350
5351                                    foreach ($files as $file) {
5352                                        if (Storage::disk('s3')->exists('uploads/'.$file->filename)) {
5353                                            $content = Storage::disk('s3')->get('uploads/'.$file->filename);
5354                                            $message->attachData($content, $file->original_name);
5355                                        } elseif ($file->file) {
5356                                            $content = $file->file;
5357                                            if (is_string($content) && base64_decode($content, true)) {
5358                                                $content = base64_decode($content);
5359                                            }
5360                                            $message->attachData($content, $file->original_name ?: 'archivo.pdf');
5361                                        }
5362                                    }
5363                                });
5364
5365                                $sentWithBackup = true;
5366                            } catch (\Exception $backupException) {
5367                                $sentWithBackup = false;
5368                                Log::error('Backup server also failed: '.$backupException->getMessage());
5369                            }
5370
5371                            if (! $sentWithBackup) {
5372                                throw new \Exception("SendGrid failed with status {$response->statusCode()}. Backup server also failed: ".$backupException->getMessage());
5373                            }
5374
5375                        } 
5376                        
5377                        if (($response->statusCode() == 202 || $sentWithBackup) && !config('services.sendgrid.block')) {
5378                            $messageId = null;
5379
5380                            if ($response->headers()) {
5381                                foreach ($response->headers() as $header) {
5382                                    if (str_starts_with(strtolower((string) $header), 'x-message-id:')) {
5383                                        $messageId = trim(substr((string) $header, strpos((string) $header, ':') + 1));
5384                                        break;
5385                                    }
5386                                }
5387                            }
5388
5389                            $sentItems[$result[$i]->company_id][] = $result[$i]->id;
5390
5391                            $comment = 'Se ha enviado la orden por correo electrónico manualmente al cliente el '.date('Y-m-d H:i:s').' por el usuario '.$user->name;
5392                            $result[$i]->last_follow_up_comment = $result[$i]->last_follow_up_comment."\n".$comment;
5393
5394                            TblQuotations::where('id', $result[$i]->id)->update(
5395                                [
5396                                    'last_follow_up_comment' => $result[$i]->last_follow_up_comment,
5397                                    'budget_status_id' => 2,
5398                                    'x_message_id' => $messageId,
5399                                    'x_status' => 'Processing',
5400                                    'updated_by' => $user->name,
5401                                    'updated_at' => date('Y-m-d H:i:s')
5402                                ]
5403                            );
5404
5405                            $jsonBody = [];
5406
5407                            foreach ($toEmail as $clientEmail) {
5408                                $isValid = $this->isEmailValid($clientEmail);
5409                                $eventStatus = 'processed';
5410                                $eventResponse = '';
5411                                if (! $isValid) {
5412                                    $eventStatus = 'invalid';
5413                                    $eventResponse = 'Invalid email address';
5414                                }
5415
5416                                array_push(
5417                                    $jsonBody,
5418                                    [
5419                                        'email' =>  $clientEmail,
5420                                        'event' => $eventStatus,
5421                                        'sg_message_id' => $messageId,
5422                                        'smtp-id' => $messageId,
5423                                        'timestamp' => strtotime(date('Y-m-d H:i:s')),
5424                                        'response' => $eventResponse
5425                                    ]
5426                                );
5427                            }
5428
5429                            TblSendgridWebhook::create(
5430                                [
5431                                    'quotation_id' => $result[$i]->id,
5432                                    'type' => 'sendToClient',
5433                                    'json_body' => json_encode($jsonBody),
5434                                    'x_message_id' => $messageId
5435                                ]
5436                            );
5437
5438                            Log::channel('email_log')->info("[RS-{$requestSize}] ID:".$result[$i]->id.' - EMAIL MANUALLY SENT');
5439                            $this->addUpdateLog($result[$i]->id, $userId, 'send_email_to_client', null, null, 5);
5440                        } else {
5441                            $error = true;
5442                            Log::channel('email_failed_log')->error("[RS-{$requestSize}] ID:".$result[$i]->id.' - '.$response->body());
5443                        }
5444                    }
5445                }
5446            }
5447
5448            $this->update_commercial_numbers($companyId);
5449
5450            Cache::flush();
5451
5452            foreach ($sentItems as $k => $v) {
5453                $sentItems[$k] = [
5454                    'total' => count($sentItems[$k]),
5455                    'company' => $emailCompany[$k]->name,
5456                    'limit' => $emailCompany[$k]->limit_send,
5457                ];
5458            }
5459
5460            $totalSent = array_values($sentItems);
5461
5462            return response(['message' => 'OK', 'data' => $totalSent]);
5463
5464        } catch (\Exception $e) {
5465            report(AppException::fromException($e, 'SEND_EMAIL_TO_CLIENT_EXCEPTION'));
5466            Log::channel('email_failed_log')->error($e->getMessage());
5467
5468            return response(['message' => 'KO', 'error' => $e->getMessage()]);
5469        }
5470
5471    }
5472
5473    function send_email_follow_ups(Request $request, $automaticSendLimit = null): ResponseFactory|HttpResponse{
5474
5475        $currentQuotationId = null;
5476        ini_set('max_execution_time', 0);
5477
5478        try {
5479
5480            $data = $request->all();
5481
5482            $startedAt = date('Y-m-d H:i:s');
5483            $toEmail = "";
5484            $userId = addslashes((string) $data['user_id']);
5485            $companyId = addslashes((string) $data['company_id']);
5486            $emailTemplateId = $data['email_template_id'];
5487            unset($data['email_template_id']);
5488
5489            $emailTemplates = [];
5490
5491            if (isset($data['html']) && ! empty($data['html'])) {
5492                $emailTemplates = array_column($data['html'], null, 'id');
5493            }
5494
5495            $emailTemplate = TblEmailConfiguration::whereIn('id', $emailTemplateId)->get()->keyBy('company_id');
5496
5497            $limit = 0;
5498
5499            $blockedDomains = [];
5500            $workingDays = 10;
5501            $limitReminderEmails = 3;
5502            $emailCompany = [];
5503
5504            $result = [];
5505
5506            if ($companyId == 0) {
5507                $indexes = $emailTemplate->keys();
5508
5509                $emailCompany = TblCompanies::whereIn('company_id', $indexes)->get()->keyBy('company_id');
5510
5511                foreach ($emailCompany as $item) {
5512                    $limit = 20;
5513
5514                    $r = new Request([
5515                        'filterModel' => $data['filterModel'],
5516                        'sortModel' => $data['sortModel'],
5517                        'start' => 0,
5518                        'end' => 999999999,
5519                        'company_id' => $data['company_id'],
5520                        'user_id' => $data['user_id'],
5521                        'ids' => $data['ids'],
5522                        'searchText' => $data['searchText'],
5523                        'ids_not_in' => $data['ids_not_in'],
5524                        'last_follow_up_date' => 1,
5525                    ]);
5526
5527                    if ($item->limit_send != null) {
5528                        $limit = $item->limit_send;
5529                    }
5530
5531                    $listQuotations = $this->list_quotations($r);
5532                    $d = $listQuotations->original['data'];
5533                    if ($automaticSendLimit != null) {
5534                        $limit = $automaticSendLimit;
5535                    }
5536
5537                    $l = 0;
5538                    for ($i = 0; $i < count($d); $i++) {
5539                        if ($d[$i]->email != null
5540                            && $d[$i]->budget_type_id != null
5541                            && $d[$i]->budget_status_id == 2
5542                            && $d[$i]->reason_for_not_following_up_id == null
5543                            && $d[$i]->total_sent < $limitReminderEmails
5544                            && $d[$i]->last_follow_up_date != null
5545                            && $d[$i]->last_follow_up_date < date('Y-m-d H:i:s')
5546                            && $d[$i]->last_follow_up_date > 0
5547                            && $item->company_id == $d[$i]->company_id
5548                        ) {
5549
5550                            if ($l == $limit) {
5551                                break;
5552                            }
5553                            array_push($result, $d[$i]);
5554                            $l++;
5555                        }
5556                    }
5557
5558                }
5559            } else {
5560
5561                $emailCompany = TblCompanies::where('company_id', $companyId)->get()->keyBy('company_id');
5562                $workingDays = $emailCompany[$companyId]->last_follow_up_date ?? 10;
5563                $limitReminderEmails = $emailCompany[$companyId]->limit_reminder_emails ?? 3;
5564
5565                if ($emailCompany[$companyId]->limit_send != null) {
5566                    $limit = $emailCompany[$companyId]->limit_send;
5567                }
5568
5569                $r = new Request([
5570                    'filterModel' => $data['filterModel'],
5571                    'sortModel' => $data['sortModel'],
5572                    'start' => 0,
5573                    'end' => 999999999,
5574                    'company_id' => $data['company_id'],
5575                    'user_id' => $data['user_id'],
5576                    'ids' => $data['ids'],
5577                    'searchText' => $data['searchText'],
5578                    'ids_not_in' => $data['ids_not_in'],
5579                    'last_follow_up_date' => 1,
5580                ]);
5581
5582                $listQuotations = $this->list_quotations($r);
5583                $d = $listQuotations->original['data'];
5584
5585                if ($automaticSendLimit != null) {
5586                    $limit = $automaticSendLimit;
5587                }
5588
5589                $l = 0;
5590                for ($i = 0; $i < count($d); $i++) {
5591                    if ($d[$i]->email != null
5592                        && $d[$i]->budget_status_id == 2
5593                        && $d[$i]->reason_for_not_following_up_id == null
5594                        && $d[$i]->total_sent < $limitReminderEmails
5595                        && $d[$i]->last_follow_up_date != null
5596                        && $d[$i]->last_follow_up_date < date('Y-m-d H:i:s')
5597                        && $d[$i]->last_follow_up_date > 0
5598                    ) {
5599                        if ($l == $limit) {
5600                            break;
5601                        }
5602                        array_push($result, $d[$i]);
5603                        $l++;
5604                    }
5605                }
5606            }
5607
5608            if (count($result) == 0) {
5609                return response(['message' => 'OK']);
5610            }
5611
5612            $user = TblUsers::where('id', $userId)->first();
5613            $error = false;
5614
5615            $sentBy = $user->name;
5616
5617            $availableParameters = [
5618                'quote_id',
5619                'company_id',
5620                'client',
5621                'client_type',
5622                'phone_number',
5623                'email',
5624                'issue_date',
5625                'request_date',
5626                'duration',
5627                'invoice_number',
5628                'type',
5629                'acceptance_date',
5630                'status',
5631                'source',
5632                'amount',
5633                'reason_for_not_following_up',
5634                'last_follow_up_date',
5635                'last_follow_up_comment',
5636                'reason_for_rejection_id',
5637                'reason_for_rejection',
5638                'commercial',
5639                'created_at',
5640                'created_by',
5641                'updated_at',
5642                'updated_by',
5643            ];
5644
5645            $dateParameters = [
5646                'issue_date',
5647                'request_date',
5648                'acceptance_date',
5649                'last_follow_up_date',
5650                'created_at',
5651                'updated_at',
5652            ];
5653
5654            if ($this->locale == 'es') {
5655                setlocale(LC_ALL, 'es_ES', 'Spanish_Spain', 'Spanish');
5656            }
5657
5658            $totalSent = 0;
5659            $totalSentIds = [];
5660            $totalFailedIds = [];
5661            $totalErrorIds = [];
5662            $currentQuotationId = null;       
5663            
5664            $sentItems = [];
5665
5666            for ($i = 0; $i < count($result); $i++) {
5667                $budgetTypeId = $result[$i]->budget_type_id;
5668                $currentQuotationId = $result[$i]->quote_id;
5669                $body = $emailTemplate[$result[$i]->company_id]->html;
5670                if (isset($data['html']) && ! empty($data['html'])) {
5671                    $body = $emailTemplates[$result[$i]->company_id]['html'];
5672                }
5673                $subject = $emailTemplate[$result[$i]->company_id]->subject;
5674                $commercialUser = $result[$i]->commercial;
5675
5676                $blockedDomains = TblBlockedDomains::where('company_id', $result[$i]->company_id)->pluck('domain')->toArray();
5677
5678                if(config('services.sendgrid.staging')){
5679                    $toEmail = $user->email;
5680                } else {
5681                    $toEmail = $result[$i]->email;
5682                }
5683
5684                preg_match_all('/{{(.*?)}}/', (string) $body, $matches);
5685
5686                $parameters = $matches[1];
5687
5688                foreach ($parameters as $parameter) {
5689
5690                    if(in_array($parameter, $dateParameters)){
5691                        if($result[$i]->{$parameter}){
5692                            $result[$i]->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result[$i]->{$parameter})));
5693                        }
5694                    }
5695
5696                    if (in_array($parameter, $availableParameters)) {
5697                        $body = str_replace('{{'.$parameter.'}}', $result[$i]->{$parameter}, $body);
5698                    }
5699                }
5700
5701                preg_match_all('/{{(.*?)}}/', (string) $subject, $matches);
5702
5703                $parameters = $matches[1];
5704
5705                foreach ($parameters as $parameter) {
5706
5707                    if(in_array($parameter, $dateParameters)){
5708                        if($result[$i]->{$parameter}){
5709                            $result[$i]->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result[$i]->{$parameter})));
5710                        }
5711                    }
5712
5713                    if (in_array($parameter, $availableParameters)) {
5714                        $subject = str_replace('{{'.$parameter.'}}', $result[$i]->{$parameter}, $subject);
5715                    }
5716                }
5717
5718                $email = new \SendGrid\Mail\Mail;
5719
5720                $templateFiles = TblEmailFiles::where('email_template_id', $emailTemplate[$result[$i]->company_id]->id)->orderBy('order', 'asc')->get();
5721
5722                foreach ($templateFiles as $item) {
5723                    $f = storage_path('app/public/uploads/'.$item->filename);
5724
5725                    if (file_exists($f)) {
5726                        $imgpath = file_get_contents($f);
5727                        $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
5728                        $mimeType = mime_content_type($f);
5729
5730                        $email->addAttachment(
5731                            $imgpath,
5732                            $mimeType,
5733                            str_replace(' ', '', $item->original_name),
5734                            'inline',
5735                            str_replace(' ', '', $item->original_name),
5736                        );
5737
5738                        $body .= "<img src='cid:{$item->original_name}' style='height: 45px; padding-right: 6px' />";
5739                    } else {
5740                        Log::channel('email_failed_log')->error('File not found: '.$f);
5741                    }
5742                }
5743
5744                $html = '<!DOCTYPE html>';
5745                $html .= '<html>';
5746                $html .= '<head>';
5747                $html .= '<meta charset="UTF-8">';
5748                $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
5749                $html .= '</head>';
5750                $html .= '<body>';
5751                $html .= $body;
5752                $html .= '</body>';
5753                $html .= '</html>';
5754
5755                if ($automaticSendLimit != null) {
5756                    $sentBy = 'System';
5757                }
5758
5759                if ($toEmail != null) {
5760
5761                    $toEmail = explode(",", (string) $toEmail);
5762                    $toEmail = array_map(trim(...), $toEmail);
5763
5764                    $companyEmail = null;
5765
5766                    $queryUsers = "SELECT sender_email AS from_email, `name` AS from_name FROM tbl_users WHERE sender_enabled = 1 AND response_id IS NOT NULL AND verified = 1 AND `name` = '{$commercialUser}'";
5767                    $commercialEmail = DB::select($queryUsers);
5768
5769                    if (count($commercialEmail) > 0) {
5770                        $companyEmail = $commercialEmail[0];
5771                    } else {
5772                        if ($emailTemplate[$result[$i]->company_id]->from_id != null) {
5773                            $companyEmail = TblCompanyEmails::where('id', $emailTemplate[$result[$i]->company_id]->from_id)->first();
5774                        } else {
5775                            $companyEmail = TblCompanyEmails::where('is_active', 1)->where('verified', 1)->where('company_id', $result[$i]->company_id)->first();
5776                        }
5777                    }
5778
5779                    if (! $companyEmail) {
5780                        return response(['message' => 'KO', 'error' => __('language.no_active_verified_sender')]);
5781                    }
5782
5783                    $ccBcc = TblCcBcc::where('company_id', $result[$i]->company_id)->get();
5784
5785                    $email->setFrom($companyEmail->from_email, $companyEmail->from_name);
5786                    $email->setSubject($subject);
5787
5788                    $s = 0;
5789                    $addTo = [];
5790                    foreach ($toEmail as $clientEmail) {
5791                        $isValid = $this->isEmailValid($clientEmail);
5792                        if ($isValid) {
5793                            $domain = substr($clientEmail, strpos($clientEmail, '@') + 1);
5794
5795                            if (! in_array($domain, $blockedDomains)) {
5796                                $email->addTo($clientEmail);
5797                                array_push($addTo, $clientEmail);
5798                                $s++;
5799                            }
5800                        } else {
5801                            TblFollowUpLogs::create(
5802                                [
5803                                    'quotation_id' => $result[$i]->id,
5804                                    'email' => $clientEmail,
5805                                    'sent_by' => $sentBy,
5806                                    'status' => 'Invalid email'
5807                                ]
5808                            );
5809                        }
5810                    }
5811
5812                    if ($s == 0) {
5813                        array_push($totalFailedIds, $result[$i]->id);
5814                        Log::channel('email_failed_log')->error($s.'ID:'.$result[$i]->id.' - '.json_encode($toEmail));
5815
5816                        continue;
5817                    }
5818
5819                    if (!config('services.sendgrid.staging')) {
5820                        if(count($ccBcc) > 0){
5821                            foreach ($ccBcc as $data) {
5822                                if (! in_array($data->email, $toEmail)) {
5823                                    $email->addBcc($data->email);
5824                                }
5825                            }
5826                        }
5827                    }
5828
5829                    $email->addContent('text/html', $html);
5830
5831                    $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
5832
5833                    $files = TblFiles::where('quotation_id', $result[$i]->id)->where('is_internal', null)->get();
5834                    $requestSize = $this->calculateEmailRequestSize($email);
5835
5836                    foreach ($files as $key => $value) {
5837                        if ($files[$key]->filename && Storage::disk('s3')->exists("uploads/" . $files[$key]->filename)) {
5838                                                        
5839                            $fileContent = Storage::disk('s3')->get("uploads/" . $files[$key]->filename);
5840                            $fileSize = strlen((string) $fileContent) / 1048576;
5841                            $fileSize = (int) ceil($fileSize);
5842
5843                            $originalName = $files[$key]->original_name ?: basename((string) $files[$key]->filename);
5844                        }
5845                        else if ($files[$key]->file) {
5846                            $fileContent = $files[$key]->file;
5847
5848                            if (is_string($fileContent) && base64_decode($fileContent, true)) {
5849                                $fileContent = base64_decode($fileContent);
5850                            }
5851
5852                            $fileSize = strlen((string) $fileContent) / 1048576;
5853                            $fileSize = (int) ceil($fileSize);
5854
5855                            $originalName = $files[$key]->original_name ?: ($files[$key]->getAttribute('file_name') ?: 'file');
5856                        } else {
5857                            continue;
5858                        }
5859
5860                        if ($fileSize > 0) {
5861                            if ($requestSize + $fileSize < 25) {
5862                                $attachment = new \SendGrid\Mail\Attachment;
5863                                $attachment->setFilename($originalName);
5864                                $attachment->setDisposition('attachment');
5865
5866                                $attachment->setContent(base64_encode((string) $fileContent));
5867
5868                                if ($files[$key]->mime_type) {
5869                                    $attachment->setType($files[$key]->mime_type);
5870                                }
5871
5872                                $email->addAttachment($attachment);
5873                                $requestSize = $this->calculateEmailRequestSize($email);
5874                            } else {
5875                                Log::warning('File omitted due to size limit: '.$originalName);
5876                            }
5877                        }
5878                    }
5879
5880                    $sentWithBackup = false;
5881                    $response = $sendgrid->send($email);
5882
5883                    if (config('services.sendgrid.block') === true) {
5884    
5885                        Log::warning("SendGrid failed with status {$response->statusCode()}. Initiating Failover...");
5886                        try {
5887                            \Illuminate\Support\Facades\Mail::mailer('smtp_backup')->send([], [], function ($message) use ($html, $companyEmail, $toEmail, $subject, $files, $user, $ccBcc): void {
5888                                $message->from($companyEmail->from_email, $companyEmail->from_name)
5889                                    ->to($toEmail)
5890                                    ->subject($subject);
5891
5892                                $message->html($html);
5893
5894                                $message->cc($user->email);
5895
5896                                foreach ($ccBcc as $bcc) {
5897                                    $message->bcc($bcc->email);
5898                                }
5899
5900                                foreach ($files as $file) {
5901                                    if (Storage::disk('s3')->exists('uploads/'.$file->filename)) {
5902                                        $content = Storage::disk('s3')->get('uploads/'.$file->filename);
5903                                        $message->attachData($content, $file->original_name);
5904                                    } elseif ($file->file) {
5905                                        $content = $file->file;
5906                                        if (is_string($content) && base64_decode($content, true)) {
5907                                            $content = base64_decode($content);
5908                                        }
5909                                        $message->attachData($content, $file->original_name ?: 'archivo.pdf');
5910                                    }
5911                                }
5912                            });
5913
5914                            $sentWithBackup = true;
5915                        } catch (\Exception $backupException) {
5916                            $sentWithBackup = false;
5917                            Log::error('Backup server also failed: '.$backupException->getMessage());
5918                        }
5919
5920                        if (! $sentWithBackup) {
5921                            throw new \Exception("SendGrid failed with status {$response->statusCode()}. Backup server also failed: ".$backupException->getMessage());
5922                        }
5923
5924                    }
5925
5926                    if (($response->statusCode() == 202 || $sentWithBackup) && !config('services.sendgrid.block')) {
5927
5928                        $messageId = null;
5929
5930                        foreach ($response->headers() as $header) {
5931                            if (str_starts_with(strtolower((string) $header), 'x-message-id:')) {
5932                                $messageId = trim(substr((string) $header, strpos((string) $header, ':') + 1));
5933                                break;
5934                            }
5935                        }
5936
5937                        $lastFollowUp = TblLastFollowUpDate::where('company_id', $companyId)->where('budget_type_id', $budgetTypeId)->first();
5938                        $workingDaysN = $workingDays;
5939
5940                        if ($companyId == 0) {
5941                            $workingDaysN = $emailCompany[$result[$i]->company_id]->last_follow_up_date ?? 10;
5942                        }
5943
5944                        if ($lastFollowUp != null) {
5945                            if ($lastFollowUp->last_follow_up_date) {
5946                                $workingDaysN = $lastFollowUp->last_follow_up_date;
5947                            }
5948                        }
5949
5950                        $comment = 'Email automático enviado el '.date('Y-m-d H:i:s').' por usuario '.$sentBy;
5951                        $result[$i]->last_follow_up_comment = $result[$i]->last_follow_up_comment."\n".$comment;
5952                        $date = strtotime("{$workingDaysN} weekdays");
5953                        $result[$i]->last_follow_up_date = date('Y-m-d H:i:s', $date);
5954                        $totalSentQ = $result[$i]->total_sent + 1;
5955
5956                        array_push($totalSentIds, $result[$i]->id);
5957                        $sentItems[$result[$i]->company_id][] = $result[$i]->id;
5958
5959                        foreach ($addTo as $addToEmail) {
5960                            TblFollowUpLogs::create(
5961                                [
5962                                    'quotation_id' => $result[$i]->id,
5963                                    'email' => $addToEmail,
5964                                    'sent_by' => $sentBy,
5965                                    'status' => 'OK'
5966                                ]
5967                            );
5968                            $this->addUpdateLog($result[$i]->id, $userId, 'send_email_follow_up', null, null, 5);
5969                        }
5970
5971                        if ($totalSentQ >= $limitReminderEmails) {
5972                            $result[$i]->reason_for_not_following_up_id = 3;
5973                            $result[$i]->last_follow_up_date = null;
5974                        }
5975
5976                        TblQuotations::where('id', $result[$i]->id)->update(
5977                            [
5978                                'last_follow_up_comment' => $result[$i]->last_follow_up_comment,
5979                                'last_follow_up_date' => $result[$i]->last_follow_up_date,
5980                                'total_sent' => $result[$i]->total_sent + 1,
5981                                'y_message_id' => $messageId,
5982                                'y_status' => 'Processing',
5983                                'reason_for_not_following_up_id' => $result[$i]->reason_for_not_following_up_id,
5984                                'updated_by' => $sentBy,
5985                                'updated_at' => date('Y-m-d H:i:s')
5986                            ]
5987                        );
5988
5989                        $jsonBody = [];
5990
5991                        foreach ($toEmail as $clientEmail) {
5992                            $isValid = $this->isEmailValid($clientEmail);
5993                            $eventStatus = 'processed';
5994                            $eventResponse = '';
5995                            if (! $isValid) {
5996                                $eventStatus = 'invalid';
5997                                $eventResponse = 'Invalid email address';
5998                            }
5999
6000                            array_push(
6001                                $jsonBody,
6002                                [
6003                                    'email' =>  $clientEmail,
6004                                    'event' => $eventStatus,
6005                                    'sg_message_id' => $messageId,
6006                                    'smtp-id' => $messageId,
6007                                    'timestamp' => strtotime(date('Y-m-d H:i:s')),
6008                                    'response' => $eventResponse
6009                                ]
6010                            );
6011                        }
6012
6013                        TblSendgridWebhook::create(
6014                            [
6015                                'quotation_id' => $result[$i]->id,
6016                                'type' => 'followUps',
6017                                'json_body' => json_encode($jsonBody),
6018                                'x_message_id' => $messageId
6019                            ]
6020                        );
6021
6022                        Log::channel('email_log')->info("[RS-{$requestSize}] ID:".$result[$i]->id.' - EMAIL SENT');
6023                        $totalSent++;
6024                        $workingDays = 10;
6025                    } else {
6026                        $error = true;
6027                        array_push($totalErrorIds, $result[$i]->id);
6028                        Log::channel('email_failed_log')->error("[RS-{$requestSize}] ID:".$result[$i]->id.' - '.$response->body());
6029
6030                        foreach ($addTo as $addToEmail) {
6031                            TblFollowUpLogs::create(
6032                                [
6033                                    'quotation_id' => $result[$i]->id,
6034                                    'email' => $addToEmail,
6035                                    'sent_by' => $sentBy,
6036                                    'status' => $response->body()
6037                                ]
6038                            );
6039                        }
6040                    }
6041
6042                    $body = '';
6043                    $subject = '';
6044                }
6045            }
6046
6047            Cache::flush();
6048
6049            Log::channel('send_email_follow_ups')->info(
6050                json_encode(
6051                    [
6052                        "success" => $totalSentIds,
6053                        "failed" => $totalFailedIds,
6054                        "error" => $totalErrorIds
6055                    ]
6056                )
6057            );
6058
6059            $this->update_commercial_numbers($companyId);
6060
6061            if ($error) {
6062                return response(['message' => 'KO']);
6063            } else {
6064
6065                if ($automaticSendLimit != null) {
6066                    TblOrdersUpdateLogs::create(
6067                        [
6068                            'company_id' => $companyId,
6069                            'to_process' => 'Orders',
6070                            'status' => 'success',
6071                            'follow_ups_affected_rows' => $totalSent,
6072                            'processed_by' => $sentBy,
6073                            'started_at' => $startedAt,
6074                            'ended_at' => date('Y-m-d H:i:s')
6075                        ]
6076                    );
6077                }
6078
6079                foreach ($sentItems as $k => $v) {
6080                    $sentItems[$k] = [
6081                        'total' => count($sentItems[$k]),
6082                        'company' => $emailCompany[$k]->name,
6083                        'limit' => $emailCompany[$k]->limit_send,
6084                    ];
6085                }
6086
6087                $totalSent = array_values($sentItems);
6088
6089                return response(['message' => 'OK', 'data' => $totalSent]);
6090            }
6091
6092        } catch (\Throwable $e) {
6093            report(AppException::fromException($e, 'SEND_EMAIL_FOLLOW_UPS_EXCEPTION'));
6094            Log::channel('email_failed_log')->error($e->getMessage());
6095
6096            return response(['message' => 'KO', 'error' => $e->getMessage(), 'quotation_id' => $currentQuotationId]);
6097        }
6098    }
6099
6100    function create_sender_identity(Request $request): ResponseFactory|HttpResponse{
6101
6102        $data = $request->all();
6103        try {
6104
6105            $sData = $data;
6106            $companyId = $data['company_id'];
6107            $createdBy = $data['created_by'];
6108            unset($data['company_id']);
6109            unset($data['created_by']);
6110
6111            $sender = TblCompanyEmails::where('from_email', $data['from_email'])->where('verified', 1)->count();
6112
6113            if ($sender > 0) {
6114                TblCompanyEmails::create($sData);
6115
6116                return response(['message' => 'OK', 'data' => $data, 'is_verified' => 'yes']);
6117            }
6118
6119            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
6120            $data['reply_to'] = $data['from_email'];
6121            $data['reply_to_name'] = $data['from_name'];
6122            $requestBody = $data;
6123            $error = false;
6124
6125            // @phpstan-ignore-next-line
6126            $response = $sendgrid->client->verified_senders()->post($requestBody);
6127
6128            if ($response->statusCode() == 201) {
6129                $x = json_decode((string) $response->body());
6130
6131                $data['company_id'] = $companyId;
6132                $data['created_by'] = $createdBy;
6133                $data['response_id'] = $x->id;
6134                TblCompanyEmails::create($data);
6135                Log::channel('email_log')->info('EMAIL: '.$data['from_email'].' - VERIFICATION SENT');
6136            } else {
6137                $error = true;
6138                Log::channel('email_log')->error('REQUEST BODY: - '.$response->body());
6139            }
6140
6141            $response = json_decode((string) $response->body());
6142
6143            if ($error) {
6144                if ($response->errors[0]->message == 'already exists' && $response->errors[0]->field == 'from_email') {
6145                    TblCompanyEmails::create($sData);
6146
6147                    return response(['message' => 'OK', 'data' => $data, 'is_verified' => 'yes']);
6148                }
6149
6150                $errMessage = $response->errors[0]->field.': '.$response->errors[0]->message;
6151
6152                return response(['message' => 'KO', 'error' => $errMessage]);
6153            } else {
6154                return response(['message' => 'OK', 'data' => $response, 'is_verified' => 'no']);
6155            }
6156
6157        } catch (\Exception $e) {
6158            report(AppException::fromException($e, 'CREATE_SENDER_IDENTITY_EXCEPTION'));
6159            Log::channel('email_log')->error('EMAIL:' . $data['from_email'] . ' - ' . $e->getMessage());
6160            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6161        }
6162    }
6163
6164    function get_sender_identity($companyId): ResponseFactory|HttpResponse{
6165
6166        try {
6167
6168            $companyId = addslashes((string) $companyId);
6169
6170            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
6171
6172            // @phpstan-ignore-next-line
6173            $response = $sendgrid->client->verified_senders()->get();
6174
6175            if ($response->statusCode() == 200) {
6176                $x = json_decode((string) $response->body())->results;
6177
6178                foreach ($x as $item) {
6179                    TblCompanyEmails::where('from_email', $item->from_email)->update([
6180                        'verified' => $item->verified,
6181                        'reply_to' => $item->reply_to,
6182                        'response_id' => $item->id
6183                    ]);
6184                }
6185            }
6186
6187            $companyEmails = TblCompanyEmails::where('company_id', $companyId)->get();
6188
6189            return response(['message' => 'OK', 'data' => $companyEmails]);
6190
6191        } catch (\Exception $e) {
6192            report(AppException::fromException($e, 'GET_SENDER_IDENTITY_EXCEPTION'));
6193            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6194        }
6195    }
6196
6197    function get_all_sender_identity($companyId): ResponseFactory|HttpResponse{
6198
6199        try {
6200
6201            $companyId = addslashes((string) $companyId);
6202
6203            $query = "SELECT
6204                        id,
6205                        response_id,
6206                        nickname,
6207                        CONCAT(nickname, ' - ', from_email) from_email,
6208                        from_name,
6209                        reply_to,
6210                        reply_to_name,
6211                        address,
6212                        address2,
6213                        state,
6214                        city,
6215                        country,
6216                        zip,
6217                        verified,
6218                        locked,
6219                        is_active,
6220                        created_by,
6221                        created_at,
6222                        updated_by,
6223                        updated_at
6224                    FROM
6225                        tbl_company_emails
6226                    WHERE
6227                        company_id != {$companyId}
6228                        AND from_email NOT IN (
6229                        SELECT
6230                            from_email
6231                        FROM
6232                            tbl_company_emails
6233                        WHERE
6234                            company_id = {$companyId}
6235                        )
6236                    ";
6237
6238            $companyEmails = DB::select($query);
6239
6240            return response(['message' => 'OK', 'data' => $companyEmails]);
6241
6242        } catch (\Exception $e) {
6243            report(AppException::fromException($e, 'GET_ALL_SENDER_IDENTITY_EXCEPTION'));
6244            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6245        }
6246    }
6247
6248    function delete_sender_identity(Request $request): ResponseFactory|HttpResponse{
6249
6250        try {
6251
6252            $data = $request->all();
6253            $responseId = addslashes((string) $data['response_id']);
6254            $id = addslashes((string) $data['id']);
6255
6256            $sender = TblCompanyEmails::where('response_id', $responseId)->count();
6257
6258            if ($sender > 1) {
6259                TblCompanyEmails::where('id', $id)->delete();
6260            }else{
6261                $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
6262
6263                $response = $sendgrid->client->verified_senders()->_($responseId)->delete();
6264
6265                if ($response->statusCode() == 204) {
6266                    TblCompanyEmails::where('response_id', $responseId)->delete();
6267                }
6268            }
6269
6270            return response(['message' => 'OK']);
6271
6272        } catch (\Exception $e) {
6273            report(AppException::fromException($e, 'DELETE_SENDER_IDENTITY_EXCEPTION'));
6274            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6275        }
6276    }
6277
6278    function create_template(Request $request): ResponseFactory|HttpResponse{
6279
6280        try {
6281
6282            $data = $request->all();
6283
6284            $files = $request->file('files');
6285            unset($data['files']);
6286
6287            if ($files) {
6288                $totalFileCount = count($files);
6289                if ($totalFileCount > 2) {
6290                    return response(['message' => 'KO', 'error' => __('language.file_count_exceeded')]);
6291                }
6292            }
6293
6294            $result = TblEmailConfiguration::create($data);
6295            $id = $result->id;
6296
6297            $directory = 'public/uploads';
6298            $origFilename = 'fireservicetitan.png';
6299            $file = 'public/uploads/fireservicetitan.png';
6300
6301            $sourcePath = public_path($origFilename);
6302            $destinationPath = 'public/uploads/'.$origFilename;
6303
6304            $filename = $id.'-EI'.time().'-'.$origFilename;
6305
6306            Storage::putFileAs($directory, new \Illuminate\Http\File($sourcePath), $filename);
6307            Storage::disk('google')->put($filename, file_get_contents(storage_path().'/app/public/uploads/'.$filename));
6308
6309            TblEmailFiles::create(
6310                [
6311                    'email_template_id' => $id,
6312                    'original_name' => $origFilename,
6313                    'filename' => $filename,
6314                    'uploaded_by' => $data['created_by']
6315                ]
6316            );
6317
6318            if ($files) {
6319
6320                $uploadedFiles = [];
6321                $i = 0;
6322
6323                $combinedFilesSize = 0;
6324
6325                foreach ($files as $file) {
6326                    $i++;
6327
6328                    $origFilename = str_replace(' ', '', $file->getClientOriginalName());
6329
6330                    $filename = $id.'-EI'.time().'-'.$origFilename;
6331
6332                    $combinedFilesSize = $combinedFilesSize + $file->getSize();
6333
6334                    if ($combinedFilesSize > 25000000) {
6335                        return response(['message' => 'KO', 'error' => __('language.file_size_exceeded')]);
6336                    }
6337
6338                    Storage::putFileAs($directory, $file, $filename);
6339                    Storage::disk('google')->put($filename, file_get_contents(storage_path().'/app/public/uploads/'.$filename));
6340
6341                    if (in_array($origFilename, $uploadedFiles)) {
6342                        $origFilename = $origFilename.$i;
6343                    }
6344
6345                    TblEmailFiles::create(
6346                        [
6347                            'email_template_id' => $id,
6348                            'original_name' => $origFilename,
6349                            'filename' => $filename,
6350                            'uploaded_by' => $data['created_by']
6351                        ]
6352                    );
6353
6354                    $uploadedFiles[] = $file->getClientOriginalName();
6355                }
6356            }
6357
6358            return response(['message' => 'OK']);
6359
6360        } catch (\Exception $e) {
6361            report(AppException::fromException($e, 'CREATE_TEMPLATE_EXCEPTION'));
6362            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6363        }
6364    }
6365
6366    function get_email_files($emailTemplateId): ResponseFactory|HttpResponse{
6367
6368        try {
6369
6370            $emailTemplateId = addslashes((string) $emailTemplateId);
6371
6372            $result = TblEmailFiles::where('email_template_id', $emailTemplateId)->orderBy('order', 'asc')->get();
6373
6374            foreach ($result as $key => $value) {
6375                $path = storage_path('app/public/uploads/'.$result[$key]->filename);
6376
6377                if (File::exists($path)) {
6378                    $fileSizeBytes = File::size($path);
6379                    $result[$key]->setAttribute('filesize', $this->human_filesize($fileSizeBytes));
6380                    $result[$key]->original_name = $result[$key]->original_name." ({$result[$key]->getAttribute('filesize')})";
6381                    $result[$key]->setAttribute('img', 'data:image/png;base64,'.base64_encode(file_get_contents($path)));
6382                }
6383            }
6384
6385            return response(['message' => 'OK', 'data' => $result]);
6386
6387        } catch (\Exception $e) {
6388            report(AppException::fromException($e, 'GET_EMAIL_FILES_EXCEPTION'));
6389            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6390        }
6391    }
6392
6393    function download_email_template_file($fileId): ResponseFactory|HttpResponse{
6394
6395        try {
6396
6397            $fileId = addslashes((string) $fileId);
6398
6399            $result = TblEmailFiles::where('file_id', $fileId)->first();
6400
6401            if ($result) {
6402                $path = storage_path('app/public/uploads/'.$result->filename);
6403
6404                if (! Storage::disk('public')->exists('uploads/'.$result->filename)) {
6405                    return response(['message' => 'KO']);
6406                }
6407
6408                return response()->download($path);
6409            }
6410
6411            return response(['message' => 'KO']);
6412
6413        } catch (\Exception $e) {
6414            report(AppException::fromException($e, 'DOWNLOAD_EMAIL_TEMPLATE_FILE_EXCEPTION'));
6415            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6416        }
6417
6418    }
6419
6420    function delete_email_template_file($fileId): ResponseFactory|HttpResponse{
6421
6422        try {
6423
6424            $fileId = addslashes((string) $fileId);
6425            $file = TblEmailFiles::where('file_id', $fileId)->first();
6426            $result = TblEmailFiles::where('file_id', $fileId)->first();
6427
6428            if ($result) {
6429                TblEmailFiles::where('file_id', $fileId)->delete();
6430                $path = storage_path('app/public/uploads/'.$result->filename);
6431                Storage::disk('public')->delete('uploads/'.$result->filename);
6432            }
6433
6434            return response(['message' => 'OK']);
6435
6436        } catch (\Exception $e) {
6437            report(AppException::fromException($e, 'DELETE_EMAIL_TEMPLATE_FILE_EXCEPTION'));
6438            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6439        }
6440    }
6441
6442    public function update_email_template_order(Request $request, $id)
6443    {
6444
6445        try {
6446
6447            $id = addslashes((string) $id);
6448            $data = $request->all();
6449
6450            foreach ($data as $item) {
6451                TblEmailFiles::where('file_id', $item['file_id'])->update(['order' => $item['order']]);
6452            }
6453
6454        } catch (\Exception $e) {
6455            report(AppException::fromException($e, 'UPDATE_EMAIL_TEMPLATE_ORDER_EXCEPTION'));
6456            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6457        }
6458
6459    }
6460
6461
6462    function update_email_template(Request $request, $id): ResponseFactory|HttpResponse{
6463
6464        try {
6465
6466            $data = $request->all();
6467
6468            $id = addslashes((string) $id);
6469
6470            $files = $request->file('files');
6471            unset($data['files']);
6472
6473            $fileCount = TblEmailFiles::where('email_template_id', $id)->count();
6474
6475            if ($files) {
6476                $totalFileCount = $fileCount + count($files);
6477                if ($totalFileCount > 3) {
6478                    return response(['message' => 'KO', 'error' => __('language.file_count_exceeded')]);
6479                }
6480            }
6481
6482            if(isset($data['cron_default'])){
6483                TblEmailConfiguration::where('company_id', $data['company_id'])->where('cron_default', 1)->update(['cron_default' => 0]);
6484            }
6485
6486            $data['updated_at'] = date('Y-m-d H:i:s');
6487            TblEmailConfiguration::where('id', $id)->update($data);
6488
6489            if ($files) {
6490
6491                $directory = 'public/uploads';
6492                $uploadedFiles = [];
6493                $i = 0;
6494
6495                $combinedFilesSize = 0;
6496                foreach ($files as $file) {
6497                    $i++;
6498                    $origFilename = str_replace(' ', '', $file->getClientOriginalName());
6499                    $filename = $id.'-EI'.time().'-'.$origFilename;
6500
6501                    $combinedFilesSize = $combinedFilesSize + $file->getSize();
6502
6503                    if ($combinedFilesSize > 25000000) {
6504                        return response(['message' => 'KO', 'error' => __('language.file_size_exceeded')]);
6505                    }
6506
6507                    Storage::putFileAs($directory, $file, $filename);
6508                    Storage::disk('google')->put($filename, file_get_contents(storage_path().'/app/public/uploads/'.$filename));
6509
6510                    if (in_array($origFilename, $uploadedFiles)) {
6511                        $origFilename = $origFilename.$i;
6512                    }
6513
6514                    TblEmailFiles::create(
6515                        [
6516                            'email_template_id' => $id,
6517                            'original_name' => $origFilename,
6518                            'filename' => $filename,
6519                            'uploaded_by' => $data['updated_by']
6520                        ]
6521                    );
6522
6523                    $uploadedFiles[] = $file->getClientOriginalName();
6524                }
6525            }
6526
6527            return response(['message' => 'OK']);
6528
6529        } catch (\Exception $e) {
6530            report(AppException::fromException($e, 'UPDATE_EMAIL_TEMPLATE_EXCEPTION'));
6531            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6532        }
6533
6534    }
6535
6536    function delete_template($id): ResponseFactory|HttpResponse{
6537
6538        try {
6539
6540            $id = addslashes((string) $id);
6541
6542            TblEmailConfiguration::where('id', $id)->delete();
6543
6544            return response(['message' => 'OK']);
6545
6546        } catch (\Exception $e) {
6547            report(AppException::fromException($e, 'DELETE_TEMPLATE_EXCEPTION'));
6548            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6549        }
6550
6551    }
6552
6553    function get_email_template($companyId): ResponseFactory|HttpResponse{
6554
6555        try {
6556
6557            $companyId = addslashes((string) $companyId);
6558
6559            $where = '';
6560            if ($companyId != 0) {
6561                $where = " a.company_id = {$companyId} ";
6562            } else {
6563                $where = " a.company_id IN ({$this->companyId}";
6564            }
6565
6566            $query = "SELECT
6567                        a.id,
6568                        a.from_id,
6569                        CASE
6570                            WHEN a.type = 'followUps' THEN 'Follow-ups'
6571                            WHEN a.type = 'sendToClient' THEN 'Send to client'
6572                        END types,
6573                        (SELECT from_email FROM tbl_company_emails WHERE id = a.from_id) from_email,
6574                        a.name,
6575                        b.name company_name,
6576                        b.company_id,
6577                        a.subject,
6578                        a.html,
6579                        a.is_active,
6580                        a.type,
6581                        a.created_by,
6582                        a.created_at,
6583                        a.updated_by,
6584                        a.updated_at,
6585                        a.cron_default
6586                    FROM tbl_email_configuration a
6587                    LEFT JOIN tbl_companies b
6588                        ON b.company_id = a.company_id
6589                    WHERE {$where}
6590                    ORDER BY a.id DESC";
6591
6592            $result = DB::select($query);
6593
6594            return response(['message' => 'OK', 'data' => $result]);
6595
6596        } catch (\Exception $e) {
6597            report(AppException::fromException($e, 'GET_EMAIL_TEMPLATE_EXCEPTION'));
6598            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6599        }
6600
6601    }
6602
6603    function update_sender_identity(Request $request, $responseId): ResponseFactory|HttpResponse{
6604
6605        try {
6606
6607            $data = $request->all();
6608            $companyId = $data['company_id'];
6609            $updatedBy = $data['updated_by'];
6610            $active = $data['is_active'];
6611            $id = addslashes((string) $data['id']);
6612            unset($data['id']);
6613            unset($data['company_id']);
6614            unset($data['updated_by']);
6615            unset($data['is_active']);
6616
6617            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
6618            $data['reply_to'] = $data['from_email'];
6619            $data['reply_to_name'] = $data['from_name'];
6620            $requestBody = $data;
6621            $error = false;
6622
6623            $response = $sendgrid->client->verified_senders()->_($responseId)->patch($requestBody);
6624
6625            if ($response->statusCode() == 200) {
6626                $x = json_decode((string) $response->body());
6627
6628                $data['updated_by'] = $updatedBy;
6629                $data['updated_at'] = date('Y-m-d H:i:s');
6630                $data['is_active'] = $active;
6631
6632                TblCompanyEmails::where('company_id', $companyId)->update(['is_active' => 0]);
6633                TblCompanyEmails::where('id', $id)->update($data);
6634
6635                Log::channel('email_log')->info('EMAIL: '.$data['from_email'].' - UPDATED');
6636            } else {
6637                $error = true;
6638                Log::channel('email_log')->error('REQUEST BODY: - '.$response->body());
6639            }
6640
6641            $response = json_decode((string) $response->body());
6642
6643            if ($error) {
6644                $errMessage = @$response->errors[0]->field.': '.@$response->errors[0]->message;
6645
6646                return response(['message' => 'KO', 'error' => $errMessage]);
6647            } else {
6648                return response(['message' => 'OK', 'data' => $response]);
6649            }
6650
6651        } catch (\Exception $e) {
6652            report(AppException::fromException($e, 'UPDATE_SENDER_IDENTITY_EXCEPTION'));
6653            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6654        }
6655
6656    }
6657
6658    function resend_verification($id): ResponseFactory|HttpResponse{
6659
6660        try {
6661
6662            $id = addslashes((string) $id);
6663
6664            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
6665
6666            $response = $sendgrid->client->verified_senders()->resend()->_($id)->post();
6667
6668            if ($response->statusCode() == 204) {
6669                return response(['message' => 'OK']);
6670            }else{
6671                $response = json_decode((string) $response->body());
6672                $errMessage = $response->errors[0]->error_id . ': ' . $response->errors[0]->message;
6673                return response(['message' => 'KO', 'error' => $errMessage]);
6674            }
6675
6676        } catch (\Exception $e) {
6677            report(AppException::fromException($e, 'RESEND_VERIFICATION_EXCEPTION'));
6678            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6679        }
6680    }
6681
6682    function list_quotation_analytics_by_performance(Request $request): ResponseFactory|HttpResponse{
6683
6684        try {
6685
6686            $data = $request->all();
6687            $companyId = addslashes((string) $data['company_id']);
6688            $where  = "";
6689
6690            if ($companyId != 0) {
6691                $where .= " AND company_id = {$companyId} AND for_add = 0 ";
6692            } else {
6693                $where .= " AND company_id IN ({$this->companyId}) AND for_add = 0 ";
6694            }
6695
6696            if (isset($data['budget_status']) && $data['budget_status'] != null) {
6697                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
6698            }
6699
6700            $latestYear = TblQuotations::max(DB::raw('YEAR(issue_date)'));
6701            $md = date('m-d');
6702
6703            $query = "SELECT
6704                        q.years,
6705                        q.totalIssue,
6706                        (q.total_acceptance_percentage - q.totalAcceptancePercentage) / q.totalAcceptancePercentage * 100 totalAcceptancePercentage,
6707                        q.totalAccept
6708                    FROM
6709                        (
6710                        SELECT
6711                            YEAR(a.issue_date) years,
6712                            (b.total_issued - COUNT(a.issue_date)) / COUNT(a.issue_date) * 100 totalIssue,
6713                            COUNT(
6714                                CASE WHEN a.acceptance_date IS NOT NULL
6715                                AND DATE_FORMAT(a.acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(a.acceptance_date), '-01-01')
6716                                AND CONCAT(YEAR(a.acceptance_date), '-{$md}')
6717                                AND YEAR(a.acceptance_date) = YEAR(a.issue_date)
6718                                AND a.budget_status_id = 3 THEN 1 END
6719                            ) / COUNT(a.issue_date) * 100 totalAcceptancePercentage,
6720                            b.total_acceptance_percentage,
6721                            (b.total_acceptance -
6722                                COUNT(
6723                                    CASE WHEN a.acceptance_date IS NOT NULL
6724                                    AND DATE_FORMAT(a.acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(a.acceptance_date), '-01-01')
6725                                    AND CONCAT(YEAR(a.acceptance_date), '-{$md}')
6726                                    AND YEAR(a.acceptance_date) = YEAR(a.issue_date)
6727                                    AND a.budget_status_id = 3 THEN 1 END)
6728                                    ) / COUNT(
6729                                            CASE WHEN a.acceptance_date IS NOT NULL
6730                                            AND DATE_FORMAT(a.acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(a.acceptance_date), '-01-01')
6731                                            AND CONCAT(YEAR(a.acceptance_date), '-{$md}')
6732                                            AND YEAR(a.acceptance_date) = YEAR(a.issue_date)
6733                                            AND a.budget_status_id = 3 THEN 1 END
6734                            ) * 100 totalAccept
6735                        FROM
6736                            tbl_quotations a
6737                            JOIN (
6738                            SELECT
6739                                YEAR(issue_date) current_year,
6740                                COUNT(issue_date) total_issued,
6741                                COUNT(
6742                                    CASE WHEN acceptance_date IS NOT NULL
6743                                    AND DATE_FORMAT(acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(acceptance_date), '-01-01')
6744                                    AND CONCAT(YEAR(acceptance_date), '-{$md}')
6745                                    AND YEAR(acceptance_date) = YEAR(issue_date)
6746                                    AND budget_status_id = 3 THEN 1 END
6747                                ) total_acceptance,
6748                                COUNT(
6749                                    CASE WHEN acceptance_date IS NOT NULL
6750                                    AND DATE_FORMAT(acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(acceptance_date), '-01-01')
6751                                    AND CONCAT(YEAR(acceptance_date), '-{$md}')
6752                                    AND YEAR(acceptance_date) = YEAR(issue_date)
6753                                    AND budget_status_id = 3 THEN 1 END
6754                                ) / COUNT(issue_date) * 100 total_acceptance_percentage
6755                            FROM
6756                                tbl_quotations
6757                            WHERE
6758                                issue_date IS NOT NULL
6759                                AND YEAR(issue_date) = {$latestYear}
6760                                AND DATE_FORMAT(issue_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(issue_date), '-01-01')
6761                                AND CONCAT(YEAR(issue_date), '-{$md}')
6762                                {$where}
6763                            GROUP by
6764                                1
6765                            ) b
6766                        WHERE
6767                            a.issue_date IS NOT NULL
6768                            AND a.budget_type_id != 7
6769                            AND a.budget_type_id IS NOT NULL
6770                            AND {$latestYear} > YEAR(a.issue_date)
6771                            AND DATE_FORMAT(a.issue_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(a.issue_date), '-01-01')
6772                            AND CONCAT(YEAR(a.issue_date), '-{$md}')
6773                        GROUP BY
6774                            1
6775                        ORDER BY
6776                            YEAR(issue_date) DESC
6777                        ) q
6778                    ";
6779
6780            $resultYtd = DB::select($query);
6781
6782            $query = "SELECT
6783                        YEAR(issue_date) years,
6784                        COUNT(issue_date) totalIssue,
6785                        COUNT(
6786                            CASE WHEN acceptance_date IS NOT NULL
6787                            AND DATE_FORMAT(acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(acceptance_date), '-01-01')
6788                            AND CONCAT(YEAR(acceptance_date), '-{$md}')
6789                            AND YEAR(acceptance_date) = YEAR(issue_date)
6790                            AND budget_status_id = 3 THEN 1 END
6791                        ) totalAccept,
6792                        COUNT(
6793                            CASE WHEN acceptance_date IS NOT NULL
6794                            AND DATE_FORMAT(acceptance_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(acceptance_date), '-01-01')
6795                            AND CONCAT(YEAR(acceptance_date), '-{$md}')
6796                            AND YEAR(acceptance_date) = YEAR(issue_date)
6797                            AND budget_status_id = 3 THEN 1 END
6798                        ) / COUNT(issue_date) * 100 totalAcceptancePercentage
6799                    FROM
6800                        tbl_quotations
6801                    WHERE
6802                        issue_date IS NOT NULL
6803                        AND budget_type_id != 7
6804                        AND budget_type_id IS NOT NULL
6805                        AND DATE_FORMAT(issue_date, '%Y-%m-%d') BETWEEN CONCAT(YEAR(issue_date), '-01-01')
6806                        AND CONCAT(YEAR(issue_date), '-{$md}')
6807                        {$where}
6808                    GROUP BY
6809                        1
6810                    ORDER BY
6811                        YEAR(issue_date) DESC
6812                    ";
6813
6814            $resultYears = DB::select($query);
6815
6816            return response(['message' => 'OK', 'ytdData' => $resultYtd, 'yearsData' => $resultYears]);
6817
6818        } catch (\Exception $e) {
6819            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_BY_PERFORMANCE_EXCEPTION'));
6820            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6821        }
6822    }
6823
6824    function list_orders_update_logs($companyId): ResponseFactory|HttpResponse{
6825
6826        try {
6827
6828            $where = '';
6829
6830            if ($companyId != 0) {
6831                $where .= " a.company_id = {$companyId}";
6832            } else {
6833                $where .= " a.company_id IN ({$this->companyId})";
6834            }
6835
6836            $query = "SELECT
6837                        a.id,
6838                        b.name company,
6839                        a.to_process,
6840                        a.rejected_affected_rows,
6841                        a.for_add_deleted_affected_rows,
6842                        a.month_change_affected_rows,
6843                        a.follow_ups_affected_rows,
6844                        a.sync_succesfull,
6845                        a.sync_error,
6846                        a.sync_error_message,
6847                        a.sync_success_ids,
6848                        a.cleared_email_errors,
6849                        a.remaining_email_errors,
6850                        CASE
6851                            WHEN a.rejected_affected_rows IS NOT NULL THEN a.rejected_affected_rows
6852                            WHEN a.for_add_deleted_affected_rows IS NOT NULL THEN a.for_add_deleted_affected_rows
6853                            WHEN a.month_change_affected_rows IS NOT NULL THEN a.month_change_affected_rows
6854                            WHEN a.follow_ups_affected_rows IS NOT NULL THEN a.follow_ups_affected_rows
6855                        END totals,
6856                        a.status,
6857                        a.processed_by,
6858                        a.started_at,
6859                        a.ended_at,
6860                        a.rejected_automatically_ids
6861                    FROM `tbl_orders_update_logs` a
6862                    LEFT JOIN tbl_companies b
6863                        ON a.company_id = b.company_id
6864                    WHERE {$where}
6865                    ORDER BY started_at DESC";
6866
6867            $result = DB::select($query);
6868
6869            return response(['message' => 'OK', 'data' => $result]);
6870
6871        } catch (\Exception $e) {
6872            report(AppException::fromException($e, 'LIST_ORDERS_UPDATE_LOGS_EXCEPTION'));
6873            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6874        }
6875
6876    }
6877
6878    function list_g3w_orders_update_logs($companyId): ResponseFactory|HttpResponse{
6879
6880        try {
6881            $result = TblG3WOrdersUpdateLogs::where('company_id', $companyId)
6882                ->where('processed_by', 'System')
6883                ->get();
6884
6885            return response(['message' => 'OK', 'data' => $result]);
6886
6887        } catch (\Exception $e) {
6888            report(AppException::fromException($e, 'LIST_G3W_ORDERS_UPDATE_LOGS_EXCEPTION'));
6889            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6890        }
6891
6892    }
6893
6894    function list_g3w_orders_failed($companyId): ResponseFactory|HttpResponse {
6895        try {
6896            $logs = TblG3WOrdersUpdateLogs::where('company_id', $companyId)
6897                ->where('sync_error', '>', 0)
6898                ->get();
6899
6900            $individualErrors = [];
6901
6902            foreach ($logs as $log) {
6903                $errorIds = json_decode((string) $log->sync_error_ids);
6904                $errorMessages = preg_split('/(?=Error (sincronizando|actualizando) el presupuesto)/', (string) $log->sync_error_message, -1, PREG_SPLIT_NO_EMPTY);
6905
6906                if (is_array($errorIds)) {
6907                    foreach ($errorIds as $index => $id) {
6908                        $rawMsg = $errorMessages[$index] ?? 'Error desconocido';
6909                        $cleanMsg = trim((string) preg_replace('/.*?el presupuesto \d+:/i', '', $rawMsg));
6910                        $cleanMsg = rtrim($cleanMsg, ',');
6911
6912                        $individualErrors[] = [
6913                            'quote_id' => $id,
6914                            'status' => 'Failed',
6915                            'started_at' => $log->started_at,
6916                            'ended_at' => $log->ended_at,
6917                            'reason' => $cleanMsg,
6918                            'log_id' => $log->id,
6919                        ];
6920                    }
6921                }
6922            }
6923
6924            return response(['message' => 'OK', 'data' => $individualErrors]);
6925
6926        } catch (\Exception $e) {
6927            report($e);
6928
6929            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6930        }
6931    }
6932
6933    function update_budget_status_rejected_manual(Request $request): ResponseFactory|HttpResponse{
6934
6935        try {
6936
6937            $data = $request->all();
6938
6939            $update = $this->update_budget_status_rejected($data['company_id'], $data['processed_by']);
6940
6941            if ($update) {
6942                return response(['message' => 'OK']);
6943            } else {
6944                return response(['message' => 'KO']);
6945            }
6946
6947        } catch (\Exception $e) {
6948            report(AppException::fromException($e, 'UPDATE_BUDGET_STATUS_REJECTED_MANUAL_EXCEPTION'));
6949            return response(['message' => 'KO', 'error' => $e->getMessage()]);
6950        }
6951    }
6952
6953    function update_budget_status_rejected($companyId = null, $processedBy = "System"): ResponseFactory|HttpResponse{
6954
6955        $startedAt = date('Y-m-d H:i:s');
6956        try {
6957
6958            $startedAt = date('Y-m-d H:i:s');
6959            $companyId = addslashes((string) $companyId);
6960            $where = "";
6961
6962            $budgetTypes = TblBudgetTypes::get();
6963
6964            if (count($budgetTypes) > 0) {
6965
6966                $companies = [];
6967
6968                if ($companyId != 0) {
6969                    $companies = TblCompanies::where('company_id', $companyId)->get();
6970                    $where = "AND company_id = {$companyId}";
6971                } else {
6972                    $companies = TblCompanies::get();
6973                }
6974
6975                for ($i = 0; $i < count($budgetTypes); $i++) {
6976
6977                    $days = $budgetTypes[$i]->duration;
6978                    $id = $budgetTypes[$i]->budget_type_id;
6979
6980                    if (! $days) {
6981                        continue;
6982                    }
6983
6984                    for ($c = 0; $c < count($companies); $c++) {
6985                        $companyId = $companies[$c]->company_id;
6986
6987                        $query = "SELECT
6988                                    GROUP_CONCAT(id) ids
6989                                FROM
6990                                    tbl_quotations
6991                                WHERE
6992                                    budget_type_id = {$id}
6993                                AND budget_status_id IN (1, 2, 11)
6994                                AND DATEDIFF(NOW(), issue_date) >= {$days}
6995                                AND for_add = 0
6996                                {$where}";
6997
6998                        $result = DB::select($query);
6999
7000                        if (count($result) > 0) {
7001
7002                            $ids = $result[0]->ids;
7003
7004                            if ($ids != null || $ids != '') {
7005                                $query = "UPDATE
7006                                            tbl_quotations
7007                                        SET
7008                                            budget_status_id = 7
7009                                        WHERE
7010                                            id IN ({$ids})";
7011
7012                                DB::select($query);
7013
7014                                TblOrdersUpdateLogs::create(
7015                                    [
7016                                        'company_id' => $companyId,
7017                                        'to_process' => 'Orders',
7018                                        'status' => 'success',
7019                                        'rejected_automatically_ids' => $ids,
7020                                        'processed_by' => $processedBy,
7021                                        'started_at' => $startedAt,
7022                                        'ended_at' => date('Y-m-d H:i:s')
7023                                    ]
7024                                );
7025                            }
7026                        }
7027                    }
7028                }
7029            }
7030
7031            Cache::flush();
7032
7033            return response(['message' => 'OK']);
7034
7035        } catch (\Exception $e) {
7036            report(AppException::fromException($e, 'UPDATE_BUDGET_STATUS_REJECTED_MANUAL_EXCEPTION'));
7037
7038            TblOrdersUpdateLogs::create(
7039                [
7040                    'company_id' => $companyId,
7041                    'to_process' => 'Orders',
7042                    'status' => $e->getMessage(),
7043                    'processed_by' => $processedBy,
7044                    'started_at' => $startedAt,
7045                    'ended_at' => date('Y-m-d H:i:s')
7046                ]
7047            );
7048
7049            return response(['message' => 'KO', 'error' => $e->getMessage()]);
7050        }
7051
7052    }
7053
7054    function bulk_update_quotation(Request $request): ResponseFactory|HttpResponse{
7055
7056        // try {
7057
7058        $data = $request->all();
7059
7060        $r = new Request([
7061            'filterModel' => $data['filterModel'],
7062            'sortModel' => $data['sortModel'],
7063            'start' => 0,
7064            'end' => 999999999,
7065            'company_id' => $data['company_id'],
7066            'user_id' => $data['user_id'],
7067            'ids' => $data['ids'],
7068            'searchText' => $data['searchText'],
7069            'ids_not_in' => $data['ids_not_in'],
7070        ]);
7071
7072        $listQuotations = $this->list_quotations($r);
7073        $d = $listQuotations->original['data'];
7074
7075        if (count($d) > 0) {
7076            if (isset($data['last_follow_up_date']) && $data['last_follow_up_date'] == 1) {
7077                unset($data['last_follow_up_date']);
7078            }
7079            // unset($data['last_follow_up_date']);
7080            unset($data['filterModel']);
7081            unset($data['sortModel']);
7082            unset($data['start']);
7083            unset($data['end']);
7084            unset($data['company_id']);
7085            unset($data['user_id']);
7086            unset($data['ids']);
7087            unset($data['searchText']);
7088            unset($data['ids_not_in']);
7089
7090            $result = [];
7091            for ($i = 0; $i < count($d); $i++) {
7092                array_push($result, $d[$i]->id);
7093            }
7094
7095            TblQuotations::whereIn('id', $result)->update($data);
7096
7097            Cache::flush();
7098        }
7099
7100        return response(['message' => 'OK', $data]);
7101
7102        // } catch (\Exception $e) {
7103        //     return response(['message' => 'KO', 'error' => $e->getMessage()]);
7104        // }
7105
7106    }
7107
7108    function move_budget_and_job(Request $request): ResponseFactory|HttpResponse{
7109
7110        try {
7111
7112            $data = $request->all();
7113            $id = addslashes((string) $data['id']);
7114            $companyId = addslashes((string) $data['company_id']);
7115
7116            unset($data['id']);
7117            unset($data['company_id']);
7118
7119            $budget = TblQuotations::where('id', $id)->first();
7120
7121            $quotationId = $budget->id;
7122            $companyIdJob = $budget->company_id;
7123
7124            $query = "SELECT
7125                        COUNT(1) total
7126                    FROM tbl_company_users a
7127                    LEFT JOIN tbl_users b
7128                        ON a.user_id = b.id
7129                    WHERE a.company_id = {$companyId}
7130                    AND b.name = '{$budget->commercial}'
7131                    ORDER BY b.name ASC";
7132
7133            $result = DB::select($query);
7134
7135            $commercial = $budget->commercial;
7136
7137            if ($result[0]->total == 0) {
7138                $commercial = $data['created_by'];
7139            }
7140
7141            $r = new Request([
7142                'created_by' => $commercial,
7143            ]);
7144
7145            $result = $this->get_number($r, $companyId, 1);
7146            $newNumber = $result->original['number'];
7147
7148            TblQuotations::where('id', $id)->update(
7149                [
7150                    'quote_id' => $newNumber,
7151                    'company_id' => $companyId,
7152                    'from_company_id' => $budget->company_id,
7153                    'commercial' => $commercial,
7154                ]
7155            );
7156
7157            $job = TblOngoingJobs::where('quotation_id', $quotationId)->first();
7158
7159            $jobId = null;
7160
7161            if ($job) {
7162                $jobId = $job->id;
7163
7164                $query = "SELECT
7165                            COUNT(1) total
7166                        FROM tbl_company_users a
7167                        LEFT JOIN tbl_users b
7168                            ON a.user_id = b.id
7169                        WHERE a.company_id = {$companyId}
7170                        AND b.name = '{$job->responsible_for_work}'
7171                        ORDER BY b.name ASC";
7172
7173                $result = DB::select($query);
7174
7175                $responsibleForWork = $job->responsible_for_work;
7176
7177                if ($result[0]->total == 0) {
7178                    $responsibleForWork = $data['created_by'];
7179                }
7180
7181                TblOngoingJobs::where('quotation_id', $id)->update(
7182                    [
7183                        'quote_id' => $newNumber,
7184                        'company_id' => $companyId,
7185                        'responsible_for_work' => $responsibleForWork,
7186                    ]
7187                );
7188            }
7189
7190            Cache::flush();
7191
7192            return response([
7193                'message' => 'OK',
7194                'quotation_id' => $quotationId,
7195                'job_id' => $jobId,
7196            ]);
7197
7198        } catch (\Exception $e) {
7199            report(AppException::fromException($e, 'MOVE_BUDGET_AND_JOB_EXCEPTION'));
7200            return response(['message' => 'KO', 'error' => $e->getMessage()]);
7201        }
7202    }
7203
7204    function list_quotation_analytics_by_types_of_budgets_created_per_week(Request $request): ResponseFactory|HttpResponse{
7205
7206        try {
7207
7208            $data = $request->all();
7209            $companyId = addslashes((string) $data['company_id']);
7210            $field = $data['field'];
7211
7212            $where = "";
7213            $whereYear = "";
7214            $dateLflArray = [];
7215            $companyIds = $this->companyIds;
7216
7217            if($companyId != 0){
7218                $companyIds = [$companyId];
7219            }
7220
7221            $acc = '';
7222            if ($field == 'acceptance_date') {
7223                $acc = ' AND q.acceptance_date IS NOT NULL ';
7224                // $field = 'created_at';
7225
7226                if (@$data['data_to_display'] == 4) {
7227                    $field = 'created_at';
7228                    $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
7229                }
7230            } else {
7231                $field = 'created_at';
7232                $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
7233            }
7234
7235            if (isset($data['years']) && $data['years'] != null) {
7236
7237                if (count($data['years']) > 0) {
7238                    foreach ($data['years'] as $year) {
7239                        if (isset($data['week']) && $data['week'] != null) {
7240                            $w = sprintf('%02d', $data['week']);
7241                            $whereYear .= " AND YEARWEEK(q.{$field}, 1) = '{$year}{$w}'";
7242                        } else {
7243                            $whereYear .= " AND YEAR(q.{$field}) = {$year}";
7244                        }
7245                    }
7246                }
7247            }
7248
7249            foreach ($companyIds as $v) {
7250
7251                $lflWhere = " AND q.company_id = {$v} ";
7252
7253                $query = "SELECT
7254                            CONCAT(
7255                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
7256                                ' - ',
7257                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
7258                            ) AS date_like,
7259                            YEAR(q.{$field}) 'year',
7260                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
7261                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
7262                            {$v} 'company_id'
7263                        FROM
7264                            tbl_quotations q
7265                        WHERE
7266                            q.{$field} IS NOT NULL
7267                            AND q.for_add = 0
7268                            {$lflWhere}
7269                            {$whereYear}
7270                        GROUP BY YEAR(q.{$field})
7271                        ORDER BY YEAR(q.{$field}) DESC";
7272
7273                $dateLike = DB::select($query);
7274
7275                $dateLflArray[$v] = $dateLike;
7276            }
7277
7278            $isFy = true;
7279
7280            if (isset($data['ytd']) && $data['ytd'] != null && $data['ytd'] == true) {
7281                $isFy = false;
7282                $ytdArray = [];
7283                $ytdAcceptanceArray = [];
7284                $lflCompanyIds = [];
7285                foreach ($dateLflArray as $k => $v) {
7286                    foreach ($dateLflArray[$k] as $item) {
7287                        $year = $item->year;
7288                        $now = date('m-d');
7289                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
7290                    }
7291
7292                    $ytdArray = implode(' OR ', $ytdArray);
7293                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
7294                    $ytdArray = [];
7295                }
7296
7297                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
7298                $where .= " AND ({$lflCompanyIds}";
7299            }
7300
7301            if (isset($data['lfl']) && $data['lfl'] != null && $data['lfl'] == true) {
7302                $isFy = false;
7303                $lflArray = [];
7304                $ytdAcceptanceArray = [];
7305                $lflCompanyIds = [];
7306                foreach ($dateLflArray as $k => $v) {
7307                    foreach ($dateLflArray[$k] as $item) {
7308                        $year = $item->year;
7309                        $min_date_like = $item->min_date_like;
7310                        $max_date_like = $item->max_date_like;
7311                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
7312                    }
7313
7314                    $lflArray = implode(' OR ', $lflArray);
7315                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
7316                    $lflArray = [];
7317                }
7318
7319                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
7320                $where .= " AND ({$lflCompanyIds}";
7321            }
7322
7323            if ($isFy) {
7324                if ($companyId != 0) {
7325                    $where .= " AND q.company_id = {$companyId} ";
7326                } else {
7327                    $where .= " AND q.company_id IN ({$this->companyId})";
7328                }
7329            }
7330
7331            if (isset($data['source']) && $data['source'] != null) {
7332                $where .= " AND s.name = '{$data['source']}'";
7333            }
7334
7335            if (isset($data['month']) && $data['month'] != null) {
7336                $where .= " AND MONTH(q.{$field}) = '{$data['month']}'";
7337            }
7338
7339            if (isset($data['commercial']) && $data['commercial'] != null) {
7340                $where .= " AND q.commercial = '{$data['commercial']}'";
7341            }
7342
7343            if (isset($data['created_by']) && $data['created_by'] != null) {
7344                $where .= " AND q.created_by = '{$data['created_by']}'";
7345            }
7346
7347            if (isset($data['budget_type']) && $data['budget_type'] != null) {
7348                $where .= " AND bt.budget_type_id = {$data['budget_type']}";
7349            }
7350
7351            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
7352                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
7353            }
7354
7355            if (isset($data['budget_status']) && $data['budget_status'] != null) {
7356                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
7357            }
7358
7359            if (isset($data['client_type']) && $data['client_type'] != null) {
7360                $where .= " AND ct.customer_type_id = {$data['client_type']}";
7361            }
7362
7363            if (isset($data['segment_id']) && $data['segment_id'] != null) {
7364                $where .= " AND q.segment_id = {$data['segment_id']}";
7365            }
7366
7367            $col = '1';
7368
7369            if (isset($data['data_to_display']) && $data['data_to_display'] != null) {
7370                if ($data['data_to_display'] == 1) {
7371                    $col = '1';
7372                }
7373
7374                if ($data['data_to_display'] == 2) {
7375                    $col = 'q.amount';
7376                }
7377            }
7378
7379            $budgetTypes = TblBudgetTypes::orderByRaw('ISNULL(priority), priority ASC')->get();
7380            $cols = '';
7381            foreach ($budgetTypes as $item) {
7382                if ($item->name == '' || $item->name == null) {
7383                    $cols .= ",COALESCE(SUM(CASE WHEN bt.name IS NULL {$acc} THEN {$col} ELSE 0 END), 0) AS 'Otros'";
7384                } else {
7385                    $cols .= ",COALESCE(SUM(CASE WHEN bt.name = '{$item->name}{$acc} THEN {$col} ELSE 0 END), 0) AS '{$item->name}'";
7386                }
7387            }
7388
7389            $budgetTypeGroups = TblBudgetTypeGroups::orderByRaw('ISNULL(priority), priority ASC')->get();
7390
7391            $colsGroups = ",COALESCE(SUM(CASE WHEN bt.name IS NULL {$acc} THEN {$col} END), 0) AS Otros";
7392
7393            foreach ($budgetTypeGroups as $item) {
7394                $budgetTypeGroupName = str_replace(' ', '', $item->name).$item->budget_type_group_id;
7395                $colsGroups .= ",GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.id END) AS 'groupConcatIds{$budgetTypeGroupName}'";
7396                $colsGroups .= ",COALESCE(SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN {$col} END), 0) AS '{$budgetTypeGroupName}'";
7397            }
7398
7399            $colsGroups .= ",COALESCE(SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN {$col} END), 0) AS total";
7400
7401            $col = $colsGroups.$cols;
7402
7403            if (@$data['data_to_display'] == 3) {
7404
7405                $cols = '';
7406                foreach ($budgetTypes as $item) {
7407                    if ($item->name == '' || $item->name == null) {
7408                        $cols .= ",COALESCE(
7409                                        SUM(CASE WHEN bt.name IS NULL {$acc} THEN q.amount ELSE 0 END) /
7410                                        SUM(CASE WHEN bt.name IS NULL {$acc} THEN 1 ELSE 0 END) * 100 , 0
7411                                    ) AS 'Otros'";
7412                    } else {
7413                        $cols .= ",COALESCE(
7414                                        SUM(CASE WHEN bt.name = '{$item->name}{$acc} THEN q.amount ELSE 0 END) /
7415                                        SUM(CASE WHEN bt.name = '{$item->name}{$acc} THEN 1 ELSE 0 END), 0
7416                                    ) AS '{$item->name}'";
7417                    }
7418                }
7419
7420                $colsGroups = ",COALESCE(
7421                                (SUM(CASE WHEN bt.name IS NULL {$acc} THEN q.amount END)) /
7422                                (SUM(CASE WHEN bt.name IS NULL {$acc} THEN 1 END))
7423                            , 0) Otros";
7424
7425                foreach ($budgetTypeGroups as $item) {
7426                    $budgetTypeGroupName = str_replace(' ', '', $item->name).$item->budget_type_group_id;
7427                    $colsGroups .= ",GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.id END) AS 'groupConcatIds{$budgetTypeGroupName}'";
7428                    $colsGroups .= ",COALESCE(
7429                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.amount END)) /
7430                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN 1 END))
7431                                    , 0) '{$budgetTypeGroupName}'";
7432                }
7433
7434                $colsGroups .= ",COALESCE(
7435                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN q.amount END)) /
7436                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN 1 END))
7437                                , 0) total";
7438
7439                $col = $colsGroups.$cols;
7440            }
7441
7442            if (@$data['data_to_display'] == 4) {
7443
7444                $cols = '';
7445
7446                foreach ($budgetTypes as $item) {
7447
7448                    if ($item->name == '' || $item->name == null) {
7449                        $cols .= ",COALESCE(
7450                                        SUM(CASE WHEN bt.name IS NULL AND q.acceptance_date IS NOT NULL THEN 1 ELSE 0 END) /
7451                                        SUM(CASE WHEN bt.name IS NULL AND q.created_at IS NOT NULL THEN 1 ELSE 0 END) * 100 , 0
7452                                ) AS 'Otros'";
7453                    } else {
7454                        $cols .= ", COALESCE(
7455                                        SUM(CASE WHEN bt.name = '{$item->name}' AND q.acceptance_date IS NOT NULL THEN 1 END) /
7456                                        SUM(CASE WHEN bt.name = '{$item->name}' AND q.created_at IS NOT NULL THEN 1 END) * 100, 0
7457                                ) AS '{$item->name}'";
7458                    }
7459                }
7460
7461                $colsGroups = ',COALESCE(
7462                                    (SUM(CASE WHEN bt.name IS NULL AND q.acceptance_date IS NOT NULL THEN 1 END)) /
7463                                    (SUM(CASE WHEN bt.name IS NULL AND q.created_at IS NOT NULL THEN 1 END)) * 100
7464                                    , 0) Otros';
7465
7466                foreach ($budgetTypeGroups as $item) {
7467                    $budgetTypeGroupName = str_replace(' ', '', $item->name).$item->budget_type_group_id;
7468                    $colsGroups .= ",GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.id END) AS 'groupConcatIds{$budgetTypeGroupName}'";
7469                    $colsGroups .= ",COALESCE(
7470                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) AND q.acceptance_date IS NOT NULL THEN 1 END)) /
7471                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) AND q.created_at IS NOT NULL THEN 1 END)) * 100
7472                                    , 0) '{$budgetTypeGroupName}'";
7473                }
7474
7475                $colsGroups .= ',COALESCE(
7476                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) AND q.acceptance_date IS NOT NULL THEN 1 END)) /
7477                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) AND q.created_at IS NOT NULL THEN 1 END)) * 100
7478                                    , 0) total';
7479
7480                $col = $colsGroups.$cols;
7481            }
7482
7483            $query = "SELECT
7484                            YEAR(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)) AS 'year',
7485                            LPAD(MONTH(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)), 2, 0) AS 'month',
7486                            LPAD(WEEK(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)), 2, 0) AS 'week',
7487                            DATE_FORMAT(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY), '%W, %M %e') 'namedate',
7488                            GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN q.id END) groupConcatIds
7489                            {$col}
7490                        FROM
7491                            tbl_quotations q
7492                            LEFT JOIN tbl_sources s ON s.source_id = q.source_id
7493                            LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
7494                            LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
7495                            LEFT JOIN tbl_budget_type_groups btg ON bt.budget_type_group_id = btg.budget_type_group_id
7496                            LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
7497                        WHERE
7498                            q.{$field} IS NOT NULL
7499                            AND q.for_add = 0
7500                            AND q.budget_type_id IS NOT NULL
7501                            AND q.budget_type_id != 7
7502                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1                            
7503                            {$where}
7504                            {$whereYear}
7505                        GROUP BY
7506                            YEAR(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)),
7507                            MONTH(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)),
7508                            WEEK(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)) WITH ROLLUP
7509                        ORDER BY
7510                            YEAR DESC,
7511                            MONTH ASC,
7512                            WEEK ASC,
7513                            DATE_FORMAT(q.{$field}, '%e') ASC";
7514            // return $query;
7515            $result = DB::select($query);
7516
7517            $query = "SELECT
7518                        btg.budget_type_group_id,
7519                        btg.name,
7520                        (
7521                            SELECT
7522                                GROUP_CONCAT(COALESCE(bt.name, '') ORDER BY ISNULL(bt.priority), bt.priority ASC SEPARATOR '|')
7523                            FROM
7524                                tbl_budget_types bt
7525                            WHERE
7526                                bt.budget_type_group_id = btg.budget_type_group_id
7527                        ) budget_types
7528                        FROM
7529                            tbl_budget_type_groups btg
7530                        ORDER BY
7531                            ISNULL(btg.priority),
7532                            btg.priority ASC";
7533
7534            $budgetTypeGroups = DB::select($query);
7535
7536            foreach ($budgetTypeGroups as $item) {
7537                $item->group_key_name = str_replace(" ", "", $item->name) . $item->budget_type_group_id;
7538                $item->budget_types = explode("|", (string) $item->budget_types);
7539            }
7540
7541            return response([
7542                'message' => 'OK',
7543                'data' => $result,
7544                'budgetTypeGroups' => $budgetTypeGroups,
7545            ]);
7546
7547        } catch (\Exception $e) {
7548            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_BY_TYPES_OF_BUDGETS_CREATED_PER_WEEK_EXCEPTION'));
7549            return response(['message' => 'KO', 'error' => $e->getMessage()]);
7550        }
7551    }
7552
7553    public function preview_file($id)
7554    {
7555
7556        try {
7557
7558            $file = TblFiles::where('file_id', $id)->first();
7559
7560            if (! $file) {
7561                return response()->json([
7562                    'message' => 'KO',
7563                    'error' => __('language.file_not_found'),
7564                ], 404);
7565            }
7566
7567            if (! Storage::disk('s3')->exists('uploads/'.$file->filename)) {
7568                return response()->json(['message' => 'File not found'], 404);
7569            }
7570
7571            $url = Storage::disk('s3')->temporaryUrl(
7572                'uploads/'.$file->filename,
7573                now()->addMinutes(5)
7574            );
7575
7576            return response()->json([
7577                'filename' => $file->filename,
7578                'url' => $url,
7579                'uploaded_by' => $file->uploaded_by,
7580                'uploaded_at' => $file->uploaded_at,
7581            ]);
7582
7583        } catch (\Exception $e) {
7584            report(AppException::fromException($e, 'PREVIEW_FILE_EXCEPTION'));
7585            return response()->json([
7586                'message' => 'KO',
7587                'error' => $e->getMessage(),
7588            ], 500);
7589        }
7590    }
7591
7592    function get_past_added_quotation(Request $request): ResponseFactory|HttpResponse{
7593
7594        try {
7595
7596            $data = $request->all();
7597            $keyword = addslashes((string) $data['keyword']);
7598            $result = [];
7599
7600            if(!empty($keyword)){
7601                $array = explode(' ', $keyword);
7602
7603                $where = '';
7604
7605                $availableParameters = ['client', 'email'];
7606
7607                $searchTextArray = explode(' ', $keyword);
7608
7609                $searchArray = [];
7610                $matchScoreArray = [];
7611                foreach ($availableParameters as $field) {
7612                    foreach ($searchTextArray as $word) {
7613                        array_push($searchArray, "({$field} LIKE '%{$word}%')");
7614                        array_push($matchScoreArray, "CASE WHEN {$field} LIKE '%{$word}%' THEN 1 ELSE 0 END");
7615                    }
7616                }
7617
7618                $searchArray = implode(' OR ', $searchArray);
7619                $matchScoreArray = implode(' + ', $matchScoreArray);
7620                $matchScoreCol = "({$matchScoreArray})";
7621                $where .= " AND ({$searchArray}";
7622
7623                $query = "SELECT
7624                            id,
7625                            client,
7626                            segment_id,
7627                            CONCAT(`client`, ' - ', email) `client_email`,
7628                            email,
7629                            phone_number,
7630                            customer_type_id,
7631                            {$matchScoreCol} match_score
7632                        FROM tbl_quotations
7633                        WHERE for_add = 0
7634                        AND email IS NOT NULL AND phone_number IS NOT NULL
7635                        {$where}
7636                        GROUP BY client, email
7637                        ORDER BY match_score DESC, client ASC
7638                        ";
7639
7640                $result = DB::select($query);
7641            }
7642
7643            return response(['message' => 'OK', 'data' => $result]);
7644
7645        } catch (\Exception $e) {
7646            report(AppException::fromException($e, 'GET_PAST_ADDED_QUOTATION_EXCEPTION'));
7647            return response(['message' => 'KO', 'error' => $e->getMessage()]);
7648        }
7649    }
7650
7651    function send_acceptance_notification($quotationId, $companyId, $userId, $updatedBy): void{
7652
7653        $budget = TblQuotations::where('id', $quotationId)->first();
7654
7655        if ($budget != null) {
7656            $to = TblToAcceptanceNotifications::where('company_id', $companyId)->get();
7657            $cc = TblCcAcceptanceNotifications::where('company_id', $companyId)->get();
7658
7659            if (count($to) > 0 && count($cc) > 0) {
7660
7661                $company = TblCompanies::where('company_id', $companyId)->first();
7662
7663                $quoteId = $budget->quote_id;
7664                $amount = $this->currency($budget->amount, 1);
7665
7666                $url = config('app.frontend_url') . "orders/{$quotationId}?company_id={$companyId}";
7667                $href = "<a href='{$url}'>{$quoteId}</a>";
7668
7669                $imgpath = file_get_contents(public_path('fireservicetitan.png'));
7670                $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
7671
7672                $body = __('language.send_acceptance_notification.body_hello');
7673                $body .= __('language.send_acceptance_notification.body_message');
7674
7675                $body = str_replace('{{client}}', $budget->client, $body);
7676                $body = str_replace('{{username}}', $updatedBy, $body);
7677                $body = str_replace('{{company}}', $company->name, $body);
7678                $body = str_replace('{{amount}}', $amount, $body);
7679                $body = str_replace('{{quote_id}}', $href, $body);
7680
7681                $body .= '<p>Fire Service Titan</p>';
7682                $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
7683
7684                $html = '<!DOCTYPE html>';
7685                $html .= '<html>';
7686                $html .= '<head>';
7687                $html .= '<meta charset="UTF-8">';
7688                $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
7689                $html .= '</head>';
7690                $html .= '<body>';
7691                $html .= $body;
7692                $html .= '</body>';
7693                $html .= '</html>';
7694
7695                $subject = __('language.send_acceptance_notification.subject');
7696                $subject = str_replace('{{quote_id}}', $quoteId, $subject);
7697
7698                $email = new \SendGrid\Mail\Mail;
7699
7700                $user = TblUsers::where('id', $userId)->first();
7701
7702                if(config('services.sendgrid.staging')){
7703                    $email->addTo($user->email);
7704                } else {
7705
7706                    $emails = [];
7707
7708                    foreach ($to as $item) {
7709                        if (! in_array($item->email, $emails)) {
7710                            array_push($emails, $item->email);
7711                            $email->addTo($item->email);
7712                        }
7713                    }
7714
7715                    foreach ($cc as $item) {
7716                        if (! in_array($item->email, $emails)) {
7717                            array_push($emails, $item->email);
7718                            $email->addCc($item->email);
7719                        }
7720                    }
7721
7722                    $email->addCc($user->email);
7723                    array_push($emails, $user->email);
7724
7725                    $ccUser = TblUsers::where('name', $budget->commercial)->first();
7726
7727                    if ($ccUser) {
7728                        if (! in_array($ccUser->email, $emails)) {
7729                            $email->addCc($ccUser->email);
7730                        }
7731                    }
7732                }
7733
7734                $email->setFrom('fire@fire.es', 'Fire Service Titan');
7735                $email->setSubject($subject);
7736                $email->addContent('text/html', $html);
7737
7738                $email->addAttachment(
7739                    $imgpath,
7740                    'image/png',
7741                    'fireservicetitan.png',
7742                    'inline',
7743                    'fireservicetitan'
7744                );
7745
7746                $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
7747
7748                $response = $sendgrid->send($email);
7749                if ($response->statusCode() == 202) {
7750                    $this->addUpdateLog($quotationId, $userId, 'send_acceptance_notification', null, null, 5);
7751                    $comment = 'Email de aprobación automática enviada al equipo de opearciones el '.date('Y-m-d H:i:s');
7752                    $budget->last_follow_up_comment = $budget->last_follow_up_comment."\n".$comment;
7753
7754                    TblQuotations::where('id', $quotationId)->update(
7755                        [
7756                            'last_follow_up_comment' => $budget->last_follow_up_comment
7757                        ]
7758                    );
7759
7760                    Log::channel('email_log')->info('ID:'.$quoteId.' - ACCEPTANCE EMAIL NOTIFICATION SENT');
7761                } else {
7762                    $error = true;
7763                    Log::channel('email_log')->error('ID:'.$quoteId.' - '.$response->body());
7764                }
7765
7766            }
7767        }
7768    }
7769
7770    function get_total_quotations_by_budget_status(Request $request): ResponseFactory|HttpResponse{
7771
7772        try {
7773
7774            $data = $request->all();
7775
7776            $companyId = addslashes((string) $data['company_id']);
7777
7778            $where = '';
7779
7780            if ($companyId != 0) {
7781                $where = " AND a.company_id = {$companyId} ";
7782            } else {
7783                $where = " AND a.company_id IN ({$this->companyId}";
7784            }
7785
7786            $user = null;
7787
7788            if (isset($data['commercial'])) {
7789                if ($data['commercial'] != 'All') {
7790                    $user = TblUsers::where('name', $data['commercial'])->first();
7791                }
7792            } else {
7793                $user = TblUsers::where('id', $this->userId)->first();
7794            }
7795
7796            $totalPendingFollowUps = 0;
7797            $totalRequestAndVisit = 0;
7798            $totalError = 0;
7799            $totalG3WError = 0;
7800            $totalSendToClient = 0;
7801
7802            $d = false;
7803
7804            if ($user != null) {
7805                $where .= " AND a.commercial = '{$user->name}";
7806                $d = true;
7807            }
7808
7809            if ($data['commercial'] == 'All') {
7810                $d = true;
7811            }
7812
7813            if ($d) {
7814                $blacklist = implode('|', $this->getBlacklistEmails());
7815                $query = "SELECT
7816                            COUNT(DISTINCT a.id) total
7817                        FROM
7818                            tbl_quotations a
7819                            LEFT JOIN (
7820                                SELECT
7821                                a.id,
7822                                SUBSTRING_INDEX(
7823                                    SUBSTRING_INDEX(a.email, ',', n.digit + 1),
7824                                    ',',
7825                                    -1
7826                                ) AS email_domain
7827                                FROM
7828                                tbl_quotations a
7829                                INNER JOIN (
7830                                    SELECT
7831                                    0 AS digit
7832                                    UNION ALL
7833                                    SELECT
7834                                    1
7835                                    UNION ALL
7836                                    SELECT
7837                                    2
7838                                    UNION ALL
7839                                    SELECT
7840                                    3
7841                                    UNION ALL
7842                                    SELECT
7843                                    4
7844                                    UNION ALL
7845                                    SELECT
7846                                    5
7847                                    UNION ALL
7848                                    SELECT
7849                                    6
7850                                    UNION ALL
7851                                    SELECT
7852                                    7
7853                                    UNION ALL
7854                                    SELECT
7855                                    8
7856                                    UNION ALL
7857                                    SELECT
7858                                    9
7859                                ) n ON LENGTH(
7860                                    REPLACE(a.email, ',', '')
7861                                ) <= LENGTH(a.email)- n.digit
7862                                GROUP BY a.id
7863                            ) temp ON a.id = temp.id
7864                            LEFT JOIN tbl_companies b
7865                                ON a.company_id = b.company_id
7866                        WHERE
7867                            a.last_follow_up_date < NOW()
7868                            AND a.budget_status_id IN (2)
7869                            AND a.email IS NOT NULL
7870                            AND a.email <> ''
7871                            AND NOT EXISTS (
7872                                SELECT
7873                                1
7874                                FROM
7875                                tbl_blocked_domains bd
7876                                WHERE
7877                                temp.email_domain LIKE CONCAT('%', bd.domain, '%')
7878                                AND bd.company_id = a.company_id
7879                            )
7880                            AND a.last_follow_up_date IS NOT NULL
7881                            AND a.reason_for_not_following_up_id IS NULL
7882                            AND a.last_follow_up_date > 0
7883                            AND a.total_sent < b.limit_reminder_emails
7884                            AND (
7885                                a.email IS NOT NULL 
7886                                AND TRIM(a.email) != '' 
7887                                AND a.email REGEXP '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}'
7888                                AND a.email NOT REGEXP '($blacklist)'
7889                            )
7890                            AND a.for_add = 0 {$where}";
7891
7892                $result = DB::select($query);
7893
7894                $totalPendingFollowUps = $result[0]->total;
7895
7896                $query = "SELECT
7897                                COUNT(1) total
7898                            FROM
7899                                tbl_quotations a
7900                            WHERE
7901                                a.budget_status_id IN (6, 8, 12)
7902                                AND a.for_add = 0
7903                                {$where}
7904                            ";
7905
7906                $result = DB::select($query);
7907
7908                $totalRequestAndVisit = $result[0]->total;
7909
7910                $blacklist = implode('|', $this->getBlacklistEmails());
7911
7912                $query = "SELECT
7913                                COUNT(1) total
7914                            FROM
7915                                tbl_quotations a
7916                            WHERE
7917                                a.for_add = 0 AND
7918                                (
7919                                    a.x_status IN ('Error','Error - Bounce','Error - Spam')
7920                                    OR (
7921                                        a.email IS NULL 
7922                                        OR TRIM(a.email) = '' 
7923                                        OR a.email NOT REGEXP '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
7924                                        OR a.email REGEXP '($blacklist)'
7925                                    )
7926                                ) 
7927                                AND a.budget_status_id IN(1, 2, 11, 17, 21, 22)
7928                                    {$where}
7929                            ";
7930
7931                $result = DB::select($query);
7932
7933                $totalError = $result[0]->total;
7934
7935                $query = "SELECT
7936                                COUNT(1) total
7937                                FROM
7938                                    tbl_quotations a
7939                                WHERE
7940                                    a.g3w_warning = 1
7941                                    AND a.for_add = 0
7942                                    {$where}
7943                                ";
7944
7945                $result = DB::select($query);
7946
7947                $totalG3WError = $result[0]->total;
7948
7949                $query = "SELECT
7950                                COUNT(1) total
7951                                FROM
7952                                    tbl_quotations a
7953                                WHERE
7954                                    a.budget_status_id = (SELECT budget_status_id FROM tbl_budget_status WHERE name = 'Validado')
7955                                    AND a.total_sent = 0
7956                                    AND a.for_add = 0
7957                                    {$where}
7958                                ";
7959
7960                $result = DB::select($query);
7961
7962                $totalSendToClient = $result[0]->total;
7963            }
7964
7965            return response([
7966                'message' => 'OK',
7967                'userId' => ($user) ? $user->id : null,
7968                'totalPendingFollowUps' => $totalPendingFollowUps,
7969                'totalRequestAndVisit' => $totalRequestAndVisit,
7970                'totalError' => $totalError,
7971                'totalG3WError' => $totalG3WError,
7972                'totalSendToClient' => $totalSendToClient,
7973            ]);
7974
7975        } catch (\Exception $e) {
7976            report(AppException::fromException($e, 'GET_TOTAL_QUPTATIONS_BY_BUDGET_STATUS_EXCEPTION'));
7977            return response(['message' => 'KO', 'error' => $e->getMessage()]);
7978        }
7979
7980    }
7981
7982    public function sendgrid_webhook_receiver(Request $request)
7983    {
7984
7985        try {
7986
7987            $data = $request->all();
7988
7989            $jsonBody = [];
7990            $order = [];
7991            $orderEmails = [];
7992
7993            Log::channel('email_log')->info('WEBHOOK: '.json_encode($data));
7994
7995            $quoteId = null;
7996
7997            // FIRE-864: SendGrid bounce/drop events should flag the quotation
7998            // as "Correo erróneo" (budget_status_id = 22) so the sales flow
7999            // shows the truth instead of defaulting to "Enviado".
8000            $errorEvents = ['bounce', 'dropped', 'blocked', 'spamreport'];
8001
8002            foreach ($data as $item) {
8003                $matches = explode(".", (string) $item['sg_message_id']);
8004                $messageId = $matches[0];
8005
8006                Log::channel('email_log')->info('MESSAGE-ID: '.$messageId);
8007
8008                $result = TblSendgridWebhook::where('x_message_id', $messageId)->first();
8009                if (! $result) {
8010                    Log::channel('email_log')->warning("No tbl_sendgrid_webhook row for x_message_id={$messageId}. Skipping event.");
8011                    continue;
8012                }
8013                $quoteId = $result->quotation_id;
8014                Log::channel('email_log')->info('SENDGRID-BODY: '.json_encode($result));
8015
8016                if (empty($order)) {
8017                    $order = TblQuotations::where('x_message_id', $messageId)->first();
8018                    if ($order) {
8019                        $quoteId = $order->id;
8020                        $orderEmails = explode(",", (string) $order->email);
8021                    }
8022                }
8023
8024                if($result->json_body == null){
8025                    array_push($jsonBody, $item);
8026                }else{
8027                    $jsonBody = json_decode((string) $result->json_body);
8028                    array_push($jsonBody, $item);
8029                }
8030
8031                Log::channel('email_log')->info('JSON-BODY: '.json_encode($jsonBody));
8032
8033                TblSendgridWebhook::where('x_message_id', $messageId)->update(
8034                    [
8035                        'json_body' => json_encode($jsonBody),
8036                        'updated_at' => date('Y-m-d H:i:s')
8037                    ]
8038                );
8039
8040                // FIRE-864: mark quotation as "correo erróneo" (22) on bounce/drop
8041                if ($quoteId !== null && isset($item['event']) && in_array(strtolower((string) $item['event']), $errorEvents, true)) {
8042                    $current = $order ?: TblQuotations::where('id', $quoteId)->first();
8043                    if ($current && (int) $current->budget_status_id !== 22) {
8044                        TblQuotations::where('id', $quoteId)->update(['budget_status_id' => 22]);
8045                        $this->addUpdateLog($quoteId, 'SendGrid', 'budget_status_id', $current->budget_status_id, 22, 4);
8046                        Log::channel('email_log')->info("FIRE-864: quotation {$quoteId} → budget_status_id=22 (correo erróneo) due to SendGrid event '{$item['event']}'.");
8047                    }
8048                }
8049
8050                if ($quoteId != null) {
8051                    $this->get_files($quoteId);
8052                }
8053
8054                Cache::flush();
8055            }
8056
8057        } catch (\Exception $e) {
8058            report(AppException::fromException($e, 'SENDGRID_WEBHOOK_RECEIVER_EXCEPTION'));
8059            return response(['message' => 'KO', 'error' => $e->getMessage()]);
8060        }
8061
8062        return response(['message' => 'OK']);
8063    }
8064
8065    function isEmailValid($email): bool {
8066        // Regular expression pattern for email validation
8067        $pattern = '/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/';
8068
8069        // Check if the email matches the pattern
8070        if (preg_match($pattern, (string) $email)) {
8071            return true; // Valid email
8072        } else {
8073            return false; // Invalid email
8074        }
8075    }
8076
8077    public function list_email_status($companyId): ResponseFactory|HttpResponse {
8078        try {
8079            $params = [];
8080
8081            if ($companyId != 0) {
8082                $where = ' company_id = ? ';
8083                $params[] = $companyId;
8084            } else {
8085                $ids = is_string($this->companyId) ? explode(',', $this->companyId) : $this->companyId;
8086                $ids = array_filter($ids); 
8087
8088                if (empty($ids)) {
8089                    return response(['message' => 'OK', 'data' => []]);
8090                }
8091
8092                $placeholders = implode(',', array_fill(0, count($ids), '?'));
8093                $where = " company_id IN ($placeholders";
8094                $params = array_values($ids);
8095            }
8096
8097            $query = "SELECT DISTINCT x_status FROM tbl_quotations WHERE {$where} AND x_status IS NOT NULL";
8098            $result = DB::select($query, $params);
8099
8100            return response([
8101                'message' => 'OK',
8102                'data' => $result,
8103            ]);
8104
8105        } catch (\Exception $e) {
8106            report($e);
8107
8108            return response(['message' => 'KO', 'error' => $e->getMessage()], 500);
8109        }
8110    }
8111
8112    function list_quotation_analytics_commercial(Request $request): ResponseFactory|HttpResponse{
8113
8114        try {
8115
8116            $data = $request->all();
8117            $companyId = addslashes((string) $data['company_id']);
8118            $field = $data['field'];
8119
8120            $where = "";
8121            $whereYear = "";
8122            $dateLflArray = [];
8123            $companyIds = $this->companyIds;
8124            $whereQ = '';
8125
8126            $acc = '';
8127            if ($field == 'acceptance_date') {
8128                $acc = ' AND q.acceptance_date IS NOT NULL ';
8129                // $field = 'created_at';
8130
8131                if (@$data['data_to_display'] == 4) {
8132                    $field = 'created_at';
8133                    $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
8134                    $whereQ .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
8135                }
8136            } else {
8137                $field = 'created_at';
8138                $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
8139                $whereQ .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
8140            }
8141
8142            if (isset($data['years']) && $data['years'] != null) {
8143                $years = implode(',', $data['years']);
8144                if (count($data['years']) > 0) {
8145                    $whereYear = " AND YEAR(q.{$field}) IN ({$years})";
8146                }
8147            }
8148
8149            if ($companyId != 0) {
8150                $companyIds = [$companyId];
8151            }
8152
8153            foreach ($companyIds as $v) {
8154
8155                $lflWhere = " AND q.company_id = {$v} ";
8156
8157                $query = "SELECT
8158                            CONCAT(
8159                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
8160                                ' - ',
8161                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
8162                            ) AS date_like,
8163                            YEAR(q.{$field}) 'year',
8164                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
8165                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
8166                            {$v} 'company_id'
8167                        FROM
8168                            tbl_quotations q
8169                        WHERE
8170                            q.{$field} IS NOT NULL
8171                            AND q.for_add = 0
8172                            {$lflWhere}
8173                            {$whereYear}
8174                        GROUP BY YEAR(q.{$field})
8175                        ORDER BY YEAR(q.{$field}) DESC";
8176
8177                $dateLike = DB::select($query);
8178
8179                $dateLflArray[$v] = $dateLike;
8180            }
8181
8182            $isFy = true;
8183
8184            if (isset($data['ytd']) && $data['ytd'] != null && $data['ytd'] == true) {
8185                $isFy = false;
8186                $ytdArray = [];
8187                $ytdAcceptanceArray = [];
8188                $lflCompanyIds = [];
8189                foreach ($dateLflArray as $k => $v) {
8190                    foreach ($dateLflArray[$k] as $item) {
8191                        $year = $item->year;
8192                        $now = date('m-d');
8193                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
8194                    }
8195
8196                    $ytdArray = implode(' OR ', $ytdArray);
8197                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
8198                    $ytdArray = [];
8199                }
8200
8201                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
8202                $where .= " AND ({$lflCompanyIds}";
8203                $whereQ .= " AND ({$lflCompanyIds}";
8204            }
8205
8206            if (isset($data['lfl']) && $data['lfl'] != null && $data['lfl'] == true) {
8207                $isFy = false;
8208                $lflArray = [];
8209                $ytdAcceptanceArray = [];
8210                $lflCompanyIds = [];
8211                foreach ($dateLflArray as $k => $v) {
8212                    foreach ($dateLflArray[$k] as $item) {
8213                        $year = $item->year;
8214                        $min_date_like = $item->min_date_like;
8215                        $max_date_like = $item->max_date_like;
8216                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
8217                    }
8218
8219                    $lflArray = implode(' OR ', $lflArray);
8220                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
8221                    $lflArray = [];
8222                }
8223
8224                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
8225                $where .= " AND ({$lflCompanyIds}";
8226                $whereQ .= " AND ({$lflCompanyIds}";
8227            }
8228
8229            if ($isFy) {
8230                if ($companyId != 0) {
8231                    $where .= " AND q.company_id = {$companyId} ";
8232                    $whereQ .= " AND q.company_id = {$companyId} ";
8233                } else {
8234                    $where .= " AND q.company_id IN ({$this->companyId})";
8235                    $whereQ .= " AND q.company_id IN ({$this->companyId})";
8236                }
8237            }
8238
8239            if (isset($data['source']) && $data['source'] != null) {
8240                $where .= " AND s.name = '{$data['source']}'";
8241            }
8242
8243            if (isset($data['month']) && $data['month'] != null) {
8244                $where .= " AND MONTH(q.{$field}) = '{$data['month']}'";
8245            }
8246
8247            if (isset($data['week']) && $data['week'] != null) {
8248                $where .= " AND WEEK(q.{$field}) = '{$data['week']}'";
8249            }
8250
8251            if (isset($data['commercial']) && $data['commercial'] != null) {
8252                $where .= " AND q.commercial = '{$data['commercial']}'";
8253            }
8254
8255            if (isset($data['created_by']) && $data['created_by'] != null) {
8256                $where .= " AND q.created_by = '{$data['created_by']}'";
8257            }
8258
8259            if (isset($data['budget_type']) && $data['budget_type'] != null) {
8260                $where .= " AND bt.budget_type_id = {$data['budget_type']}";
8261            }
8262
8263            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
8264                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
8265            }
8266
8267            if (isset($data['budget_status']) && $data['budget_status'] != null) {
8268                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
8269            }
8270
8271            if (isset($data['client_type']) && $data['client_type'] != null) {
8272                $where .= " AND ct.customer_type_id = {$data['client_type']}";
8273            }
8274
8275            if (isset($data['segment_id']) && $data['segment_id'] != null) {
8276                $where .= " AND q.segment_id = {$data['segment_id']}";
8277            }
8278
8279            $col = '1';
8280
8281            if (isset($data['data_to_display']) && $data['data_to_display'] != null) {
8282                if ($data['data_to_display'] == 1) {
8283                    $col = '1';
8284                }
8285
8286                if ($data['data_to_display'] == 2) {
8287                    $col = 'q.amount';
8288                }
8289            }
8290
8291            $query = "SELECT
8292                        q.commercial 'name',
8293                        COUNT(q.commercial) totalCommercial
8294                    FROM tbl_quotations q
8295                    WHERE
8296                        q.for_add = 0
8297                        AND q.{$field} IS NOT NULL
8298                        {$whereQ}
8299                    GROUP BY q.commercial
8300                    HAVING COUNT(q.commercial) > 0
8301                    ORDER BY totalCommercial DESC";
8302
8303            $resultCommercials = DB::select($query);
8304
8305            $now = TblQuotations::whereIn('company_id', $this->companyIds)->orderBy($field, 'DESC')->pluck($field)->first();
8306            $weekNumber = date('W', strtotime((string) $now));
8307            $thisWeek = date('Y-m-d', strtotime($now . " - " . (date('N', strtotime((string) $now)) - 1) . " days"));
8308
8309            $query = "SELECT
8310                        q.commercial 'name',
8311                        COUNT(q.commercial) totalCommercial
8312                    FROM tbl_quotations q
8313                    WHERE
8314                        q.for_add = 0
8315                        AND q.{$field} IS NOT NULL
8316                        AND YEARWEEK(q.{$field}, 1) = YEARWEEK(NOW(), 1)
8317                        {$whereQ}
8318                    GROUP BY q.commercial
8319                    HAVING COUNT(q.commercial) > 0
8320                    ORDER BY totalCommercial DESC";
8321
8322            $resultCommercialsOrder = DB::select($query);
8323
8324            $namesToRemove = [];
8325
8326            foreach ($resultCommercialsOrder as $item) {
8327                $namesToRemove[$item->name] = true;
8328            }
8329
8330            $resultArray = [];
8331            foreach ($resultCommercials as $item) {
8332                if (! isset($namesToRemove[$item->name])) {
8333                    $resultArray[] = $item;
8334                }
8335            }
8336
8337            $resultCommercials = array_merge($resultCommercialsOrder, $resultArray);
8338
8339            $cols = '';
8340
8341            $colsGroupConcatIds = '';
8342
8343            foreach ($resultCommercials as $item) {
8344                $cols .= ",COALESCE(
8345                    SUM(
8346                        CASE WHEN q.commercial = '{$item->name}{$acc} THEN {$col} ELSE 0 END
8347                    ), 0
8348                ) '{$item->name}'";
8349
8350                $colsGroupConcatIds .= ",GROUP_CONCAT(CASE WHEN q.commercial = '{$item->name}{$acc} THEN q.id END) 'groupConcatIds-{$item->name}'";
8351            }
8352
8353            $cols .= ",COALESCE(
8354                SUM(
8355                    CASE WHEN q.commercial IS NOT NULL {$acc} THEN {$col} ELSE 0 END
8356                ), 0
8357            ) total";
8358
8359            if (@$data['data_to_display'] == 3) {
8360
8361                $cols = '';
8362
8363                foreach ($resultCommercials as $item) {
8364                    $cols .= ",COALESCE(
8365                        (
8366                            SUM(CASE WHEN q.commercial = '{$item->name}{$acc} THEN q.amount END)
8367                        ) /
8368                        (
8369                            SUM(CASE WHEN q.commercial = '{$item->name}{$acc} THEN 1 END)
8370                        )
8371                    , 0) '{$item->name}'";
8372                }
8373
8374                $cols .= ",COALESCE(
8375                    (
8376                        SUM(CASE WHEN q.commercial IS NOT NULL {$acc} THEN q.amount END)
8377                    ) /
8378                    (
8379                        SUM(CASE WHEN q.commercial IS NOT NULL {$acc} THEN 1 END)
8380                    )
8381                , 0) total";
8382            }
8383
8384            if (@$data['data_to_display'] == 4) {
8385
8386                $cols = '';
8387
8388                foreach ($resultCommercials as $item) {
8389                    $cols .= ",COALESCE(
8390                        (
8391                            SUM(CASE WHEN q.commercial = '{$item->name}' AND q.acceptance_date IS NOT NULL THEN 1 END)
8392                        ) /
8393                        (
8394                            SUM(CASE WHEN q.commercial = '{$item->name}' AND q.created_at IS NOT NULL THEN 1 END)
8395                        ) * 100
8396                    , 0) '{$item->name}'";
8397                }
8398
8399                $cols .= ',COALESCE(
8400                    (
8401                        SUM(CASE WHEN q.commercial IS NOT NULL AND q.acceptance_date IS NOT NULL THEN 1 END)
8402                    ) /
8403                    (
8404                        SUM(CASE WHEN q.commercial IS NOT NULL AND q.created_at IS NOT NULL THEN 1 END)
8405                    ) * 100
8406                , 0) total';
8407            }
8408
8409            $query = "SELECT
8410                            YEAR(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)) AS 'year',
8411                            LPAD(MONTH(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)), 2, 0) AS 'month',
8412                            LPAD(WEEK(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)), 2, 0) AS 'week',
8413                            DATE_FORMAT(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY), '%W, %M %e') 'namedate',
8414                            GROUP_CONCAT(
8415                                CASE WHEN q.{$field} IS NOT NULL
8416                                THEN q.id END
8417                            ) AS groupConcatIds
8418                            {$colsGroupConcatIds}
8419                            {$cols}
8420                        FROM
8421                            tbl_quotations q
8422                            LEFT JOIN tbl_sources s ON s.source_id = q.source_id
8423                            LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
8424                            LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
8425                            LEFT JOIN tbl_budget_type_groups btg ON bt.budget_type_group_id = btg.budget_type_group_id
8426                            LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
8427                        WHERE
8428                            q.{$field} IS NOT NULL
8429                            AND q.for_add = 0
8430                            AND q.budget_type_id != 7
8431                            AND q.budget_type_id IS NOT NULL
8432                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
8433                            {$where}
8434                            {$whereYear}
8435                        GROUP BY
8436                            YEAR(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)),
8437                            MONTH(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)),
8438                            WEEK(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)) WITH ROLLUP
8439                        ORDER BY
8440                            YEAR DESC,
8441                            MONTH ASC,
8442                            WEEK ASC,
8443                            DATE_FORMAT(q.{$field}, '%e') ASC";
8444
8445            $value = Cache::get(base64_encode($query));
8446
8447            if (! $value) {
8448                $result = DB::select($query);
8449
8450                Cache::put(base64_encode($query), $result, 600);
8451            } else {
8452                $result = $value;
8453            }
8454
8455            return response([
8456                'message' => 'OK',
8457                'data' => $result,
8458                'commercials' => $resultCommercials,
8459            ]);
8460
8461        } catch (\Exception $e) {
8462            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_COMMERCIAL_EXCEPTION'));
8463            return response(['message' => 'KO', 'error' => $e->getMessage()]);
8464        }
8465    }
8466
8467    function clear_open_data($companyId): ResponseFactory|HttpResponse{
8468
8469        try {
8470
8471            $companyIds = [$companyId];
8472            if ($companyId == 0) {
8473                $companyIds = $this->companyIds;
8474            }
8475
8476            $user = TblUsers::where('id', $this->userId)->first();
8477
8478            if (count($companyIds) > 0) {
8479
8480                foreach ($companyIds as $id) {
8481                    $startedAt = date('Y-m-d H:i:s');
8482                    $affectedRows = DB::delete("DELETE FROM tbl_quotations WHERE company_id = {$id} AND for_add = 1 AND DATE_FORMAT(created_at, '%Y-%m-%d') != DATE_FORMAT(NOW(), '%Y-%m-%d')");
8483
8484                    TblOrdersUpdateLogs::create(
8485                        [
8486                            'company_id' => $id,
8487                            'to_process' => 'Orders',
8488                            'status' => 'success',
8489                            'for_add_deleted_affected_rows' => $affectedRows,
8490                            'processed_by' => $user->name,
8491                            'started_at' => $startedAt,
8492                            'ended_at' => date('Y-m-d H:i:s')
8493                        ]
8494                    );
8495                }
8496            }
8497
8498            return response([
8499                'message' => 'OK',
8500            ]);
8501
8502        } catch (\Exception $e) {
8503            report(AppException::fromException($e, 'CLEAR_OPEN_DATA_EXCEPTION'));
8504            return response(['message' => 'KO', 'error' => $e->getMessage()]);
8505        }
8506
8507    }
8508
8509    function list_quotation_analytics_order_size(Request $request): ResponseFactory|HttpResponse{
8510
8511        try {
8512
8513            $data = $request->all();
8514            $companyId = addslashes((string) $data['company_id']);
8515            $field = $data['field'];
8516
8517            $where = "";
8518            $dateLflArray = [];
8519            $whereYear = "";
8520            $companyIds = $this->companyIds;
8521
8522            if ($field == 'acceptance_date') {
8523                if (@$data['data_to_display'] == 4) {
8524                    $field = 'created_at';
8525                    $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
8526                }
8527            } else {
8528                $field = 'created_at';
8529                $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
8530            }
8531
8532            if (isset($data['years']) && $data['years'] != null) {
8533                $years = implode(',', $data['years']);
8534                if (count($data['years']) > 0) {
8535                    $whereYear = " AND YEAR(q.{$field}) IN ({$years})";
8536                }
8537            }
8538
8539            if ($companyId != 0) {
8540                $companyIds = [$companyId];
8541            }
8542
8543            foreach ($companyIds as $v) {
8544
8545                $lflWhere = " AND q.company_id = {$v} ";
8546
8547                $query = "SELECT
8548                            CONCAT(
8549                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
8550                                ' - ',
8551                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
8552                            ) AS date_like,
8553                            YEAR(q.{$field}) 'year',
8554                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
8555                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
8556                            {$v} 'company_id'
8557                        FROM
8558                            tbl_quotations q
8559                        WHERE
8560                            q.{$field} IS NOT NULL
8561                            AND q.for_add = 0
8562                            {$lflWhere}
8563                            {$whereYear}
8564                        GROUP BY YEAR(q.{$field})
8565                        ORDER BY YEAR(q.{$field}) DESC";
8566
8567                $dateLike = DB::select($query);
8568
8569                $dateLflArray[$v] = $dateLike;
8570            }
8571
8572            $isFy = true;
8573
8574            if (isset($data['ytd']) && $data['ytd'] != null && $data['ytd'] == true) {
8575                $isFy = false;
8576                $ytdArray = [];
8577                $ytdAcceptanceArray = [];
8578                $lflCompanyIds = [];
8579                foreach ($dateLflArray as $k => $v) {
8580                    foreach ($dateLflArray[$k] as $item) {
8581                        $year = $item->year;
8582                        $now = date('m-d');
8583                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
8584                    }
8585
8586                    $ytdArray = implode(' OR ', $ytdArray);
8587                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
8588                    $ytdArray = [];
8589                }
8590
8591                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
8592                $where .= " AND ({$lflCompanyIds}";
8593            }
8594
8595            if (isset($data['lfl']) && $data['lfl'] != null && $data['lfl'] == true) {
8596                $isFy = false;
8597                $lflArray = [];
8598                $ytdAcceptanceArray = [];
8599                $lflCompanyIds = [];
8600                foreach ($dateLflArray as $k => $v) {
8601                    foreach ($dateLflArray[$k] as $item) {
8602                        $year = $item->year;
8603                        $min_date_like = $item->min_date_like;
8604                        $max_date_like = $item->max_date_like;
8605                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
8606                    }
8607
8608                    $lflArray = implode(' OR ', $lflArray);
8609                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
8610                    $lflArray = [];
8611                }
8612
8613                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
8614                $where .= " AND ({$lflCompanyIds}";
8615            }
8616
8617            if ($isFy) {
8618                if ($companyId != 0) {
8619                    $where .= " AND q.company_id = {$companyId} ";
8620                } else {
8621                    $where .= " AND q.company_id IN ({$this->companyId})";
8622                }
8623            }
8624
8625            if (isset($data['source']) && $data['source'] != null) {
8626                $where .= " AND s.name = '{$data['source']}'";
8627            }
8628
8629            if (isset($data['source']) && $data['source'] != null) {
8630                $where .= " AND s.name = '{$data['source']}'";
8631            }
8632
8633            if (isset($data['commercial']) && $data['commercial'] != null) {
8634                $where .= " AND q.commercial = '{$data['commercial']}'";
8635            }
8636
8637            if (isset($data['created_by']) && $data['created_by'] != null) {
8638                $where .= " AND q.created_by = '{$data['created_by']}'";
8639            }
8640
8641            if (isset($data['budget_type']) && $data['budget_type'] != null) {
8642                $where .= " AND bt.budget_type_id = {$data['budget_type']}";
8643            }
8644
8645            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
8646                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
8647            }
8648
8649            if (isset($data['budget_status']) && $data['budget_status'] != null) {
8650                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
8651            }
8652
8653            if (isset($data['client_type']) && $data['client_type'] != null) {
8654                $where .= " AND ct.customer_type_id = {$data['client_type']}";
8655            }
8656
8657            if (isset($data['segment_id']) && $data['segment_id'] != null) {
8658                $where .= " AND q.segment_id = {$data['segment_id']}";
8659            }
8660
8661            $col = 'q.one';
8662
8663            if (isset($data['data_to_display']) && $data['data_to_display'] != null) {
8664                if ($data['data_to_display'] == 1) {
8665                    $col = 'q.one';
8666                }
8667
8668                if ($data['data_to_display'] == 2) {
8669                    $col = 'q.amount';
8670                }
8671            }
8672
8673            if ((isset($data['start_date']) && $data['start_date'] != null) && isset($data['end_date']) && $data['end_date'] != null) {
8674                $where .= " AND q.{$field} BETWEEN '{$data['start_date']}' AND '{$data['end_date']}";
8675            } elseif ((isset($data['start_date']) && $data['start_date'] == null || $data['start_date'] == '') && isset($data['end_date']) && $data['end_date'] != null) {
8676                $where .= " AND q.{$field} = '{$data['end_date']}";
8677            }
8678
8679            $whereQ = $where;
8680
8681            $sortBy = [
8682                0 => 'q.amount < 100',
8683                1 => 'q.amount BETWEEN 100 AND 500',
8684                2 => 'q.amount BETWEEN 500 AND 2000',
8685                3 => 'q.amount BETWEEN 2000 AND 10000',
8686                4 => 'q.amount BETWEEN 10000 AND 30000',
8687                5 => 'q.amount BETWEEN 30000 AND 100000',
8688                6 => 'q.amount BETWEEN 100000 AND 999999999999'
8689            ];
8690
8691            $query = "SELECT
8692                        q.commercial 'name',
8693                        COUNT(q.commercial) totalCommercial
8694                    FROM tbl_quotations q
8695                    LEFT JOIN tbl_sources s ON s.source_id = q.source_id
8696                    LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
8697                    LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
8698                    LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
8699                    WHERE
8700                        q.for_add = 0
8701                        AND q.{$field} IS NOT NULL                        
8702                        AND q.budget_type_id != 7
8703                        AND q.budget_type_id IS NOT NULL
8704                        {$whereQ}
8705                        {$whereYear}
8706                    GROUP BY q.commercial
8707                    HAVING COUNT(q.commercial) > 0
8708                    ORDER BY totalCommercial DESC";
8709
8710            $resultCommercials = DB::select($query);
8711
8712            if (isset($data['sort_by'])) {
8713                if ($data['sort_by'] != 7) {
8714                    $s = $sortBy[$data['sort_by']];
8715                    $whereQ .= " AND {$s} ";
8716                }
8717            }
8718
8719            $num = 'COUNT(1)';
8720
8721            if (@$data['data_to_display'] == 2) {
8722                $num = 'SUM(q.amount)';
8723            }
8724
8725            if (@$data['data_to_display'] == 3) {
8726                $num = 'SUM(q.amount) / COUNT(1)';
8727            }
8728
8729            if (@$data['data_to_display'] == 4) {
8730                $num = '(SUM(CASE WHEN q.acceptance_date IS NOT NULL THEN 1 ELSE 0 END) / COUNT(q.created_at) * 100)';
8731            }
8732
8733            $query = "SELECT
8734                    q.commercial 'name',
8735                    COUNT(q.commercial) totalCommercial,
8736                    {$num} num
8737                FROM tbl_quotations q
8738                LEFT JOIN tbl_sources s ON s.source_id = q.source_id
8739                LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
8740                LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
8741                LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
8742                WHERE
8743                    q.for_add = 0
8744                    AND q.{$field} IS NOT NULL                    
8745                    AND q.budget_type_id != 7
8746                    AND q.budget_type_id IS NOT NULL
8747                    {$whereQ}
8748                    {$whereYear}
8749                GROUP BY q.commercial
8750                HAVING COUNT(q.commercial) > 0
8751                ORDER BY num DESC";
8752
8753            $resultCommercialsOrder = DB::select($query);
8754
8755            foreach ($resultCommercialsOrder as $item) {
8756                $namesToRemove[$item->name] = true;
8757            }
8758
8759            $resultArray = [];
8760            foreach ($resultCommercials as $item) {
8761                if (! isset($namesToRemove[$item->name])) {
8762                    $resultArray[] = $item;
8763                }
8764            }
8765
8766            $resultCommercials = array_merge($resultCommercialsOrder, $resultArray);
8767
8768            $cols = '';
8769
8770            $colsGroupConcatIds = '';
8771
8772            foreach ($resultCommercials as $item) {
8773                $cols .= ",COALESCE(
8774                    SUM(
8775                        CASE WHEN q.commercial = '{$item->name}' THEN {$col} ELSE 0 END
8776                    ), 0
8777                ) '{$item->name}'";
8778
8779                $colsGroupConcatIds .= ",GROUP_CONCAT(CASE WHEN q.commercial = '{$item->name}' THEN q.id END) 'groupConcatIds-{$item->name}'";
8780            }
8781
8782            $cols .= ",COALESCE(
8783                SUM(
8784                    CASE WHEN q.commercial IS NOT NULL THEN {$col} ELSE 0 END
8785                ), 0
8786            ) total";
8787
8788            $range = "CASE
8789                        WHEN amount < 100 THEN '< 100€'
8790                        WHEN amount BETWEEN 100 AND 500 THEN '100€ - 500€'
8791                        WHEN amount BETWEEN 500 AND 2000 THEN '500€ - 2k€'
8792                        WHEN amount BETWEEN 2000 AND 10000 THEN '2k€ - 10k€'
8793                        WHEN amount BETWEEN 10000 AND 30000 THEN '10k€ - 30k€'
8794                        WHEN amount BETWEEN 30000 AND 100000 THEN '30k€ - 100k€'
8795                        WHEN amount BETWEEN 100000 AND 999999999999 THEN '> 100k€'
8796                    END AS amount_range";
8797
8798            if (@$data['data_to_display'] == 3) {
8799
8800                $range = "CASE
8801                            WHEN SUM(amount) / COUNT(1) < 100 THEN '< 100€'
8802                            WHEN SUM(amount) / COUNT(1) BETWEEN 100 AND 500 THEN '100€ - 500€'
8803                            WHEN SUM(amount) / COUNT(1) BETWEEN 500 AND 2000 THEN '500€ - 2k€'
8804                            WHEN SUM(amount) / COUNT(1) BETWEEN 2000 AND 10000 THEN '2k€ - 10k€'
8805                            WHEN SUM(amount) / COUNT(1) BETWEEN 10000 AND 30000 THEN '10k€ - 30k€'
8806                            WHEN SUM(amount) / COUNT(1) BETWEEN 30000 AND 100000 THEN '30k€ - 100k€'
8807                            WHEN SUM(amount) / COUNT(1) BETWEEN 100000 AND 999999999999 THEN '> 100k€'
8808                        END AS amount_range";
8809
8810                $cols = '';
8811
8812                foreach ($resultCommercials as $item) {
8813                    $cols .= ",COALESCE(
8814                        (
8815                            SUM(CASE WHEN q.commercial = '{$item->name}' THEN q.amount END)
8816                        ) /
8817                        (
8818                            SUM(CASE WHEN q.commercial = '{$item->name}' THEN q.one END)
8819                        )
8820                    , 0) '{$item->name}'";
8821                }
8822
8823                $cols .= ',COALESCE(
8824                    (
8825                        SUM(CASE WHEN q.commercial IS NOT NULL THEN q.amount END)
8826                    ) /
8827                    (
8828                        SUM(CASE WHEN q.commercial IS NOT NULL THEN q.one END)
8829                    )
8830                , 0) total';
8831            }
8832
8833            if (@$data['data_to_display'] == 4) {
8834
8835                $cols = '';
8836
8837                foreach ($resultCommercials as $item) {
8838                    $cols .= ",COALESCE(
8839                        (
8840                            SUM(CASE WHEN q.commercial = '{$item->name}' THEN q.acceptanceDate END)
8841                        ) /
8842                        (
8843                            SUM(CASE WHEN q.commercial = '{$item->name}' THEN q.createdAt END)
8844                        ) * 100
8845                    , 0) '{$item->name}'";
8846                }
8847
8848                $cols .= ',COALESCE(
8849                    (
8850                        SUM(CASE WHEN q.commercial IS NOT NULL THEN q.acceptanceDate END)
8851                    ) /
8852                    (
8853                        SUM(CASE WHEN q.commercial IS NOT NULL THEN q.createdAt END)
8854                    ) * 100
8855                , 0) total';
8856            }
8857
8858            $query = "WITH amount_ranges AS (
8859                            SELECT '< 100€' AS amount_range
8860                            UNION ALL SELECT '100€ - 500€'
8861                            UNION ALL SELECT '500€ - 2k€'
8862                            UNION ALL SELECT '2k€ - 10k€'
8863                            UNION ALL SELECT '10k€ - 30k€'
8864                            UNION ALL SELECT '30k€ - 100k€'
8865                            UNION ALL SELECT '> 100k€'
8866                        )
8867
8868                        SELECT
8869                            ar.amount_range,
8870                             GROUP_CONCAT(
8871                                q.id
8872                            ) AS groupConcatIds
8873                            {$colsGroupConcatIds}
8874                            {$cols}
8875                        FROM
8876                            amount_ranges ar
8877                        LEFT JOIN (
8878                            SELECT
8879                                commercial,
8880                                GROUP_CONCAT(
8881                                    CASE WHEN q.{$field} IS NOT NULL THEN id END
8882                                ) id,
8883                                amount AS amount,
8884                                COUNT(CASE WHEN q.created_at IS NOT NULL THEN 1 END) createdAt,
8885                                COUNT(CASE WHEN q.acceptance_date IS NOT NULL THEN 1 END) acceptanceDate,
8886                                COUNT(1) AS one,
8887                                {$range}
8888                            FROM
8889                                tbl_quotations q
8890                                LEFT JOIN tbl_sources s ON s.source_id = q.source_id
8891                                LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
8892                                LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
8893                                LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
8894                            WHERE
8895                                q.{$field} IS NOT NULL
8896                                AND q.for_add = 0                                
8897                                AND q.budget_type_id != 7
8898                                AND q.budget_type_id IS NOT NULL
8899                                AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
8900                                {$where}
8901                                {$whereYear}
8902                            GROUP BY
8903                                commercial,
8904                                id
8905                        ) AS q ON ar.amount_range = q.amount_range
8906                        GROUP BY
8907                            ar.amount_range WITH ROLLUP
8908                        ORDER BY
8909                            CASE
8910                                WHEN ar.amount_range = '< 100€' THEN 1
8911                                WHEN ar.amount_range = '100€ - 500€' THEN 2
8912                                WHEN ar.amount_range = '500€ - 2k€' THEN 3
8913                                WHEN ar.amount_range = '2k€ - 10k€' THEN 4
8914                                WHEN ar.amount_range = '10k€ - 30k€' THEN 5
8915                                WHEN ar.amount_range = '30k€ - 100k€' THEN 6
8916                                ELSE 7
8917                            END";
8918
8919            $value = Cache::get(base64_encode($query));
8920
8921            if (! $value) {
8922                $result = DB::select($query);
8923
8924                Cache::put(base64_encode($query), $result, 600);
8925            } else {
8926                $result = $value;
8927            }
8928
8929            return response([
8930                'message' => 'OK',
8931                'data' => $result,
8932                'commercials' => $resultCommercials,
8933            ]);
8934
8935        } catch (\Exception $e) {
8936            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_COMMERCIAL_EXCEPTION'));
8937            return response(['message' => 'KO', 'error' => $e->getMessage()]);
8938        }
8939
8940    }
8941
8942    function send_email_template_preview($emailTemplateId): ResponseFactory|HttpResponse{
8943
8944        try {
8945
8946            $emailTemplateId = addslashes((string) $emailTemplateId);
8947
8948            $emailTemplate = TblEmailConfiguration::where('id', $emailTemplateId)->first();
8949
8950            $user = TblUsers::where('id', $this->userId)->first();
8951            $error = false;
8952
8953            $toEmail = $user->email;
8954
8955            $availableParameters = [
8956                'quote_id',
8957                'company_id',
8958                'client',
8959                'client_type',
8960                'phone_number',
8961                'email',
8962                'issue_date',
8963                'request_date',
8964                'duration',
8965                'invoice_number',
8966                'type',
8967                'acceptance_date',
8968                'status',
8969                'source',
8970                'amount',
8971                'reason_for_not_following_up',
8972                'last_follow_up_date',
8973                'last_follow_up_comment',
8974                'reason_for_rejection_id',
8975                'reason_for_rejection',
8976                'commercial',
8977                'created_at',
8978                'created_by',
8979                'updated_at',
8980                'updated_by',
8981            ];
8982
8983            $dateParameters = [
8984                'issue_date',
8985                'request_date',
8986                'acceptance_date',
8987                'last_follow_up_date',
8988                'created_at',
8989                'updated_at',
8990            ];
8991
8992            if($this->locale == 'es'){
8993                setlocale(LC_ALL, "es_ES", 'Spanish_Spain', 'Spanish');
8994                setlocale(LC_ALL, "es_ES", 'Spanish_Spain', 'Spanish');
8995            }
8996
8997            $body = $emailTemplate->html;
8998            $subject = $emailTemplate->subject;
8999
9000            preg_match_all('/{{(.*?)}}/', (string) $body, $matches);
9001
9002            $parameters = $matches[1];
9003
9004            $result = TblQuotations::where('for_add', 0)->whereIn('company_id', $this->companyIds)->first();
9005
9006            foreach ($parameters as $parameter) {
9007
9008                if(in_array($parameter, $dateParameters)){
9009                    if($result->{$parameter}){
9010                        $result->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result->{$parameter})));
9011                    }
9012                }
9013
9014                if (in_array($parameter, $availableParameters)) {
9015                    $body = str_replace('{{'.$parameter.'}}', $result->{$parameter}, $body);
9016                }
9017            }
9018
9019            preg_match_all('/{{(.*?)}}/', (string) $subject, $matches);
9020
9021            $parameters = $matches[1];
9022
9023            foreach ($parameters as $parameter) {
9024
9025                if(in_array($parameter, $dateParameters)){
9026                    if($result->{$parameter}){
9027                        $result->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result->{$parameter})));
9028                    }
9029                }
9030
9031                if (in_array($parameter, $availableParameters)) {
9032                    $subject = str_replace('{{'.$parameter.'}}', $result->{$parameter}, $subject);
9033                }
9034            }
9035
9036            $email = new \SendGrid\Mail\Mail;
9037
9038            $templateFiles = TblEmailFiles::where('email_template_id', $emailTemplateId)->orderBy('order', 'asc')->get();
9039
9040            foreach ($templateFiles as $item) {
9041                $f = storage_path('app/public/uploads/'.$item->filename);
9042                $imgpath = file_get_contents($f);
9043                $mimeType = mime_content_type($f);
9044
9045                $email->addAttachment(
9046                    $imgpath,
9047                    $mimeType,
9048                    str_replace(' ', '', $item->original_name),
9049                    'inline',
9050                    str_replace(' ', '', $item->original_name),
9051                );
9052
9053                $body .= "<img src='cid:{$item->original_name}' style='height: 45px; padding-right: 6px' />";
9054            }
9055
9056            $html = '<!DOCTYPE html>';
9057            $html .= '<html>';
9058            $html .= '<head>';
9059            $html .= '<meta charset="UTF-8">';
9060            $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
9061            $html .= '</head>';
9062            $html .= '<body>';
9063            $html .= $body;
9064            $html .= '</body>';
9065            $html .= '</html>';
9066
9067            if ($toEmail != null) {
9068
9069                $companyEmail = null;
9070
9071                if ($emailTemplate->from_id != null) {
9072                    $companyEmail = TblCompanyEmails::where('id', $emailTemplate->from_id)->first();
9073                } else {
9074                    $companyEmail = TblCompanyEmails::where('is_active', 1)->where('verified', 1)->where('company_id', $result->company_id)->first();
9075                }
9076
9077                if (! $companyEmail) {
9078                    return response(['message' => 'KO', 'error' => __('language.no_active_verified_sender')]);
9079                }
9080
9081                $email->setFrom($companyEmail->from_email, $companyEmail->from_name);
9082                $email->setSubject($subject);
9083                $email->addTo($toEmail);
9084                $email->addContent('text/html', $html);
9085
9086                $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
9087
9088                $response = $sendgrid->send($email);
9089                if ($response->statusCode() == 202) {
9090                    return response(['message' => 'OK']);
9091                }
9092            }
9093
9094            return response(['message' => 'KO']);
9095
9096        } catch (\Exception $e) {
9097            report(AppException::fromException($e, 'SEND_EMAIL_TEMPLATE_EXCEPTION'));
9098            return response(['message' => 'KO', 'error' => $e->getMessage()]);
9099        }
9100
9101    }
9102
9103    function list_quotation_analytics_by_types_of_budgets_company_per_week(Request $request): ResponseFactory|HttpResponse{
9104
9105        try {
9106
9107            $data = $request->all();
9108            $companyId = addslashes((string) $data['company_id']);
9109            $field = $data['field'];
9110
9111            $where = "";
9112            $whereYear = "";
9113            $dateLflArray = [];
9114            $companyIds = $this->companyIds;
9115
9116            $acc = '';
9117            if ($field == 'acceptance_date') {
9118                $acc = ' AND q.acceptance_date IS NOT NULL ';
9119                // $field = 'created_at';
9120
9121                if (@$data['data_to_display'] == 4) {
9122                    $field = 'created_at';
9123                    $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
9124                }
9125            } else {
9126                $field = 'created_at';
9127                $where .= ' AND YEAR(q.created_at) = YEAR(q.issue_date)';
9128            }
9129
9130            if ($companyId != 0) {
9131                $companyIds = [$companyId];
9132            }
9133
9134            if (isset($data['years']) && $data['years'] != null) {
9135
9136                if (count($data['years']) > 0) {
9137                    foreach ($data['years'] as $year) {
9138                        if (isset($data['week']) && $data['week'] != null) {
9139                            $w = sprintf('%02d', $data['week']);
9140                            $whereYear .= " AND YEARWEEK(q.{$field}, 1) = '{$year}{$w}'";
9141                        } else {
9142                            $whereYear .= " AND YEAR(q.{$field}) = {$year}";
9143                        }
9144                    }
9145                }
9146            }
9147
9148            foreach ($companyIds as $v) {
9149
9150                $lflWhere = " AND q.company_id = {$v} ";
9151
9152                $query = "SELECT
9153                            CONCAT(
9154                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
9155                                ' - ',
9156                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
9157                            ) AS date_like,
9158                            YEAR(q.{$field}) 'year',
9159                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
9160                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
9161                            {$v} 'company_id'
9162                        FROM
9163                            tbl_quotations q
9164                        WHERE
9165                            q.{$field} IS NOT NULL
9166                            AND q.for_add = 0
9167                            {$lflWhere}
9168                            {$whereYear}
9169                        GROUP BY YEAR(q.{$field})
9170                        ORDER BY YEAR(q.{$field}) DESC";
9171
9172                $dateLike = DB::select($query);
9173
9174                $dateLflArray[$v] = $dateLike;
9175            }
9176
9177            $isFy = true;
9178
9179            if (isset($data['ytd']) && $data['ytd'] != null && $data['ytd'] == true) {
9180                $isFy = false;
9181                $ytdArray = [];
9182                $ytdAcceptanceArray = [];
9183                $lflCompanyIds = [];
9184                foreach ($dateLflArray as $k => $v) {
9185                    foreach ($dateLflArray[$k] as $item) {
9186                        $year = $item->year;
9187                        $now = date('m-d');
9188                        array_push($ytdArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN '{$year}-01-01' AND '{$year}-{$now}'");
9189                    }
9190
9191                    $ytdArray = implode(' OR ', $ytdArray);
9192                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$ytdArray})");
9193                    $ytdArray = [];
9194                }
9195
9196                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
9197                $where .= " AND ({$lflCompanyIds}";
9198            }
9199
9200            if (isset($data['lfl']) && $data['lfl'] != null && $data['lfl'] == true) {
9201                $isFy = false;
9202                $lflArray = [];
9203                $ytdAcceptanceArray = [];
9204                $lflCompanyIds = [];
9205                foreach ($dateLflArray as $k => $v) {
9206                    foreach ($dateLflArray[$k] as $item) {
9207                        $year = $item->year;
9208                        $min_date_like = $item->min_date_like;
9209                        $max_date_like = $item->max_date_like;
9210                        array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
9211                    }
9212
9213                    $lflArray = implode(' OR ', $lflArray);
9214                    array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
9215                    $lflArray = [];
9216                }
9217
9218                $lflCompanyIds = implode(' OR ', $lflCompanyIds);
9219                $where .= " AND ({$lflCompanyIds}";
9220            }
9221
9222            if ($isFy) {
9223                if ($companyId != 0) {
9224                    $where .= " AND q.company_id = {$companyId} ";
9225                } else {
9226                    $where .= " AND q.company_id IN ({$this->companyId})";
9227                }
9228            }
9229
9230            if (isset($data['source']) && $data['source'] != null) {
9231                $where .= " AND s.name = '{$data['source']}'";
9232            }
9233
9234            if (isset($data['month']) && $data['month'] != null) {
9235                $where .= " AND MONTH(q.{$field}) = '{$data['month']}'";
9236            }
9237
9238            if (isset($data['commercial']) && $data['commercial'] != null) {
9239                $where .= " AND q.commercial = '{$data['commercial']}'";
9240            }
9241
9242            if (isset($data['created_by']) && $data['created_by'] != null) {
9243                $where .= " AND q.created_by = '{$data['created_by']}'";
9244            }
9245
9246            if (isset($data['budget_type']) && $data['budget_type'] != null) {
9247                $where .= " AND bt.budget_type_id = {$data['budget_type']}";
9248            }
9249
9250            if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
9251                $where .= " AND bt.budget_type_group_id = {$data['budget_type_group']}";
9252            }
9253
9254            if (isset($data['budget_status']) && $data['budget_status'] != null) {
9255                $where .= " AND bs.budget_status_id = {$data['budget_status']}";
9256            }
9257
9258            if (isset($data['client_type']) && $data['client_type'] != null) {
9259                $where .= " AND ct.customer_type_id = {$data['client_type']}";
9260            }
9261
9262            if (isset($data['segment_id']) && $data['segment_id'] != null) {
9263                $where .= " AND q.segment_id = {$data['segment_id']}";
9264            }
9265
9266            $col = '1';
9267
9268            if (isset($data['data_to_display']) && $data['data_to_display'] != null) {
9269                if ($data['data_to_display'] == 1) {
9270                    $col = '1';
9271                }
9272
9273                if ($data['data_to_display'] == 2) {
9274                    $col = 'q.amount';
9275                }
9276            }
9277
9278            $budgetTypes = TblBudgetTypes::orderByRaw('ISNULL(priority), priority ASC')->get();
9279            $cols = '';
9280            foreach ($budgetTypes as $item) {
9281                if ($item->name == '' || $item->name == null) {
9282                    $cols .= ",COALESCE(SUM(CASE WHEN bt.name IS NULL {$acc} THEN {$col} ELSE 0 END), 0) AS 'Otros'";
9283                } else {
9284                    $cols .= ",COALESCE(SUM(CASE WHEN bt.name = '{$item->name}{$acc} THEN {$col} ELSE 0 END), 0) AS '{$item->name}'";
9285                }
9286            }
9287
9288            $budgetTypeGroups = TblBudgetTypeGroups::orderByRaw('ISNULL(priority), priority ASC')->get();
9289
9290            $colsGroups = ",COALESCE(SUM(CASE WHEN bt.name IS NULL {$acc} THEN {$col} END), 0) AS Otros";
9291
9292            foreach ($budgetTypeGroups as $item) {
9293                $budgetTypeGroupName = str_replace(' ', '', $item->name).$item->budget_type_group_id;
9294                $colsGroups .= ",GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.id END) AS 'groupConcatIds{$budgetTypeGroupName}'";
9295                $colsGroups .= ",COALESCE(SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN {$col} END), 0) AS '{$budgetTypeGroupName}'";
9296            }
9297
9298            $colsGroups .= ",COALESCE(SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN {$col} END), 0) AS total";
9299
9300            $col = $colsGroups.$cols;
9301
9302            if (@$data['data_to_display'] == 3) {
9303
9304                $cols = '';
9305                foreach ($budgetTypes as $item) {
9306                    if ($item->name == '' || $item->name == null) {
9307                        $cols .= ",COALESCE(
9308                                        SUM(CASE WHEN bt.name IS NULL {$acc} THEN q.amount ELSE 0 END) /
9309                                        SUM(CASE WHEN bt.name IS NULL {$acc} THEN 1 ELSE 0 END) * 100
9310                                    , 0) AS 'Otros'";
9311                    } else {
9312                        $cols .= ", COALESCE(
9313                                        SUM(CASE WHEN bt.name = '{$item->name}{$acc} THEN q.amount ELSE 0 END) /
9314                                        SUM(CASE WHEN bt.name = '{$item->name}{$acc} THEN 1 ELSE 0 END)
9315                                    , 0) AS '{$item->name}'";
9316                    }
9317                }
9318
9319                $colsGroups = ",COALESCE(
9320                                (SUM(CASE WHEN bt.name IS NULL {$acc} THEN q.amount END)) /
9321                                (SUM(CASE WHEN bt.name IS NULL {$acc} THEN 1 END))
9322                            , 0) Otros";
9323
9324                foreach ($budgetTypeGroups as $item) {
9325                    $budgetTypeGroupName = str_replace(' ', '', $item->name).$item->budget_type_group_id;
9326                    $colsGroups .= ",GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.id END) AS 'groupConcatIds{$budgetTypeGroupName}'";
9327                    $colsGroups .= ",COALESCE(
9328                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.amount END)) /
9329                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN 1 END))
9330                                    , 0) '{$budgetTypeGroupName}'";
9331                }
9332
9333                $colsGroups .= ",COALESCE(
9334                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN q.amount END)) /
9335                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN 1 END))
9336                                , 0) total";
9337
9338                $col = $colsGroups.$cols;
9339            }
9340
9341            if (@$data['data_to_display'] == 4) {
9342
9343                $cols = '';
9344
9345                foreach ($budgetTypes as $item) {
9346
9347                    if ($item->name == '' || $item->name == null) {
9348                        $cols .= ",COALESCE(
9349                                        SUM(CASE WHEN bt.name IS NULL AND q.acceptance_date IS NOT NULL THEN 1 ELSE 0 END) /
9350                                        SUM(CASE WHEN bt.name IS NULL AND q.created_at IS NOT NULL THEN 1 ELSE 0 END) * 100
9351                                    , 0) AS 'Otros'";
9352                    } else {
9353                        $cols .= ", COALESCE(
9354                                        SUM(CASE WHEN bt.name = '{$item->name}' AND q.acceptance_date IS NOT NULL THEN 1 END) /
9355                                        SUM(CASE WHEN bt.name = '{$item->name}' AND q.created_at IS NOT NULL THEN 1 END) * 100
9356                                    , 0) AS '{$item->name}'";
9357                    }
9358                }
9359
9360                $colsGroups = ',COALESCE(
9361                                    (SUM(CASE WHEN bt.name IS NULL AND q.acceptance_date IS NOT NULL THEN 1 END)) /
9362                                    (SUM(CASE WHEN bt.name IS NULL AND q.created_at IS NOT NULL THEN 1 END)) * 100
9363                                , 0) Otros';
9364
9365                foreach ($budgetTypeGroups as $item) {
9366                    $budgetTypeGroupName = str_replace(' ', '', $item->name).$item->budget_type_group_id;
9367                    $colsGroups .= ",GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) {$acc} THEN q.id END) AS 'groupConcatIds{$budgetTypeGroupName}'";
9368                    $colsGroups .= ",COALESCE(
9369                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) AND q.acceptance_date IS NOT NULL THEN 1 END)) /
9370                                        (SUM(CASE WHEN (bt.budget_type_group_id = {$item->budget_type_group_id} OR bt.name IS NULL) AND q.created_at IS NOT NULL THEN 1 END)) * 100
9371                                    , 0) '{$budgetTypeGroupName}'";
9372                }
9373
9374                $colsGroups .= ',COALESCE(
9375                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) AND q.acceptance_date IS NOT NULL THEN 1 END)) /
9376                                    (SUM(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) AND q.created_at IS NOT NULL THEN 1 END)) * 100
9377                                    , 0) total';
9378
9379                $col = $colsGroups.$cols;
9380            }
9381
9382            $query = "SELECT
9383                            YEAR(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)) AS 'year',
9384                            LPAD(MONTH(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)), 2, 0) AS 'month',
9385                            q.company_id,
9386                            c.name 'companyName',
9387                            GROUP_CONCAT(CASE WHEN (bt.budget_type_group_id IS NOT NULL OR bt.name IS NULL) {$acc} THEN q.id END) groupConcatIds
9388                            {$col}
9389                        FROM
9390                            tbl_quotations q
9391                            LEFT JOIN tbl_sources s ON s.source_id = q.source_id
9392                            LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
9393                            LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
9394                            LEFT JOIN tbl_budget_type_groups btg ON bt.budget_type_group_id = btg.budget_type_group_id
9395                            LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
9396                            LEFT JOIN tbl_companies c ON q.company_id = c.company_id
9397                        WHERE
9398                            q.{$field} IS NOT NULL
9399                            AND q.for_add = 0
9400                            AND q.budget_type_id != 7
9401                            AND q.budget_type_id IS NOT NULL
9402                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1                            
9403                            {$where}
9404                            {$whereYear}
9405                        GROUP BY
9406                            YEAR(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)),
9407                            MONTH(DATE_ADD(q.{$field}, INTERVAL - WEEKDAY(q.{$field}) DAY)),
9408                            q.company_id WITH ROLLUP
9409                        ORDER BY
9410                            YEAR DESC,
9411                            MONTH ASC,
9412                            q.company_id ASC,
9413                            DATE_FORMAT(q.{$field}, '%e') ASC";
9414
9415            $result = DB::select($query);
9416
9417            $query = "SELECT
9418                        btg.budget_type_group_id,
9419                        btg.name,
9420                        (
9421                            SELECT
9422                                GROUP_CONCAT(COALESCE(bt.name, '') ORDER BY ISNULL(bt.priority), bt.priority ASC SEPARATOR '|')
9423                            FROM
9424                                tbl_budget_types bt
9425                            WHERE
9426                                bt.budget_type_group_id = btg.budget_type_group_id
9427                        ) budget_types
9428                        FROM
9429                            tbl_budget_type_groups btg
9430                        ORDER BY
9431                            ISNULL(btg.priority),
9432                            btg.priority ASC";
9433
9434            $budgetTypeGroups = DB::select($query);
9435
9436            foreach ($budgetTypeGroups as $item) {
9437                $item->group_key_name = str_replace(" ", "", $item->name) . $item->budget_type_group_id;
9438                $item->budget_types = explode("|", (string) $item->budget_types);
9439            }
9440
9441            return response([
9442                'message' => 'OK',
9443                'data' => $result,
9444                'budgetTypeGroups' => $budgetTypeGroups,
9445            ]);
9446
9447        } catch (\Exception $e) {
9448            report(AppException::fromException($e, 'LIST_QUOTATION_ANALYTICS_BY_TYPES_OF_BUDGETS_COMPANT_PER_WEEK_EXCEPTION'));
9449            return response(['message' => 'KO', 'error' => $e->getMessage()]);
9450        }
9451    }
9452
9453    function request_permission_commercial(Request $request): ResponseFactory|HttpResponse{
9454
9455        try {
9456
9457            $data = $request->all();
9458
9459            $id = addslashes((string) $data['id']);
9460            $requestedBy = $data['requested_by'];
9461            $body = '';
9462
9463            $result = TblQuotations::where('id', $id)->first();
9464
9465            $subject = __('language.request_permission_commercial.subject');
9466            $subject = str_replace('{{quote_id}}', $result->quote_id, $subject);
9467            $subject = str_replace('{{username}}', $requestedBy, $subject);
9468
9469            $email = new \SendGrid\Mail\Mail;
9470
9471            $imgpath = file_get_contents(public_path('fireservicetitan.png'));
9472
9473            $email->addAttachment(
9474                $imgpath,
9475                'image/png',
9476                'fireservicetitan.png',
9477                'inline',
9478                'fireservicetitan'
9479            );
9480
9481            $url = config('app.frontend_url') . "orders/{$id}?company_id={$result->company_id}";
9482            $href = "<a href='{$url}'>{$result->quote_id}</a>";
9483
9484            $user = TblUsers::where('name', $requestedBy)->first();
9485
9486            $urlClick = config('app.frontend_url') . "update?confirm_request={$id}&requested_by={$user->id}&quote_id={$result->quote_id}";
9487            $company = TblCompanies::where('company_id', $result->company_id)->first();
9488
9489            $body .= __('language.request_permission_commercial.body_hello');
9490            $body .= __('language.request_permission_commercial.body_message');
9491
9492            $amount = $this->currency($result->amount, 1);
9493
9494            $body = str_replace('{{commercial}}', $result->commercial, $body);
9495            $body = str_replace('{{company}}', $company->name, $body);
9496            $body = str_replace('{{client}}', $result->client, $body);
9497            $body = str_replace('{{amount}}', $amount, $body);
9498            $body = str_replace('{{quote_id}}', $href, $body);
9499            $body = str_replace('{{click}}', $urlClick, $body);
9500            $body = str_replace('{{username}}', $requestedBy, $body);
9501
9502            $body .= '<p>Fire Service Titan</p>';
9503            $body .= "<img src='cid:fireservicetitan' style='height: 45px;' />";
9504
9505            $html = '<!DOCTYPE html>';
9506            $html .= '<html>';
9507            $html .= '<head>';
9508            $html .= '<meta charset="UTF-8">';
9509            $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
9510            $html .= '</head>';
9511            $html .= '<body>';
9512            $html .= $body;
9513            $html .= '</body>';
9514            $html .= '</html>';
9515
9516            $user = TblUsers::where('id', $this->userId)->first();
9517
9518            if(config('services.sendgrid.staging')){
9519                $email->addTo($user->email);
9520            } else {
9521                $email->addTo('luis.collar@fire.es');
9522
9523                $user = TblUsers::where('name', $result->created_by)->first();
9524                if ($user && $user->email != 'luis.collar@fire.es') {
9525                    $email->addTo($user->email);
9526                }
9527
9528                if ($result->created_by != $result->commercial) {
9529                    $user = TblUsers::where('name', $result->commercial)->first();
9530                    if ($user->email != 'luis.collar@fire.es') {
9531                        $email->addTo($user->email);
9532                    }
9533                }
9534            }
9535
9536            $email->setFrom('titan@fire.es');
9537            $email->setSubject($subject);
9538
9539            $email->addContent('text/html', $html);
9540
9541            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
9542
9543            $response = $sendgrid->send($email);
9544            if ($response->statusCode() == 202) {
9545                return response(['message' => 'OK']);
9546            }
9547
9548            $this->addUpdateLog($id, $requestedBy, 'request_permission_commercial', null, null, 4);
9549
9550            return response(['message' => 'KO']);
9551
9552        } catch (\Exception $e) {
9553            report(AppException::fromException($e, 'REQUEST_PERMISSION_EXCEPTION'));
9554            return response(['message' => 'KO', 'error' => $e->getMessage()]);
9555        }
9556
9557    }
9558
9559    function confirm_update_commercial(Request $request): ResponseFactory|HttpResponse{
9560
9561        try {
9562            sleep(3);
9563            $data = $request->all();
9564            $id = addslashes((string) $data['id']);
9565            $userId = addslashes((string) $data['requested_by']);
9566
9567            $user = TblUsers::where('id', $userId)->first();
9568
9569            if ($user) {
9570
9571                TblQuotations::where('id', $data['id'])->update(
9572                    [
9573                        'commercial' => $user->name,
9574                        'updated_at' => date('Y-m-d H:i:s')
9575                    ]
9576                );
9577
9578            } else {
9579                return response(['message' => 'KO', 'error' => 'invalid_user']);
9580            }
9581
9582            return response(['message' => 'OK']);
9583
9584        } catch (\Exception $e) {
9585            report(AppException::fromException($e, 'CONFIRM_UPDATE_COMMERCIAL_EXCEPTION'));
9586            return response(['message' => 'KO', 'error' => $e->getMessage()]);
9587        }
9588    }
9589
9590    function calculateEmailRequestSize(Mail $email): int{
9591
9592        $size = 0;
9593
9594        // Add size of 'from', 'to', 'subject', 'content'
9595        $from = $email->getFrom();
9596        $size += strlen(json_encode([
9597            'from' => $from->getEmail().' '.$from->getName(),
9598            'subject' => $email->getSubject(),
9599        ]));
9600
9601        // Add size of 'to' (recipients)
9602        $personalizations = $email->getPersonalizations() ?? [];
9603        foreach ($personalizations as $personalization) {
9604            foreach ($personalization->getTos() as $to) {
9605                $size += strlen($to->getEmail().' '.$to->getName());
9606            }
9607        }
9608
9609        // Add size of content
9610        foreach ($email->getContents() as $content) {
9611            $size += strlen((string) $content->getValue());
9612        }
9613
9614        // Add size of attachments (if any)
9615
9616        if ($email->getAttachments() != null && $email->getAttachments() != '') {
9617            foreach ($email->getAttachments() as $attachment) {
9618                $size += strlen($attachment->getContent()); // Base64 encoded size
9619                $size += strlen($attachment->getFilename());
9620                $size += strlen($attachment->getType());
9621            }
9622        }
9623
9624        $sizeInMegabytes = $size / 1048576; // 1 MB = 1,048,576 bytes
9625
9626        return (int) ceil($sizeInMegabytes);
9627    }
9628
9629    public function list_quotation_analytics_commercial_productivity(Request $request): ResponseFactory|HttpResponse{
9630
9631        // try {
9632
9633        $data = $request->all();
9634        $companyId = addslashes((string) $data['company_id']);
9635
9636        $where = '';
9637        $whereYear = '';
9638        $whereVisit = '';
9639
9640        $dateLflArray = [];
9641        $companyIds = $this->companyIds;
9642
9643        if ($companyId != 0) {
9644            $companyIds = [$companyId];
9645        }
9646
9647        $field = 'issue_date';
9648
9649        if (isset($data['years']) && $data['years'] != null) {
9650            $years = implode(',', $data['years']);
9651            if (count($data['years']) > 0) {
9652                $whereYear = " AND YEAR(q.{$field}) IN ({$years})";
9653            }
9654        }
9655
9656        foreach ($companyIds as $v) {
9657
9658            $lflWhere = " AND q.company_id = {$v} ";
9659
9660            $query = "SELECT
9661                            CONCAT(
9662                                DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field}),
9663                                ' - ',
9664                                DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%c/%e/'), YEAR({$field})
9665                            ) AS date_like,
9666                            YEAR(q.{$field}) 'year',
9667                            DATE_FORMAT((SELECT MIN(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS min_date_like,
9668                            DATE_FORMAT((SELECT MAX(q.{$field}) FROM tbl_quotations q WHERE q.{$field} IS NOT NULL {$lflWhere}), '%m-%d') AS max_date_like,
9669                            {$v} 'company_id'
9670                        FROM
9671                            tbl_quotations q
9672                        WHERE
9673                            q.{$field} IS NOT NULL
9674                            AND q.for_add = 0
9675                            {$lflWhere}
9676                            {$whereYear}
9677                        GROUP BY YEAR(q.{$field})
9678                        ORDER BY YEAR(q.{$field}) DESC";
9679
9680            $dateLike = DB::select($query);
9681
9682            if (count($dateLike) > 0) {
9683                $dateLflArray[$v] = $dateLike;
9684            }
9685        }
9686
9687        $whereAcceptanceDate = '';
9688
9689        if (isset($data['source']) && $data['source'] != null) {
9690            $where .= " AND s.name = '{$data['source']}'";
9691        }
9692
9693        if (isset($data['month']) && $data['month'] != null) {
9694            $where .= " AND MONTH(q.created_at) = '{$data['month']}'";
9695        }
9696
9697        if (isset($data['week']) && $data['week'] != null) {
9698            $where .= " AND WEEK(q.created_at) = '{$data['week']}'";
9699        }
9700
9701        if (isset($data['commercial']) && $data['commercial'] != null) {
9702            $commercial = implode("','", $data['commercial']);
9703            if (count($data['commercial']) > 0) {
9704                $where .= " AND q.commercial IN ('{$commercial}') ";
9705                $whereVisit .= " AND q.commercial IN ('{$commercial}') ";
9706            }
9707        }
9708
9709        if (isset($data['created_by']) && $data['created_by'] != null) {
9710            $created_by = implode("','", $data['created_by']);
9711            if (count($data['created_by']) > 0) {
9712                $where .= " AND q.created_by IN ('{$created_by}')";
9713            }
9714        }
9715
9716        if (isset($data['budget_type']) && $data['budget_type'] != null) {
9717            $where .= " AND bt.budget_type_id = {$data['budget_type']}";
9718        }
9719
9720        if (isset($data['budget_type_group']) && $data['budget_type_group'] != null) {
9721            $budgetTypeGroupIds = implode(',', $data['budget_type_group']);
9722            if (count($data['budget_type_group']) > 0) {
9723                $where .= " AND bt.budget_type_group_id IN ({$budgetTypeGroupIds})";
9724            }
9725        }
9726
9727        if (isset($data['budget_status']) && $data['budget_status'] != null) {
9728            $where .= " AND bs.budget_status_id = {$data['budget_status']}";
9729        }
9730
9731        if (isset($data['client_type']) && $data['client_type'] != null) {
9732            $where .= " AND ct.customer_type_id = {$data['client_type']}";
9733        }
9734
9735        if (isset($data['segment_id']) && $data['segment_id'] != null) {
9736            $where .= " AND q.segment_id = {$data['segment_id']}";
9737        }
9738
9739        if (isset($data['role_id']) && $data['role_id'] != null) {
9740            $roleId = implode(',', $data['role_id']);
9741            if (count($data['role_id']) > 0) {
9742                $where .= " AND r.role_id IN ({$roleId})";
9743                $whereVisit .= " AND r.role_id IN ({$roleId}";
9744            }
9745        }
9746
9747        $groupByFilter = 2;
9748        if (isset($data['group_by']) && $data['group_by'] != null) {
9749            $groupByFilter = $data['group_by'];
9750        }
9751
9752        $groupBy = '1, 2, 3, q.commercial, budget_type';
9753
9754        if ($groupByFilter == 1) {
9755            $groupBy = '1, q.commercial, 2, 3, budget_type';
9756        }
9757
9758        if ($groupByFilter == 3) {
9759            $groupBy = '1, budget_type, q.commercial, 2, 3';
9760        }
9761
9762        $aggregatedBy = 1;
9763        $aggregatedCol = "LPAD(q.month, 2, 0) AS 'month', LPAD(q.week, 2, 0) AS 'week', ";
9764        $aggregatedByCalc = ' / 4';
9765        if (isset($data['aggregated_by']) && $data['aggregated_by'] != null) {
9766            $aggregatedBy = $data['aggregated_by'];
9767            if ($data['aggregated_by'] == 1) {
9768
9769                $groupBy = '1, 2, 3, q.commercial, budget_type';
9770
9771                if ($groupByFilter == 1) {
9772                    $groupBy = '1, q.commercial, 2, 3, budget_type';
9773                }
9774
9775                if ($groupByFilter == 3) {
9776                    $groupBy = '1, budget_type, q.commercial, 2, 3';
9777                }
9778
9779                $aggregatedCol = "LPAD(q.month, 2, 0) AS 'month', LPAD(q.week, 2, 0) AS 'week', ";
9780            } elseif ($data['aggregated_by'] == 2) {
9781                $groupBy = '1, 2, q.commercial, budget_type';
9782
9783                if ($groupByFilter == 1) {
9784                    $groupBy = '1, q.commercial, 2, budget_type';
9785                }
9786
9787                if ($groupByFilter == 3) {
9788                    $groupBy = '1, budget_type, q.commercial, 2';
9789                }
9790
9791                $aggregatedCol = "LPAD(q.month, 2, 0) AS 'month', NULL AS 'week',";
9792                $aggregatedByCalc = '';
9793            } elseif ($data['aggregated_by'] == 3) {
9794                $groupBy = '1, q.commercial, budget_type';
9795
9796                if ($groupByFilter == 3) {
9797                    $groupBy = '1, budget_type, q.commercial';
9798                }
9799
9800                $aggregatedCol = "NULL AS 'month', NULL AS 'week',";
9801                $aggregatedByCalc = ' * 12';
9802            }
9803        }
9804
9805        $whereAcceptanceDate = $where;
9806        $whereCreatedAt = $where;
9807
9808        $isFy = true;
9809
9810        if ($companyId != 0) {
9811            $where .= " AND q.company_id = {$companyId} ";
9812            $whereCreatedAt .= " AND q.company_id = {$companyId} ";
9813            $whereAcceptanceDate .= " AND q.company_id = {$companyId} ";
9814            $whereVisit .= " AND q.company_id = {$companyId} ";
9815        } else {
9816            $where .= " AND q.company_id IN ({$this->companyId}";
9817            $whereCreatedAt .= " AND q.company_id IN ({$this->companyId}";
9818            $whereAcceptanceDate .= " AND q.company_id IN ({$this->companyId}";
9819            $whereVisit .= " AND q.company_id IN ({$this->companyId}";
9820        }
9821
9822        if (isset($data['campaign']) && $data['campaign'] != null) {
9823            $campaign = implode("','", $data['campaign']);
9824            if (count($data['campaign']) > 0) {
9825                $whereVisit .= " AND q.campaign IN ('{$campaign}')";
9826            }
9827        }
9828
9829        if (isset($data['ytd']) && $data['ytd'] != null && $data['ytd'] == true) {
9830            $isFy = false;
9831            $now = date('m-d');
9832
9833            $where .= " AND q.{$field} BETWEEN DATE_FORMAT(q.{$field}, '%Y-01-01') AND DATE_FORMAT(q.{$field}, '%Y-{$now}') ";
9834            $whereCreatedAt .= " AND q.created_at BETWEEN DATE_FORMAT(q.created_at, '%Y-01-01') AND DATE_FORMAT(q.created_at, '%Y-{$now}') ";
9835            $whereAcceptanceDate .= " AND q.acceptance_date BETWEEN DATE_FORMAT(q.acceptance_date, '%Y-01-01') AND DATE_FORMAT(q.acceptance_date, '%Y-{$now}') ";
9836            $whereVisit .= " AND q.visit_date BETWEEN DATE_FORMAT(q.visit_date, '%Y-01-01') AND DATE_FORMAT(q.visit_date, '%Y-{$now}') ";
9837        }
9838
9839        if (isset($data['lfl']) && $data['lfl'] != null && $data['lfl'] == true) {
9840            $isFy = false;
9841            $lflArray = [];
9842            $ytdAcceptanceArray = [];
9843            $lflCompanyIds = [];
9844            $lflCompanyIdsAcc = [];
9845            foreach ($dateLflArray as $k => $v) {
9846                foreach ($dateLflArray[$k] as $item) {
9847                    $year = $item->year;
9848                    $min_date_like = $item->min_date_like;
9849                    $max_date_like = $item->max_date_like;
9850                    array_push($lflArray, "DATE_FORMAT(q.{$field}, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
9851                    array_push($ytdAcceptanceArray, "DATE_FORMAT(q.acceptance_date, '%Y-%m-%d') BETWEEN LEAST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}') AND GREATEST('{$year}-{$min_date_like}', '{$year}-{$max_date_like}')");
9852                }
9853
9854                $lflArray = implode(' OR ', $lflArray);
9855                array_push($lflCompanyIds, "q.company_id = {$k} AND ({$lflArray})");
9856                $lflArray = [];
9857
9858                $ytdAcceptanceArray = implode(' OR ', $ytdAcceptanceArray);
9859                array_push($lflCompanyIdsAcc, "q.company_id = {$k} AND ({$ytdAcceptanceArray})");
9860                $ytdAcceptanceArray = [];
9861            }
9862
9863            $lflCompanyIds = implode(' OR ', $lflCompanyIds);
9864            $where .= " AND ({$lflCompanyIds}";
9865
9866            $lflCompanyIdsAcc = implode(' OR ', $lflCompanyIdsAcc);
9867            $whereAcceptanceDate .= " AND ({$lflCompanyIdsAcc}";
9868        }
9869
9870        $orderBy = 'CASE WHEN q.commercial IS NULL OR q.budget_type IS NULL THEN q.priority END DESC, q.priority ASC,';
9871
9872        if (isset($data['order_by'])) {
9873            $col = $data['order_by']['column'];
9874            $sort = $data['order_by']['sort'];
9875
9876            if (! empty($sort) || $sort != null) {
9877                $orderBy = "CASE WHEN q.commercial IS NULL OR q.budget_type IS NULL THEN {$col} END DESC, {$col} {$sort},";
9878            }
9879        }
9880
9881        $visitTypes = TblVisitTypeGroups::orderByRaw('ISNULL(priority), priority ASC')->get();
9882
9883        $visitCols = '';
9884        $visitMainTableCols = '';
9885        $visitSubMainTableCols = '';
9886        $visitSubTableCols = '';
9887        $visitMainCols = '';
9888
9889        $visitCall = ['Visita', 'Llamada'];
9890
9891        foreach ($visitCall as $value) {
9892            foreach ($visitTypes as $item) {
9893                $visitTypeNames = $value.$item->visit_type_group_id;
9894                $visitCols .= ",COUNT(CASE WHEN q.visit_date IS NOT NULL AND v.visit_type_group_id = {$item->visit_type_group_id} AND q.visit_call = '{$value}' THEN 1 END) AS 'total{$visitTypeNames}'";
9895                $visitCols .= ",GROUP_CONCAT(CASE WHEN q.visit_date IS NOT NULL AND v.visit_type_group_id = {$item->visit_type_group_id} AND q.visit_call = '{$value}' THEN q.id END) AS 'groupConcatIds{$visitTypeNames}'";
9896                $visitMainTableCols .= ",COALESCE(SUM(q.total{$visitTypeNames}), 0) AS 'total{$visitTypeNames}'";
9897                $visitMainTableCols .= ",GROUP_CONCAT(q.groupConcatIds{$visitTypeNames})  AS 'groupConcatIds{$visitTypeNames}'";
9898                $visitSubMainTableCols .= ",q.total{$visitTypeNames}";
9899                $visitSubMainTableCols .= ",q.groupConcatIds{$visitTypeNames}";
9900                $visitSubTableCols .= ",0 AS total{$visitTypeNames}";
9901                $visitSubTableCols .= ",NULL AS groupConcatIds{$visitTypeNames}";
9902                $visitMainCols .= ",COALESCE(SUM(q.total{$visitTypeNames}), 0) total{$visitTypeNames}";
9903                $visitMainCols .= ",GROUP_CONCAT(q.groupConcatIds{$visitTypeNames}) groupConcatIds{$visitTypeNames}";
9904            }
9905        }
9906
9907        $businessGoalsDefault = TblBusinessGoals::where('is_default', 1)->where('budget_type_group_id', 999999999)->first();
9908
9909        $businessGoalsDefault->issue_objective ??= 1;
9910        $businessGoalsDefault->acceptance_objective ??= 1;
9911        $businessGoalsDefault->new_objective ??= 1;
9912        $businessGoalsDefault->is_amount ??= 1;
9913
9914        $gO = '';
9915
9916        if ($groupByFilter != 3) {
9917            $gO = "ORDER BY
9918                        1 DESC,
9919                        2 ASC,
9920                        3 ASC,
9921                        q.commercial ASC,
9922                        {$orderBy}
9923                        DATE_FORMAT(q.namedate, '%e') ASC";
9924        } else {
9925            $gO = 'ORDER BY
9926                        1 DESC,
9927                        2 ASC,
9928                        3 ASC,
9929                        budget_type ASC,
9930                        q.commercial ASC';
9931        }
9932
9933        $query = "WITH business_goal_objective_users AS (
9934                        SELECT
9935                            bg.user_id,
9936                            bg.issue_objective,
9937                            bg.acceptance_objective,
9938                            bg.new_objective,
9939                            bg.is_amount
9940                        FROM tbl_business_goals bg
9941                        WHERE bg.budget_type_group_id = 999999999
9942                    ), business_goal_objective_roles AS (
9943                        SELECT
9944                            bg.role_id,
9945                            bg.issue_objective,
9946                            bg.acceptance_objective,
9947                            bg.new_objective,
9948                            bg.is_amount
9949                        FROM tbl_business_goals bg
9950                        WHERE bg.budget_type_group_id = 999999999
9951                    )
9952
9953                    SELECT
9954                        q.year,
9955                        {$aggregatedCol}
9956                        q.namedate created_at,
9957                        q.commercial,
9958                        q.budget_type,
9959                        COALESCE(SUM(q.issue_date), 0) totalIssue,
9960                        COALESCE(SUM(q.created_at), 0) totalCreatedAt,
9961                        GROUP_CONCAT(q.groupConcatIds) groupConcatIds,
9962                        CASE
9963                            WHEN SUM(q.is_amountIssue) > 0 THEN
9964                                (SUM(q.revenueIssue) / NULLIF(SUM(q.issueObjective), 0)) * 100
9965                            ELSE
9966                                (SUM(q.issue_date) / NULLIF(SUM(q.issueObjective), 0)) * 100
9967                        END AS totalIssueObjective,
9968                        CASE
9969                            WHEN (CASE
9970                                WHEN SUM(q.is_amountIssue) > 0 THEN
9971                                    (SUM(q.revenueIssue) / NULLIF(SUM(q.issueObjective), 0)) * 100
9972                                ELSE
9973                                    (SUM(q.issue_date) / NULLIF(SUM(q.issueObjective), 0)) * 100
9974                            END) BETWEEN 1 AND 70 THEN 'text-danger'
9975                            WHEN (CASE
9976                                WHEN SUM(q.is_amountIssue) > 0 THEN
9977                                    (SUM(q.revenueIssue) / NULLIF(SUM(q.issueObjective), 0)) * 100
9978                                ELSE
9979                                    (SUM(q.issue_date) / NULLIF(SUM(q.issueObjective), 0)) * 100
9980                            END) BETWEEN 70 AND 90 THEN 'text-warning'
9981                        END textIssueColor,
9982                        SUM(q.revenueIssue) revenueIssue,
9983                        CASE
9984                            WHEN SUM(q.is_amountIssue) > 0 THEN
9985                                (SUM(q.revenueIssue) / NULLIF(SUM(q.issueObjectiveMonthly), 0)) * 100
9986                            ELSE
9987                                (SUM(q.issue_date) / NULLIF(SUM(q.issueObjectiveMonthly), 0)) * 100
9988                        END AS totalIssueObjectiveMonthly,
9989                        CASE
9990                            WHEN SUM(q.is_amountIssue) > 0 THEN
9991                                (SUM(q.revenueIssue) / NULLIF(SUM(q.issueObjectiveYearly), 0)) * 100
9992                            ELSE
9993                                (SUM(q.issue_date) / NULLIF(SUM(q.issueObjectiveYearly), 0)) * 100
9994                        END AS totalIssueObjectiveYearly,
9995                        COALESCE(SUM(q.acceptance_date), 0) totalAcceptance,
9996                        GROUP_CONCAT(q.groupConcatCreatedAtIds) groupConcatCreatedAtIds,
9997                        SUM(q.totalIssueLessThan5) AS totalIssueLessThan5,
9998                        GROUP_CONCAT(q.groupConcatIdsIssueLessThan5) AS groupConcatIdsIssueLessThan5,
9999                        GROUP_CONCAT(q.groupConcatAcceptanceIds) groupConcatAcceptanceIds,
10000                        CASE
10001                            WHEN SUM(q.is_amountAcceptance) > 0 THEN
10002                                (SUM(q.revenueAcceptance) / NULLIF(SUM(q.acceptanceObjective), 0)) * 100
10003                            ELSE
10004                                (SUM(q.acceptance_date) / NULLIF(SUM(q.acceptanceObjective), 0)) * 100
10005                        END AS totalAcceptanceObjective,
10006                        CASE
10007                            WHEN SUM(q.is_amountAcceptance) > 0 THEN
10008                                (SUM(q.revenueAcceptance) / NULLIF(SUM(q.acceptanceObjectiveMonthly), 0)) * 100
10009                            ELSE
10010                                (SUM(q.acceptance_date) / NULLIF(SUM(q.acceptanceObjectiveMonthly), 0)) * 100
10011                        END AS totalAcceptanceObjectiveMonthly,
10012                        CASE
10013                            WHEN SUM(q.is_amountAcceptance) > 0 THEN
10014                                (SUM(q.revenueAcceptance) / NULLIF(SUM(q.acceptanceObjectiveYearly), 0)) * 100
10015                            ELSE
10016                                (SUM(q.acceptance_date) / NULLIF(SUM(q.acceptanceObjectiveYearly), 0)) * 100
10017                        END AS totalAcceptanceObjectiveYearly,
10018                        CASE
10019                            WHEN (CASE
10020                                WHEN SUM(q.is_amountAcceptance) > 0 THEN
10021                                    (SUM(q.revenueAcceptance) / NULLIF(SUM(q.acceptanceObjective), 0)) * 100
10022                                ELSE
10023                                    (SUM(q.acceptance_date) / NULLIF(SUM(q.acceptanceObjective), 0)) * 100
10024                            END) BETWEEN 1 AND 70 THEN 'text-danger'
10025                            WHEN (CASE
10026                                WHEN SUM(q.is_amountAcceptance) > 0 THEN
10027                                    (SUM(q.revenueAcceptance) / NULLIF(SUM(q.acceptanceObjective), 0)) * 100
10028                                ELSE
10029                                    (SUM(q.acceptance_date) / NULLIF(SUM(q.acceptanceObjective), 0)) * 100
10030                            END) BETWEEN 70 AND 90 THEN 'text-warning'
10031                        END textAcceptanceColor,
10032                        SUM(q.revenueAcceptance) revenueAcceptance,
10033                        COALESCE(SUM(q.totalRejected), 0) totalRejected,
10034                        GROUP_CONCAT(q.groupConcatRejectedIds) groupConcatRejectedIds,
10035                        SUM(q.revenueRejected) revenueRejected,
10036                        COALESCE(SUM(q.totalNew)) totalNew,
10037                        GROUP_CONCAT(q.groupConcatNewIds) groupConcatNewIds,
10038                        CASE
10039                            WHEN SUM(q.is_amountNew) > 0 THEN
10040                                (SUM(q.revenueNew) / NULLIF(SUM(q.newObjective), 0)) * 100
10041                            ELSE
10042                                (SUM(q.totalNew) / NULLIF(SUM(q.newObjective), 0)) * 100
10043                        END AS totalNewObjective,
10044                        CASE
10045                            WHEN SUM(q.is_amountNew) > 0 THEN
10046                                (SUM(q.revenueNew) / NULLIF(SUM(q.newObjectiveMonthly), 0)) * 100
10047                            ELSE
10048                                (SUM(q.totalNew) / NULLIF(SUM(q.newObjectiveMonthly), 0)) * 100
10049                        END AS totalNewObjectiveMonthly,
10050                        CASE
10051                            WHEN SUM(q.is_amountNew) > 0 THEN
10052                                (SUM(q.revenueNew) / NULLIF(SUM(q.newObjectiveYearly), 0)) * 100
10053                            ELSE
10054                                (SUM(q.totalNew) / NULLIF(SUM(q.newObjectiveYearly), 0)) * 100
10055                        END AS totalNewObjectiveYearly,
10056                        CASE
10057                            WHEN (CASE
10058                                WHEN SUM(q.is_amountNew) > 0 THEN
10059                                    (SUM(q.revenueNew) / NULLIF(SUM(q.newObjective), 0)) * 100
10060                                ELSE
10061                                    (SUM(q.totalNew) / NULLIF(SUM(q.newObjective), 0)) * 100
10062                            END) BETWEEN 1 AND 70 THEN 'text-danger'
10063                            WHEN (CASE
10064                                WHEN SUM(q.is_amountNew) > 0 THEN
10065                                    (SUM(q.revenueNew) / NULLIF(SUM(q.newObjective), 0)) * 100
10066                                ELSE
10067                                    (SUM(q.totalNew) / NULLIF(SUM(q.newObjective), 0)) * 100
10068                            END) BETWEEN 70 AND 90 THEN 'text-warning'
10069                        END textNewColor,
10070                        SUM(q.revenueNew) revenueNew,
10071                        COALESCE(SUM(q.totalVisit), 0) totalVisit,
10072                        GROUP_CONCAT(q.groupConcatVisitIds) groupConcatVisitIds,
10073                        COALESCE(SUM(q.totalCall), 0) totalCall,
10074                        GROUP_CONCAT(q.groupConcatCallIds) groupConcatCallIds,
10075                        SUM(q.is_amountIssue) AS is_amountIssue,
10076                        SUM(q.is_amountNew) AS is_amountNew,
10077                        SUM(q.is_amountAcceptance) AS is_amountAcceptance,
10078                        SUM(q.issueObjective) AS issueObjective,
10079                        SUM(q.issueObjectiveMonthly) AS issueObjectiveMonthly,
10080                        SUM(q.issueObjectiveYearly) AS issueObjectiveYearly,
10081                        SUM(q.newObjective) AS newObjective,
10082                        SUM(q.newObjectiveMonthly) AS newObjectiveMonthly,
10083                        SUM(q.newObjectiveYearly) AS newObjectiveYearly,
10084                        SUM(q.acceptanceObjective) AS acceptanceObjective,
10085                        SUM(q.acceptanceObjectiveMonthly) AS acceptanceObjectiveMonthly,
10086                        SUM(q.acceptanceObjectiveYearly) AS acceptanceObjectiveYearly
10087                        {$visitMainCols}
10088                    FROM
10089                    (
10090                        SELECT
10091                            q.year,
10092                            q.month,
10093                            q.week,
10094                            q.namedate,
10095                            SUM(q.issue_date) AS issue_date,
10096                            q.acceptance_date,
10097                            q.created_at,
10098                            q.commercial,
10099                            q.budget_type,
10100                            GROUP_CONCAT(q.groupConcatIds) AS groupConcatIds,
10101                            CASE
10102                                WHEN q.is_amountIssue > 0 THEN
10103                                    SUM(q.revenueIssue / q.issueObjective) * 100
10104                                ELSE
10105                                    SUM(q.issue_date / q.issueObjective) * 100
10106                                END
10107                            AS totalIssueObjective,
10108                            CASE
10109                                WHEN q.is_amountIssue > 0 THEN
10110                                    SUM(q.revenueIssue / q.issueObjectiveMonthly) * 100
10111                                ELSE
10112                                    SUM(q.issue_date / q.issueObjectiveMonthly) * 100
10113                                END
10114                            AS totalIssueObjectiveMonthly,
10115                            CASE
10116                                WHEN q.is_amountIssue > 0 THEN
10117                                    SUM(q.revenueIssue / q.issueObjectiveYearly) * 100
10118                                ELSE
10119                                    SUM(q.issue_date / q.issueObjectiveYearly) * 100
10120                                END
10121                            AS totalIssueObjectiveYearly,
10122                            SUM(q.revenueIssue) revenueIssue,
10123                            GROUP_CONCAT(q.groupConcatCreatedAtIds) AS groupConcatCreatedAtIds,
10124                            SUM(q.totalIssueLessThan5) totalIssueLessThan5,
10125                            GROUP_CONCAT(q.groupConcatIdsIssueLessThan5) groupConcatIdsIssueLessThan5,
10126                            GROUP_CONCAT(q.groupConcatAcceptanceIds) AS groupConcatAcceptanceIds,
10127                            SUM(q.acceptanceObjective) totalAcceptanceObjective,
10128                            SUM(q.acceptanceObjectiveMonthly) totalAcceptanceObjectiveMonthly,
10129                            SUM(q.acceptanceObjectiveYearly) totalAcceptanceObjectiveYearly,
10130                            q.revenueAcceptance,
10131                            SUM(q.totalRejected) AS totalRejected,
10132                            GROUP_CONCAT(q.groupConcatRejectedIds) AS groupConcatRejectedIds,
10133                            SUM(q.revenueRejected) revenueRejected,
10134                            SUM(q.totalNew) AS totalNew,
10135                            GROUP_CONCAT(q.groupConcatNewIds) AS groupConcatNewIds,
10136                            CASE
10137                                WHEN q.is_amountNew > 0 THEN
10138                                    SUM(q.revenueNew / q.newObjective) * 100
10139                                ELSE
10140                                    SUM(q.totalNew / q.newObjective) * 100
10141                                END
10142                            AS totalNewObjective,
10143                             CASE
10144                                WHEN q.is_amountNew > 0 THEN
10145                                    SUM(q.revenueNew / q.newObjectiveMonthly) * 100
10146                                ELSE
10147                                    SUM(q.totalNew / q.newObjectiveMonthly) * 100
10148                                END
10149                            AS totalNewObjectiveMonthly,
10150                             CASE
10151                                WHEN q.is_amountNew > 0 THEN
10152                                    SUM(q.revenueNew / q.newObjectiveYearly) * 100
10153                                ELSE
10154                                    SUM(q.totalNew / q.newObjectiveYearly) * 100
10155                                END
10156                            AS totalNewObjectiveYearly,
10157                            SUM(q.revenueNew) revenueNew,
10158                            q.totalVisit,
10159                            q.groupConcatVisitIds,
10160                            q.totalCall,
10161                            q.groupConcatCallIds,
10162                            q.priority,
10163                            SUM(q.is_amountIssue) AS is_amountIssue,
10164                            SUM(q.is_amountNew) AS is_amountNew,
10165                            SUM(q.is_amountAcceptance) AS is_amountAcceptance,
10166                            SUM(q.issueObjective) AS issueObjective,
10167                            SUM(q.issueObjectiveMonthly) AS issueObjectiveMonthly,
10168                            SUM(q.issueObjectiveYearly) AS issueObjectiveYearly,
10169                            SUM(q.newObjective) AS newObjective,
10170                            SUM(q.newObjectiveMonthly) AS newObjectiveMonthly,
10171                            SUM(q.newObjectiveYearly) AS newObjectiveYearly,
10172                            SUM(q.acceptanceObjective) AS acceptanceObjective,
10173                            SUM(q.acceptanceObjectiveMonthly) AS acceptanceObjectiveMonthly,
10174                            SUM(q.acceptanceObjectiveYearly) AS acceptanceObjectiveYearly
10175                            {$visitSubMainTableCols}
10176                        FROM (
10177                            SELECT
10178                                YEAR(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) 'year',
10179                                MONTH(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) 'month',
10180                                WEEK(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY)) 'week',
10181                                DATE_FORMAT(DATE_ADD(q.issue_date, INTERVAL - WEEKDAY(q.issue_date) DAY), '%W, %M %e') namedate,
10182                                COUNT(CASE WHEN q.issue_date IS NOT NULL THEN 1 END) issue_date,
10183                                0 acceptance_date,
10184                                0 created_at,
10185                                q.commercial,
10186                                btg.name budget_type,
10187                                GROUP_CONCAT(CASE WHEN q.issue_date IS NOT NULL THEN q.id END) AS groupConcatIds,
10188
10189                                SUM(CASE WHEN q.issue_date IS NOT NULL THEN q.amount END) AS revenueIssue,
10190                                NULL groupConcatCreatedAtIds,
10191                                0 totalIssueLessThan5,
10192                                NULL groupConcatIdsIssueLessThan5,
10193                                NULL groupConcatAcceptanceIds,
10194
10195                                0 revenueAcceptance,
10196                                COUNT(CASE WHEN bs.name = 'Rechazado' THEN 1 END) AS totalRejected,
10197                                GROUP_CONCAT(DISTINCT CASE WHEN bs.name = 'Rechazado' THEN q.id END) AS groupConcatRejectedIds,
10198                                COALESCE(SUM(CASE WHEN bs.name = 'Rechazado' THEN q.amount END), 0) AS revenueRejected,
10199                                COUNT(CASE WHEN ct.name = 'Nuevo' THEN 1 END) AS totalNew,
10200                                GROUP_CONCAT(DISTINCT CASE WHEN ct.name = 'Nuevo' THEN q.id END) AS groupConcatNewIds,
10201
10202                                COALESCE(SUM(CASE WHEN ct.name = 'Nuevo' THEN q.amount END), 0) revenueNew,
10203                                0 totalVisit,
10204                                NULL groupConcatVisitIds,
10205                                0 totalCall,
10206                                NULL groupConcatCallIds,
10207                                btg.priority,
10208                                CAST(
10209                                    CASE
10210                                        WHEN bg.issue_objective IS NOT NULL THEN bg.is_amount
10211                                        WHEN bgou.issue_objective IS NOT NULL THEN bgou.is_amount
10212                                        WHEN bg1.issue_objective IS NOT NULL THEN bg1.is_amount
10213                                        WHEN bgor.issue_objective IS NOT NULL THEN bgor.is_amount
10214                                        WHEN bgde.issue_objective IS NOT NULL THEN bgde.is_amount
10215                                        WHEN bg.issue_objective IS NULL AND bg1.issue_objective IS NULL THEN {$businessGoalsDefault->is_amount}
10216                                    END
10217                                AS DOUBLE) AS is_amountIssue,
10218                                CAST(
10219                                    CASE
10220                                        WHEN bg.new_objective IS NOT NULL THEN bg.is_amount
10221                                        WHEN bgou.new_objective IS NOT NULL THEN bgou.is_amount
10222                                        WHEN bg1.new_objective IS NOT NULL THEN bg1.is_amount
10223                                        WHEN bgor.new_objective IS NOT NULL THEN bgor.is_amount
10224                                        WHEN bgde.new_objective IS NOT NULL THEN bgde.is_amount
10225                                        WHEN bg.new_objective IS NULL AND bg1.new_objective IS NULL THEN {$businessGoalsDefault->is_amount}
10226                                    END
10227                                AS DOUBLE) AS is_amountNew,
10228                                0 is_amountAcceptance,
10229                                CAST(
10230                                    CASE
10231                                        WHEN bg.issue_objective IS NOT NULL THEN bg.issue_objective
10232                                        WHEN bgou.issue_objective IS NOT NULL THEN bgou.issue_objective
10233                                        WHEN bg1.issue_objective IS NOT NULL THEN bg1.issue_objective
10234                                        WHEN bgor.issue_objective IS NOT NULL THEN bgor.issue_objective
10235                                        WHEN bgde.issue_objective IS NOT NULL THEN bgde.issue_objective
10236                                        WHEN bg.issue_objective IS NULL AND bg1.issue_objective IS NULL THEN {$businessGoalsDefault->issue_objective}
10237                                    END {$aggregatedByCalc}
10238                                AS DOUBLE) AS issueObjective,
10239                                CAST(
10240                                    CASE
10241                                        WHEN bg.issue_objective IS NOT NULL THEN bg.issue_objective
10242                                        WHEN bgou.issue_objective IS NOT NULL THEN bgou.issue_objective
10243                                        WHEN bg1.issue_objective IS NOT NULL THEN bg1.issue_objective
10244                                        WHEN bgor.issue_objective IS NOT NULL THEN bgor.issue_objective
10245                                        WHEN bgde.issue_objective IS NOT NULL THEN bgde.issue_objective
10246                                        WHEN bg.issue_objective IS NULL AND bg1.issue_objective IS NULL THEN {$businessGoalsDefault->issue_objective}
10247                                    END
10248                                AS DOUBLE) AS issueObjectiveMonthly,
10249                                CAST(
10250                                    CASE
10251                                        WHEN bg.issue_objective IS NOT NULL THEN bg.issue_objective
10252                                        WHEN bgou.issue_objective IS NOT NULL THEN bgou.issue_objective
10253                                        WHEN bg1.issue_objective IS NOT NULL THEN bg1.issue_objective
10254                                        WHEN bgor.issue_objective IS NOT NULL THEN bgor.issue_objective
10255                                        WHEN bgde.issue_objective IS NOT NULL THEN bgde.issue_objective
10256                                        WHEN bg.issue_objective IS NULL AND bg1.issue_objective IS NULL THEN {$businessGoalsDefault->issue_objective}
10257                                    END * 12
10258                                AS DOUBLE) AS issueObjectiveYearly,
10259                                CAST(
10260                                    CASE
10261                                        WHEN bg.new_objective IS NOT NULL THEN bg.new_objective
10262                                        WHEN bgou.new_objective IS NOT NULL THEN bgou.new_objective
10263                                        WHEN bg1.new_objective IS NOT NULL THEN bg1.new_objective
10264                                        WHEN bgor.new_objective IS NOT NULL THEN bgor.new_objective
10265                                        WHEN bgde.new_objective IS NOT NULL THEN bgde.new_objective
10266                                        WHEN bg.new_objective IS NULL AND bg1.new_objective IS NULL THEN {$businessGoalsDefault->new_objective}
10267                                    END {$aggregatedByCalc}
10268                                AS DOUBLE) AS newObjective,
10269                                CAST(
10270                                    CASE
10271                                        WHEN bg.new_objective IS NOT NULL THEN bg.new_objective
10272                                        WHEN bgou.new_objective IS NOT NULL THEN bgou.new_objective
10273                                        WHEN bg1.new_objective IS NOT NULL THEN bg1.new_objective
10274                                        WHEN bgor.new_objective IS NOT NULL THEN bgor.new_objective
10275                                        WHEN bgde.new_objective IS NOT NULL THEN bgde.new_objective
10276                                        WHEN bg.new_objective IS NULL AND bg1.new_objective IS NULL THEN {$businessGoalsDefault->new_objective}
10277                                    END
10278                                AS DOUBLE) AS newObjectiveMonthly,
10279                                CAST(
10280                                    CASE
10281                                        WHEN bg.new_objective IS NOT NULL THEN bg.new_objective
10282                                        WHEN bgou.new_objective IS NOT NULL THEN bgou.new_objective
10283                                        WHEN bg1.new_objective IS NOT NULL THEN bg1.new_objective
10284                                        WHEN bgor.new_objective IS NOT NULL THEN bgor.new_objective
10285                                        WHEN bgde.new_objective IS NOT NULL THEN bgde.new_objective
10286                                        WHEN bg.new_objective IS NULL AND bg1.new_objective IS NULL THEN {$businessGoalsDefault->new_objective}
10287                                    END * 12
10288                                AS DOUBLE) AS newObjectiveYearly,
10289                                0 acceptanceObjective,
10290                                0 acceptanceObjectiveMonthly,
10291                                0 acceptanceObjectiveYearly
10292                                {$visitSubTableCols}
10293                            FROM
10294                            tbl_quotations q
10295                                LEFT JOIN tbl_sources s ON s.source_id = q.source_id
10296                                LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
10297                                LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
10298                                LEFT JOIN tbl_budget_type_groups btg ON btg.budget_type_group_id = bt.budget_type_group_id
10299                                LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
10300                                LEFT JOIN tbl_users u ON q.commercial = u.name
10301                                LEFT JOIN tbl_roles r ON u.role_id = r.role_id
10302                                LEFT JOIN tbl_business_goals bg ON bg.budget_type_group_id = btg.budget_type_group_id AND bg.user_id = u.id
10303                                LEFT JOIN tbl_business_goals bg1 ON bg1.budget_type_group_id = btg.budget_type_group_id AND bg1.role_id = r.role_id
10304                                LEFT JOIN tbl_business_goals bgde ON bgde.budget_type_group_id = btg.budget_type_group_id AND bgde.is_default = 1
10305                                LEFT JOIN business_goal_objective_users bgou ON bgou.user_id = u.id
10306                                LEFT JOIN business_goal_objective_roles bgor ON bgor.role_id = r.role_id
10307                            WHERE
10308                                q.budget_type_id != 7
10309                                AND q.budget_type_id IS NOT NULL
10310                                AND q.for_add = 0
10311                                AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
10312                                AND bt.include = 1
10313                                AND (q.commercial IS NOT NULL AND q.commercial != '')
10314                                {$where}
10315                                {$whereYear}
10316                            GROUP BY
10317                                {$groupBy}
10318                        ) q
10319                        GROUP BY
10320                            {$groupBy} WITH ROLLUP
10321
10322                        UNION ALL
10323
10324                        SELECT
10325                            q.year,
10326                            q.month,
10327                            q.week,
10328                            q.namedate,
10329                            q.issue_date,
10330                            SUM(q.acceptance_date) AS acceptance_date,
10331                            q.created_at,
10332                            q.commercial,
10333                            q.budget_type,
10334                            q.groupConcatIds,
10335                            q.issueObjective totalIssueObjective,
10336                            q.issueObjectiveMonthly totalIssueObjectiveMonthly,
10337                            q.issueObjectiveYearly totalIssueObjectiveYearly,
10338                            q.revenueIssue,
10339                            q.groupConcatCreatedAtIds,
10340                            q.totalIssueLessThan5,
10341                            q.groupConcatIdsIssueLessThan5,
10342                            GROUP_CONCAT(q.groupConcatAcceptanceIds) AS groupConcatAcceptanceIds,
10343                            CASE
10344                                WHEN q.is_amountAcceptance > 0 THEN
10345                                    SUM(q.revenueAcceptance / q.acceptanceObjective) * 100
10346                                ELSE
10347                                    SUM(q.acceptance_date / q.acceptanceObjective) * 100
10348                                END
10349                            AS totalAcceptanceObjective,
10350                            CASE
10351                                WHEN q.is_amountAcceptance > 0 THEN
10352                                    SUM(q.revenueAcceptance / q.acceptanceObjectiveMonthly) * 100
10353                                ELSE
10354                                    SUM(q.acceptance_date / q.acceptanceObjectiveMonthly) * 100
10355                                END
10356                            AS totalAcceptanceObjectiveMonthly,
10357                            CASE
10358                                WHEN q.is_amountAcceptance > 0 THEN
10359                                    SUM(q.revenueAcceptance / q.acceptanceObjectiveYearly) * 100
10360                                ELSE
10361                                    SUM(q.acceptance_date / q.acceptanceObjectiveYearly) * 100
10362                                END
10363                            AS totalAcceptanceObjectiveYearly,
10364                            SUM(q.revenueAcceptance) revenueAcceptance,
10365                            q.totalRejected,
10366                            q.groupConcatRejectedIds,
10367                            q.revenueRejected,
10368                            q.totalNew,
10369                            q.groupConcatNewIds,
10370                            q.newObjective totalNewObjective,
10371                            q.newObjectiveMonthly totalNewObjectiveMonthly,
10372                            q.newObjectiveYearly totalNewObjectiveYearly,
10373                            q.revenueNew,
10374                            q.totalVisit,
10375                            q.groupConcatVisitIds,
10376                            q.totalCall,
10377                            q.groupConcatCallIds,
10378                            q.priority,
10379                            SUM(q.is_amountIssue) AS is_amountIssue,
10380                            q.is_amountNew,
10381                            q.is_amountAcceptance,
10382                            SUM(q.issueObjective) AS issueObjective,
10383                            SUM(q.issueObjectiveMonthly) AS issueObjectiveMonthly,
10384                            SUM(q.issueObjectiveYearly) AS issueObjectiveYearly,
10385                            SUM(q.newObjective) AS newObjective,
10386                            SUM(q.newObjectiveMonthly) AS newObjectiveMonthly,
10387                            SUM(q.newObjectiveYearly) AS newObjectiveYearly,
10388                            SUM(q.acceptanceObjective) AS acceptanceObjective,
10389                            SUM(q.acceptanceObjectiveMonthly) AS acceptanceObjectiveMonthly,
10390                            SUM(q.acceptanceObjectiveYearly) AS acceptanceObjectiveYearly
10391                            {$visitSubMainTableCols}
10392                        FROM (
10393                            SELECT
10394                                YEAR(DATE_ADD(q.acceptance_date, INTERVAL - WEEKDAY(q.acceptance_date) DAY)) 'year',
10395                                MONTH(DATE_ADD(q.acceptance_date, INTERVAL - WEEKDAY(q.acceptance_date) DAY)) 'month',
10396                                WEEK(DATE_ADD(q.acceptance_date, INTERVAL - WEEKDAY(q.acceptance_date) DAY)) 'week',
10397                                DATE_FORMAT(DATE_ADD(q.acceptance_date, INTERVAL - WEEKDAY(q.acceptance_date) DAY), '%W, %M %e') namedate,
10398                                0 issue_date,
10399                                COUNT(CASE WHEN q.acceptance_date IS NOT NULL THEN 1 END) acceptance_date,
10400                                0 created_at,
10401                                q.commercial,
10402                                btg.name budget_type,
10403                                NULL groupConcatIds,
10404
10405                                0 revenueIssue,
10406                                NULL groupConcatCreatedAtIds,
10407                                0 totalIssueLessThan5,
10408                                NULL groupConcatIdsIssueLessThan5,
10409                                GROUP_CONCAT(CASE WHEN q.acceptance_date IS NOT NULL THEN q.id END) AS groupConcatAcceptanceIds,
10410
10411                                COALESCE(SUM(CASE WHEN q.acceptance_date IS NOT NULL THEN q.amount END), 0) AS revenueAcceptance,
10412                                0 totalRejected,
10413                                NULL groupConcatRejectedIds,
10414                                0 revenueRejected,
10415                                0 totalNew,
10416                                NULL groupConcatNewIds,
10417                                NULL totalNewObjective,
10418                                NULL totalNewObjectiveMonthly,
10419                                NULL totalNewObjectiveYearly,
10420                                0 revenueNew,
10421                                0 totalVisit,
10422                                NULL groupConcatVisitIds,
10423                                0 totalCall,
10424                                NULL groupConcatCallIds,
10425                                btg.priority,
10426                                0 is_amountIssue,
10427                                0 is_amountNew,
10428                                CAST(
10429                                    CASE
10430                                        WHEN bg.acceptance_objective IS NOT NULL THEN bg.is_amount
10431                                        WHEN bgou.acceptance_objective IS NOT NULL THEN bgou.is_amount
10432                                        WHEN bg1.acceptance_objective IS NOT NULL THEN bg1.is_amount
10433                                        WHEN bgor.acceptance_objective IS NOT NULL THEN bgor.is_amount
10434                                        WHEN bgde.acceptance_objective IS NOT NULL THEN bgde.is_amount
10435                                        WHEN bg.acceptance_objective IS NULL AND bg1.acceptance_objective IS NULL THEN {$businessGoalsDefault->is_amount}
10436                                    END
10437                                AS DOUBLE) AS is_amountAcceptance,
10438                                0 issueObjective,
10439                                0 issueObjectiveMonthly,
10440                                0 issueObjectiveYearly,
10441                                0 newObjective,
10442                                0 newObjectiveMonthly,
10443                                0 newObjectiveYearly,
10444                                CAST(
10445                                    CASE
10446                                        WHEN bg.acceptance_objective IS NOT NULL THEN bg.acceptance_objective
10447                                        WHEN bgou.acceptance_objective IS NOT NULL THEN bgou.acceptance_objective
10448                                        WHEN bg1.acceptance_objective IS NOT NULL THEN bg1.acceptance_objective
10449                                        WHEN bgor.acceptance_objective IS NOT NULL THEN bgor.acceptance_objective
10450                                        WHEN bgde.acceptance_objective IS NOT NULL THEN bgde.acceptance_objective
10451                                        WHEN bg.acceptance_objective IS NULL AND bg1.acceptance_objective IS NULL THEN {$businessGoalsDefault->acceptance_objective}
10452                                    END {$aggregatedByCalc}
10453                                AS DOUBLE) AS acceptanceObjective,
10454                                CAST(
10455                                    CASE
10456                                        WHEN bg.acceptance_objective IS NOT NULL THEN bg.acceptance_objective
10457                                        WHEN bgou.acceptance_objective IS NOT NULL THEN bgou.acceptance_objective
10458                                        WHEN bg1.acceptance_objective IS NOT NULL THEN bg1.acceptance_objective
10459                                        WHEN bgor.acceptance_objective IS NOT NULL THEN bgor.acceptance_objective
10460                                        WHEN bgde.acceptance_objective IS NOT NULL THEN bgde.acceptance_objective
10461                                        WHEN bg.acceptance_objective IS NULL AND bg1.acceptance_objective IS NULL THEN {$businessGoalsDefault->acceptance_objective}
10462                                    END
10463                                AS DOUBLE) AS acceptanceObjectiveMonthly,
10464                                CAST(
10465                                    CASE
10466                                        WHEN bg.acceptance_objective IS NOT NULL THEN bg.acceptance_objective
10467                                        WHEN bgou.acceptance_objective IS NOT NULL THEN bgou.acceptance_objective
10468                                        WHEN bg1.acceptance_objective IS NOT NULL THEN bg1.acceptance_objective
10469                                        WHEN bgor.acceptance_objective IS NOT NULL THEN bgor.acceptance_objective
10470                                        WHEN bgde.acceptance_objective IS NOT NULL THEN bgde.acceptance_objective
10471                                        WHEN bg.acceptance_objective IS NULL AND bg1.acceptance_objective IS NULL THEN {$businessGoalsDefault->acceptance_objective}
10472                                    END * 12
10473                                AS DOUBLE) AS acceptanceObjectiveYearly
10474                                {$visitSubTableCols}
10475                            FROM
10476                            tbl_quotations q
10477                                LEFT JOIN tbl_sources s ON s.source_id = q.source_id
10478                                LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
10479                                LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
10480                                LEFT JOIN tbl_budget_type_groups btg ON btg.budget_type_group_id = bt.budget_type_group_id
10481                                LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
10482                                LEFT JOIN tbl_users u ON q.commercial = u.name
10483                                LEFT JOIN tbl_roles r ON u.role_id = r.role_id
10484                                LEFT JOIN tbl_business_goals bg ON bg.budget_type_group_id = btg.budget_type_group_id AND bg.user_id = u.id
10485                                LEFT JOIN tbl_business_goals bg1 ON bg1.budget_type_group_id = btg.budget_type_group_id AND bg1.role_id = r.role_id
10486                                LEFT JOIN tbl_business_goals bgde ON bgde.budget_type_group_id = btg.budget_type_group_id AND bgde.is_default = 1
10487                                LEFT JOIN business_goal_objective_users bgou ON bgou.user_id = u.id
10488                                LEFT JOIN business_goal_objective_roles bgor ON bgor.role_id = r.role_id
10489                            WHERE
10490                                q.budget_type_id != 7
10491                                AND q.budget_type_id IS NOT NULL
10492                                AND q.for_add = 0
10493                                AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
10494                                AND bt.include = 1
10495                                AND (q.commercial IS NOT NULL AND q.commercial != '')
10496                                {$whereAcceptanceDate}
10497                                {$whereYear}
10498                            GROUP BY
10499                                {$groupBy}
10500                        ) q
10501                        GROUP BY
10502                            {$groupBy} WITH ROLLUP
10503
10504                        UNION ALL
10505
10506                        SELECT
10507                            q.year,
10508                            q.month,
10509                            q.week,
10510                            q.namedate,
10511                            q.issue_date,
10512                            q.acceptance_date,
10513                            SUM(q.created_at) AS created_at,
10514                            q.commercial,
10515                            q.budget_type,
10516                            q.groupConcatIds,
10517                            q.issueObjective totalIssueObjective,
10518                            q.issueObjectiveMonthly totalIssueObjectiveMonthly,
10519                            q.issueObjectiveYearly totalIssueObjectiveYearly,
10520                            q.revenueIssue,
10521                            GROUP_CONCAT(q.groupConcatCreatedAtIds) AS groupConcatCreatedAtIds,
10522                            SUM(q.totalIssueLessThan5) AS totalIssueLessThan5,
10523                            GROUP_CONCAT(q.groupConcatIdsIssueLessThan5) AS groupConcatIdsIssueLessThan5,
10524                            NULL groupConcatAcceptanceIds,
10525                            0 totalAcceptanceObjective,
10526                            0 totalAcceptanceObjectiveMonthly,
10527                            0 totalAcceptanceObjectiveYearly,
10528                            SUM(q.revenueAcceptance) revenueAcceptance,
10529                            q.totalRejected,
10530                            q.groupConcatRejectedIds,
10531                            q.revenueRejected,
10532                            q.totalNew,
10533                            q.groupConcatNewIds,
10534                            q.newObjective totalNewObjective,
10535                            q.newObjectiveMonthly totalNewObjectiveMonthly,
10536                            q.newObjectiveYearly totalNewObjectiveYearly,
10537                            q.revenueNew,
10538                            q.totalVisit,
10539                            q.groupConcatVisitIds,
10540                            q.totalCall,
10541                            q.groupConcatCallIds,
10542                            q.priority,
10543                            SUM(q.is_amountIssue) AS is_amountIssue,
10544                            q.is_amountNew,
10545                            q.is_amountAcceptance,
10546                            SUM(q.issueObjective) AS issueObjective,
10547                            SUM(q.issueObjectiveMonthly) AS issueObjectiveMonthly,
10548                            SUM(q.issueObjectiveYearly) AS issueObjectiveYearly,
10549                            SUM(q.newObjective) AS newObjective,
10550                            SUM(q.newObjectiveMonthly) AS newObjectiveMonthly,
10551                            SUM(q.newObjectiveYearly) AS newObjectiveYearly,
10552                            SUM(q.acceptanceObjective) AS acceptanceObjective,
10553                            SUM(q.acceptanceObjectiveMonthly) AS acceptanceObjectiveMonthly,
10554                            SUM(q.acceptanceObjectiveYearly) AS acceptanceObjectiveYearly
10555                            {$visitSubMainTableCols}
10556                        FROM (
10557                            SELECT
10558                                YEAR(DATE_ADD(q.created_at, INTERVAL - WEEKDAY(q.created_at) DAY)) 'year',
10559                                MONTH(DATE_ADD(q.created_at, INTERVAL - WEEKDAY(q.created_at) DAY)) 'month',
10560                                WEEK(DATE_ADD(q.created_at, INTERVAL - WEEKDAY(q.created_at) DAY)) 'week',
10561                                DATE_FORMAT(DATE_ADD(q.created_at, INTERVAL - WEEKDAY(q.created_at) DAY), '%W, %M %e') namedate,
10562                                0 issue_date,
10563                                0 acceptance_date,
10564                                COUNT(CASE WHEN q.created_at IS NOT NULL THEN 1 END) created_at,
10565                                q.commercial,
10566                                btg.name budget_type,
10567                                NULL groupConcatIds,
10568
10569                                0 revenueIssue,
10570                                GROUP_CONCAT(CASE WHEN q.created_at IS NOT NULL THEN q.id END) AS groupConcatCreatedAtIds,
10571                                COUNT(CASE WHEN ABS(DATEDIFF(q.created_at, q.issue_date)) < 5 THEN 1 END) AS totalIssueLessThan5,
10572                                GROUP_CONCAT(CASE WHEN ABS(DATEDIFF(q.created_at, q.issue_date)) < 5 THEN q.id END) AS groupConcatIdsIssueLessThan5,
10573                                NULL groupConcatAcceptanceIds,
10574
10575                                0 AS revenueAcceptance,
10576                                0 totalRejected,
10577                                NULL groupConcatRejectedIds,
10578                                0 revenueRejected,
10579                                0 totalNew,
10580                                NULL groupConcatNewIds,
10581                                NULL totalNewObjective,
10582                                NULL totalNewObjectiveMonthly,
10583                                NULL totalNewObjectiveYearly,
10584                                0 revenueNew,
10585                                0 totalVisit,
10586                                NULL groupConcatVisitIds,
10587                                0 totalCall,
10588                                NULL groupConcatCallIds,
10589                                btg.priority,
10590                                0 is_amountIssue,
10591                                0 is_amountNew,
10592                                0 is_amountAcceptance,
10593                                0 issueObjective,
10594                                0 issueObjectiveMonthly,
10595                                0 issueObjectiveYearly,
10596                                0 newObjective,
10597                                0 newObjectiveMonthly,
10598                                0 newObjectiveYearly,
10599                                0 acceptanceObjective,
10600                                0 acceptanceObjectiveMonthly,
10601                                0 acceptanceObjectiveYearly
10602                                {$visitSubTableCols}
10603                            FROM
10604                            tbl_quotations q
10605                                LEFT JOIN tbl_sources s ON s.source_id = q.source_id
10606                                LEFT JOIN tbl_budget_status bs ON bs.budget_status_id = q.budget_status_id
10607                                LEFT JOIN tbl_budget_types bt ON q.budget_type_id = bt.budget_type_id
10608                                LEFT JOIN tbl_budget_type_groups btg ON btg.budget_type_group_id = bt.budget_type_group_id
10609                                LEFT JOIN tbl_customer_types ct ON q.customer_type_id = ct.customer_type_id
10610                                LEFT JOIN tbl_users u ON q.commercial = u.name
10611                                LEFT JOIN tbl_roles r ON u.role_id = r.role_id
10612                            WHERE
10613                                q.budget_type_id != 7
10614                                AND q.budget_type_id IS NOT NULL
10615                                AND q.for_add = 0
10616                                AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
10617                                AND bt.include = 1
10618                                AND (q.commercial IS NOT NULL AND q.commercial != '')
10619                                {$whereCreatedAt}
10620                                {$whereYear}
10621                            GROUP BY
10622                                {$groupBy}
10623                        ) q
10624                        GROUP BY
10625                            {$groupBy} WITH ROLLUP
10626
10627                        UNION ALL
10628
10629                        SELECT
10630                            YEAR(DATE_ADD(q.visit_date, INTERVAL - WEEKDAY(q.visit_date) DAY)) YEAR,
10631                            MONTH(DATE_ADD(q.visit_date, INTERVAL - WEEKDAY(q.visit_date) DAY)) MONTH,
10632                            WEEK(DATE_ADD(q.visit_date, INTERVAL - WEEKDAY(q.visit_date) DAY)) WEEK,
10633                            DATE_FORMAT(DATE_ADD(visit_date, INTERVAL - WEEKDAY(q.visit_date) DAY), '%W, %M %e') namedate,
10634                            0 issue_date,
10635                            0 acceptance_date,
10636                            0 created_at,
10637                            commercial,
10638                            NULL budget_type,
10639                            NULL groupConcatIds,
10640                            NULL totalIssueObjective,
10641                            NULL totalIssueObjectiveMonthly,
10642                            NULL totalIssueObjectiveYearly,
10643                            0 revenueIssue,
10644                            NULL groupConcatCreatedAtIds,
10645                            0 totalIssueLessThan5,
10646                            NULL groupConcatIdsIssueLessThan5,
10647                            NULL groupConcatAcceptanceIds,
10648                            NULL totalAcceptanceObjective,NULL totalAcceptanceObjectiveMonthly,
10649                            NULL totalAcceptanceObjectiveYearly,
10650                            0 revenueAcceptance,
10651                            0 totalRejected,
10652                            NULL groupConcatRejectedIds,
10653                            0 revenueRejected,
10654                            0 totalNew,
10655                            NULL groupConcatNewIds,
10656                            NULL totalNewObjective,NULL totalNewObjectiveMonthly,
10657                            NULL totalNewObjectiveYearly,
10658                            0 revenueNew,
10659                            COUNT(CASE WHEN q.visit_date IS NOT NULL AND q.visit_call = 'Visita' THEN 1 END) totalVisit,
10660                            GROUP_CONCAT(CASE WHEN q.visit_date IS NOT NULL AND q.visit_call = 'Visita' THEN q.id END) AS groupConcatVisitIds,
10661                            COUNT(CASE WHEN q.visit_date IS NOT NULL AND q.visit_call = 'Llamada' THEN 1 END) totalCall,
10662                            GROUP_CONCAT(CASE WHEN q.visit_date IS NOT NULL AND q.visit_call = 'Llamada' THEN q.id END) AS groupConcatCallIds,
10663                            0 priority,
10664                            0 is_amountIssue,
10665                            0 is_amountNew,
10666                            0 is_amountAcceptance,
10667                            0 issueObjective,
10668                            0 issueObjectiveMonthly,
10669                            0 issueObjectiveYearly,
10670                            0 newObjective,
10671                            0 newObjectiveMontly,
10672                            0 newObjectiveYearly,
10673                            0 acceptanceObjective,
10674                            0 acceptanceObjectiveMonthly,
10675                            0 acceptanceObjectiveYearly
10676                            {$visitCols}
10677                        FROM
10678                            tbl_pipelines q
10679                        LEFT JOIN tbl_users u ON q.commercial = u.name
10680                        LEFT JOIN tbl_roles r ON u.role_id = r.role_id
10681                        LEFT JOIN tbl_visit_types v ON q.visit_type_id = v.visit_type_id
10682                        WHERE q.visit_date IS NOT NULL
10683                            {$whereVisit}
10684                        GROUP BY
10685                            {$groupBy}
10686                             WITH ROLLUP
10687                    ) q
10688                    WHERE
10689                        q.year NOT IN (2021, 2022)
10690                    GROUP BY
10691                        {$groupBy}
10692                    {$gO}";
10693
10694        $value = Cache::get(base64_encode($query));
10695
10696        if (! $value) {
10697            $result = DB::select($query);
10698
10699            $structureData = new StructureData;
10700            $result = $structureData->parse($result, $groupByFilter, $aggregatedBy);
10701
10702            Cache::put(base64_encode($query), $result, 600);
10703        } else {
10704            $result = $value;
10705        }
10706
10707        return response([
10708            'message' => 'OK',
10709            'data' => $result,
10710        ]);
10711
10712        // } catch (\Exception $e) {
10713        //     return response(['message' => 'KO', 'error' => $e->getMessage()]);
10714        // }
10715    }
10716
10717    function list_quotations_deleted($companyId): ResponseFactory|HttpResponse{
10718
10719        try {
10720
10721            $companyId = addslashes((string) $companyId);
10722            $where = "";
10723
10724            if ($companyId != 0) {
10725                $where = " a.company_id = {$companyId} ";
10726            } else {
10727                $where = " a.company_id IN ({$this->companyId})";
10728            }
10729
10730            $query = "SELECT
10731                        a.id,
10732                        a.quote_id,
10733                        a.internal_quote_id,
10734                        a.company_id,
10735                        b.name company_name,
10736                        a.client,
10737                        c.name client_type,
10738                        c.customer_type_id,
10739                        s.name segment,
10740                        s.segment_id,
10741                        a.request_date,
10742                        a.visit_date,
10743                        a.issue_date,
10744                        a.acceptance_date,
10745                        a.internal_quote_id,
10746                        DATE_FORMAT(a.request_date, '%d/%m/%Y') request_date_translate,
10747                        DATE_FORMAT(a.issue_date, '%d/%m/%Y') issue_date_translate,
10748                        DATE_FORMAT(a.acceptance_date, '%d/%m/%Y') acceptance_date_translate,
10749                        DATE_FORMAT(a.last_follow_up_date, '%d/%m/%Y') last_follow_up_date_translate,
10750                        DATE_FORMAT(a.created_at, '%d/%m/%Y') created_at_translate,
10751                        DATE_FORMAT(a.accepted_at, '%d/%m/%Y') accepted_at_translate,
10752                        a.phone_number,
10753                        a.email,
10754                        a.duration,
10755                        a.order_number,
10756                        d.name 'type',
10757                        d.budget_type_id,
10758                        e.name 'status',
10759                        e.budget_status_id,
10760                        f.name as source,
10761                        f.source_id,
10762                        a.amount,
10763                        g.name reason_for_not_following_up,
10764                        a.reason_for_not_following_up_id,
10765                        a.reason_for_rejection_id,
10766                        a.last_follow_up_date,
10767                        a.last_follow_up_comment,
10768                        CASE WHEN a.reason_for_rejection_id IS NULL THEN a.reason_for_rejection ELSE h.name END reason_for_rejection,
10769                        a.commercial,
10770                        a.user_commercial_by_g3w,
10771                        a.user_create_by_g3w,
10772                        a.created_by,
10773                        a.created_at,
10774                        a.updated_by,
10775                        a.updated_at,
10776                        a.total_sent,
10777                        a.has_attachment,
10778                        a.for_approval,
10779                        a.box_work_g3w,
10780                        a.people_assigned_to_the_job,
10781                        a.duration_of_job_in_days,
10782                        a.estimated_cost_of_materials,
10783                        a.budget_margin_enabled,
10784                        a.question_enabled,
10785                        a.cost_of_labor,
10786                        a.total_cost_of_job,
10787                        CASE WHEN a.budget_margin_enabled > 0 THEN a.invoice_margin ELSE NULL END invoice_margin,
10788                        CASE WHEN a.budget_margin_enabled > 0 THEN a.margin_for_the_company ELSE NULL END margin_for_the_company,
10789                        a.margin_on_invoice_per_day_per_worker,
10790                        a.revenue_per_date_per_worked,
10791                        a.commission_cost,
10792                        a.commission_pct,
10793                        a.gross_margin,
10794                        a.labor_percentage,
10795                        a.question_ids,
10796                        a.question_ids_no,
10797                        a.approved_at,
10798                        a.approved_by,
10799                        a.rejected_at,
10800                        a.rejected_by,
10801                        a.approved_at_v2,
10802                        a.approved_by_v2,
10803                        a.rejected_at_v2,
10804                        a.rejected_by_v2,
10805                        a.accepted_at,
10806                        a.accepted_by,
10807                        a.is_validated,
10808                        a.resource_id,
10809                        a.x_status,
10810                        a.likehood,
10811                        a.sync_import,
10812                        a.sync_import_edited,
10813                        a.g3w_warning,
10814                        a.g3w_warning_fields
10815                    FROM tbl_quotations_deleted a                    
10816                    LEFT JOIN tbl_companies b ON a.company_id = b.company_id
10817                    LEFT JOIN tbl_customer_types c ON a.customer_type_id = c.customer_type_id
10818                    LEFT JOIN tbl_segments s ON a.segment_id = s.segment_id
10819                    LEFT JOIN tbl_budget_types d ON a.budget_type_id = d.budget_type_id
10820                    LEFT JOIN tbl_budget_status e ON a.budget_status_id = e.budget_status_id
10821                    LEFT JOIN tbl_sources f ON a.source_id = f.source_id
10822                    LEFT JOIN tbl_reason_for_not_following_up g ON a.reason_for_not_following_up_id = g.reason_for_not_following_up_id
10823                    LEFT JOIN tbl_reason_for_rejection h ON a.reason_for_rejection_id = h.reason_for_rejection_id
10824                    WHERE {$where}
10825                    ORDER BY a.updated_at DESC";
10826
10827            $result = DB::select($query);
10828
10829            return response([
10830                'message' => 'OK',
10831                'data' => $result,
10832            ]);
10833        } catch (\Exception $e) {
10834            report(AppException::fromException($e, 'LIST_QUOTATIONS_DELETED_EXCEPTION'));
10835            return response(['message' => 'KO', 'error' => $e->getMessage()]);
10836        }
10837    }
10838
10839    function delete_sengrid($id): ResponseFactory|HttpResponse{
10840
10841        try {
10842
10843            $id = addslashes((string) $id);
10844
10845            $order = TblQuotations::where('id', $id)->first();
10846
10847            if ($order) {
10848                if ($order->x_message_id != null) {
10849                    TblSendgridWebhook::where('quotation_id', $id)->where('x_message_id', $order->x_message_id)->delete();
10850
10851                    TblQuotations::where('id', $id)->update(
10852                        [
10853                            'x_message_id' => null,
10854                            'x_status' => null
10855                        ]
10856                    );
10857
10858                    Cache::flush();
10859                }
10860            }
10861
10862            return response([
10863                'message' => 'OK',
10864            ]);
10865
10866        } catch (\Exception $e) {
10867            report(AppException::fromException($e, 'DELETE_SENDGRID_EXCEPTION'));
10868            return response(['message' => 'KO', 'error' => $e->getMessage()]);
10869        }
10870    }
10871
10872    function download_productivity_commercial(Request $request): ResponseFactory|HttpResponse{
10873
10874        try {
10875
10876            ini_set('max_execution_time', 123456);
10877
10878            $data = $request->all();
10879
10880            $result = $this->list_quotation_analytics_commercial_productivity($request);
10881            $result = $result->original['data'];
10882
10883            $spreadsheet = new Spreadsheet;
10884            $worksheet = new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, 'Inputs');
10885            $spreadsheet->addSheet($worksheet, 0);
10886            $col = range('A', 'Z');
10887
10888            for ($i = 0; $i < 26; $i++) {
10889                $worksheet->getColumnDimension($col[$i])->setAutoSize(true);
10890                if ($i != 1) {
10891                    $worksheet->getStyle($col[$i])
10892                        ->getAlignment()
10893                        ->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
10894                }
10895            }
10896
10897            $l = 1;
10898            $worksheet->setCellValue('A'.$l, 'Años');
10899
10900            if ($data['group_by'] == 1) {
10901                $worksheet->setCellValue('B'.$l, 'Comercials');
10902                $worksheet->setCellValue('C'.$l, 'Meses');
10903                $worksheet->setCellValue('D'.$l, 'Semanas');
10904            } else {
10905                $worksheet->setCellValue('B'.$l, 'Meses');
10906                $worksheet->setCellValue('C'.$l, 'Semanas');
10907                $worksheet->setCellValue('D'.$l, 'Comercials');
10908            }
10909
10910            $worksheet->setCellValue('E'.$l, 'Categorías de presupuesto');
10911            $worksheet->setCellValue('F'.$l, 'Presupuestos emitidos (#)');
10912            $worksheet->setCellValue('G'.$l, 'Presupuestos emitidos (€)');
10913            $worksheet->setCellValue('H'.$l, 'Presupuestos emitidos (Objetivo)');
10914            $worksheet->setCellValue('I'.$l, 'Presupuestos aceptados (#)');
10915            $worksheet->setCellValue('J'.$l, 'Presupuestos aceptados (€)');
10916            $worksheet->setCellValue('K'.$l, 'Presupuestos aceptados (Objetivo)');
10917            $worksheet->setCellValue('L'.$l, 'Presupuestos rechazados (#)');
10918            $worksheet->setCellValue('M'.$l, 'Presupuestos rechazados (€)');
10919            $worksheet->setCellValue('N'.$l, 'Presupuestos emitidos a nuevos clientes (#)');
10920            $worksheet->setCellValue('O'.$l, 'Presupuestos emitidos a nuevos clientes (€)');
10921            $worksheet->setCellValue('P'.$l, 'Presupuestos emitidos a nuevos clientes (Objetivo)');
10922            $worksheet->setCellValue('Q'.$l, 'Venta (Llamada #)');
10923            $worksheet->setCellValue('R'.$l, 'Venta (Visita #)');
10924            $worksheet->setCellValue('S'.$l, 'Servicio (Llamada #)');
10925            $worksheet->setCellValue('T'.$l, 'Servicio (Visita #)');
10926
10927            $l++;
10928
10929            foreach ($result as $item) {
10930
10931                $worksheet->setCellValue('A'.$l, $item['year']);
10932                $worksheet->setCellValue('F'.$l, $item['totalIssue']);
10933                $worksheet->setCellValue('G'.$l, $item['revenueIssue']);
10934                $worksheet->setCellValue('H'.$l, '-');
10935
10936                $worksheet->setCellValue('I'.$l, $item['totalAcceptance']);
10937                $worksheet->setCellValue('J'.$l, $item['revenueAcceptance']);
10938                $worksheet->setCellValue('K'.$l, '-');
10939
10940                $worksheet->setCellValue('L'.$l, $item['totalRejected']);
10941                $worksheet->setCellValue('M'.$l, $item['revenueRejected']);
10942
10943                $worksheet->setCellValue('N'.$l, $item['totalNew']);
10944                $worksheet->setCellValue('O'.$l, $item['revenueNew']);
10945                $worksheet->setCellValue('P'.$l, '-');
10946
10947                $worksheet->setCellValue('Q'.$l, $item['totalLlamada1']);
10948                $worksheet->setCellValue('R'.$l, $item['totalVisita1']);
10949
10950                $worksheet->setCellValue('S'.$l, $item['totalLlamada2']);
10951                $worksheet->setCellValue('T'.$l, $item['totalVisita2']);
10952
10953                $l++;
10954
10955                if ($data['group_by'] == 1) {
10956
10957                    if (count($item['commercials']) > 0) {
10958
10959                        foreach ($item['commercials'] as $c) {
10960                            $worksheet->setCellValue('A'.$l, $item['year']);
10961
10962                            $worksheet->setCellValue('B'.$l, $c['commercial']);
10963                            $worksheet->setCellValue('F'.$l, $c['totalIssue']);
10964                            $worksheet->setCellValue('G'.$l, $c['revenueIssue']);
10965                            $worksheet->setCellValue('H'.$l, $c['totalIssueObjectiveYearly']);
10966
10967                            $worksheet->setCellValue('I'.$l, $c['totalAcceptance']);
10968                            $worksheet->setCellValue('J'.$l, $c['revenueAcceptance']);
10969                            $worksheet->setCellValue('K'.$l, $c['totalAcceptanceObjectiveYearly']);
10970
10971                            $worksheet->setCellValue('L'.$l, $c['totalRejected']);
10972                            $worksheet->setCellValue('M'.$l, $c['revenueRejected']);
10973
10974                            $worksheet->setCellValue('N'.$l, $c['totalNew']);
10975                            $worksheet->setCellValue('O'.$l, $c['revenueNew']);
10976                            $worksheet->setCellValue('P'.$l, $c['totalNewObjectiveYearly']);
10977
10978                            $worksheet->setCellValue('Q'.$l, $c['totalLlamada1']);
10979                            $worksheet->setCellValue('R'.$l, $c['totalVisita1']);
10980
10981                            $worksheet->setCellValue('S'.$l, $c['totalLlamada2']);
10982                            $worksheet->setCellValue('T'.$l, $c['totalVisita2']);
10983                            $l++;
10984
10985                            if (isset($c['budget_types']) && count($c['budget_types']) > 0) {
10986                                foreach ($c['budget_types'] as $b) {
10987                                    $worksheet->setCellValue('A'.$l, $item['year']);
10988                                    $worksheet->setCellValue('B'.$l, $c['commercial']);
10989
10990                                    $worksheet->setCellValue('E'.$l, $b['name']);
10991                                    $worksheet->setCellValue('F'.$l, $b['totalIssue']);
10992                                    $worksheet->setCellValue('G'.$l, $b['revenueIssue']);
10993                                    $worksheet->setCellValue('H'.$l, $b['totalIssueObjective']);
10994
10995                                    $worksheet->setCellValue('I'.$l, $b['totalAcceptance']);
10996                                    $worksheet->setCellValue('J'.$l, $b['revenueAcceptance']);
10997                                    $worksheet->setCellValue('K'.$l, $b['totalAcceptanceObjective']);
10998
10999                                    $worksheet->setCellValue('L'.$l, $b['totalRejected']);
11000                                    $worksheet->setCellValue('M'.$l, $b['revenueRejected']);
11001
11002                                    $worksheet->setCellValue('N'.$l, $b['totalNew']);
11003                                    $worksheet->setCellValue('O'.$l, $b['revenueNew']);
11004                                    $worksheet->setCellValue('P'.$l, $b['totalNewObjective']);
11005
11006                                    $worksheet->setCellValue('Q'.$l, $b['totalLlamada1']);
11007                                    $worksheet->setCellValue('R'.$l, $b['totalVisita1']);
11008
11009                                    $worksheet->setCellValue('S'.$l, $b['totalLlamada2']);
11010                                    $worksheet->setCellValue('T'.$l, $b['totalVisita2']);
11011                                    $l++;
11012                                }
11013                            }
11014
11015                            if (isset($c['months']) && count($c['months']) > 0) {
11016                                foreach ($c['months'] as $m) {
11017                                    $worksheet->setCellValue('A'.$l, $item['year']);
11018                                    $worksheet->setCellValue('B'.$l, $c['commercial']);
11019
11020                                    $worksheet->setCellValue('C'.$l, $m['month']);
11021                                    $worksheet->setCellValue('F'.$l, $m['totalIssue']);
11022                                    $worksheet->setCellValue('G'.$l, $m['revenueIssue']);
11023                                    $worksheet->setCellValue('H'.$l, $m['totalIssueObjectiveMonthly']);
11024
11025                                    $worksheet->setCellValue('I'.$l, $m['totalAcceptance']);
11026                                    $worksheet->setCellValue('J'.$l, $m['revenueAcceptance']);
11027                                    $worksheet->setCellValue('K'.$l, $m['totalAcceptanceObjectiveMonthly']);
11028
11029                                    $worksheet->setCellValue('L'.$l, $m['totalRejected']);
11030                                    $worksheet->setCellValue('M'.$l, $m['revenueRejected']);
11031
11032                                    $worksheet->setCellValue('N'.$l, $m['totalNew']);
11033                                    $worksheet->setCellValue('O'.$l, $m['revenueNew']);
11034                                    $worksheet->setCellValue('P'.$l, $m['totalNewObjectiveMonthly']);
11035
11036                                    $worksheet->setCellValue('Q'.$l, $m['totalLlamada1']);
11037                                    $worksheet->setCellValue('R'.$l, $m['totalVisita1']);
11038
11039                                    $worksheet->setCellValue('S'.$l, $m['totalLlamada2']);
11040                                    $worksheet->setCellValue('T'.$l, $m['totalVisita2']);
11041                                    $l++;
11042
11043                                    if (isset($m['weeks']) && count(@$m['weeks']) > 0 && count(@$m['weeks']) != 1) {
11044                                        foreach ($m['weeks'] as $w) {
11045                                            $worksheet->setCellValue('A'.$l, $item['year']);
11046                                            $worksheet->setCellValue('B'.$l, $c['commercial']);
11047                                            $worksheet->setCellValue('C'.$l, $m['month']);
11048
11049                                            $worksheet->setCellValue('D'.$l, $w['created_at']);
11050                                            $worksheet->setCellValue('F'.$l, $w['totalIssue']);
11051                                            $worksheet->setCellValue('G'.$l, $w['revenueIssue']);
11052                                            $worksheet->setCellValue('H'.$l, $w['totalIssueObjective']);
11053
11054                                            $worksheet->setCellValue('I'.$l, $w['totalAcceptance']);
11055                                            $worksheet->setCellValue('J'.$l, $w['revenueAcceptance']);
11056                                            $worksheet->setCellValue('K'.$l, $w['totalAcceptanceObjective']);
11057
11058                                            $worksheet->setCellValue('L'.$l, $w['totalRejected']);
11059                                            $worksheet->setCellValue('M'.$l, $w['revenueRejected']);
11060
11061                                            $worksheet->setCellValue('N'.$l, $w['totalNew']);
11062                                            $worksheet->setCellValue('O'.$l, $w['revenueNew']);
11063                                            $worksheet->setCellValue('P'.$l, $w['totalNewObjective']);
11064
11065                                            $worksheet->setCellValue('Q'.$l, $w['totalLlamada1']);
11066                                            $worksheet->setCellValue('R'.$l, $w['totalVisita1']);
11067
11068                                            $worksheet->setCellValue('S'.$l, $w['totalLlamada2']);
11069                                            $worksheet->setCellValue('T'.$l, $w['totalVisita2']);
11070                                            $l++;
11071
11072                                            if (count($w['budget_types']) > 0) {
11073                                                foreach ($w['budget_types'] as $b) {
11074                                                    $worksheet->setCellValue('A'.$l, $item['year']);
11075                                                    $worksheet->setCellValue('B'.$l, $c['commercial']);
11076                                                    $worksheet->setCellValue('C'.$l, $m['month']);
11077
11078                                                    $worksheet->setCellValue('E'.$l, $b['name']);
11079                                                    $worksheet->setCellValue('F'.$l, $b['totalIssue']);
11080                                                    $worksheet->setCellValue('G'.$l, $b['revenueIssue']);
11081                                                    $worksheet->setCellValue('H'.$l, $b['totalIssueObjective']);
11082
11083                                                    $worksheet->setCellValue('I'.$l, $b['totalAcceptance']);
11084                                                    $worksheet->setCellValue('J'.$l, $b['revenueAcceptance']);
11085                                                    $worksheet->setCellValue('K'.$l, $b['totalAcceptanceObjective']);
11086
11087                                                    $worksheet->setCellValue('L'.$l, $b['totalRejected']);
11088                                                    $worksheet->setCellValue('M'.$l, $b['revenueRejected']);
11089
11090                                                    $worksheet->setCellValue('N'.$l, $b['totalNew']);
11091                                                    $worksheet->setCellValue('O'.$l, $b['revenueNew']);
11092                                                    $worksheet->setCellValue('P'.$l, $b['totalNewObjective']);
11093
11094                                                    $worksheet->setCellValue('Q'.$l, $b['totalLlamada1']);
11095                                                    $worksheet->setCellValue('R'.$l, $b['totalVisita1']);
11096
11097                                                    $worksheet->setCellValue('S'.$l, $b['totalLlamada2']);
11098                                                    $worksheet->setCellValue('T'.$l, $b['totalVisita2']);
11099                                                    $l++;
11100                                                }
11101                                            }
11102                                        }
11103                                    } elseif (isset($m['weeks']) && count(@$m['weeks']) == 1) {
11104                                        foreach ($m['weeks'] as $w) {
11105                                            if (count($w['budget_types']) > 0) {
11106                                                foreach ($w['budget_types'] as $b) {
11107                                                    $worksheet->setCellValue('A'.$l, $item['year']);
11108                                                    $worksheet->setCellValue('B'.$l, $c['commercial']);
11109                                                    $worksheet->setCellValue('C'.$l, $m['month']);
11110
11111                                                    $worksheet->setCellValue('E'.$l, $b['name']);
11112                                                    $worksheet->setCellValue('F'.$l, $b['totalIssue']);
11113                                                    $worksheet->setCellValue('G'.$l, $b['revenueIssue']);
11114                                                    $worksheet->setCellValue('H'.$l, $b['totalIssueObjective']);
11115
11116                                                    $worksheet->setCellValue('I'.$l, $b['totalAcceptance']);
11117                                                    $worksheet->setCellValue('J'.$l, $b['revenueAcceptance']);
11118                                                    $worksheet->setCellValue('K'.$l, $b['totalAcceptanceObjective']);
11119
11120                                                    $worksheet->setCellValue('L'.$l, $b['totalRejected']);
11121                                                    $worksheet->setCellValue('M'.$l, $b['revenueRejected']);
11122
11123                                                    $worksheet->setCellValue('N'.$l, $b['totalNew']);
11124                                                    $worksheet->setCellValue('O'.$l, $b['revenueNew']);
11125                                                    $worksheet->setCellValue('P'.$l, $b['totalNewObjective']);
11126
11127                                                    $worksheet->setCellValue('Q'.$l, $b['totalLlamada1']);
11128                                                    $worksheet->setCellValue('R'.$l, $b['totalVisita1']);
11129
11130                                                    $worksheet->setCellValue('S'.$l, $b['totalLlamada2']);
11131                                                    $worksheet->setCellValue('T'.$l, $b['totalVisita2']);
11132                                                    $l++;
11133                                                }
11134                                            }
11135                                        }
11136
11137                                    }
11138                                }
11139                            }
11140                        }
11141                    }
11142                } else {
11143
11144                    if (isset($item['months']) && count($item['months']) > 0) {
11145                        foreach ($item['months'] as $m) {
11146                            $worksheet->setCellValue('A'.$l, $item['year']);
11147                            $worksheet->setCellValue('B'.$l, $m['month']);
11148                            $worksheet->setCellValue('F'.$l, $m['totalIssue']);
11149                            $worksheet->setCellValue('G'.$l, $m['revenueIssue']);
11150                            $worksheet->setCellValue('H'.$l, '-');
11151
11152                            $worksheet->setCellValue('I'.$l, $m['totalAcceptance']);
11153                            $worksheet->setCellValue('J'.$l, $m['revenueAcceptance']);
11154                            $worksheet->setCellValue('K'.$l, '-');
11155
11156                            $worksheet->setCellValue('L'.$l, $m['totalRejected']);
11157                            $worksheet->setCellValue('M'.$l, $m['revenueRejected']);
11158
11159                            $worksheet->setCellValue('N'.$l, $m['totalNew']);
11160                            $worksheet->setCellValue('O'.$l, $m['revenueNew']);
11161                            $worksheet->setCellValue('P'.$l, '-');
11162
11163                            $worksheet->setCellValue('Q'.$l, $m['totalLlamada1']);
11164                            $worksheet->setCellValue('R'.$l, $m['totalVisita1']);
11165
11166                            $worksheet->setCellValue('S'.$l, $m['totalLlamada2']);
11167                            $worksheet->setCellValue('T'.$l, $m['totalVisita2']);
11168                            $l++;
11169
11170                            if (isset($m['weeks']) && count(@$m['weeks']) > 0) {
11171                                foreach ($m['weeks'] as $w) {
11172                                    $worksheet->setCellValue('A'.$l, $item['year']);
11173                                    $worksheet->setCellValue('B'.$l, $m['month']);
11174
11175                                    $worksheet->setCellValue('C'.$l, $w['created_at']);
11176                                    $worksheet->setCellValue('F'.$l, $w['totalIssue']);
11177                                    $worksheet->setCellValue('G'.$l, $w['revenueIssue']);
11178                                    $worksheet->setCellValue('H'.$l, '-');
11179
11180                                    $worksheet->setCellValue('I'.$l, $w['totalAcceptance']);
11181                                    $worksheet->setCellValue('J'.$l, $w['revenueAcceptance']);
11182                                    $worksheet->setCellValue('K'.$l, '-');
11183
11184                                    $worksheet->setCellValue('L'.$l, $w['totalRejected']);
11185                                    $worksheet->setCellValue('M'.$l, $w['revenueRejected']);
11186
11187                                    $worksheet->setCellValue('N'.$l, $w['totalNew']);
11188                                    $worksheet->setCellValue('O'.$l, $w['revenueNew']);
11189                                    $worksheet->setCellValue('P'.$l, '-');
11190
11191                                    $worksheet->setCellValue('Q'.$l, $w['totalLlamada1']);
11192                                    $worksheet->setCellValue('R'.$l, $w['totalVisita1']);
11193
11194                                    $worksheet->setCellValue('S'.$l, $w['totalLlamada2']);
11195                                    $worksheet->setCellValue('T'.$l, $w['totalVisita2']);
11196                                    $l++;
11197
11198                                    if (count($w['commercials']) > 0) {
11199                                        foreach ($w['commercials'] as $c) {
11200                                            $worksheet->setCellValue('A'.$l, $item['year']);
11201                                            $worksheet->setCellValue('B'.$l, $m['month']);
11202
11203                                            $worksheet->setCellValue('D'.$l, $c['commercial']);
11204                                            $worksheet->setCellValue('F'.$l, $c['totalIssue']);
11205                                            $worksheet->setCellValue('G'.$l, $c['revenueIssue']);
11206                                            $worksheet->setCellValue('H'.$l, $c['totalIssueObjective']);
11207
11208                                            $worksheet->setCellValue('I'.$l, $c['totalAcceptance']);
11209                                            $worksheet->setCellValue('J'.$l, $c['revenueAcceptance']);
11210                                            $worksheet->setCellValue('K'.$l, $c['totalAcceptanceObjective']);
11211
11212                                            $worksheet->setCellValue('L'.$l, $c['totalRejected']);
11213                                            $worksheet->setCellValue('M'.$l, $c['revenueRejected']);
11214
11215                                            $worksheet->setCellValue('N'.$l, $c['totalNew']);
11216                                            $worksheet->setCellValue('O'.$l, $c['revenueNew']);
11217                                            $worksheet->setCellValue('P'.$l, $c['totalNewObjective']);
11218
11219                                            $worksheet->setCellValue('Q'.$l, $c['totalLlamada1']);
11220                                            $worksheet->setCellValue('R'.$l, $c['totalVisita1']);
11221
11222                                            $worksheet->setCellValue('S'.$l, $c['totalLlamada2']);
11223                                            $worksheet->setCellValue('T'.$l, $c['totalVisita2']);
11224                                            $l++;
11225
11226                                            if (count($c['budget_types']) > 0) {
11227                                                foreach ($c['budget_types'] as $b) {
11228                                                    $worksheet->setCellValue('A'.$l, $item['year']);
11229                                                    $worksheet->setCellValue('B'.$l, $m['month']);
11230                                                    $worksheet->setCellValue('D'.$l, $c['commercial']);
11231
11232                                                    $worksheet->setCellValue('E'.$l, $b['name']);
11233                                                    $worksheet->setCellValue('F'.$l, $b['totalIssue']);
11234                                                    $worksheet->setCellValue('G'.$l, $b['revenueIssue']);
11235                                                    $worksheet->setCellValue('H'.$l, $b['totalIssueObjective']);
11236
11237                                                    $worksheet->setCellValue('I'.$l, $b['totalAcceptance']);
11238                                                    $worksheet->setCellValue('J'.$l, $b['revenueAcceptance']);
11239                                                    $worksheet->setCellValue('K'.$l, $b['totalAcceptanceObjective']);
11240
11241                                                    $worksheet->setCellValue('L'.$l, $b['totalRejected']);
11242                                                    $worksheet->setCellValue('M'.$l, $b['revenueRejected']);
11243
11244                                                    $worksheet->setCellValue('N'.$l, $b['totalNew']);
11245                                                    $worksheet->setCellValue('O'.$l, $b['revenueNew']);
11246                                                    $worksheet->setCellValue('P'.$l, $b['totalNewObjective']);
11247
11248                                                    $worksheet->setCellValue('Q'.$l, $b['totalLlamada1']);
11249                                                    $worksheet->setCellValue('R'.$l, $b['totalVisita1']);
11250
11251                                                    $worksheet->setCellValue('S'.$l, $b['totalLlamada2']);
11252                                                    $worksheet->setCellValue('T'.$l, $b['totalVisita2']);
11253                                                    $l++;
11254                                                }
11255                                            }
11256                                        }
11257                                    }
11258                                }
11259                            }
11260
11261                            if (count($m['commercials']) > 0) {
11262                                foreach ($m['commercials'] as $c) {
11263                                    $worksheet->setCellValue('A'.$l, $item['year']);
11264                                    $worksheet->setCellValue('B'.$l, $m['month']);
11265
11266                                    $worksheet->setCellValue('D'.$l, $c['commercial']);
11267                                    $worksheet->setCellValue('F'.$l, $c['totalIssue']);
11268                                    $worksheet->setCellValue('G'.$l, $c['revenueIssue']);
11269                                    $worksheet->setCellValue('H'.$l, $c['totalIssueObjectiveYearly']);
11270
11271                                    $worksheet->setCellValue('I'.$l, $c['totalAcceptance']);
11272                                    $worksheet->setCellValue('J'.$l, $c['revenueAcceptance']);
11273                                    $worksheet->setCellValue('K'.$l, $c['totalAcceptanceObjectiveYearly']);
11274
11275                                    $worksheet->setCellValue('L'.$l, $c['totalRejected']);
11276                                    $worksheet->setCellValue('M'.$l, $c['revenueRejected']);
11277
11278                                    $worksheet->setCellValue('N'.$l, $c['totalNew']);
11279                                    $worksheet->setCellValue('O'.$l, $c['revenueNew']);
11280                                    $worksheet->setCellValue('P'.$l, $c['totalNewObjectiveYearly']);
11281
11282                                    $worksheet->setCellValue('Q'.$l, $c['totalLlamada1']);
11283                                    $worksheet->setCellValue('R'.$l, $c['totalVisita1']);
11284
11285                                    $worksheet->setCellValue('S'.$l, $c['totalLlamada2']);
11286                                    $worksheet->setCellValue('T'.$l, $c['totalVisita2']);
11287                                    $l++;
11288
11289                                    if (count($c['budget_types']) > 0) {
11290                                        foreach ($c['budget_types'] as $b) {
11291                                            $worksheet->setCellValue('A'.$l, $item['year']);
11292                                            $worksheet->setCellValue('B'.$l, $m['month']);
11293                                            $worksheet->setCellValue('D'.$l, $c['commercial']);
11294
11295                                            $worksheet->setCellValue('E'.$l, $b['name']);
11296                                            $worksheet->setCellValue('F'.$l, $b['totalIssue']);
11297                                            $worksheet->setCellValue('G'.$l, $b['revenueIssue']);
11298                                            $worksheet->setCellValue('H'.$l, $b['totalIssueObjective']);
11299
11300                                            $worksheet->setCellValue('I'.$l, $b['totalAcceptance']);
11301                                            $worksheet->setCellValue('J'.$l, $b['revenueAcceptance']);
11302                                            $worksheet->setCellValue('K'.$l, $b['totalAcceptanceObjective']);
11303
11304                                            $worksheet->setCellValue('L'.$l, $b['totalRejected']);
11305                                            $worksheet->setCellValue('M'.$l, $b['revenueRejected']);
11306
11307                                            $worksheet->setCellValue('N'.$l, $b['totalNew']);
11308                                            $worksheet->setCellValue('O'.$l, $b['revenueNew']);
11309                                            $worksheet->setCellValue('P'.$l, $b['totalNewObjective']);
11310
11311                                            $worksheet->setCellValue('Q'.$l, $b['totalLlamada1']);
11312                                            $worksheet->setCellValue('R'.$l, $b['totalVisita1']);
11313
11314                                            $worksheet->setCellValue('S'.$l, $b['totalLlamada2']);
11315                                            $worksheet->setCellValue('T'.$l, $b['totalVisita2']);
11316                                            $l++;
11317                                        }
11318                                    }
11319                                }
11320                            }
11321                        }
11322                    }
11323
11324                    if (count($item['commercials']) > 0) {
11325                        foreach ($item['commercials'] as $c) {
11326                            $worksheet->setCellValue('A'.$l, $item['year']);
11327
11328                            $worksheet->setCellValue('D'.$l, $c['commercial']);
11329                            $worksheet->setCellValue('F'.$l, $c['totalIssue']);
11330                            $worksheet->setCellValue('G'.$l, $c['revenueIssue']);
11331                            $worksheet->setCellValue('H'.$l, $c['totalIssueObjectiveYearly']);
11332
11333                            $worksheet->setCellValue('I'.$l, $c['totalAcceptance']);
11334                            $worksheet->setCellValue('J'.$l, $c['revenueAcceptance']);
11335                            $worksheet->setCellValue('K'.$l, $c['totalAcceptanceObjectiveYearly']);
11336
11337                            $worksheet->setCellValue('L'.$l, $c['totalRejected']);
11338                            $worksheet->setCellValue('M'.$l, $c['revenueRejected']);
11339
11340                            $worksheet->setCellValue('N'.$l, $c['totalNew']);
11341                            $worksheet->setCellValue('O'.$l, $c['revenueNew']);
11342                            $worksheet->setCellValue('P'.$l, $c['totalNewObjectiveYearly']);
11343
11344                            $worksheet->setCellValue('Q'.$l, $c['totalLlamada1']);
11345                            $worksheet->setCellValue('R'.$l, $c['totalVisita1']);
11346
11347                            $worksheet->setCellValue('S'.$l, $c['totalLlamada2']);
11348                            $worksheet->setCellValue('T'.$l, $c['totalVisita2']);
11349                            $l++;
11350
11351                            if (count($c['budget_types']) > 0) {
11352                                foreach ($c['budget_types'] as $b) {
11353                                    $worksheet->setCellValue('A'.$l, $item['year']);
11354                                    $worksheet->setCellValue('D'.$l, $c['commercial']);
11355
11356                                    $worksheet->setCellValue('E'.$l, $b['name']);
11357                                    $worksheet->setCellValue('F'.$l, $b['totalIssue']);
11358                                    $worksheet->setCellValue('G'.$l, $b['revenueIssue']);
11359                                    $worksheet->setCellValue('H'.$l, $b['totalIssueObjective']);
11360
11361                                    $worksheet->setCellValue('I'.$l, $b['totalAcceptance']);
11362                                    $worksheet->setCellValue('J'.$l, $b['revenueAcceptance']);
11363                                    $worksheet->setCellValue('K'.$l, $b['totalAcceptanceObjective']);
11364
11365                                    $worksheet->setCellValue('L'.$l, $b['totalRejected']);
11366                                    $worksheet->setCellValue('M'.$l, $b['revenueRejected']);
11367
11368                                    $worksheet->setCellValue('N'.$l, $b['totalNew']);
11369                                    $worksheet->setCellValue('O'.$l, $b['revenueNew']);
11370                                    $worksheet->setCellValue('P'.$l, $b['totalNewObjective']);
11371
11372                                    $worksheet->setCellValue('Q'.$l, $b['totalLlamada1']);
11373                                    $worksheet->setCellValue('R'.$l, $b['totalVisita1']);
11374
11375                                    $worksheet->setCellValue('S'.$l, $b['totalLlamada2']);
11376                                    $worksheet->setCellValue('T'.$l, $b['totalVisita2']);
11377                                    $l++;
11378                                }
11379                            }
11380                        }
11381                    }
11382                }
11383
11384                $l++;
11385            }
11386
11387            if ($data['group_by'] == 1) {
11388                if ($data['aggregated_by'] == 3) {
11389                    $worksheet->removeColumn('C');
11390                    $worksheet->removeColumn('C');
11391                } elseif ($data['aggregated_by'] == 2) {
11392                    $worksheet->removeColumn('D');
11393                }
11394            } else {
11395                if ($data['aggregated_by'] == 3) {
11396                    $worksheet->removeColumn('B');
11397                    $worksheet->removeColumn('B');
11398                } elseif ($data['aggregated_by'] == 2) {
11399                    $worksheet->removeColumn('C');
11400                }
11401            }
11402
11403            $writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
11404            ob_start();
11405            $writer->save('php://output');
11406            $file = ob_get_contents();
11407            ob_end_clean();
11408
11409            return response($file);
11410
11411        } catch (\Exception $e) {
11412            report(AppException::fromException($e, 'DOWNLOAD_PRODUCTIVITY_COMMERCIAL_EXCEPTION'));
11413            return response(['message' => 'KO', 'error' => $e->getMessage()]);
11414        }
11415
11416    }
11417
11418    function update_commercial_numbers($companyId): void
11419    {
11420        $phpBinary = '/usr/bin/php';
11421
11422        $artisanPath = escapeshellarg(base_path('artisan'));
11423
11424        $command = sprintf(
11425            '%s %s update:commercial-numbers %s > /dev/null 2>&1 &',
11426            $phpBinary,
11427            $artisanPath,
11428            $companyId
11429        );
11430
11431        exec($command, $output, $returnVar);
11432    }
11433
11434    function list_quotation_analytics_by_service_type(Request $request): ResponseFactory|HttpResponse{
11435
11436        try {
11437
11438            $data = $request->all();
11439            $companyId = addslashes((string) $data['company_id']);
11440            $where  = "";
11441
11442            if ($companyId != 0) {
11443                $where .= " AND c.company_id = {$companyId} ";
11444            } else {
11445                $where .= " AND c.company_id IN ({$this->companyId}";
11446            }
11447
11448            $col = '1';
11449
11450            if (isset($data['data_to_display']) && $data['data_to_display'] != null) {
11451                if ($data['data_to_display'] == 1) {
11452                    $col = '1';
11453                }
11454
11455                if ($data['data_to_display'] == 2) {
11456                    $col = 'q.amount';
11457                }
11458            }
11459
11460            if (isset($data['approvals'])) {
11461                $approvals = addslashes($data['approvals']);
11462
11463                if ($approvals == 2) {
11464                    $where .= ' AND q.for_approval != 1 ';
11465                }
11466
11467                if ($approvals == 3) {
11468                    $where .= ' AND q.for_approval > 0 ';
11469                }
11470
11471                if ($approvals == 4) {
11472                    $where .= ' AND q.approved_by IS NOT NULL';
11473                }
11474
11475                if ($approvals == 5) {
11476                    $where .= ' AND q.for_approval = 2 AND q.approved_by IS NULL';
11477                }
11478
11479                if ($approvals == 6) {
11480                    $where .= ' AND q.approved_by IS NOT NULL AND q.approved_by_v2 IS NOT NULL';
11481                }
11482
11483                if ($approvals == 7) {
11484                    $where .= ' AND q.for_approval = 3 AND q.approved_by IS NOT NULL AND q.approved_by_v2 IS NULL';
11485                }
11486
11487                if ($approvals == 8) {
11488                    $where .= ' AND q.for_approval = 3 AND q.approved_by IS NULL AND q.approved_by_v2 IS NOT NULL';
11489                }
11490
11491                if ($approvals == 9) {
11492                    $where .= ' AND q.for_approval = 3 AND q.approved_by IS NULL AND q.approved_by_v2 IS NULL';
11493                }
11494            }
11495
11496            if (isset($data['role_id']) && $data['role_id'] != null) {
11497                $roleIds = implode(',', $data['role_id']);
11498                if (count($data['role_id']) > 0) {
11499                    $where .= " AND u.role_id IN ({$roleIds}, 999999999)";
11500                }
11501            }
11502
11503            if (isset($data['client_type']) && $data['client_type'] != null) {
11504                $where .= " AND q.customer_type_id = {$data['client_type']}";
11505            }
11506
11507            $weekDay = '- WEEKDAY(q.date)';
11508
11509            if(isset($data['start_of_the_week']) && $data['start_of_the_week'] != null){
11510                $weekDay = match ($data['start_of_the_week']) {
11511                    0 => "- WEEKDAY(q.date)",
11512                    1 => "(1 - WEEKDAY(q.date))",
11513                    2 => "(2 - WEEKDAY(q.date))",
11514                    3 => "(3 - WEEKDAY(q.date))",
11515                    4 => "(4 - WEEKDAY(q.date))",
11516                    default => "- WEEKDAY(q.date)",
11517                };
11518            }
11519
11520            $whereDates = '';
11521
11522            if ((isset($data['start_date']) && $data['start_date'] != null) && (isset($data['end_date']) && $data['end_date'] != null)) {
11523                $whereDates = " AND q.date BETWEEN '{$data['start_date']}' AND '{$data['end_date']}";
11524            }
11525
11526            $query = "SELECT
11527                        q.region,
11528                        YEAR(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) 'year',
11529                        LPAD(MONTH(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)), 2, 0) 'month',
11530                        LPAD(WEEK(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)), 2, 0) 'week',
11531                        DATE_FORMAT(DATE_ADD(q.date, INTERVAL {$weekDay} DAY), '%W, %M %e') namedate,
11532
11533                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' THEN q.id END) AS groupConcatIdsTotalEnviado,
11534                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END), 0) AS totalIssueEnviado,
11535
11536                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN q.id END) AS groupConcatIdsMantenimientoEnviado,
11537                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN {$col} END), 0) totalMantenimientoEnviado,
11538                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalMantenimientoPercentageEnviado,
11539
11540                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN q.id END) AS groupConcatIdsCorrectivosEnviado,
11541                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN {$col} END), 0) totalCorrectivosEnviado,
11542                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalCorrectivosPercentageEnviado,
11543
11544                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN q.id END) AS groupConcatIdsObrasEnviado,
11545                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN {$col} END), 0) totalObrasEnviado,
11546                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalObrasPercentageEnviado,
11547
11548                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN q.id END) AS groupConcatIdsOtrosEnviado,
11549                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END), 0) totalOtrosEnviado,
11550                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalOtrosPercentageEnviado,
11551
11552                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN q.id END) AS groupConcatIdsOtrosEnviado,
11553                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END), 0) totalOtrosEnviado,
11554                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalOtrosPercentageEnviado,
11555
11556                        CASE
11557                            WHEN SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11558                            ELSE SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.s1 ELSE 0 END)
11559                                / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END)
11560                        END AS weightedAverageMarginForTheCompanyEnviado,
11561                        CASE
11562                            WHEN SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11563                            ELSE SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.s2 ELSE 0 END)
11564                                / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END)
11565                        END AS weightedAverageInvoiceEnviado,
11566
11567                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' THEN q.id END) AS groupConcatIdsTotalAceptado,
11568                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END), 0) AS totalIssueAceptado,
11569
11570                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN q.id END) AS groupConcatIdsMantenimientoAceptado,
11571                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN {$col} END), 0) totalMantenimientoAceptado,
11572                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalMantenimientoPercentageAceptado,
11573
11574                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN q.id END) AS groupConcatIdsCorrectivosAceptado,
11575                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN {$col} END), 0) totalCorrectivosAceptado,
11576                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalCorrectivosPercentageAceptado,
11577
11578                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN q.id END) AS groupConcatIdsObrasAceptado,
11579                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN {$col} END), 0) totalObrasAceptado,
11580                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalObrasPercentageAceptado,
11581
11582                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN q.id END) AS groupConcatIdsOtrosAceptado,
11583                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END), 0) totalOtrosAceptado,
11584                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalOtrosPercentageAceptado,
11585
11586                        CASE
11587                            WHEN SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11588                            ELSE SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.s1 ELSE 0 END)
11589                                / SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END)
11590                        END AS weightedAverageMarginForTheCompanyAceptado,
11591                        CASE
11592                            WHEN SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11593                            ELSE SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.s2 ELSE 0 END)
11594                                / SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END)
11595                        END AS weightedAverageInvoiceAceptado,
11596
11597                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' THEN 1 END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalIssuePercentage,
11598                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN {$col} END) * 100, 0) totalMantenimientoPercentage,
11599                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN {$col} END) * 100, 0) totalCorrectivosPercentage,
11600                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN {$col} END) * 100, 0) totalObrasPercentage,
11601                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) * 100 , 0) totalOtrosPercentage,
11602
11603                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL THEN {$col} END) * 100, 0) totalIssuePercentageLead,
11604                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id = 3 THEN {$col} END) * 100, 0) totalMantenimientoPercentageLead,
11605                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id = 5 THEN {$col} END) * 100, 0) totalCorrectivosPercentageLead,
11606                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id = 4 THEN {$col} END) * 100, 0) totalObrasPercentageLead,
11607                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) * 100 , 0) totalOtrosPercentageLead
11608                    FROM
11609                    (
11610                        SELECT
11611                            c.region,
11612                            q.issue_date AS DATE,
11613                            'issue' AS date_type,
11614                            q.acceptance_date,
11615                            q.issue_date,
11616                            q.id,
11617                            q.margin_for_the_company,
11618                            CASE
11619                                WHEN bt.budget_type_group_id = 4
11620                                AND q.budget_margin_enabled > 0
11621                                AND q.budget_margin_enabled IS NOT NULL
11622                                AND q.margin_for_the_company <> 0
11623                            THEN q.margin_for_the_company * q.amount
11624                            END s1,
11625                            q.invoice_margin,
11626                            CASE
11627                                WHEN bt.budget_type_group_id = 4
11628                                AND q.budget_margin_enabled > 0
11629                                AND q.budget_margin_enabled IS NOT NULL
11630                                AND q.invoice_margin <> 0
11631                            THEN q.invoice_margin * q.amount
11632                            END s2,
11633                            q.budget_type_id,
11634                            q.budget_status_id,
11635                            q.amount,
11636                            bt.budget_type_group_id,
11637                            q.budget_margin_enabled
11638                        FROM
11639                            tbl_quotations q
11640                        JOIN tbl_companies c
11641                            ON c.company_id = q.company_id
11642                        LEFT JOIN tbl_budget_types bt
11643                            ON q.budget_type_id = bt.budget_type_id
11644                        LEFT JOIN tbl_users u
11645                            ON u.name = q.created_by
11646                        LEFT JOIN tbl_roles r
11647                            ON r.role_id = u.role_id
11648                        WHERE
11649                            q.issue_date IS NOT NULL
11650                            AND q.for_add = 0
11651                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
11652                            AND (q.commercial IS NOT NULL AND q.commercial != '')
11653                            AND q.budget_type_id != 7
11654                            AND q.budget_type_id IS NOT NULL
11655                            {$where}
11656
11657                        UNION ALL
11658
11659                        SELECT
11660                            c.region,
11661                            q.acceptance_date AS DATE,
11662                            'acceptance' AS date_type,
11663                            q.acceptance_date,
11664                            q.issue_date,
11665                            q.id,
11666                            q.margin_for_the_company,
11667                            CASE
11668                                WHEN bt.budget_type_group_id = 4
11669                                AND q.budget_margin_enabled > 0
11670                                AND q.budget_margin_enabled IS NOT NULL
11671                                AND q.margin_for_the_company > 0
11672                            THEN q.margin_for_the_company * q.amount
11673                            END s1,
11674                            q.invoice_margin,
11675                            CASE
11676                                WHEN bt.budget_type_group_id = 4
11677                                AND q.budget_margin_enabled <> 0
11678                                AND q.budget_margin_enabled IS NOT NULL
11679                                AND q.invoice_margin <> 0
11680                            THEN q.invoice_margin * q.amount
11681                            END s2,
11682                            q.budget_type_id,
11683                            q.budget_status_id,
11684                            q.amount,
11685                            bt.budget_type_group_id,
11686                            q.budget_margin_enabled
11687                        FROM
11688                            tbl_quotations q
11689                        JOIN tbl_companies c
11690                            ON c.company_id = q.company_id
11691                        LEFT JOIN tbl_budget_types bt
11692                            ON q.budget_type_id = bt.budget_type_id
11693                        LEFT JOIN tbl_users u
11694                            ON u.name = q.created_by
11695                        LEFT JOIN tbl_roles r
11696                            ON r.role_id = u.role_id
11697                        WHERE
11698                            q.acceptance_date IS NOT NULL
11699                            AND q.for_add = 0
11700                            AND q.budget_type_id IS NOT NULL
11701                            AND q.budget_type_id != 7
11702                            AND q.acceptance_date != '0000-00-00 00:00:00'
11703                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
11704                            {$where}
11705                    ) AS q
11706                    WHERE q.date != '0000-00-00 00:00:00' {$whereDates}
11707                    GROUP BY
11708                        q.region,
11709                        YEAR(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)),
11710                        MONTH(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)),
11711                        WEEK(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) WITH ROLLUP
11712                    ORDER BY
11713                        q.region IS NULL,
11714                        q.region,
11715                        CASE WHEN q.region IS NOT NULL
11716                            AND YEAR(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) IS NULL
11717                            AND MONTH(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) IS NULL
11718                            AND WEEK(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) IS NULL
11719                        THEN 0
11720                        ELSE 1 END,
11721                    YEAR DESC,
11722                    MONTH ASC,
11723                    WEEK ASC";
11724
11725            $value = Cache::get(base64_encode($query));
11726
11727            if (! $value) {
11728                $result = DB::select($query);
11729
11730                Cache::put(base64_encode($query), $result, 600);
11731            } else {
11732                $result = $value;
11733            }
11734
11735            $query = "SELECT
11736                        q.region,
11737                        YEAR(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) 'year',
11738                        LPAD(MONTH(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)), 2, 0) 'month',
11739                        LPAD(WEEK(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)), 2, 0) 'week',
11740                        DATE_FORMAT(DATE_ADD(q.date, INTERVAL {$weekDay} DAY), '%W, %M %e') namedate,
11741
11742                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' THEN q.id END) AS groupConcatIdsTotalEnviado,
11743                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END), 0) AS totalIssueEnviado,
11744
11745                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN q.id END) AS groupConcatIdsMantenimientoEnviado,
11746                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN {$col} END), 0) totalMantenimientoEnviado,
11747                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalMantenimientoPercentageEnviado,
11748
11749                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN q.id END) AS groupConcatIdsCorrectivosEnviado,
11750                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN {$col} END), 0) totalCorrectivosEnviado,
11751                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalCorrectivosPercentageEnviado,
11752
11753                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN q.id END) AS groupConcatIdsObrasEnviado,
11754                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN {$col} END), 0) totalObrasEnviado,
11755                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalObrasPercentageEnviado,
11756
11757                        GROUP_CONCAT(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN q.id END) AS groupConcatIdsOtrosEnviado,
11758                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END), 0) totalOtrosEnviado,
11759                        COALESCE(SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalOtrosPercentageEnviado,
11760
11761                        CASE
11762                            WHEN SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11763                            ELSE SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.s1 ELSE 0 END)
11764                                / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END)
11765                        END AS weightedAverageMarginForTheCompanyEnviado,
11766                        CASE
11767                            WHEN SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11768                            ELSE SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.s2 ELSE 0 END)
11769                                / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END)
11770                        END AS weightedAverageInvoiceEnviado,
11771
11772                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' THEN q.id END) AS groupConcatIdsTotalAceptado,
11773                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END), 0) AS totalIssueAceptado,
11774
11775                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN q.id END) AS groupConcatIdsMantenimientoAceptado,
11776                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN {$col} END), 0) totalMantenimientoAceptado,
11777                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalMantenimientoPercentageAceptado,
11778
11779                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN q.id END) AS groupConcatIdsCorrectivosAceptado,
11780                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN {$col} END), 0) totalCorrectivosAceptado,
11781                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalCorrectivosPercentageAceptado,
11782
11783                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN q.id END) AS groupConcatIdsObrasAceptado,
11784                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN {$col} END), 0) totalObrasAceptado,
11785                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalObrasPercentageAceptado,
11786
11787                        GROUP_CONCAT(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN q.id END) AS groupConcatIdsOtrosAceptado,
11788                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END), 0) totalOtrosAceptado,
11789                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) * 100, 0) totalOtrosPercentageAceptado,
11790
11791                        CASE
11792                            WHEN SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11793                            ELSE SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.s1 ELSE 0 END)
11794                                / SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.margin_for_the_company <> 0 THEN q.amount ELSE 0 END)
11795                        END AS weightedAverageMarginForTheCompanyAceptado,
11796                        CASE
11797                            WHEN SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END) = 0 THEN 0
11798                            ELSE SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.s2 ELSE 0 END)
11799                                / SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 AND q.budget_margin_enabled > 0 AND q.invoice_margin <> 0 THEN q.amount ELSE 0 END)
11800                        END AS weightedAverageInvoiceAceptado,
11801
11802                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' THEN {$col} END) * 100, 0) totalIssuePercentage,
11803                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 3 THEN {$col} END) * 100, 0) totalMantenimientoPercentage,
11804                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 5 THEN {$col} END) * 100, 0) totalCorrectivosPercentage,
11805                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id = 4 THEN {$col} END) * 100, 0) totalObrasPercentage,
11806                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) * 100 , 0) totalOtrosPercentage,
11807
11808                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL THEN {$col} END) * 100, 0) totalIssuePercentageLead,
11809                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id = 3 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id = 3 THEN {$col} END) * 100, 0) totalMantenimientoPercentageLead,
11810                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id = 5 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id = 5 THEN {$col} END) * 100, 0) totalCorrectivosPercentageLead,
11811                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id = 4 THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id = 4 THEN {$col} END) * 100, 0) totalObrasPercentageLead,
11812                        COALESCE(SUM(CASE WHEN q.date_type = 'acceptance' AND q.issue_date IS NOT NULL AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) / SUM(CASE WHEN q.date_type = 'issue' AND q.acceptance_date IS NOT NULL AND q.budget_type_group_id IN (6, 7, 8) THEN {$col} END) * 100 , 0) totalOtrosPercentageLead
11813                    FROM
11814                    (
11815                        SELECT
11816                            'Total Grupo FIRE' region,
11817                            q.issue_date AS DATE,
11818                            'issue' AS date_type,
11819                            q.acceptance_date,
11820                            q.issue_date,
11821                            q.id,
11822                            q.margin_for_the_company,
11823                            CASE
11824                                WHEN bt.budget_type_group_id = 4
11825                                AND q.budget_margin_enabled > 0
11826                                AND q.budget_margin_enabled IS NOT NULL
11827                                AND q.margin_for_the_company <> 0
11828                            THEN q.margin_for_the_company * q.amount
11829                            END s1,
11830                            q.invoice_margin,
11831                            CASE
11832                                WHEN bt.budget_type_group_id = 4
11833                                AND q.budget_margin_enabled > 0
11834                                AND q.budget_margin_enabled IS NOT NULL
11835                                AND q.invoice_margin <> 0
11836                            THEN q.invoice_margin * q.amount
11837                            END s2,
11838                            q.budget_type_id,
11839                            q.budget_status_id,
11840                            q.amount,
11841                            bt.budget_type_group_id,
11842                            q.budget_margin_enabled
11843                        FROM
11844                            tbl_quotations q
11845                        JOIN tbl_companies c
11846                            ON c.company_id = q.company_id
11847                        LEFT JOIN tbl_budget_types bt
11848                            ON q.budget_type_id = bt.budget_type_id
11849                        LEFT JOIN tbl_users u
11850                            ON u.name = q.created_by
11851                        LEFT JOIN tbl_roles r
11852                            ON r.role_id = u.role_id
11853                        WHERE
11854                            q.issue_date IS NOT NULL
11855                            AND q.for_add = 0
11856                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
11857                            AND (q.commercial IS NOT NULL AND q.commercial != '')
11858                            AND q.budget_type_id != 7
11859                            AND q.budget_type_id IS NOT NULL
11860                            {$where}
11861
11862                        UNION ALL
11863
11864                        SELECT
11865                            'Total Grupo FIRE' region,
11866                            q.acceptance_date AS DATE,
11867                            'acceptance' AS date_type,
11868                            q.acceptance_date,
11869                            q.issue_date,
11870                            q.id,
11871                            q.margin_for_the_company,
11872                            CASE
11873                                WHEN bt.budget_type_group_id = 4
11874                                AND q.budget_margin_enabled > 0
11875                                AND q.budget_margin_enabled IS NOT NULL
11876                                AND q.margin_for_the_company <> 0
11877                            THEN q.margin_for_the_company * q.amount
11878                            END s1,
11879                            q.invoice_margin,
11880                            CASE
11881                                WHEN bt.budget_type_group_id = 4
11882                                AND q.budget_margin_enabled > 0
11883                                AND q.budget_margin_enabled IS NOT NULL
11884                                AND q.invoice_margin <> 0
11885                            THEN q.invoice_margin * q.amount
11886                            END s2,
11887                            q.budget_type_id,
11888                            q.budget_status_id,
11889                            q.amount,
11890                            bt.budget_type_group_id,
11891                            q.budget_margin_enabled
11892                        FROM
11893                            tbl_quotations q
11894                        JOIN tbl_companies c
11895                            ON c.company_id = q.company_id
11896                        LEFT JOIN tbl_budget_types bt
11897                            ON q.budget_type_id = bt.budget_type_id
11898                        LEFT JOIN tbl_users u
11899                            ON u.name = q.created_by
11900                        LEFT JOIN tbl_roles r
11901                            ON r.role_id = u.role_id
11902                        WHERE
11903                            q.acceptance_date IS NOT NULL
11904
11905                            AND q.for_add = 0
11906                            AND q.amount REGEXP '^[0-9]+\\.?[0-9]*$' = 1
11907                            AND (q.commercial IS NOT NULL AND q.commercial != '')
11908                            AND q.budget_type_id != 7
11909                            AND q.budget_type_id IS NOT NULL
11910                            {$where}
11911                    ) AS q
11912                    WHERE q.date != '0000-00-00 00:00:00' {$whereDates}
11913                    GROUP BY
11914                        q.region,
11915                        YEAR(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)),
11916                        MONTH(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)),
11917                        WEEK(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) WITH ROLLUP
11918                    ORDER BY
11919                        q.region IS NULL,
11920                        q.region,
11921                        CASE WHEN q.region IS NOT NULL
11922                            AND YEAR(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) IS NULL
11923                            AND MONTH(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) IS NULL
11924                            AND WEEK(DATE_ADD(q.date, INTERVAL {$weekDay} DAY)) IS NULL
11925                        THEN 0
11926                        ELSE 1 END,
11927                    YEAR DESC,
11928                    MONTH ASC,
11929                    WEEK ASC";
11930
11931            $totalGroupValue = Cache::get(base64_encode($query));
11932
11933            if (! $totalGroupValue) {
11934                $totalGroup = DB::select($query);
11935
11936                Cache::put(base64_encode($query), $totalGroup, 600);
11937            } else {
11938                $totalGroup = $totalGroupValue;
11939            }
11940
11941            array_pop($result);
11942            $merged = array_merge($result, $totalGroup);
11943
11944            return response([
11945                'message' => 'OK',
11946                'data' => $merged,
11947            ]);
11948
11949        } catch (\Exception $e) {
11950            report(AppException::fromException($e, 'LIST_QUOTATIONS_ANALYTICS_BY_SERVICE_TYPE_EXCEPTION'));
11951            return response(['message' => 'KO', 'error' => $e->getMessage()]);
11952        }
11953
11954    }
11955
11956    public function getIdsFromInternalQuoteIds($ids){
11957        $idsArray = array_filter(explode(',', (string) $ids), is_numeric(...));
11958        return TblQuotations::whereIn("internal_quote_id", $idsArray)
11959            ->pluck("id")
11960            ->toArray();
11961    }
11962
11963    public function checkQuotationExistByInternalQuoteId(Request $request): ResponseFactory|HttpResponse
11964    {
11965        try{
11966            $idsString = $request->all()["ids"];
11967            $ids = explode(',',(string) $idsString);
11968            $region = urldecode((string) request()->header('Region'));
11969            $company = TblCompanies::where("region", $region)->first();
11970
11971            if (! $company) {
11972                throw new \Exception('Region no encontrada');
11973            }
11974
11975            $companyId = $company->company_id;
11976
11977            $idsChecked = [];
11978
11979            foreach ($ids as $id) {
11980                $quote = TblQuotations::where('internal_quote_id', $id)->where('company_id', $companyId)->first();
11981                if (
11982                    ($companyId === 18 || $companyId === 22)
11983                    && ! $quote
11984                ) {
11985                    $quote = TblQuotations::where('internal_quote_id', $id)->whereIn('company_id', [18, 22])->first();
11986                }
11987                $idsChecked[$id] = $quote ? $quote->id : null;
11988            }
11989
11990            return response([
11991                'message' => 'OK',
11992                'data' => $idsChecked,
11993            ]);
11994
11995        }catch (\Exception $e) {
11996            report(AppException::fromException($e, 'CHECK_QUOTATION_EXIST_BY_INTERNAL_QUOTE_ID_EXCEPTION'));
11997            return response(['message' => 'KO', 'error' => $e->getMessage()]);
11998        }
11999
12000    }
12001
12002    public function addUpdateLog($id, $userId, $field, $oldData, $newData, int $category = 4): void {
12003        $categoryOptions = [
12004            'Crear solicitud',
12005            'Modificacion de solicitud',
12006            'Creacion presupuesto',
12007            'Solicitud a presupuesto',
12008            'Modificacion de presupuesto',
12009            'Accion del presupuesto',
12010            'Eliminacion del presupuesto',
12011            'Convertir presupuesto a trabajo',
12012        ];
12013
12014        if ($field === 'amount') {
12015            $oldData = (float) $oldData;
12016            $newData = (float) $newData;
12017        }
12018
12019        if (
12020            ($oldData === $newData && ! is_null($oldData)) ||
12021            $field === 'updated_at' ||
12022            $field === 'created_at' ||
12023            $field === 'last_follow_up_comment' ||
12024            $field === 'reason_for_rejection_id' ||
12025            $field === 'for_add' ||
12026            $field === 'has_attachment' ||
12027            $field === 'question_ids' ||
12028            $field === 'question_ids_no' ||
12029            $field === 'x_message_id' ||
12030            $field === 'from_company_id' ||
12031            $field === 'type_by_g3w' ||
12032            $field === 'user_create_by_g3w' ||
12033            $field === 'user_commercial_by_g3w' ||
12034            $field === 'user_technical_by_g3w' ||
12035            $field === 'user_responsible_by_g3w' ||
12036            $field === 'resource_id' ||
12037            $field === 'y_message_id' ||
12038            $field === 'y_status' ||
12039            $field === 'sync_import_edited' ||
12040            $field === 'updated_by' ||
12041            $field === 'likehood' ||
12042            $field === 'g3w_warning' ||
12043            $field === 'gross_margin' ||
12044            $field === 'duration' ||
12045            ($field === 'budget_type_id' && is_null($oldData) && is_null($newData)) ||
12046            ($field === 'margin_on_invoice_per_day_per_worker' && $newData === 0)
12047        ) {
12048            return;
12049        }
12050
12051        $oldRegister = null;
12052        $newRegister = null;
12053
12054        if (! is_null($oldData) || ! is_null($newData)) {
12055            switch ($field) {
12056                case 'company_id':
12057                    $oldRegister = TblCompanies::where('company_id', $oldData)->first();
12058                    $newRegister = TblCompanies::where('company_id', $newData)->first();
12059                    break;
12060                case 'customer_type_id':
12061                    $oldRegister = TblCustomerTypes::where('customer_type_id', $oldData)->first();
12062                    $newRegister = TblCustomerTypes::where('customer_type_id', $newData)->first();
12063                    break;
12064                case 'segment_id':
12065                    $oldRegister = TblSegments::where('segment_id', $oldData)->first();
12066                    $newRegister = TblSegments::where('segment_id', $newData)->first();
12067                    break;
12068                case 'budget_type_id':
12069                    $oldRegister = TblBudgetTypes::where('budget_type_id', $oldData)->first();
12070                    $newRegister = TblBudgetTypes::where('budget_type_id', $newData)->first();
12071                    break;
12072                case 'budget_status_id':
12073                    $oldRegister = TblBudgetStatus::where('budget_status_id', $oldData)->first();
12074                    $newRegister = TblBudgetStatus::where('budget_status_id', $newData)->first();
12075                    break;
12076                case 'source_id':
12077                    $oldRegister = TblSources::where('source_id', $oldData)->first();
12078                    $newRegister = TblSources::where('source_id', $newData)->first();
12079                    break;
12080            }
12081        }
12082
12083        $finalOld = $oldRegister ? $oldRegister->name : ($oldData ?? 'N/A');
12084        $finalNew = $newRegister ? $newRegister->name : ($newData ?? 'N/A');
12085
12086        if (is_numeric($userId)) {
12087            $userObj = TblUsers::where('id', $userId)->first();
12088            $userName = $userObj ? $userObj->name : "Usuario desconocido ($userId)";
12089        } else {
12090            $userName = $userId;
12091        }
12092
12093        TblQuotationsLog::create([
12094            'category' => $categoryOptions[$category] ?? 'Otros',
12095            'quotation_id' => $id,
12096            'user' => $userName,
12097            'field' => $field,
12098            'old_value' => $finalOld,
12099            'new_value' => $finalNew,
12100        ]);
12101    }
12102
12103    public function setSolicitudDuplicity(Request $request): ResponseFactory|HttpResponse{
12104        try{
12105            $type = $request->all()["type"];
12106            $quoteId = $request->all()["quoteId"];
12107            $companyId = $request->all()["companyId"];
12108
12109            $quote = TblQuotations::where('quote_id', $quoteId)->where('company_id', $companyId)->first();
12110
12111            if (! $quote) {
12112                throw new \Exception('Quote no encontrada');
12113            }
12114
12115            $newIdSolicitudDuplicityValue = null;
12116
12117            if ($type === 'reject') {
12118                $quote->budget_status_id = 20;
12119                $newIdSolicitudDuplicityValue = 'R'.$quote->id_solicitud_duplicity;
12120            }
12121
12122            $this->addUpdateLog($quote->id, 'IA', 'id_solicitud_duplicity', $quote->id_solicitud_duplicity, $newIdSolicitudDuplicityValue);
12123
12124            $quote->id_solicitud_duplicity = $newIdSolicitudDuplicityValue;
12125
12126            $quote->save();
12127
12128            return response([
12129                'message' => 'OK',
12130                'data' => $quote,
12131            ]);
12132
12133        }catch (\Exception $e) {
12134            report(AppException::fromException($e, 'SET_SOLICITUD_DUPLICITY_EXCEPTION'));
12135            return response(['message' => 'KO', 'error' => $e->getMessage()]);
12136        }
12137    }
12138
12139    public function getQuoteIdOfDuplicityById($id): ResponseFactory|HttpResponse{
12140        try{
12141            $quote = TblQuotations::where("id", $id)->first();            
12142
12143            if (! $quote) {
12144                throw new \Exception('Quote no encontrada');
12145            }
12146
12147            return response([
12148                'message' => 'OK',
12149                'data' => $quote,
12150            ]);
12151
12152        } catch (\Exception $e) {
12153            report(AppException::fromException($e, 'GET_QUOTE_ID_OF_DUPLICITY_BY_QUOTE_ID_EXCEPTION'));
12154            return response(['message' => 'KO', 'error' => $e->getMessage()]);
12155        }
12156    }
12157
12158    public function download_s3_files(Request $request)
12159    {
12160        ini_set('max_execution_time', 123456);
12161
12162        try {
12163
12164            $data = $request->all();
12165            $zipName = 'files_'.time().'.zip';
12166
12167            $r = new Request([
12168                'filterModel' => $data['filterModel'],
12169                'sortModel' => $data['sortModel'],
12170                'start' => 0,
12171                'end' => 999999999,
12172                'company_id' => $data['company_id'],
12173                'user_id' => $data['user_id'],
12174                'ids' => $data['ids'],
12175                'searchText' => $data['searchText'],
12176                'ids_not_in' => $data['ids_not_in'],
12177            ]);
12178
12179            $result = $this->list_quotations($r);
12180            $result = $result->original['data'];
12181            $orderIds = array_column($result, 'id');
12182
12183            $files = TblFiles::whereIn('quotation_id', $orderIds)->where('is_internal', null)->get();
12184            if ($files->isEmpty()) {
12185                return response(['message' => 'KO', 'error' => 'No files found']);
12186            }
12187
12188            $tempDir = storage_path('app/temp');
12189
12190            if (! file_exists($tempDir)) {
12191                mkdir($tempDir, 0755, true);
12192            }
12193
12194            $zipPath = $tempDir.'/'.$zipName;
12195
12196            $zip = new ZipArchive;
12197            if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
12198                return response(['message' => 'KO', 'error' => 'Could not create ZIP file']);
12199            }
12200
12201            foreach ($files as $file) {
12202                $filePath = 'uploads/'.$file->filename;
12203
12204                if (Storage::disk('s3')->exists($filePath)) {
12205                    $contents = Storage::disk('s3')->get($filePath);
12206                    $zip->addFromString(basename($filePath), $contents);
12207                }
12208            }
12209
12210            $zip->close();
12211
12212            if (! file_exists($zipPath)) {
12213                return response(['message' => 'KO', 'error' => 'ZIP file creation failed'], 500);
12214            }
12215
12216            $content = file_get_contents($zipPath);
12217
12218            unlink($zipPath);
12219
12220            return response($content, 200, [
12221                'Content-Type' => 'application/zip',
12222                'Content-Disposition' => 'attachment; filename="'.$zipName.'"',
12223            ]);
12224        } catch (\Exception $e) {
12225            /** @disregard P1014 */
12226            $e->exceptionCode = 'DOWNLOAD_S3_FILES_EXCEPTION';
12227            report($e);
12228
12229            return response(['message' => 'KO', 'error' => $e->getMessage()]);
12230        }
12231    }
12232
12233    /**
12234     * FIRE-976: Return budget status and amount by Gestiona internal_quote_id + Region.
12235     * If the quotation doesn't exist locally, syncs it from Gestiona first.
12236     */
12237    public function getQuotationStatusByInternalId(Request $request, $id): ResponseFactory|\Illuminate\Http\Response
12238    {
12239        try {
12240            $region = urldecode((string) $request->header('Region'));
12241            if ($region === 'Catalunya') {
12242                $region = 'Cataluña';
12243            }
12244
12245            $company = TblCompanies::where('region', $region)->first();
12246            if (!$company) {
12247                return response(['message' => 'KO', 'error' => 'Region not found'], 404);
12248            }
12249
12250            $companyId = $company->company_id;
12251
12252            $quotation = $this->findQuotationByInternalId($id, $companyId);
12253
12254            // Found locally — return Titan data
12255            if ($quotation) {
12256                $statusName = null;
12257                if ($quotation->budget_status_id) {
12258                    $status = TblBudgetStatus::where('budget_status_id', $quotation->budget_status_id)->first();
12259                    $statusName = $status?->name;
12260                }
12261
12262                return response([
12263                    'message' => 'OK',
12264                    'data' => [
12265                        'internal_quote_id' => $quotation->internal_quote_id,
12266                        'amount' => $quotation->amount,
12267                        'budget_status_id' => $quotation->budget_status_id,
12268                        'budget_status' => $statusName,
12269                    ],
12270                ]);
12271            }
12272
12273            // Not found locally — try syncing from Gestiona
12274            $presupuestosService = app(\App\Services\PresupuestosService::class);
12275            $syncResult = $presupuestosService->syncById($id, $region);
12276
12277            if (!empty($syncResult['success'])) {
12278                // Sync succeeded — return freshly created Titan data
12279                $quotation = $this->findQuotationByInternalId($id, $companyId);
12280                if ($quotation) {
12281                    $statusName = null;
12282                    if ($quotation->budget_status_id) {
12283                        $status = TblBudgetStatus::where('budget_status_id', $quotation->budget_status_id)->first();
12284                        $statusName = $status?->name;
12285                    }
12286
12287                    return response([
12288                        'message' => 'OK',
12289                        'data' => [
12290                            'internal_quote_id' => $quotation->internal_quote_id,
12291                            'amount' => $quotation->amount,
12292                            'budget_status_id' => $quotation->budget_status_id,
12293                            'budget_status' => $statusName,
12294                        ],
12295                    ]);
12296                }
12297            }
12298
12299            // Sync failed — try to return raw Gestiona data with normalized status
12300            $gestionaResult = $presupuestosService->fetchFromGestiona($id, $region);
12301
12302            if (!empty($gestionaResult['success'])) {
12303                return response([
12304                    'message' => 'OK',
12305                    'data' => $gestionaResult['data'],
12306                    'source' => 'gestiona',
12307                ]);
12308            }
12309
12310            return response(['message' => 'KO', 'error' => 'Quotation not found'], 404);
12311        } catch (\Exception $e) {
12312            report(AppException::fromException($e, 'GET_QUOTATION_STATUS_BY_INTERNAL_ID_EXCEPTION'));
12313            return response(['message' => 'KO', 'error' => $e->getMessage()], 500);
12314        }
12315    }
12316
12317    /**
12318     * FIRE-976: Find a quotation by internal_quote_id + company_id, with 18/22 fallback.
12319     */
12320    private function findQuotationByInternalId($id, int $companyId)
12321    {
12322        $quotation = TblQuotations::where('internal_quote_id', $id)
12323            ->where('company_id', $companyId)
12324            ->first();
12325
12326        if (!$quotation && ($companyId === 18 || $companyId === 22)) {
12327            $quotation = TblQuotations::where('internal_quote_id', $id)
12328                ->whereIn('company_id', [18, 22])
12329                ->first();
12330        }
12331
12332        return $quotation;
12333    }
12334}