diff --git a/Modules/Project/Entities/ProjectStages.php b/Modules/Project/Entities/ProjectStages.php new file mode 100644 index 0000000000..c7a18c4c28 --- /dev/null +++ b/Modules/Project/Entities/ProjectStages.php @@ -0,0 +1,17 @@ +belongsTo(Project::class, 'project_id', 'id'); + } +} diff --git a/Modules/Project/Entities/ProjectStagesListing.php b/Modules/Project/Entities/ProjectStagesListing.php new file mode 100644 index 0000000000..5136c81199 --- /dev/null +++ b/Modules/Project/Entities/ProjectStagesListing.php @@ -0,0 +1,14 @@ +authorizeResource(Project::class); $this->service = $service; + $this->projectService = $projectService; } /** @@ -109,6 +112,7 @@ public function show(Request $request, Project $project) 'effortData' => $effortData, 'totalEffort' => json_encode($totalEffort), 'dailyEffort' => $dailyEffort, + 'stages' => $this->projectService->getProjectStages($project), ]); } @@ -197,6 +201,47 @@ public function projectResource() 'jobName' => $jobName, ]); } + + public function manageStage(Request $request) + { + $validatedData = $request->validate([ + 'project_id' => 'required|integer|exists:projects,id', + 'newStages' => 'nullable|array', + 'newStages.*.stage_name' => 'required|string', + 'newStages.*.comments' => 'nullable|string', + 'newStages.*.status' => 'nullable|string', + 'newStages.*.expected_end_date' => 'required|date', + 'deletedStages' => 'nullable|array', + 'deletedStages.*' => 'required|integer|exists:project_old_stages,id', + 'updatedStages' => 'nullable|array', + 'updatedStages.*.id' => 'required|integer|exists:project_old_stages,id', + ]); + + if (empty($validatedData['deletedStages']) && empty($validatedData['newStages']) && empty($validatedData['updatedStages'])) { + return response()->json([ + 'status' => 400, + 'error' => 'No New Changes Detected', + ], 400); + } + + if (! empty($validatedData['deletedStages'])) { + $this->projectService->removeStage($validatedData['deletedStages']); + } + + if (! empty($validatedData['newStages'])) { + $this->projectService->storeStage($validatedData['newStages'], $validatedData['project_id']); + } + + if (! empty($validatedData['updatedStages'])) { + $this->projectService->updateStage($validatedData['updatedStages']); + } + + return response()->json([ + 'status' => 200, + 'success' => 'Stages managed successfully!', + ]); + } + // storing Contract related data here private function getContractData(Project $project) { diff --git a/Modules/Project/Resources/views/layouts/master.blade.php b/Modules/Project/Resources/views/layouts/master.blade.php index ed4b265e74..eaa1a8fb1d 100644 --- a/Modules/Project/Resources/views/layouts/master.blade.php +++ b/Modules/Project/Resources/views/layouts/master.blade.php @@ -4,4 +4,5 @@ @endsection @section('css_scripts') + @endsection diff --git a/Modules/Project/Resources/views/show.blade.php b/Modules/Project/Resources/views/show.blade.php index d54c4da754..95c7737eac 100644 --- a/Modules/Project/Resources/views/show.blade.php +++ b/Modules/Project/Resources/views/show.blade.php @@ -294,7 +294,7 @@
@can('finance_reports.view') -
+

Contract History

@@ -320,7 +320,199 @@

-
@endcan + +
+ @include('project::subviews.project-stages') +
+
+
@endsection +@section('vue_scripts') + +@endsection \ No newline at end of file diff --git a/Modules/Project/Resources/views/subviews/project-stages.blade.php b/Modules/Project/Resources/views/subviews/project-stages.blade.php new file mode 100644 index 0000000000..b2ed7455a9 --- /dev/null +++ b/Modules/Project/Resources/views/subviews/project-stages.blade.php @@ -0,0 +1,85 @@ +
+
+

Project Stages

+ +
+
+
+
+ @csrf +
+ + + + + + + + + + + + + + + + +
@{{ index + 1 }} +

Stage Name

+ +

@{{ stage.stage_name }}

+
+

Start Date

+ +

@{{ formatDisplayDate(stage.start_date) }}

+
+

Expected End Date

+ +

@{{ formatDisplayDate(stage.expected_end_date) }}

+
+

Actual End Date

+ +

@{{ formatDisplayDate(stage.end_date) }}

+
+

Status

+ +
+

Comments

+ +

+
+

Action

+ + +
+ Duration: + +
+
+
+ +
+
+
+
+ + +
+
+
+
+ Loading... +
+
+
+
+
+
+
+
+
diff --git a/Modules/Project/Routes/web.php b/Modules/Project/Routes/web.php index 79c151a686..131dc14a95 100644 --- a/Modules/Project/Routes/web.php +++ b/Modules/Project/Routes/web.php @@ -22,5 +22,6 @@ Route::delete('client/{project}/edit', 'ProjectController@destroy')->name('project.destroy'); Route::post('/project-fte-export', 'ProjectController@projectFTEExport')->name('project.fte.export'); Route::get('/project-resource', 'ProjectController@projectResource')->name('project.resource-requirement'); + Route::post('/projects/manage-stage', 'ProjectController@manageStage')->name('projects.manage-stage'); //Route::get('/', 'ProjectController@edit')->name('project.edit'); }); diff --git a/Modules/Project/Services/ProjectService.php b/Modules/Project/Services/ProjectService.php index e6b7850287..98a137f197 100644 --- a/Modules/Project/Services/ProjectService.php +++ b/Modules/Project/Services/ProjectService.php @@ -17,6 +17,8 @@ use Modules\Project\Entities\ProjectMeta; use Modules\Project\Entities\ProjectRepository; use Modules\Project\Entities\ProjectResourceRequirement; +use Modules\Project\Entities\ProjectStages; +use Modules\Project\Entities\ProjectStagesListing; use Modules\Project\Entities\ProjectTeamMember; use Modules\Project\Entities\ProjectTeamMembersEffort; use Modules\Project\Exports\ProjectFTEExport; @@ -381,6 +383,84 @@ public function getProjectApprovedPipelineHour($project) ]; } + public function getProjectStages(Project $project) + { + $project_id = $project->id; + $stages = ProjectStages::where('project_id', $project_id)->orderBy('id')->get(); + + return $stages; + } + + public function storeStage(array $newStages, int $projectId) + { + foreach ($newStages as $stage) { + $stageData = $this->prepareStageData($stage); + + ProjectStages::create(array_merge($stageData, [ + 'project_id' => $projectId, + 'created_at' => now(), + 'updated_at' => now(), + ])); + } + } + + public function updateStage(array $updatedStages) + { + foreach ($updatedStages as $stage) { + $stageData = $this->prepareStageData($stage); + + $existingStage = ProjectStages::find($stage['id']); + if ($existingStage) { + $existingStage->update(array_merge($stageData, [ + 'updated_at' => now(), + ])); + } + } + } + + public function removeStage(array $idArr) + { + ProjectStages::whereIn('id', $idArr)->delete(); + } + + public function createProjectStageList(string $stageName) + { + $formattedStageName = strtolower($stageName); + ProjectStagesListing::firstOrCreate( + ['name' => $formattedStageName] + ); + } + + private function prepareStageData(array $stage): array + { + $startDate = null; + $endDate = null; + $duration = null; + + if ($stage['start_date']) { + $formattedStartDate = Carbon::parse($stage['start_date']); + $startDate = $formattedStartDate->setTimezone(config('app.timezone'))->format(config('constants.datetime_format')); + } + + if ($stage['end_date']) { + $formattedEndDate = Carbon::parse($stage['end_date']); + $endDate = $formattedEndDate->setTimezone(config('app.timezone'))->format(config('constants.datetime_format')); + $duration = Carbon::parse($stage['start_date'])->diffInSeconds($formattedEndDate); + } + + $this->createProjectStageList($stage['stage_name']); + + return [ + 'stage_name' => $stage['stage_name'], + 'comments' => $stage['comments'] ?? null, + 'status' => $stage['status'] ?? 'pending', + 'start_date' => $startDate, + 'end_date' => $endDate, + 'expected_end_date' => $stage['expected_end_date'], + 'duration' => $duration, + ]; + } + private function getListTabCounts($filters, $showAllProjects, $userId) { $counts = [ diff --git a/database/migrations/2024_06_15_142418_update_project_old_stage_table.php b/database/migrations/2024_06_15_142418_update_project_old_stage_table.php new file mode 100644 index 0000000000..ab01c5bac3 --- /dev/null +++ b/database/migrations/2024_06_15_142418_update_project_old_stage_table.php @@ -0,0 +1,60 @@ +dropForeign(['project_id']); + + $table->dropColumn(['project_id', 'name', 'cost', 'currency_cost', 'type', 'cost_include_gst']); + $table->text('comments')->nullable(); + $table->datetime('start_date')->nullable()->change(); + $table->datetime('end_date')->nullable()->change(); + $table->date('expected_end_date')->nullable()->after('end_date'); + }); + Schema::table('project_old_stages', function (Blueprint $table) { + $table->unsignedBigInteger('project_id')->nullable()->after('id'); + $table->text('stage_name')->after('project_id'); + $table->text('status')->after('stage_name')->nullable(); + $table->Integer('duration')->after('status')->nullable(); + $table->foreign('project_id')->references('id')->on('projects'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('project_old_stages', function (Blueprint $table) { + $table->dropColumn('comments'); + $table->dropColumn('stage_name'); + $table->dropColumn('status'); + $table->dropColumn('project_id'); + $table->dropColumn('duration'); + $table->dropColumn('expected_end_date'); + }); + Schema::table('project_old_stages', function (Blueprint $table) { + $table->string('name')->nullable(); + $table->decimal('cost', 8, 2)->nullable(); + $table->string('currency_cost')->nullable(); + $table->string('type')->nullable(); + $table->string('cost_include_gst')->nullable(); + $table->date('start_date')->nullable()->change(); + $table->date('end_date')->nullable()->change(); + $table->unsignedInteger('project_id'); + }); + } +} diff --git a/database/migrations/2024_06_24_181032_create_project_stages_new_listing_table.php b/database/migrations/2024_06_24_181032_create_project_stages_new_listing_table.php new file mode 100644 index 0000000000..0c80f5fcc9 --- /dev/null +++ b/database/migrations/2024_06_24_181032_create_project_stages_new_listing_table.php @@ -0,0 +1,32 @@ +increments('id'); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('project_stages_listing'); + } +}