Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.11% covered (warning)
86.11%
31 / 36
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ShippingZoneController
86.11% covered (warning)
86.11%
31 / 36
62.50% covered (warning)
62.50%
5 / 8
10.27
0.00% covered (danger)
0.00%
0 / 1
 index
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 create
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 store
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 edit
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 update
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 destroy
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 validatedZone
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 syncRates
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Http\Controllers\admin;
4
5use App\Http\Controllers\Controller;
6use App\Models\ShippingZone;
7use Illuminate\Http\RedirectResponse;
8use Illuminate\Http\Request;
9use Illuminate\View\View;
10
11/**
12 * Settings → Shipping Zones. Zones (e.g. "Inside Dhaka", "Outside Dhaka",
13 * "International") each carry one or more rates. The storefront checkout
14 * uses these when at least one zone with a rate exists; otherwise it falls
15 * back to the flat Checkout & Delivery list untouched (see
16 * App\Services\Shipping\ShippingService and OrderService::resolveShippingCharge()).
17 */
18class ShippingZoneController extends Controller
19{
20    public function index(): View
21    {
22        $zones = ShippingZone::query()->with('rates')->orderBy('sort_order')->orderBy('name')->get();
23
24        return view('admin.shipping.index', compact('zones'));
25    }
26
27    public function create(): View
28    {
29        return view('admin.shipping.form', ['zone' => new ShippingZone(['is_active' => true, 'cod_allowed' => true])]);
30    }
31
32    public function store(Request $request): RedirectResponse
33    {
34        $zone = ShippingZone::create($this->validatedZone($request));
35        $this->syncRates($zone, $request);
36
37        return redirect()->route('admin.shipping-zones.index')->with('success', 'Shipping zone created.');
38    }
39
40    public function edit(ShippingZone $shippingZone): View
41    {
42        $shippingZone->load('rates');
43
44        return view('admin.shipping.form', ['zone' => $shippingZone]);
45    }
46
47    public function update(Request $request, ShippingZone $shippingZone): RedirectResponse
48    {
49        $shippingZone->update($this->validatedZone($request));
50        $this->syncRates($shippingZone, $request);
51
52        return redirect()->route('admin.shipping-zones.index')->with('success', 'Shipping zone updated.');
53    }
54
55    public function destroy(ShippingZone $shippingZone): RedirectResponse
56    {
57        $shippingZone->delete();
58
59        return redirect()->route('admin.shipping-zones.index')->with('success', 'Shipping zone deleted.');
60    }
61
62    private function validatedZone(Request $request): array
63    {
64        $data = $request->validate([
65            'name' => ['required', 'string', 'max:120'],
66            'areas' => ['nullable', 'string', 'max:255'],
67            'is_international' => ['nullable', 'boolean'],
68            'cod_allowed' => ['nullable', 'boolean'],
69            'is_active' => ['nullable', 'boolean'],
70            'sort_order' => ['nullable', 'integer'],
71        ]);
72
73        $data['is_international'] = $request->boolean('is_international');
74        $data['cod_allowed'] = $request->boolean('cod_allowed');
75        $data['is_active'] = $request->boolean('is_active');
76        $data['sort_order'] = $data['sort_order'] ?? 0;
77
78        return $data;
79    }
80
81    /** Replace this zone's rates with whatever the repeater posted. */
82    private function syncRates(ShippingZone $zone, Request $request): void
83    {
84        $rows = collect($request->input('rates', []))
85            ->filter(fn ($r) => filled($r['label'] ?? null));
86
87        $zone->rates()->delete();
88
89        foreach ($rows->values() as $i => $row) {
90            $zone->rates()->create([
91                'label' => $row['label'],
92                'charge' => (float) ($row['charge'] ?? 0),
93                'free_above' => filled($row['free_above'] ?? null) ? (float) $row['free_above'] : null,
94                'sort_order' => $i,
95            ]);
96        }
97    }
98}