88use Illuminate \Http \RedirectResponse ;
99use Illuminate \Http \Request ;
1010use Illuminate \Support \Facades \DB ;
11+ use Illuminate \Validation \Rule ;
1112use Inertia \Inertia ;
1213use Inertia \Response ;
1314
@@ -18,16 +19,18 @@ public function index(Request $request): Response
1819 $ this ->authorize ('viewAny ' , Budget::class);
1920
2021 $ budgets = Budget::withCount ('lines ' )
21- ->orderByDesc ('year ' )
22+ ->orderByDesc ('fiscal_year ' )
2223 ->orderByDesc ('id ' )
23- ->get ()
24- ->map (fn ($ b ) => [
25- 'id ' => $ b ->id ,
26- 'name ' => $ b ->name ,
27- 'year ' => $ b ->year ,
28- 'period_type ' => $ b ->period_type ,
29- 'status ' => $ b ->status ,
30- 'lines_count ' => $ b ->lines_count ,
24+ ->paginate (15 )
25+ ->through (fn ($ b ) => [
26+ 'id ' => $ b ->id ,
27+ 'name ' => $ b ->name ,
28+ 'fiscal_year ' => $ b ->fiscal_year ?? $ b ->year ,
29+ 'year ' => $ b ->year ,
30+ 'period_type ' => $ b ->period_type ,
31+ 'status ' => $ b ->status ,
32+ 'lines_count ' => $ b ->lines_count ,
33+ 'total_budgeted ' => null ,
3134 ]);
3235
3336 return Inertia::render ('Finance/Budgets/Index ' , [
@@ -62,22 +65,37 @@ public function store(Request $request): RedirectResponse
6265 {
6366 $ this ->authorize ('create ' , Budget::class);
6467
68+ $ tenantId = app ('tenant ' )->id ;
69+ $ fiscalYear = $ request ->input ('fiscal_year ' ) ?? $ request ->input ('year ' );
70+
6571 $ validated = $ request ->validate ([
66- 'name ' => 'required|string|max:191 ' ,
67- 'year ' => 'required|integer|min:2000|max:2100 ' ,
68- 'period_type ' => 'required|in:annual,monthly,quarterly ' ,
69- 'notes ' => 'nullable|string ' ,
70- 'lines ' => 'required|array|min:1 ' ,
71- 'lines.*.account_id ' => 'required|exists:accounts,id ' ,
72- 'lines.*.period ' => 'required|integer|min:0|max:12 ' ,
73- 'lines.*.amount ' => 'required|numeric|min:0 ' ,
72+ 'name ' => [
73+ 'required ' ,
74+ 'string ' ,
75+ 'max:255 ' ,
76+ Rule::unique ('budgets ' )->where (fn ($ q ) => $ q
77+ ->where ('fiscal_year ' , $ fiscalYear )
78+ ->where ('tenant_id ' , $ tenantId )
79+ ->whereNull ('deleted_at ' )
80+ ),
81+ ],
82+ 'fiscal_year ' => ['required ' , 'integer ' , 'min:2000 ' , 'max:2100 ' ],
83+ 'period_type ' => ['required ' , Rule::in (['annual ' , 'quarterly ' , 'monthly ' ])],
84+ 'notes ' => ['nullable ' , 'string ' ],
85+ 'lines ' => ['required ' , 'array ' , 'min:1 ' ],
86+ 'lines.*.account_id ' => ['required ' , Rule::exists ('accounts ' , 'id ' )],
87+ 'lines.*.period ' => ['required ' , 'integer ' , 'min:0 ' , 'max:12 ' ],
88+ 'lines.*.amount ' => ['required ' , 'numeric ' , 'min:0 ' ],
7489 ]);
7590
76- DB ::transaction (function () use ($ validated , $ request ) {
91+ $ fy = $ validated ['fiscal_year ' ];
92+
93+ $ budget = DB ::transaction (function () use ($ validated , $ request , $ tenantId , $ fy ) {
7794 $ budget = Budget::create ([
78- 'tenant_id ' => $ request -> user ()-> tenant_id ,
95+ 'tenant_id ' => $ tenantId ,
7996 'name ' => $ validated ['name ' ],
80- 'year ' => $ validated ['year ' ],
97+ 'fiscal_year ' => $ fy ,
98+ 'year ' => $ fy ,
8199 'period_type ' => $ validated ['period_type ' ],
82100 'notes ' => $ validated ['notes ' ] ?? null ,
83101 'status ' => 'draft ' ,
@@ -86,15 +104,18 @@ public function store(Request $request): RedirectResponse
86104
87105 foreach ($ validated ['lines ' ] as $ line ) {
88106 $ budget ->lines ()->create ([
107+ 'tenant_id ' => $ tenantId ,
89108 'account_id ' => $ line ['account_id ' ],
90109 'period ' => $ line ['period ' ],
91110 'amount ' => $ line ['amount ' ],
92111 'notes ' => $ line ['notes ' ] ?? null ,
93112 ]);
94113 }
114+
115+ return $ budget ;
95116 });
96117
97- return redirect ()->route ('finance.budgets.index ' )
118+ return redirect ()->route ('finance.budgets.show ' , $ budget )
98119 ->with ('success ' , 'Budget created successfully. ' );
99120 }
100121
@@ -104,9 +125,9 @@ public function show(Budget $budget): Response
104125 $ budget ->load (['lines.account ' ]);
105126
106127 $ tenantId = request ()->user ()->tenant_id ;
107- $ year = $ budget ->year ;
128+ $ year = $ budget ->fiscal_year ?? $ budget -> year ;
108129
109- // Compute actuals from posted journal entries for this year
130+ // Compute actuals from posted journal entries for this fiscal year
110131 $ actuals = DB ::table ('journal_lines ' )
111132 ->join ('journal_entries ' , 'journal_lines.journal_entry_id ' , '= ' , 'journal_entries.id ' )
112133 ->join ('accounts ' , 'journal_lines.account_id ' , '= ' , 'accounts.id ' )
@@ -129,10 +150,10 @@ public function show(Budget $budget): Response
129150 $ actualAmount = 0 ;
130151 if ($ actual ) {
131152 $ actualAmount = $ actual ->type === 'income '
132- ? (float )$ actual ->total_credit - (float )$ actual ->total_debit
133- : (float )$ actual ->total_debit - (float )$ actual ->total_credit ;
153+ ? (float ) $ actual ->total_credit - (float ) $ actual ->total_debit
154+ : (float ) $ actual ->total_debit - (float ) $ actual ->total_credit ;
134155 }
135- $ variance = $ actualAmount - (float )$ line ->amount ;
156+ $ variance = $ actualAmount - (float ) $ line ->amount ;
136157 $ variancePct = $ line ->amount != 0 ? round ($ variance / $ line ->amount * 100 , 1 ) : null ;
137158
138159 return [
@@ -142,7 +163,7 @@ public function show(Budget $budget): Response
142163 'account_name ' => $ line ->account ->name ,
143164 'account_type ' => $ line ->account ->type ,
144165 'period ' => $ line ->period ,
145- 'budget ' => round ((float )$ line ->amount , 2 ),
166+ 'budget ' => round ((float ) $ line ->amount , 2 ),
146167 'actual ' => round ($ actualAmount , 2 ),
147168 'variance ' => round ($ variance , 2 ),
148169 'variance_pct ' => $ variancePct ,
@@ -153,6 +174,7 @@ public function show(Budget $budget): Response
153174 'budget ' => [
154175 'id ' => $ budget ->id ,
155176 'name ' => $ budget ->name ,
177+ 'fiscal_year ' => $ budget ->fiscal_year ?? $ budget ->year ,
156178 'year ' => $ budget ->year ,
157179 'period_type ' => $ budget ->period_type ,
158180 'status ' => $ budget ->status ,
@@ -179,4 +201,22 @@ public function destroy(Budget $budget): RedirectResponse
179201 return redirect ()->route ('finance.budgets.index ' )
180202 ->with ('success ' , 'Budget deleted. ' );
181203 }
204+
205+ public function activate (Budget $ budget ): RedirectResponse
206+ {
207+ $ this ->authorize ('update ' , $ budget );
208+
209+ $ budget ->activate ();
210+
211+ return redirect ()->back ()->with ('success ' , 'Budget activated. ' );
212+ }
213+
214+ public function close (Budget $ budget ): RedirectResponse
215+ {
216+ $ this ->authorize ('update ' , $ budget );
217+
218+ $ budget ->close ();
219+
220+ return redirect ()->back ()->with ('success ' , 'Budget closed. ' );
221+ }
182222}
0 commit comments