Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.83% covered (warning)
63.83%
30 / 47
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccountController
63.83% covered (warning)
63.83%
30 / 47
33.33% covered (danger)
33.33%
3 / 9
30.68
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 index
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 create
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 store
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 edit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 update
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 destroy
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 validated
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 selectableBranches
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace App\Http\Controllers\admin;
4
5use App\Http\Controllers\Controller;
6use App\Models\Account;
7use App\Models\Branch;
8use App\Services\Accounting\AccountingService;
9use App\Services\Location\BranchContext;
10use Illuminate\Http\RedirectResponse;
11use Illuminate\Http\Request;
12use Illuminate\View\View;
13
14/**
15 * Settings → Accounting → Accounts. Cash / bank / mobile-wallet accounts —
16 * every income, expense, or transfer transaction belongs to one of these.
17 */
18class AccountController extends Controller
19{
20    public function __construct(
21        private readonly AccountingService $accounting,
22        private readonly BranchContext $branchContext,
23    ) {}
24
25    public function index(): View
26    {
27        $accounts = $this->branchContext->accounts()->orderByDesc('is_active')->orderBy('name')->get();
28        $balances = $accounts->mapWithKeys(fn ($a) => [$a->id => $this->accounting->balance($a)]);
29
30        return view('admin.accounting.accounts.index', compact('accounts', 'balances'));
31    }
32
33    public function create(): View
34    {
35        return view('admin.accounting.accounts.form', [
36            'account' => new Account(['type' => 'cash', 'is_active' => true]),
37            'branches' => $this->selectableBranches(),
38        ]);
39    }
40
41    public function store(Request $request): RedirectResponse
42    {
43        $data = $this->validated($request);
44        $this->branchContext->ensureBranch($data['branch_id'] ?? null);
45
46        if ($data['is_default']) {
47            Account::where('is_default', true)->where('branch_id', $data['branch_id'] ?? null)->update(['is_default' => false]);
48        }
49
50        Account::create($data + ['created_by' => $request->user()->id]);
51
52        return redirect()->route('admin.accounts.index')->with('success', 'Account created.');
53    }
54
55    public function edit(Account $account): View
56    {
57        $this->branchContext->ensureBranch($account->branch_id);
58        $branches = $this->selectableBranches();
59
60        return view('admin.accounting.accounts.form', compact('account', 'branches'));
61    }
62
63    public function update(Request $request, Account $account): RedirectResponse
64    {
65        $data = $this->validated($request);
66        $this->branchContext->ensureBranch($account->branch_id);
67        $this->branchContext->ensureBranch($data['branch_id'] ?? null);
68
69        if ($data['is_default'] && ! $account->is_default) {
70            Account::where('is_default', true)->where('branch_id', $data['branch_id'] ?? null)->update(['is_default' => false]);
71        }
72
73        $account->update($data);
74
75        return redirect()->route('admin.accounts.index')->with('success', 'Account updated.');
76    }
77
78    public function destroy(Account $account): RedirectResponse
79    {
80        $this->branchContext->ensureBranch($account->branch_id);
81        if ($account->transactions()->exists() || $account->incomingTransfers()->exists()) {
82            return back()->with('error', 'This account has transactions posted against it and can\'t be deleted. Deactivate it instead.');
83        }
84
85        $account->delete();
86
87        return redirect()->route('admin.accounts.index')->with('success', 'Account deleted.');
88    }
89
90    private function validated(Request $request): array
91    {
92        $data = $request->validate([
93            'name' => ['required', 'string', 'max:150'],
94            'type' => ['required', 'in:cash,bank,mobile_wallet'],
95            'opening_balance' => ['nullable', 'numeric'],
96            'is_default' => ['nullable', 'boolean'],
97            'is_active' => ['nullable', 'boolean'],
98            'branch_id' => feature('branch_management') ? ['nullable', 'exists:branches,id'] : ['nullable'],
99        ]);
100
101        $data['opening_balance'] = $data['opening_balance'] ?? 0;
102        $data['is_default'] = $request->boolean('is_default');
103        $data['is_active'] = $request->boolean('is_active');
104        $data['branch_id'] = feature('branch_management') ? ($data['branch_id'] ?? null) : null;
105
106        return $data;
107    }
108
109    private function selectableBranches()
110    {
111        if (! feature('branch_management')) {
112            return collect();
113        }
114
115        return Branch::where('status', true)
116            ->when($this->branchContext->id(), fn ($query, $branchId) => $query->whereKey($branchId))
117            ->pluck('name', 'id');
118    }
119}