diff --git a/app/Http/Controllers/ProjectClosureController.php b/app/Http/Controllers/ProjectClosureController.php index a87c3f9..a7ab53f 100644 --- a/app/Http/Controllers/ProjectClosureController.php +++ b/app/Http/Controllers/ProjectClosureController.php @@ -17,7 +17,8 @@ class ProjectClosureController extends Controller { public function closureForm(Project $project): Response { $this->authorize('update-project', $project); - abort_if($project->hasSubmittedClosure(), 403, 'Closure already submitted'); + abort_unless(in_array($project->getClosureStatus(), [ProjectClosureStatus::NOT_SUBMITTED, ProjectClosureStatus::REJECTED_AND_RESUBMIT]) + , 403, 'Closure already submitted'); $project->load(['department', 'participants', 'participants.user']); $project->participants->transform(function (ProjectParticipant $participant) { @@ -42,7 +43,9 @@ public function closureSubmit(Request $request, Project $project) { 'action' => 'nullable|string', ]); $this->authorize('update-project', $project); - abort_if($project->hasSubmittedClosure(), 403, 'Closure already submitted.'); + abort_if($project->hasSubmittedClosure() + and $project->getClosureStatus() !== ProjectClosureStatus::REJECTED_AND_RESUBMIT, + 403, 'Closure already submitted.'); $action = $request->input('action'); $project->objectives = $request->input('objectives'); @@ -187,7 +190,8 @@ public function approvalSubmit(Request $request, Project $project) { 'approve_participants' => 'nullable|required_if:approve,yes|array', ]); $closureStatus = $project->getClosureStatus(); - abort_unless(in_array($closureStatus, [ProjectClosureStatus::SUBMITTED, ProjectClosureStatus::REVIEWING]), 403, + abort_unless(in_array($closureStatus, + [ProjectClosureStatus::SUBMITTED, ProjectClosureStatus::REVIEWING, ProjectClosureStatus::REJECTED_AND_RESUBMIT]), 403, 'Closure approved or hasn\'t been submitted.'); $project->closure_approved_by = $request->user()->id; @@ -198,6 +202,9 @@ public function approvalSubmit(Request $request, Project $project) { $project->closure_approved_message = null; $project->participants()->whereIn('user_id', $request->input('approve_participants'))->update(['approve_status' => 1]); $project->participants()->whereNotIn('user_id', $request->input('approve_participants'))->update(['approve_status' => -1]); + } elseif ($request->input('allow_resubmit', false)) { + $project->closure_approved_status = -2; + $project->closure_approved_message = $request->input('reason'); } else { $project->closure_approved_status = -1; $project->closure_approved_message = $request->input('reason'); diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 9ea9f47..c7b0219 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -7,6 +7,7 @@ use App\Models\Project; use App\Models\ProjectParticipant; use App\Models\User; +use App\ProjectClosureStatus; use Carbon\Carbon; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Encryption\DecryptException; @@ -137,13 +138,18 @@ public function show(Request $request, Project $project) { return $participant; }); + $project->closure_status = $project->getClosureStatus(); $project->shouldBeClosed = ( $canUpdateProject and $project->created_at->isBetween(now()->subYear(), now()->subWeeks(1)) and $project->documents->isNotEmpty() and ($project->documents->where('tag', 'summary')->isEmpty() or (!$project->hasSubmittedClosure() and $project->canSubmitClosure())) ); - $project->shouldVerify = $project->canVerify() and $project->participants->where('user_id', Auth::id())->isNotEmpty(); + $project->shouldVerify = ( + $project->canVerify() + and $project->participants->where('user_id', Auth::id())->isNotEmpty() + and ($project->closure_status != ProjectClosureStatus::REJECTED_AND_RESUBMIT) + ); // and !in_array($project->department_id, [32, 38, 39]); @@ -160,6 +166,12 @@ public function edit(Request $request, Project $project): Response { $project->organizers = $project->participants()->with('user')->where('type', 'organizer')->get()->map(function (ProjectParticipant $p) { return ['name' => $p->user->name, 'student_id' => $p->user->student_id]; }); + $project->closure_status = $project->getClosureStatus(); + + // Disable editing if closure is submitted, unless for faculty users + abort_if(!in_array($project->closure_status, + [ProjectClosureStatus::NOT_SUBMITTED, ProjectClosureStatus::REJECTED_AND_RESUBMIT]) and $request->user()->cannot('faculty-action'), + 403, 'Cannot edit after submitted closure.'); return Inertia::render('ProjectCreate', [ 'item' => $project->castDateAsDateString(), @@ -199,7 +211,8 @@ public function update(Request $request, Project $project) { $this->authorize('update-project', $project); // Disable editing if closure is submitted, unless for faculty users - abort_if($project->hasSubmittedClosure() and $request->user()->cannot('faculty-action'), + abort_if(!in_array($project->getClosureStatus(), + [ProjectClosureStatus::NOT_SUBMITTED, ProjectClosureStatus::REJECTED_AND_RESUBMIT]) and $request->user()->cannot('faculty-action'), 403, 'Cannot edit after submitted closure.'); $project->fill($request->all()); diff --git a/app/Models/Project.php b/app/Models/Project.php index 39bf64b..986d951 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -61,6 +61,8 @@ class Project extends Model { 'period_end' => 'date:j M Y', 'objectives' => 'array', 'expense' => 'array', + 'closure_submitted_at' => 'datetime', + 'closure_approved_at' => 'datetime', ]; protected $hidden = ['user_id']; @@ -182,7 +184,9 @@ public static function advisorList(): \Illuminate\Support\Collection * Is the project's period end within the summary time limit? */ public function canSubmitClosure(): bool { - return ($this->period_end->diffInDays() <= self::SUMMARY_TIME_LIMIT) or ($this->year == 2567 and now()->isBefore('2024-09-30')); + return ($this->period_end->diffInDays() <= self::SUMMARY_TIME_LIMIT) + or ($this->year == 2567 and now()->isBefore('2024-09-30')) + or ($this->closure_approved_status == -2 and $this->closure_approved_at->diffInDays() <= self::SUMMARY_TIME_LIMIT); } public function hasSubmittedClosure(): bool { @@ -201,6 +205,10 @@ public function getClosureStatus(): ProjectClosureStatus { return ProjectClosureStatus::APPROVED; case -1: return ProjectClosureStatus::REJECTED; + case -2: + if ($this->closure_approved_at?->isAfter($this->closure_submitted_at)) { + return ProjectClosureStatus::REJECTED_AND_RESUBMIT; + } } // Count participants who have verified $organizers = $this->participants->where('type', 'organizer'); @@ -218,7 +226,11 @@ public function getClosureStatus(): ProjectClosureStatus { } public function canVerify(): bool { - return in_array($this->getClosureStatus(), [ProjectClosureStatus::SUBMITTED, ProjectClosureStatus::REVIEWING]) - and ($this->period_end->diffInDays() <= self::VERIFICATION_TIME_LIMIT or ($this->year == 2567 and now()->isBefore('2024-10-31'))); + return in_array($this->getClosureStatus(), + [ProjectClosureStatus::SUBMITTED, ProjectClosureStatus::REVIEWING, ProjectClosureStatus::REJECTED_AND_RESUBMIT]) + and (($this->period_end->diffInDays() <= self::VERIFICATION_TIME_LIMIT) + or ($this->year == 2567 and now()->isBefore('2024-10-31')) + or ($this->closure_approved_status == -2 and $this->closure_approved_at->diffInDays() <= self::SUMMARY_TIME_LIMIT) + ); } } diff --git a/app/ProjectClosureStatus.php b/app/ProjectClosureStatus.php index 7672f3f..33ad017 100644 --- a/app/ProjectClosureStatus.php +++ b/app/ProjectClosureStatus.php @@ -8,4 +8,5 @@ enum ProjectClosureStatus: int { case REVIEWING = 5; case APPROVED = 10; case REJECTED = -1; + case REJECTED_AND_RESUBMIT = -2; // Rejected but allow resubmission within the time limit } diff --git a/resources/js/Components/ClosureStatusText.vue b/resources/js/Components/ClosureStatusText.vue new file mode 100644 index 0000000..9f8e42e --- /dev/null +++ b/resources/js/Components/ClosureStatusText.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/Pages/ProjectApproval.vue b/resources/js/Pages/ProjectApproval.vue index cda6a79..ecbc0b6 100644 --- a/resources/js/Pages/ProjectApproval.vue +++ b/resources/js/Pages/ProjectApproval.vue @@ -18,27 +18,6 @@ โครงการที่จะบันทึกเป็นส่วนหนึ่งของ Activity Transcript ต้องผ่านการรับรองรายชื่อนิสิตผู้เกี่ยวข้อง โดยนิสิตผู้รับผิดชอบและผู้ปฏิบัติงานทุกคนภายใน 60 วัน นับจากสิ้นสุดกิจกรรม

-
-
เงื่อนไขจำนวนนิสิตผู้เกี่ยวข้อง เพื่อบันทึกใน Activity Transcript (Student Profile)
- -
@@ -51,7 +30,7 @@ - รายละเอียดทั้งหมด + รายละเอียดโครงการ @@ -70,18 +49,7 @@
- - {{ - { - 1: 'ส่งปิดโครงแล้ว', - 5: 'นิสิตผู้เกี่ยวข้องยืนยันแล้ว รอพิจารณา', - 10: 'อนุมัติ', - '-1': 'ไม่อนุมัติ' - }[item.closure_status] ?? item.closure_status - }} - - +
@@ -291,6 +259,18 @@
+
+
+ +
+
+ +

ระบบจะขยายกรอบเวลาในการส่งใหม่ออกไปอีก 30 วัน

+
+
@@ -300,6 +280,22 @@
+
+
+

+ ไม่อนุมัติรายงานผลโครงการ +

+
+
+
+

+ +

+ เหตุผล  + {{ item.closure_approved_message }} +
+
+

ดูประวัติ

@@ -309,7 +305,7 @@