Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 374
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
GestionaController
0.00% covered (danger)
0.00%
0 / 374
0.00% covered (danger)
0.00%
0 / 14
4830
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
2
 updateApiCredentials
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getApiDetails
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 getLastUpdate
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getAcceptanceWarnings
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
42
 getSyncStatus
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getG3wActive
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 updateG3wActive
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getMappings
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 setMappings
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
156
 deleteMappings
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getAllBudgetMonitor
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 1
132
 syncAllBudgetMonitor
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
42
 getDuplicatedValues
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2namespace App\Http\Controllers;
3
4use App\Models\TblCompanies;
5use App\Models\TblG3wLastUpdate;
6use App\Models\TblQuotations;
7use App\Models\TblSegmentG3wMapping;
8use App\Models\TblSourceG3wMapping;
9use App\Models\TblSources;
10use App\Models\TblStatusG3wMapping;
11use App\Models\TblTypeG3wMapping;
12use App\Models\TblUserG3wMapping;
13use App\Models\TblUsers;
14use App\Services\PresupuestosService;
15use Illuminate\Http\Request;
16use App\Services\GestionaService;
17use Illuminate\Support\Carbon;
18use Illuminate\Support\Facades\Log;
19use Illuminate\Support\Facades\DB;
20use Illuminate\Support\Facades\Schema;
21use function Illuminate\Events\queueable;
22
23class GestionaController extends Controller
24{
25    protected $gestionaService;
26    protected $presupuestoService;
27    protected $tables;
28
29    public function __construct(GestionaService $gestionaService, PresupuestosService $presupuestoService)
30    {
31        $this->gestionaService = $gestionaService;
32        $this->presupuestoService = $presupuestoService;
33        $this->tables = [
34            "status" => [
35                "model" => TblStatusG3wMapping::class,
36                "index" => "budget_status_id",
37                "relation" => "budgetStatus",
38                "tblQuotationsG3wNameColumn" => "status_by_g3w",
39                "tblMappingNameColumn" => "name_g3w",
40                "tblQuotationsNameColumn" => "budget_status_id",
41                "requestNameKey" => "budget_status_id"
42            ],
43            "segment" => [
44                "model" => TblSegmentG3wMapping::class,
45                "index" => "segment_id",
46                "relation" => "segment",
47                "tblQuotationsG3wNameColumn" => "segment_by_g3w",
48                "tblMappingNameColumn" => "name_g3w",
49                "tblQuotationsNameColumn" => "segment_id",
50                "requestNameKey" => "segment_id"
51            ],
52            "type" => [
53                "model" => TblTypeG3wMapping::class,
54                "index" => "budget_type_name",
55                "relation" => "budgetType",
56                "tblQuotationsG3wNameColumn" => "type_by_g3w",
57                "tblMappingNameColumn" => "id_g3w",
58                "tblQuotationsNameColumn" => "budget_type_id",
59                "requestNameKey" => "id_g3w"
60            ],
61            "source" => [
62                "model" => TblSourceG3wMapping::class,
63                "index" => "source_name",
64                "relation" => "source",
65                "tblQuotationsG3wNameColumn" => "source_by_g3w",
66                "tblMappingNameColumn" => "id_g3w",
67                "tblQuotationsNameColumn" => "source_id",
68                "requestNameKey" => "id_g3w"
69            ],
70            "user" => [
71                "model" => TblUserG3wMapping::class,
72                "index" => "id_fst",
73                "relation" => "user",
74                "tblQuotationsG3wNameColumn" => "G3W_code",
75                "tblMappingNameColumn" => "id_fst",
76                "tblQuotationsNameColumn" => "G3W_code",
77                "requestNameKey" => "id_fst"
78            ],
79        ];
80    }
81
82    /**
83     * Sincroniza un presupuesto por su ID.
84     *
85     * @param $id
86     * @return \Illuminate\Http\JsonResponse
87     */
88    public function updateApiCredentials(Request $request)
89    {
90        try {
91            $region = @getallheaders()["Region"];
92
93            if($region == "Catalunya"){
94                $region = "Cataluña";
95            }
96
97            $params = $request->only(['url', 'user', 'password', 'user_id']);
98            $this->gestionaService->updateApiCredentials($params, $region);
99            return response()->json(['message' => 'Credentials updated successfully'], 200);
100        } catch (\Exception $e) {
101            return response()->json(['error' => $e->getMessage()], 400);
102        }
103    }
104
105    /**
106     * @return \Illuminate\Http\JsonResponse
107     * @throws \Exception
108     */
109    public function getApiDetails(){
110        try {
111            $region = @getallheaders()["Region"];
112
113            if($region === "Catalunya"){
114                $region="Cataluña";
115            }
116
117            $apiDetailsResponse = $this->gestionaService->getApiDetails($region);
118
119            if(!$apiDetailsResponse){
120                throw new \Exception('Api details not found');
121            }
122
123            $apiDetails = json_decode($apiDetailsResponse->getContent(), true);
124
125            return response()->json([
126                'url' => $apiDetails['url'] ?? null,
127                'user' => $apiDetails['user'] ?? null,
128            ], 200);
129
130        } catch (\Illuminate\Database\QueryException $e) {
131            return response()->json(['error' => $e->getMessage()], 400);
132        }
133    }
134
135    public function getLastUpdate(){
136        try {
137            $region = @getallheaders()["Region"];
138
139            if($region === "Catalunya"){
140                $region="Cataluña";
141            }
142
143            $apiLastUpdateResponse = $this->gestionaService->getLastUpdate($region);
144
145            if(!$apiLastUpdateResponse){
146                throw new \Exception('Last update not found');
147            }
148
149            $apiLastUpdate = json_decode($apiLastUpdateResponse->getContent(), true);
150
151            return response()->json([
152                'lastUpdate' => $apiLastUpdate['lastUpdate'] ?? null,
153                'updatingNow' => $apiLastUpdate['updatingNow'] ?? null,
154            ], 200);
155
156        } catch (\Illuminate\Database\QueryException $e) {
157            Log::error('Error en getLastUpdate: ' . $e->getMessage());
158            return response()->json(['error' => $e->getMessage()], 400);
159        }
160    }
161
162    public function getAcceptanceWarnings(Request $request)
163    {
164        try {
165            $region = @getallheaders()["Region"] ?? null;
166
167            if (!$region) {
168                return response()->json(['error' => 'Region header is missing'], 400);
169            }
170
171            if ($region == "Catalunya") {
172                $region = "Cataluña";
173            }
174
175            $commercial = $request->input('commercial', null);
176
177            $company = TblCompanies::where('region', $region)->first();
178            if (!$company) {
179                return response()->json([
180                    'countWarnings' => 0
181                ], 200);
182            }
183
184            $companyId = $company->company_id;
185
186            /*$countWarnings = TblQuotations::where('sync_import', 1)
187                ->where('company_id', $companyId)
188                ->when(!empty($commercial) && $commercial !== "All", function ($query) use ($commercial) {
189                    return $query->where('commercial', $commercial);
190                })
191                ->where(function ($query) {
192                    $query->WhereIn('budget_status_id', [13, 14])
193                        ->orWhere(function ($subQuery) {
194                            $subQuery->whereNull('commercial')
195                                ->orWhereNotExists(function ($subQuery) {
196                                    $subQuery->select(DB::raw(1))
197                                        ->from('tbl_users')
198                                        ->whereColumn('tbl_users.name', 'tbl_quotations.commercial');
199                                });
200                        })
201                        ->orWhereNull('phone_number')
202                        ->orWhereNull('source_id')
203                        ->orWhereNull('budget_type_id')
204                        ->orWhere(function ($subQuery) {
205                            $subQuery->whereNull('client')
206                                ->orWhere(DB::raw('TRIM(client)'), '');
207                        })
208                        ->orWhere(function ($subQuery) {
209                            $subQuery->whereNull('email')
210                                ->orWhere(DB::raw('TRIM(email)'), '');
211                        });
212                })
213                ->count();*/
214
215            $countWarnings = TblQuotations::where('sync_import', 1)
216                ->where('company_id', $companyId)
217                ->where('g3w_warning', 1)
218                ->when(!empty($commercial) && $commercial !== "All", function ($query) use ($commercial) {
219                    return $query->where('commercial', $commercial);
220                })
221                ->count();
222
223
224            return response()->json([
225                'countWarnings' => $countWarnings
226            ], 200);
227
228        } catch (\Illuminate\Database\QueryException $e) {
229            Log::error('Error en getAcceptanceWarnings: ' . $e->getMessage());
230            return response()->json(['error' => $e->getMessage()], 400);
231        }
232    }
233
234    public function getSyncStatus(){
235        try {
236            $region = @getallheaders()["Region"];
237
238            if($region === "Catalunya"){
239                $region="Cataluña";
240            }
241
242            return response()->json([
243                'status' => $this->gestionaService->getSyncStatus($region)
244            ], 200);
245
246        } catch (\Illuminate\Database\QueryException $e) {
247            return response()->json(['error' => $e->getMessage()], 400);
248        }
249    }
250
251    public function getG3wActive(){
252        try {
253            $region = @getallheaders()["Region"];
254
255            if($region === "Catalunya"){
256                $region="Cataluña";
257            }
258
259            return response()->json([
260                'active' => $this->gestionaService->getG3wActive($region)
261            ], 200);
262
263        } catch (\Illuminate\Database\QueryException $e) {
264            Log::error('Error en getG3wActive: ' . $e->getMessage());
265            return response()->json(['error' => $e->getMessage()], 400);
266        }
267    }
268
269    public function updateG3wActive(Request $request){
270        try {
271            $region = @getallheaders()["Region"];
272
273            if($region === "Catalunya"){
274                $region="Cataluña";
275            }
276
277            $active = $request->input('active');
278
279            $this->gestionaService->updateG3wActive($active, $region);
280
281            return response()->json([
282                'success' => 1
283            ], 200);
284
285        } catch (\Illuminate\Database\QueryException $e) {
286            return response()->json(['error' => $e->getMessage()], 400);
287        }
288    }
289
290    public function getMappings(Request $request)
291    {
292        try {
293
294            if (!$request->has("type") || !array_key_exists($request->type, $this->tables)) {
295                throw new \Exception('Type parameter is missing or invalid');
296            }
297
298            $tableConfig = $this->tables[$request->type];
299
300            if (isset($tableConfig['model'])) {
301                $mappings = $tableConfig['model']::with([$tableConfig['relation'] => function ($query) use ($tableConfig) {
302                    $relatedModel = (new $tableConfig['model'])->{$tableConfig['relation']}()->getRelated();
303                    $relatedTable = $relatedModel->getTable();
304
305                    if (Schema::hasColumn($relatedTable, 'priority')) {
306                        $query->orderBy('priority', 'desc');
307                    }
308                }])->get();
309            }
310            else {
311                $mappings = DB::table($tableConfig['table'])->get();
312            }
313
314            return response()->json($mappings);
315
316        } catch (\Exception $e) {
317            return response()->json(['error' => $e->getMessage()], 400);
318        }
319    }
320
321    public function setMappings(Request $request)
322    {
323        try{
324            if(!$request->has('type')){
325                return response()->json(['message' => 'Not type especified']);
326            }
327
328            $tableConfig = $this->tables[$request->type];
329
330            if (!isset($tableConfig['tblQuotationsG3wNameColumn'], $tableConfig['tblMappingNameColumn'], $tableConfig['tblQuotationsNameColumn'], $tableConfig['requestNameKey'])) {
331                return response()->json(['message' => 'Missing configuration keys'], 500);
332            }
333
334            $data = $request->except('type');
335
336            if(!$request->has('id')){
337                $tableConfig['model']::create($data);
338                return response()->json(['message' => $request->type . ' created successfully']);
339            }
340
341            if ($tableConfig['relation'] === "user") {
342                if(is_numeric($request["id"])){
343                    $rowMapping = $tableConfig['model']::where("id_fst", $request["id"])->first();
344                    $data = $request->except('id');
345
346                    if (!$rowMapping) {
347                        $tableConfig['model']::create($data);
348                        return response()->json(['error' => 'User mapping not found'], 404);
349                    }
350
351                    $userName = TblUsers::where('id', $rowMapping->id_fst)->first()->name;
352                    $nameG3w = $rowMapping->name_g3w;
353
354                    TblQuotations::where("user_create_by_g3w", $nameG3w)
355                        ->update(['created_by' => $userName]);
356
357                    TblQuotations::where("user_commercial_by_g3w", $nameG3w)
358                        ->update(['commercial' => $userName]);
359
360                    TblQuotations::where("user_create_by_g3w", $nameG3w)
361                        ->whereNull('commercial')
362                        ->whereNull('user_commercial_by_g3w')
363                        ->update(['commercial' => $nameG3w]);
364
365
366                    $rowMapping->update($data);
367
368                } else {
369                    $rowMapping = $tableConfig['model']::where("name_g3w", $request["id"])->first();
370
371                    if (!$rowMapping) {
372                        return response()->json(['error' => 'User mapping not found'], 404);
373                    }
374
375                    $rowMapping->update(array('id_fst' => $request["id_fst"]));
376
377                    $userName = TblUsers::where('id', $request["id_fst"])->first();
378
379                    if(!$userName){
380                        return response()->json(['error' => 'User mapping not found'], 404);
381                    }
382
383                    TblQuotations::where("user_create_by_g3w", $request["id"])
384                        ->update(['created_by' => $userName->name]);
385
386                    TblQuotations::where("user_commercial_by_g3w", $request["id"])
387                        ->update(['commercial' => $userName->name]);
388
389                    TblQuotations::where("user_create_by_g3w", $request["id"])
390                        ->whereNull('commercial')
391                        ->whereNull('user_commercial_by_g3w')
392                        ->update(['commercial' => $userName->name]);
393
394                }
395
396                return response()->json(['message' => $request->type . ' edited successfully']);
397            }
398
399            $rowMapping = $tableConfig['model']::where("id", $request["id"])->first();
400
401            if ($tableConfig['relation'] === "source") {
402                $data = [];
403                $sourceRow = TblSources::where('source_id', $request["id_fst"])->first();
404                if(!$sourceRow){
405                    return response()->json(['error' => 'Source mapping not found'], 404);
406                }
407
408                $data["source_name"] = $sourceRow->name;
409            }
410
411            $rowMapping->update($data);
412
413            TblQuotations::where($tableConfig['tblQuotationsG3wNameColumn'], $rowMapping[$tableConfig['tblMappingNameColumn']])
414                            ->update([$tableConfig['tblQuotationsNameColumn'] => $rowMapping[$tableConfig['requestNameKey']]]);
415
416            return response()->json(['message' => $request->type . ' edited successfully']);
417
418        }catch (\Illuminate\Database\QueryException $e) {
419            return response()->json(['error' => $e->getMessage()], 400);
420        }
421    }
422
423    public function deleteMappings(Request $request)
424    {
425        try{
426            if(!$request->has('id')){
427                return response()->json(['message' => 'Can`t be deleted without ID']);
428            }
429
430            if(!$request->has('type')){
431                return response()->json(['message' => 'Not type especified']);
432            }
433
434            $tableConfig = $this->tables[$request->type];
435
436            if($tableConfig['relation'] === "user"){
437                $tableConfig['model']::where("id_fst", $request["id"])->delete();
438                return response()->json(['message' => $request->type . ' deleted successfully']);
439            }
440
441            $tableConfig['model']::where("id", $request["id"])->delete();
442            return response()->json(['message' => $request->type . ' deleted successfully']);
443
444        }catch (\Illuminate\Database\QueryException $e) {
445            return response()->json(['error' => $e->getMessage()], 400);
446        }
447    }
448
449    public function getAllBudgetMonitor(Request $request)
450    {
451        try {
452            $data = $request->all();
453            $region = urldecode(@getallheaders()["Region"]);
454            $page = $data["page"];
455
456            $daysOffset = $page * 10;
457            $endDate = Carbon::today()->subDays($daysOffset);
458            $startDate = $endDate->copy()->addDays(10);
459
460            $dataResponse = [];
461            $currentDate = $startDate->copy();
462            $companyId = TblCompanies::where("region", $region)->first()->company_id;
463            $alreadyG3wBudgets = [];
464
465            while ($currentDate > $endDate) {
466                $dateStr = $currentDate->format('Y-m-d');
467
468                $g3wBudgets = $this->gestionaService->getBudgetsByDay($dateStr, $region);
469                if (is_string($g3wBudgets)) {
470                    $g3wBudgets = json_decode($g3wBudgets, true);
471                }
472
473                $g3wBudgetIds = array_map(function($item) {
474                    return $item["ID"];
475                }, $g3wBudgets);
476
477                $newG3wBudgetIds = array_diff($g3wBudgetIds, $alreadyG3wBudgets);
478                $alreadyG3wBudgets = array_merge($alreadyG3wBudgets, $newG3wBudgetIds);
479                $countG3wBudgets = count($newG3wBudgetIds);
480
481                $fstBudgets = TblQuotations::where("company_id", $companyId)
482                    ->where(function($query) {
483                        $query->where("sync_import", 1)
484                            ->orWhere("sync_import_edited", 1);
485                    })
486                    ->whereDate("created_at", $dateStr)
487                    ->pluck("internal_quote_id")
488                    ->toArray();
489
490                if ($companyId == 18 || $companyId == 22){
491                    $fstBudgets = TblQuotations::whereIn("company_id", [18, 22])
492                        ->where(function($query) {
493                            $query->where("sync_import", 1)
494                                ->orWhere("sync_import_edited", 1);
495                        })
496                        ->whereDate("created_at", $dateStr)
497                        ->pluck("internal_quote_id")
498                        ->toArray();
499                }
500
501                $countFstBudgets = count($fstBudgets);
502
503                $missingIds = array_diff($newG3wBudgetIds, $fstBudgets);
504
505                $existingIds = TblQuotations::where("company_id", $companyId)
506                    ->where(function($query) {
507                        $query->where("sync_import", 1)
508                            ->orWhere("sync_import_edited", 1);
509                    })
510                    ->whereIn("internal_quote_id", $missingIds)
511                    ->pluck("internal_quote_id")
512                    ->toArray();
513
514                if($companyId == 18 || $companyId == 22){
515                    $existingIds = TblQuotations::where("company_id", $companyId)
516                        ->where(function($query) {
517                            $query->where("sync_import", 1)
518                                ->orWhere("sync_import_edited", 1);
519                        })
520                        ->whereIn("internal_quote_id", $missingIds)
521                        ->pluck("internal_quote_id")
522                        ->toArray();
523                }
524
525                $finalMissingIds = array_diff($missingIds, $existingIds);
526
527                $duplicatedFst = $this->getDuplicatedValues($fstBudgets);
528
529                $deletedIds = array_diff($fstBudgets, $newG3wBudgetIds);
530                foreach ($deletedIds as $key => $id){
531                    $deleted = $this->gestionaService->checkDeleted($id, $region);
532                    if(!$deleted){
533                        unset($deletedIds[$key]);
534                    }
535                }
536
537                $dataResponse[] = [
538                    'date' => $dateStr,
539                    'g3wBudgets' => array_values($newG3wBudgetIds),
540                    'countG3wBudgets' => $countG3wBudgets,
541                    'fstBudgets' => $fstBudgets,
542                    'countfstBudgets' => $countFstBudgets,
543                    'missingIds' => array_values($finalMissingIds),
544                    'duplicatedFst' => $duplicatedFst,
545                    'deletedIds' => array_values($deletedIds)
546                ];
547
548                $currentDate->subDay();
549
550            }
551
552            usort($dataResponse, function($a, $b) {
553                return strtotime($b['date']) - strtotime($a['date']);
554            });
555
556            return response()->json([
557                'data' => $dataResponse,
558                'meta' => [
559                    'current_page' => (int)$page,
560                    'date_range' => [
561                        'start' => $startDate->format('Y-m-d'),
562                        'end' => $endDate->format('Y-m-d')
563                    ]
564                ]]);
565        } catch (\Exception $e){
566            Log::error('Error en getAllBudgetMonitor: ' . $e->getMessage());
567
568            if($e->getMessage() == "API URL is not defined."){
569                return response()->json([
570                    'error' => 'KO',
571                    'message' => 'La conexión con la API de G3W no está configurada. Por favor, configúrala en los ajustes de la empresa.'
572                ], 200);
573            }
574
575            return response()->json([
576                'message' => 'Error interno del servidor'
577            ], 500);
578        }
579    }
580
581    public function syncAllBudgetMonitor(Request $request)
582    {
583        try {
584            $data = $request->all();
585            if (app()->runningInConsole()) {
586                $region = $request->header('Region');
587            } else {
588                $headers = getallheaders();
589                $region = urldecode($headers["Region"] ?? '');
590            }
591            $day = $data["day"];
592            $dataResponse = [];
593            $companyId = TblCompanies::where("region", $region)->first()->company_id;
594            $alreadyG3wBudgets = [];
595
596            $g3wBudgets = $this->gestionaService->getBudgetsByDay($day, $region);
597
598            if (is_string($g3wBudgets)) {
599                $g3wBudgets = json_decode($g3wBudgets, true);
600            }
601
602            $g3wBudgetIds = array_map(function($item) {
603                return $item["ID"];
604            }, $g3wBudgets);
605
606            $newG3wBudgetIds = array_diff($g3wBudgetIds, $alreadyG3wBudgets);
607            $alreadyG3wBudgets = array_merge($alreadyG3wBudgets, $newG3wBudgetIds);
608
609            $fstBudgets = TblQuotations::where("company_id", $companyId)
610                ->where("sync_import", 1)
611                ->whereDate("created_at", $day)
612                ->pluck("internal_quote_id")
613                ->toArray();
614
615            $missingIds = array_diff($newG3wBudgetIds, $fstBudgets);
616
617            $existingIds = TblQuotations::where("company_id", $companyId)
618                ->where("sync_import", 1)
619                ->whereIn("internal_quote_id", $missingIds)
620                ->pluck("internal_quote_id")
621                ->toArray();
622
623            $finalMissingIds = array_diff($missingIds, $existingIds);
624
625            $duplicatedFst = $this->getDuplicatedValues($fstBudgets);
626
627            $deletedIds = array_diff($fstBudgets, $newG3wBudgetIds);
628
629            foreach ($deletedIds as $key => $id){
630                $deleted = $this->gestionaService->checkDeleted($id, $region);
631                if(!$deleted){
632                    unset($deletedIds[$key]);
633                }
634            }
635
636            $this->presupuestoService->syncByIds(implode(',', $finalMissingIds), $region, $day);
637
638            $dataResponse[] = [
639                'missingIds' => array_values($finalMissingIds),
640                'deletedIds' => array_values($deletedIds)
641            ];
642
643            return response()->json([
644                'success' => true,
645                'message' => "Presupuestos que faltan sincronizados correctamente",
646            ]);
647        } catch (\Exception $e){
648            Log::error('Error en getAllBudgetMonitor: ' . $e->getMessage());
649
650            return response()->json([
651                'message' => 'Error interno del servidor'
652            ], 500);
653        }
654    }
655
656    private function getDuplicatedValues($fstBudgets) {
657        $filteredValues = array_filter($fstBudgets, function($value) {
658            return $value !== null;
659        });
660
661        if (empty($filteredValues)) {
662            return [];
663        }
664
665        $counts = array_count_values($filteredValues);
666        $duplicates = array_filter($counts, function($count) {
667            return $count > 1;
668        });
669
670        return array_keys($duplicates);
671    }
672}