Skip to content

Commit 7759bc3

Browse files
committed
fix: Phase 7 review fixes — bill payment guard + test hardening
- BillController: reject payment recording on non-received bills - BillTest: scope Bill::latest() to tenant, load payments in total test, add staff-cannot-create and draft-payment-rejected tests - ReportTest: add staff-403 tests, fix date range on zero-net test, add total_liabilities assertion to balance sheet equation test - Show.tsx: use replaceAll() for multi-underscore payment methods All 192 tests passing; npm run build clean. https://claude.ai/code/session_01RdUGwo74JXChRCF88Yu27d
1 parent 0e65d5c commit 7759bc3

4 files changed

Lines changed: 50 additions & 5 deletions

File tree

erp/app/Modules/Finance/Http/Controllers/BillController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ public function recordPayment(StorePaymentRequest $request, Bill $bill): Redirec
138138
{
139139
$this->authorize('update', $bill);
140140

141+
if ($bill->status !== 'received') {
142+
return back()->withErrors(['status' => 'Payments can only be recorded on received bills.']);
143+
}
144+
141145
$data = $request->validated();
142146

143147
DB::transaction(function () use ($data, $bill) {

erp/resources/js/Pages/Finance/Bills/Show.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export default function BillShow({ bill }: Props) {
163163
{bill.payments!.map((p) => (
164164
<tr key={p.id}>
165165
<td className="px-4 py-3">{p.payment_date}</td>
166-
<td className="px-4 py-3 capitalize">{p.method.replace('_', ' ')}</td>
166+
<td className="px-4 py-3 capitalize">{p.method.replaceAll('_', ' ')}</td>
167167
<td className="px-4 py-3 text-slate-500">{p.reference ?? '—'}</td>
168168
<td className="px-4 py-3 text-right font-medium">{Number(p.amount).toFixed(2)}</td>
169169
</tr>

erp/tests/Feature/Finance/BillTest.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@
5757
'items' => [
5858
['description' => 'Service', 'quantity' => 1, 'unit_price' => 100, 'tax_rate' => 0],
5959
],
60-
]);
60+
])
61+
->assertRedirect();
6162

62-
$bill = Bill::latest()->first();
63+
$bill = Bill::where('tenant_id', $this->tenant->id)->latest()->first();
6364
expect($bill->number)->toStartWith('BILL-');
6465
});
6566

@@ -86,7 +87,7 @@
8687
'tax_rate' => 10,
8788
]);
8889

89-
$bill->load('items');
90+
$bill->load(['items', 'payments']);
9091

9192
expect($bill->subtotal)->toBe(200.0);
9293
expect($bill->tax_total)->toBe(20.0);
@@ -177,3 +178,30 @@
177178
->delete("/finance/bills/{$bill->id}")
178179
->assertStatus(403);
179180
});
181+
182+
test('staff cannot create a bill', function () {
183+
$this->actingAs($this->staff)
184+
->post('/finance/bills', [
185+
'issue_date' => '2026-01-01',
186+
'items' => [['description' => 'X', 'quantity' => 1, 'unit_price' => 10, 'tax_rate' => 0]],
187+
])
188+
->assertStatus(403);
189+
});
190+
191+
test('payment cannot be recorded on a draft bill', function () {
192+
$bill = Bill::create([
193+
'tenant_id' => $this->tenant->id,
194+
'issue_date' => now()->toDateString(),
195+
]);
196+
197+
// status is 'draft' — payment should be rejected
198+
$this->actingAs($this->admin)
199+
->post("/finance/bills/{$bill->id}/payments", [
200+
'amount' => '50.00',
201+
'payment_date' => now()->toDateString(),
202+
'method' => 'cash',
203+
])
204+
->assertSessionHasErrors('status');
205+
206+
expect(BillPayment::where('bill_id', $bill->id)->exists())->toBeFalse();
207+
});

erp/tests/Feature/Finance/ReportTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
$this->tenant = Tenant::create(['name' => 'Report Co', 'slug' => 'report-co']);
1414
$this->admin = User::factory()->create(['tenant_id' => $this->tenant->id]);
1515
$this->admin->assignRole('super-admin');
16+
$this->staff = User::factory()->create(['tenant_id' => $this->tenant->id]);
17+
$this->staff->assignRole('staff');
1618
});
1719

1820
test('profit and loss report is accessible', function () {
@@ -29,7 +31,7 @@
2931

3032
test('profit and loss net is zero with no posted entries', function () {
3133
$this->actingAs($this->admin)
32-
->get('/finance/reports/profit-loss')
34+
->get('/finance/reports/profit-loss?from=2026-01-01&to=2026-12-31')
3335
->assertInertia(fn ($p) => $p->where('net', 0));
3436
});
3537

@@ -89,6 +91,7 @@
8991
->get('/finance/reports/balance-sheet?as_of=2026-06-01')
9092
->assertInertia(fn ($p) => $p
9193
->where('total_assets', 1000)
94+
->where('total_liabilities', 0)
9295
->where('total_equity', 1000)
9396
);
9497

@@ -99,3 +102,13 @@
99102
$this->get('/finance/reports/profit-loss')
100103
->assertRedirect();
101104
});
105+
106+
test('staff cannot access financial reports', function () {
107+
$this->actingAs($this->staff)
108+
->get('/finance/reports/profit-loss')
109+
->assertStatus(403);
110+
111+
$this->actingAs($this->staff)
112+
->get('/finance/reports/balance-sheet')
113+
->assertStatus(403);
114+
});

0 commit comments

Comments
 (0)