55use App \Http \Controllers \Controller ;
66use App \Modules \Finance \Models \Contact ;
77use App \Modules \Finance \Models \Project ;
8+ use App \Modules \Finance \Models \ProjectTask ;
89use App \Modules \Finance \Models \ProjectTimeEntry ;
910use Illuminate \Http \RedirectResponse ;
1011use Illuminate \Http \Request ;
1314
1415class ProjectController extends Controller
1516{
16- public function index (): Response
17+ public function index (Request $ request ): Response
1718 {
1819 $ this ->authorize ('viewAny ' , Project::class);
1920
20- $ projects = Project::withCount (['timeEntries ' ])
21- ->with ('contact ' )
22- ->orderByDesc ('created_at ' )
23- ->get ()
24- ->map (fn ($ p ) => [
25- 'id ' => $ p ->id ,
26- 'name ' => $ p ->name ,
27- 'description ' => $ p ->description ,
28- 'status ' => $ p ->status ,
29- 'budget ' => $ p ->budget ,
30- 'contact_id ' => $ p ->contact_id ,
31- 'invoice_id ' => $ p ->invoice_id ,
32- 'starts_on ' => $ p ->starts_on ?->toDateString(),
33- 'ends_on ' => $ p ->ends_on ?->toDateString(),
34- 'contact ' => $ p ->contact ? ['id ' => $ p ->contact ->id , 'name ' => $ p ->contact ->name ] : null ,
35- 'time_entries_count ' => $ p ->time_entries_count ,
36- ]);
21+ $ query = Project::with ('contact ' )->orderByDesc ('created_at ' );
22+
23+ if ($ request ->filled ('status ' )) {
24+ $ query ->where ('status ' , $ request ->status );
25+ }
26+
27+ $ projects = $ query ->paginate (15 )->withQueryString ();
3728
3829 return Inertia::render ('Finance/Projects/Index ' , [
3930 'projects ' => $ projects ,
40- 'breadcrumbs ' => [
41- ['label ' => 'Finance ' ],
42- ['label ' => 'Projects ' ],
43- ],
31+ 'filters ' => $ request ->only ('status ' ),
4432 ]);
4533 }
4634
@@ -52,11 +40,6 @@ public function create(): Response
5240
5341 return Inertia::render ('Finance/Projects/Create ' , [
5442 'contacts ' => $ contacts ,
55- 'breadcrumbs ' => [
56- ['label ' => 'Finance ' ],
57- ['label ' => 'Projects ' , 'href ' => '/finance/projects ' ],
58- ['label ' => 'New Project ' ],
59- ],
6043 ]);
6144 }
6245
@@ -65,133 +48,110 @@ public function store(Request $request): RedirectResponse
6548 $ this ->authorize ('create ' , Project::class);
6649
6750 $ validated = $ request ->validate ([
68- 'name ' => ['required ' , 'string ' , 'max:255 ' ],
69- 'description ' => ['nullable ' , 'string ' ],
70- 'status ' => ['required ' , 'in:draft,active,completed,cancelled ' ],
71- 'budget ' => ['nullable ' , 'numeric ' , 'min:0 ' ],
72- 'contact_id ' => ['nullable ' , 'exists:contacts,id ' ],
73- 'invoice_id ' => ['nullable ' , 'exists:invoices,id ' ],
74- 'starts_on ' => ['nullable ' , 'date ' ],
75- 'ends_on ' => ['nullable ' , 'date ' ],
51+ 'name ' => ['required ' , 'string ' , 'max:255 ' ],
52+ 'contact_id ' => ['nullable ' , 'exists:contacts,id ' ],
53+ 'status ' => ['nullable ' , 'in:planning,active,on_hold,completed,cancelled ' ],
54+ 'start_date ' => ['nullable ' , 'date ' ],
55+ 'end_date ' => ['nullable ' , 'date ' ],
56+ 'budget ' => ['nullable ' , 'numeric ' , 'min:0 ' ],
57+ 'billing_type ' => ['required ' , 'in:fixed,hourly,non_billable ' ],
58+ 'hourly_rate ' => ['nullable ' , 'numeric ' , 'min:0 ' ],
59+ 'description ' => ['nullable ' , 'string ' ],
7660 ]);
7761
62+ $ validated ['status ' ] = $ validated ['status ' ] ?? 'planning ' ;
63+
7864 $ project = Project::create (array_merge ($ validated , [
7965 'tenant_id ' => $ request ->user ()->tenant_id ,
8066 ]));
8167
82- return redirect ()->route ('finance.projects.show ' , $ project )
83- ->with ('success ' , 'Project created successfully. ' );
68+ return redirect ()->route ('finance.projects.show ' , $ project );
8469 }
8570
8671 public function show (Project $ project ): Response
8772 {
8873 $ this ->authorize ('view ' , $ project );
8974
90- $ project ->load (['timeEntries.user ' , 'contact ' , 'invoice ' , 'attachments ' ]);
75+ $ project ->load (['tasks.assignedTo ' , 'timeEntries.user ' , 'timeEntries.task ' , 'contact ' ]);
76+
77+ $ projectData = $ project ->toArray ();
78+ $ projectData ['total_hours ' ] = $ project ->total_hours ;
79+ $ projectData ['total_billed ' ] = $ project ->total_billed ;
80+ $ projectData ['completion_percent ' ] = $ project ->completion_percent ;
9181
9282 return Inertia::render ('Finance/Projects/Show ' , [
93- 'project ' => [
94- 'id ' => $ project ->id ,
95- 'name ' => $ project ->name ,
96- 'description ' => $ project ->description ,
97- 'status ' => $ project ->status ,
98- 'budget ' => $ project ->budget ,
99- 'contact_id ' => $ project ->contact_id ,
100- 'invoice_id ' => $ project ->invoice_id ,
101- 'starts_on ' => $ project ->starts_on ?->toDateString(),
102- 'ends_on ' => $ project ->ends_on ?->toDateString(),
103- 'contact ' => $ project ->contact ? ['id ' => $ project ->contact ->id , 'name ' => $ project ->contact ->name ] : null ,
104- 'invoice ' => $ project ->invoice ? ['id ' => $ project ->invoice ->id , 'reference ' => $ project ->invoice ->number ?? '# ' . $ project ->invoice ->id ] : null ,
105- 'total_hours ' => $ project ->total_hours ,
106- 'billable_hours ' => $ project ->billable_hours ,
107- 'attachments ' => $ project ->attachments ->map (fn ($ a ) => [
108- 'id ' => $ a ->id , 'filename ' => $ a ->filename , 'disk ' => $ a ->disk ,
109- 'path ' => $ a ->path , 'mime_type ' => $ a ->mime_type , 'size ' => $ a ->size ,
110- 'uploaded_by ' => $ a ->uploaded_by , 'created_at ' => $ a ->created_at ?->toIso8601String(),
111- ]),
112- 'time_entries ' => $ project ->timeEntries ->map (fn ($ e ) => [
113- 'id ' => $ e ->id ,
114- 'project_id ' => $ e ->project_id ,
115- 'user_id ' => $ e ->user_id ,
116- 'description ' => $ e ->description ,
117- 'hours ' => $ e ->hours ,
118- 'billable ' => $ e ->billable ,
119- 'billed ' => $ e ->billed ,
120- 'entry_date ' => $ e ->entry_date ->toDateString (),
121- 'user ' => $ e ->user ? ['id ' => $ e ->user ->id , 'name ' => $ e ->user ->name ] : null ,
122- ]),
123- ],
124- 'contacts ' => Contact::orderBy ('name ' )->get (['id ' , 'name ' ]),
125- 'breadcrumbs ' => [
126- ['label ' => 'Finance ' ],
127- ['label ' => 'Projects ' , 'href ' => '/finance/projects ' ],
128- ['label ' => $ project ->name ],
129- ],
83+ 'project ' => $ projectData ,
13084 ]);
13185 }
13286
133- public function update ( Request $ request , Project $ project ): RedirectResponse
87+ public function destroy ( Project $ project ): RedirectResponse
13488 {
135- $ this ->authorize ('update ' , $ project );
136-
137- $ validated = $ request ->validate ([
138- 'name ' => ['required ' , 'string ' , 'max:255 ' ],
139- 'description ' => ['nullable ' , 'string ' ],
140- 'status ' => ['required ' , 'in:draft,active,completed,cancelled ' ],
141- 'budget ' => ['nullable ' , 'numeric ' , 'min:0 ' ],
142- 'contact_id ' => ['nullable ' , 'exists:contacts,id ' ],
143- 'invoice_id ' => ['nullable ' , 'exists:invoices,id ' ],
144- 'starts_on ' => ['nullable ' , 'date ' ],
145- 'ends_on ' => ['nullable ' , 'date ' ],
146- ]);
89+ $ this ->authorize ('delete ' , $ project );
14790
148- $ project ->update ( $ validated );
91+ $ project ->delete ( );
14992
150- return redirect ()->route ('finance.projects.show ' , $ project )
151- ->with ('success ' , 'Project updated successfully. ' );
93+ return redirect ()->route ('finance.projects.index ' );
15294 }
15395
154- public function destroy (Project $ project ): RedirectResponse
96+ public function activate (Project $ project ): RedirectResponse
15597 {
156- $ this ->authorize ('delete ' , $ project );
98+ $ this ->authorize ('update ' , $ project );
15799
158- $ project ->delete ();
100+ $ project ->activate ();
159101
160- return redirect ()->route ('finance.projects.index ' )
161- ->with ('success ' , 'Project deleted. ' );
102+ return redirect ()->back ();
162103 }
163104
164- public function storeTimeEntry ( Request $ request , Project $ project ): RedirectResponse
105+ public function complete ( Project $ project ): RedirectResponse
165106 {
166107 $ this ->authorize ('update ' , $ project );
167108
109+ $ project ->complete ();
110+
111+ return redirect ()->back ();
112+ }
113+
114+ public function addTask (Request $ request , Project $ project ): RedirectResponse
115+ {
116+ $ this ->authorize ('create ' , Project::class);
117+
168118 $ validated = $ request ->validate ([
169- 'description ' => ['required ' , 'string ' ],
170- 'hours ' => ['required ' , 'numeric ' , 'min:0.1 ' ],
171- 'billable ' => ['boolean ' ],
172- 'entry_date ' => ['required ' , 'date ' ],
119+ 'title ' => ['required ' , 'string ' , 'max:255 ' ],
120+ 'priority ' => ['nullable ' , 'in:low,medium,high ' ],
121+ 'due_date ' => ['nullable ' , 'date ' ],
122+ 'estimated_hours ' => ['nullable ' , 'numeric ' ],
123+ 'description ' => ['nullable ' , 'string ' ],
124+ 'assigned_to ' => ['nullable ' , 'exists:users,id ' ],
173125 ]);
174126
175- $ project ->timeEntries ()->create (array_merge ($ validated , [
176- 'user_id ' => auth ()->id (),
127+ $ validated ['priority ' ] = $ validated ['priority ' ] ?? 'medium ' ;
128+
129+ ProjectTask::create (array_merge ($ validated , [
130+ 'project_id ' => $ project ->id ,
131+ 'tenant_id ' => $ project ->tenant_id ,
177132 ]));
178133
179- return back ()->with ( ' success ' , ' Time entry logged. ' );
134+ return redirect ()->back ( );
180135 }
181136
182- public function markBilled (Request $ request , Project $ project ): RedirectResponse
137+ public function addTimeEntry (Request $ request , Project $ project ): RedirectResponse
183138 {
184- $ this ->authorize ('update ' , $ project );
139+ $ this ->authorize ('create ' , Project::class );
185140
186141 $ validated = $ request ->validate ([
187- 'entry_ids ' => ['array ' ],
188- 'entry_ids.* ' => ['exists:project_time_entries,id ' ],
142+ 'hours ' => ['required ' , 'numeric ' , 'min:0.01 ' ],
143+ 'entry_date ' => ['required ' , 'date ' ],
144+ 'description ' => ['nullable ' , 'string ' ],
145+ 'task_id ' => ['nullable ' , 'exists:project_tasks,id ' ],
146+ 'is_billable ' => ['boolean ' ],
189147 ]);
190148
191- ProjectTimeEntry::whereIn ('id ' , $ validated ['entry_ids ' ] ?? [])
192- ->where ('project_id ' , $ project ->id )
193- ->update (['billed ' => true ]);
149+ ProjectTimeEntry::create (array_merge ($ validated , [
150+ 'project_id ' => $ project ->id ,
151+ 'tenant_id ' => $ project ->tenant_id ,
152+ 'user_id ' => auth ()->id (),
153+ ]));
194154
195- return back ()->with ( ' success ' , ' Entries marked as billed. ' );
155+ return redirect ()->back ( );
196156 }
197157}
0 commit comments