Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PricesController
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 4
156
0.00% covered (danger)
0.00%
0 / 1
 byPostalCode
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
30
 listServices
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 buildMatch
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
6
 normalizeRegion
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Models\PricePostalCode;
6use App\Models\PriceScoreTierService;
7use App\Models\PriceService;
8use Illuminate\Http\Request;
9use Illuminate\Support\Facades\Log;
10
11class PricesController extends Controller
12{
13    /**
14     * FIRE-933: Return the full price list for a given postal code.
15     * FIRE-935: Returns a `matches` array to support postal codes that exist
16     * across multiple regions. Always returns an array (1+ elements) on success.
17     */
18    public function byPostalCode(Request $request, string $postalCode)
19    {
20        try {
21            // FIRE-935: Validate postal code format before hitting the DB.
22            if (!preg_match('/^\d{5}$/', $postalCode)) {
23                return response([
24                    'message' => 'KO',
25                    'error' => 'Invalid postal code format',
26                ], 400);
27            }
28
29            $region = $this->normalizeRegion($request->query('region'));
30
31            $query = PricePostalCode::with('zone')
32                ->where('postal_code', $postalCode);
33
34            if ($region) {
35                $query->whereHas('zone', fn ($q) => $q->where('region', $region));
36            }
37
38            $rows = $query->get();
39
40            if ($rows->isEmpty()) {
41                return response([
42                    'message' => 'KO',
43                    'error' => 'Postal code not found in price catalog',
44                ], 404);
45            }
46
47            $matches = $rows->map(fn ($ppc) => $this->buildMatch($ppc))->values();
48
49            return response([
50                'message' => 'OK',
51                'data' => [
52                    'postal_code' => $postalCode,
53                    'matches' => $matches,
54                ],
55            ]);
56        } catch (\Exception $e) {
57            Log::channel('third-party')->error('[prices:api] byPostalCode failed: ' . $e->getMessage());
58
59            return response([
60                'message' => 'KO',
61                'error' => 'Internal error',
62            ], 500);
63        }
64    }
65
66    /**
67     * FIRE-933: Return the active service catalog for frontend dropdowns.
68     */
69    public function listServices()
70    {
71        try {
72            $services = PriceService::where('is_active', true)
73                ->orderBy('priority')
74                ->get(['id', 'name', 'description', 'category', 'unit', 'priority']);
75
76            return response([
77                'message' => 'OK',
78                'data' => $services,
79            ]);
80        } catch (\Exception $e) {
81            Log::channel('third-party')->error('[prices:api] listServices failed: ' . $e->getMessage());
82
83            return response([
84                'message' => 'KO',
85                'error' => 'Internal error',
86            ], 500);
87        }
88    }
89
90    /**
91     * FIRE-935: Build a single-region "match" entry for a postal code row.
92     * Fetches the tier prices for the row's region + score_cp combination.
93     */
94    private function buildMatch(PricePostalCode $ppc): array
95    {
96        $region = $ppc->zone->region;
97        $prices = [];
98
99        if ($ppc->score_cp !== null) {
100            $prices = PriceScoreTierService::with('service')
101                ->where('region', $region)
102                ->where('score_cp', $ppc->score_cp)
103                ->whereNull('effective_from')
104                ->whereHas('service', fn ($q) => $q->where('is_active', true))
105                ->get()
106                ->map(fn ($tier) => [
107                    'service_id' => $tier->service->id,
108                    'service_name' => $tier->service->name,
109                    'description' => $tier->service->description,
110                    'category' => $tier->service->category,
111                    'unit' => $tier->service->unit,
112                    'price' => (float) $tier->price,
113                ])
114                ->sortBy(fn ($p) => $p['service_id'])
115                ->values()
116                ->all();
117        }
118
119        return [
120            'region' => $region,
121            'zone' => [
122                'id' => $ppc->zone->id,
123                'name' => $ppc->zone->zone_name,
124                'type' => $ppc->zone->zone_type,
125            ],
126            'score_cp' => $ppc->score_cp,
127            'num_clients' => $ppc->num_clients,
128            'weighted_clients' => $ppc->weighted_clients,
129            'prices' => $prices,
130        ];
131    }
132
133    /**
134     * Normalize a region string: urldecode + handle Catalunya -> Cataluña.
135     */
136    private function normalizeRegion(?string $region): ?string
137    {
138        if (!$region) {
139            return null;
140        }
141
142        $region = urldecode($region);
143
144        return $region === 'Catalunya' ? 'Cataluña' : $region;
145    }
146}