Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
86.11% |
31 / 36 |
|
62.50% |
5 / 8 |
CRAP | |
0.00% |
0 / 1 |
| ShippingZoneController | |
86.11% |
31 / 36 |
|
62.50% |
5 / 8 |
10.27 | |
0.00% |
0 / 1 |
| index | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| create | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| store | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| edit | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| update | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| destroy | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| validatedZone | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
| syncRates | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace App\Http\Controllers\admin; |
| 4 | |
| 5 | use App\Http\Controllers\Controller; |
| 6 | use App\Models\ShippingZone; |
| 7 | use Illuminate\Http\RedirectResponse; |
| 8 | use Illuminate\Http\Request; |
| 9 | use 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 | */ |
| 18 | class 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 | } |