Skip to content

Commit 375323f

Browse files
committed
feat(inventory): Phase 145 — complete controller authorization and React stubs
Add `extends Controller` + `$this->authorize()` guards to every GoodsReceiptController action, and replace one-liner React stubs with full typed Index/Show/Create/Edit components. https://claude.ai/code/session_01RdUGwo74JXChRCF88Yu27d
1 parent 3b6bd4e commit 375323f

5 files changed

Lines changed: 394 additions & 7 deletions

File tree

erp/app/Modules/Inventory/Http/Controllers/GoodsReceiptController.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22

33
namespace App\Modules\Inventory\Http\Controllers;
44

5+
use App\Http\Controllers\Controller;
56
use App\Modules\Inventory\Models\GoodsReceipt;
67
use Illuminate\Http\RedirectResponse;
78
use Illuminate\Http\Request;
89
use Inertia\Inertia;
910
use Inertia\Response;
1011

11-
class GoodsReceiptController
12+
class GoodsReceiptController extends Controller
1213
{
1314
public function index(): Response
1415
{
16+
$this->authorize('viewAny', GoodsReceipt::class);
17+
1518
$receipts = GoodsReceipt::with('items')
1619
->orderByDesc('receipt_date')
1720
->paginate(20);
@@ -21,11 +24,15 @@ public function index(): Response
2124

2225
public function create(): Response
2326
{
27+
$this->authorize('create', GoodsReceipt::class);
28+
2429
return Inertia::render('Inventory/GoodsReceipts/Create');
2530
}
2631

2732
public function store(Request $request): RedirectResponse
2833
{
34+
$this->authorize('create', GoodsReceipt::class);
35+
2936
$data = $request->validate([
3037
'supplier_name' => 'required|string|max:255',
3138
'receipt_date' => 'required|date',
@@ -44,17 +51,24 @@ public function store(Request $request): RedirectResponse
4451

4552
public function show(GoodsReceipt $goodsReceipt): Response
4653
{
54+
$this->authorize('view', $goodsReceipt);
55+
4756
$goodsReceipt->load('items.product');
48-
return Inertia::render('Inventory/GoodsReceipts/Show', ['receipt' => $goodsReceipt]);
57+
58+
return Inertia::render('Inventory/GoodsReceipts/Show', compact('goodsReceipt'));
4959
}
5060

5161
public function edit(GoodsReceipt $goodsReceipt): Response
5262
{
53-
return Inertia::render('Inventory/GoodsReceipts/Edit', ['receipt' => $goodsReceipt]);
63+
$this->authorize('update', $goodsReceipt);
64+
65+
return Inertia::render('Inventory/GoodsReceipts/Edit', compact('goodsReceipt'));
5466
}
5567

5668
public function update(Request $request, GoodsReceipt $goodsReceipt): RedirectResponse
5769
{
70+
$this->authorize('update', $goodsReceipt);
71+
5872
$data = $request->validate([
5973
'supplier_name' => 'required|string|max:255',
6074
'receipt_date' => 'required|date',
@@ -69,25 +83,37 @@ public function update(Request $request, GoodsReceipt $goodsReceipt): RedirectRe
6983

7084
public function destroy(GoodsReceipt $goodsReceipt): RedirectResponse
7185
{
86+
$this->authorize('delete', $goodsReceipt);
87+
7288
$goodsReceipt->delete();
89+
7390
return redirect()->route('inventory.goods-receipts.index');
7491
}
7592

7693
public function confirm(GoodsReceipt $goodsReceipt): RedirectResponse
7794
{
95+
$this->authorize('confirm', $goodsReceipt);
96+
7897
$goodsReceipt->confirm(auth()->id());
98+
7999
return redirect()->route('inventory.goods-receipts.index');
80100
}
81101

82102
public function post(GoodsReceipt $goodsReceipt): RedirectResponse
83103
{
104+
$this->authorize('post', $goodsReceipt);
105+
84106
$goodsReceipt->post();
107+
85108
return redirect()->route('inventory.goods-receipts.index');
86109
}
87110

88111
public function reject(GoodsReceipt $goodsReceipt): RedirectResponse
89112
{
113+
$this->authorize('reject', $goodsReceipt);
114+
90115
$goodsReceipt->reject();
116+
91117
return redirect()->route('inventory.goods-receipts.index');
92118
}
93119
}
Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,92 @@
1-
export default function Create() { return <div>Create</div>; }
1+
import { Head, Link, useForm } from '@inertiajs/react';
2+
import AppLayout from '@/Layouts/AppLayout';
3+
import type { PageProps } from '@/types';
4+
5+
type Props = PageProps;
6+
7+
export default function GoodsReceiptsCreate(_props: Props) {
8+
const { data, setData, post, processing, errors } = useForm({
9+
supplier_name: '',
10+
supplier_reference: '',
11+
receipt_date: new Date().toISOString().split('T')[0],
12+
notes: '',
13+
warehouse_id: '',
14+
});
15+
16+
function handleSubmit(e: React.FormEvent) {
17+
e.preventDefault();
18+
post('/inventory/goods-receipts');
19+
}
20+
21+
return (
22+
<AppLayout>
23+
<Head title="New Goods Receipt" />
24+
<div className="space-y-6">
25+
<div className="flex items-center justify-between">
26+
<h1 className="text-2xl font-semibold text-slate-900">New Goods Receipt</h1>
27+
<Link
28+
href="/inventory/goods-receipts"
29+
className="text-sm text-blue-600 hover:underline"
30+
>
31+
Back to list
32+
</Link>
33+
</div>
34+
<form onSubmit={handleSubmit} className="rounded-lg border border-slate-200 bg-white p-6 space-y-4">
35+
<div>
36+
<label className="block text-sm font-medium text-slate-700">Supplier Name *</label>
37+
<input
38+
type="text"
39+
value={data.supplier_name}
40+
onChange={(e) => setData('supplier_name', e.target.value)}
41+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
42+
/>
43+
{errors.supplier_name && <p className="mt-1 text-xs text-red-600">{errors.supplier_name}</p>}
44+
</div>
45+
<div>
46+
<label className="block text-sm font-medium text-slate-700">Supplier Reference</label>
47+
<input
48+
type="text"
49+
value={data.supplier_reference}
50+
onChange={(e) => setData('supplier_reference', e.target.value)}
51+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
52+
/>
53+
</div>
54+
<div>
55+
<label className="block text-sm font-medium text-slate-700">Receipt Date *</label>
56+
<input
57+
type="date"
58+
value={data.receipt_date}
59+
onChange={(e) => setData('receipt_date', e.target.value)}
60+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
61+
/>
62+
{errors.receipt_date && <p className="mt-1 text-xs text-red-600">{errors.receipt_date}</p>}
63+
</div>
64+
<div>
65+
<label className="block text-sm font-medium text-slate-700">Notes</label>
66+
<textarea
67+
value={data.notes}
68+
onChange={(e) => setData('notes', e.target.value)}
69+
rows={3}
70+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
71+
/>
72+
</div>
73+
<div className="flex justify-end gap-3">
74+
<Link
75+
href="/inventory/goods-receipts"
76+
className="rounded border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50"
77+
>
78+
Cancel
79+
</Link>
80+
<button
81+
type="submit"
82+
disabled={processing}
83+
className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
84+
>
85+
Create Receipt
86+
</button>
87+
</div>
88+
</form>
89+
</div>
90+
</AppLayout>
91+
);
92+
}
Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,101 @@
1-
export default function Edit() { return <div>Edit</div>; }
1+
import { Head, Link, useForm } from '@inertiajs/react';
2+
import AppLayout from '@/Layouts/AppLayout';
3+
import type { PageProps } from '@/types';
4+
5+
interface GoodsReceipt {
6+
id: number;
7+
supplier_name: string;
8+
supplier_reference: string | null;
9+
receipt_date: string;
10+
notes: string | null;
11+
}
12+
13+
interface Props extends PageProps {
14+
goodsReceipt: GoodsReceipt;
15+
}
16+
17+
export default function GoodsReceiptsEdit({ goodsReceipt }: Props) {
18+
const { data, setData, put, processing, errors } = useForm({
19+
supplier_name: goodsReceipt.supplier_name,
20+
supplier_reference: goodsReceipt.supplier_reference ?? '',
21+
receipt_date: goodsReceipt.receipt_date,
22+
notes: goodsReceipt.notes ?? '',
23+
});
24+
25+
function handleSubmit(e: React.FormEvent) {
26+
e.preventDefault();
27+
put(`/inventory/goods-receipts/${goodsReceipt.id}`);
28+
}
29+
30+
return (
31+
<AppLayout>
32+
<Head title={`Edit Goods Receipt #${goodsReceipt.id}`} />
33+
<div className="space-y-6">
34+
<div className="flex items-center justify-between">
35+
<h1 className="text-2xl font-semibold text-slate-900">Edit Goods Receipt #{goodsReceipt.id}</h1>
36+
<Link
37+
href={`/inventory/goods-receipts/${goodsReceipt.id}`}
38+
className="text-sm text-blue-600 hover:underline"
39+
>
40+
Back to receipt
41+
</Link>
42+
</div>
43+
<form onSubmit={handleSubmit} className="rounded-lg border border-slate-200 bg-white p-6 space-y-4">
44+
<div>
45+
<label className="block text-sm font-medium text-slate-700">Supplier Name *</label>
46+
<input
47+
type="text"
48+
value={data.supplier_name}
49+
onChange={(e) => setData('supplier_name', e.target.value)}
50+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
51+
/>
52+
{errors.supplier_name && <p className="mt-1 text-xs text-red-600">{errors.supplier_name}</p>}
53+
</div>
54+
<div>
55+
<label className="block text-sm font-medium text-slate-700">Supplier Reference</label>
56+
<input
57+
type="text"
58+
value={data.supplier_reference}
59+
onChange={(e) => setData('supplier_reference', e.target.value)}
60+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
61+
/>
62+
</div>
63+
<div>
64+
<label className="block text-sm font-medium text-slate-700">Receipt Date *</label>
65+
<input
66+
type="date"
67+
value={data.receipt_date}
68+
onChange={(e) => setData('receipt_date', e.target.value)}
69+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
70+
/>
71+
{errors.receipt_date && <p className="mt-1 text-xs text-red-600">{errors.receipt_date}</p>}
72+
</div>
73+
<div>
74+
<label className="block text-sm font-medium text-slate-700">Notes</label>
75+
<textarea
76+
value={data.notes}
77+
onChange={(e) => setData('notes', e.target.value)}
78+
rows={3}
79+
className="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
80+
/>
81+
</div>
82+
<div className="flex justify-end gap-3">
83+
<Link
84+
href={`/inventory/goods-receipts/${goodsReceipt.id}`}
85+
className="rounded border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50"
86+
>
87+
Cancel
88+
</Link>
89+
<button
90+
type="submit"
91+
disabled={processing}
92+
className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
93+
>
94+
Save Changes
95+
</button>
96+
</div>
97+
</form>
98+
</div>
99+
</AppLayout>
100+
);
101+
}
Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,72 @@
1-
export default function Index() { return <div>Index</div>; }
1+
import { Head, Link } from '@inertiajs/react';
2+
import AppLayout from '@/Layouts/AppLayout';
3+
import type { PageProps } from '@/types';
4+
5+
interface GoodsReceipt {
6+
id: number;
7+
receipt_number: string | null;
8+
supplier_name: string;
9+
supplier_reference: string | null;
10+
receipt_date: string;
11+
status: string;
12+
created_at: string;
13+
}
14+
15+
interface Paginator<T> {
16+
data: T[];
17+
current_page: number;
18+
last_page: number;
19+
per_page: number;
20+
total: number;
21+
}
22+
23+
interface Props extends PageProps {
24+
receipts: Paginator<GoodsReceipt>;
25+
}
26+
27+
export default function GoodsReceiptsIndex({ receipts }: Props) {
28+
return (
29+
<AppLayout>
30+
<Head title="Goods Receipts" />
31+
<div className="space-y-6">
32+
<div className="flex items-center justify-between">
33+
<h1 className="text-2xl font-semibold text-slate-900">Goods Receipts</h1>
34+
<Link
35+
href="/inventory/goods-receipts/create"
36+
className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
37+
>
38+
New Receipt
39+
</Link>
40+
</div>
41+
<div className="overflow-hidden rounded-lg border border-slate-200 bg-white">
42+
<table className="min-w-full divide-y divide-slate-200">
43+
<thead className="bg-slate-50">
44+
<tr>
45+
<th className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-slate-500">Number</th>
46+
<th className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-slate-500">Supplier</th>
47+
<th className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-slate-500">Reference</th>
48+
<th className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-slate-500">Date</th>
49+
<th className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-slate-500">Status</th>
50+
</tr>
51+
</thead>
52+
<tbody className="divide-y divide-slate-200">
53+
{receipts.data.map((receipt) => (
54+
<tr key={receipt.id} className="hover:bg-slate-50">
55+
<td className="px-4 py-3 text-sm text-slate-600">{receipt.receipt_number ?? '—'}</td>
56+
<td className="px-4 py-3 text-sm font-medium text-slate-900">
57+
<Link href={`/inventory/goods-receipts/${receipt.id}`} className="hover:underline">
58+
{receipt.supplier_name}
59+
</Link>
60+
</td>
61+
<td className="px-4 py-3 text-sm text-slate-600">{receipt.supplier_reference ?? '—'}</td>
62+
<td className="px-4 py-3 text-sm text-slate-600">{receipt.receipt_date}</td>
63+
<td className="px-4 py-3 text-sm text-slate-600 capitalize">{receipt.status}</td>
64+
</tr>
65+
))}
66+
</tbody>
67+
</table>
68+
</div>
69+
</div>
70+
</AppLayout>
71+
);
72+
}

0 commit comments

Comments
 (0)