Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
44.44% covered (danger)
44.44%
16 / 36
12.50% covered (danger)
12.50%
1 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
PriceListController
44.44% covered (danger)
44.44%
16 / 36
12.50% covered (danger)
12.50%
1 / 8
27.15
0.00% covered (danger)
0.00%
0 / 1
 index
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 create
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 store
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 edit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 destroy
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 options
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 validated
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
3.05
1<?php
2
3namespace App\Http\Controllers\admin;
4
5use App\Http\Controllers\Controller;
6use App\Models\admin\Product;
7use App\Models\Category;
8use App\Models\CustomerGroup;
9use App\Models\PriceList;
10use Illuminate\Http\RedirectResponse;
11use Illuminate\Http\Request;
12use Illuminate\Validation\ValidationException;
13use Illuminate\View\View;
14
15/**
16 * Sales → Price Lists. Each row: for one customer group, either a specific
17 * product or a whole category, from a minimum quantity upward, either an
18 * explicit price or a percent off. Resolved at cart-add time by
19 * App\Services\Pricing\PriceService. Gated by the `wholesale_pricing`
20 * feature.
21 */
22class PriceListController extends Controller
23{
24    public function index(): View
25    {
26        $rules = PriceList::query()->with(['group', 'product', 'category'])
27            ->orderBy('customer_group_id')
28            ->paginate(20);
29
30        return view('admin.price-lists.index', compact('rules'));
31    }
32
33    public function create(): View
34    {
35        return view('admin.price-lists.form', [
36            'rule' => new PriceList(['min_qty' => 1]),
37        ] + $this->options());
38    }
39
40    public function store(Request $request): RedirectResponse
41    {
42        PriceList::create($this->validated($request));
43
44        return redirect()->route('admin.price-lists.index')->with('success', 'Price rule created.');
45    }
46
47    public function edit(PriceList $priceList): View
48    {
49        return view('admin.price-lists.form', ['rule' => $priceList] + $this->options());
50    }
51
52    public function update(Request $request, PriceList $priceList): RedirectResponse
53    {
54        $priceList->update($this->validated($request));
55
56        return redirect()->route('admin.price-lists.index')->with('success', 'Price rule updated.');
57    }
58
59    public function destroy(PriceList $priceList): RedirectResponse
60    {
61        $priceList->delete();
62
63        return redirect()->route('admin.price-lists.index')->with('success', 'Price rule deleted.');
64    }
65
66    private function options(): array
67    {
68        return [
69            'groups' => CustomerGroup::query()->orderBy('sort_order')->orderBy('name')->pluck('name', 'id'),
70            'products' => Product::query()->orderBy('name')->pluck('name', 'id'),
71            'categories' => Category::query()->orderBy('name')->pluck('name', 'id'),
72        ];
73    }
74
75    private function validated(Request $request): array
76    {
77        $data = $request->validate([
78            'customer_group_id' => ['required', 'exists:customer_groups,id'],
79            'product_id' => ['nullable', 'exists:products,id'],
80            'category_id' => ['nullable', 'exists:categories,id'],
81            'min_qty' => ['required', 'integer', 'min:1'],
82            'price' => ['nullable', 'numeric', 'min:0'],
83            'percent_off' => ['nullable', 'numeric', 'min:0', 'max:100'],
84        ]);
85
86        if (blank($data['product_id'] ?? null) === blank($data['category_id'] ?? null)) {
87            throw ValidationException::withMessages([
88                'product_id' => 'Choose exactly one: a product, or a category — not both, not neither.',
89            ]);
90        }
91
92        if (blank($data['price'] ?? null) === blank($data['percent_off'] ?? null)) {
93            throw ValidationException::withMessages([
94                'price' => 'Set exactly one: a fixed price, or a percent off — not both, not neither.',
95            ]);
96        }
97
98        return $data;
99    }
100}