Skip to content

Commit

Permalink
feat: allow rejecting closure with resubmission
Browse files Browse the repository at this point in the history
  • Loading branch information
keenthekeen committed Sep 6, 2024
1 parent 6093f0f commit a0ec328
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 51 deletions.
13 changes: 10 additions & 3 deletions app/Http/Controllers/ProjectClosureController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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');
Expand Down Expand Up @@ -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;
Expand All @@ -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');
Expand Down
17 changes: 15 additions & 2 deletions app/Http/Controllers/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]);

Expand All @@ -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(),
Expand Down Expand Up @@ -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());
Expand Down
18 changes: 15 additions & 3 deletions app/Models/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down Expand Up @@ -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 {
Expand All @@ -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');
Expand All @@ -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)
);
}
}
1 change: 1 addition & 0 deletions app/ProjectClosureStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
24 changes: 24 additions & 0 deletions resources/js/Components/ClosureStatusText.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import {CheckCircleIcon} from "@heroicons/vue/20/solid";
defineProps({
closure_status: Number,
});
</script>

<template>
<span
:class="{'text-blue-600': closure_status === 5, 'text-green-600': closure_status === 10,'text-gray-600': closure_status <= -1,'text-yellow-600': closure_status === 1}">
{{
{
0: 'ยังไม่รายงานผล',
1: 'ส่งปิดโครงแล้ว',
5: 'นิสิตผู้เกี่ยวข้องยืนยันแล้ว รอพิจารณา',
10: 'อนุมัติ',
'-1': 'ไม่อนุมัติ',
'-2': 'ไม่อนุมัติ ให้แก้ไข',
}[closure_status] ?? closure_status
}}
<CheckCircleIcon v-if="closure_status === 10" class="inline-block h-4 w-4"/>
</span>
</template>
68 changes: 33 additions & 35 deletions resources/js/Pages/ProjectApproval.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,6 @@
โครงการที่จะบันทึกเป็นส่วนหนึ่งของ Activity Transcript ต้องผ่านการรับรองรายชื่อนิสิตผู้เกี่ยวข้อง
โดยนิสิตผู้รับผิดชอบและผู้ปฏิบัติงานทุกคน<b>ภายใน 60 วัน นับจากสิ้นสุดกิจกรรม</b>
</p>
<div class="mt-4 text-gray-500">
<h6 class="font-semibold">เงื่อนไขจำนวนนิสิตผู้เกี่ยวข้อง เพื่อบันทึกใน Activity Transcript (Student Profile)</h6>
<ul class="mt-1 space-y-1 text-sm text-gray-500 list-inside list-disc">
<li>บทบาทของนิสิตผู้เกี่ยวข้อง ประกอบด้วย ผู้รับผิดชอบ ผู้ปฏิบัติงาน และผู้เข้าร่วม
ตามสัดส่วนความรับผิดชอบในการดำเนินงาน
</li>
<li>ผู้รับผิดชอบ พึงมีจำนวนไม่เกินร้อยละ 20 ของจำนวนผู้ปฏิบัติงาน ยกเว้นโครงการที่ไม่มีนิสิตเป็นผู้ปฏิบัติงาน
</li>
<li>ผู้ปฏิบัติงาน พึงมีจำนวนไม่เกิน 2 ใน 3 ของผู้มีส่วนร่วมในกิจกรรมทั้งหมด ทั้งผู้รับผิดชอบ ผู้ปฏิบัติงาน
และผู้เข้าร่วม
ทั้งนิสิตและบุคคลภายนอก
</li>
<li>ผู้เข้าร่วม ต้องลงชื่อเข้าร่วมกิจกรรมทุกวัน ทุกครึ่งวัน หรือตามความเหมาะสมต่อลักษณะกิจกรรม
โดยมีหลักฐานว่าเข้าร่วมกิจกรรมไม่น้อยกว่า 2 ใน 3 ของระยะเวลากิจกรรมทั้งหมด
ให้ผู้รับผิดชอบโครงการเก็บรักษาหลักฐานดังกล่าวไว้
</li>
<li>กิจกรรมที่มีความจำเป็นต้องแบ่งบทบาทของนิสิตในสัดส่วนที่ไม่เป็นไปตามเงื่อนไขข้างต้น
ให้ปรึกษาผู้ช่วย/รองคณบดีฝ่ายกิจการนิสิตพิจารณาอนุญาตเป็นรายกรณี
</li>
</ul>
</div>
</template>
<div class="max-w-7xl mx-auto pt-4 pb-10 sm:px-6 lg:px-8">
<div class="bg-white shadow overflow-hidden sm:rounded-lg my-4">
Expand All @@ -51,7 +30,7 @@
<PencilIcon class="inline-block h-4 w-4"/>
</a>
<a :href="route('projects.show', {project: item.id})" target="_blank" class="ml-4 text-orange-500 hover:text-orange-700">
รายละเอียดทั้งหมด
รายละเอียดโครงการ
</a>
<a v-if="item.summary_document" class="ml-4 text-yellow-500 hover:text-yellow-700"
:href="route('documents.show', {document: item.summary_document.id})" target="_blank">
Expand All @@ -70,18 +49,7 @@
</div>
<div class="col-span-2 space-y-2">
<jet-label value="สถานะ"/>
<span
:class="{'text-blue-600': item.closure_status === 5, 'text-green-600': item.closure_status === 10,'text-gray-600': item.closure_status === 10,'text-yellow-600': item.closure_status === 1}">
{{
{
1: 'ส่งปิดโครงแล้ว',
5: 'นิสิตผู้เกี่ยวข้องยืนยันแล้ว รอพิจารณา',
10: 'อนุมัติ',
'-1': 'ไม่อนุมัติ'
}[item.closure_status] ?? item.closure_status
}}
<CheckCircleIcon v-if="item.closure_status === 10" class="inline-block h-4 w-4"/>
</span>
<ClosureStatusText class="font-bold" :closure_status="item.closure_status"/>
</div>
<div class="col-span-4 space-y-2">
<jet-label value="หน่วยงาน (สพจ.)"/>
Expand Down Expand Up @@ -291,6 +259,18 @@
<Input id="reason" type="text" class="mt-1 block w-full" v-model.trim="form.reason" ref="reason" required/>
<InputError :message="form.errors.reason" class="mt-2"/>
</div>
<div class="flex items-start col-span-6">
<div class="flex items-center h-5">
<Checkbox id="allow_resubmit" :checked="form.allow_resubmit"
@update:checked="newValue => form.allow_resubmit = newValue"/>
</div>
<div class="ml-3 text-sm">
<label for="allow_resubmit" class="font-medium text-gray-700">
ให้ไปแก้ไขและส่งใหม่
</label>
<p class="text-gray-400">ระบบจะขยายกรอบเวลาในการส่งใหม่ออกไปอีก 30 วัน</p>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-end px-4 py-3 bg-gray-50 text-right sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md">
Expand All @@ -300,6 +280,22 @@
</div>
</div>
</div>
<div v-if="item.closure_status <= -1" class="bg-white shadow overflow-hidden sm:rounded-lg my-4">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
ไม่อนุมัติรายงานผลโครงการ
</h3>
</div>
<div class="border-t border-gray-200">
<div class="px-4 py-4 sm:px-6">
<p class="font-bold text-lg">
<ClosureStatusText :closure_status="item.closure_status"/>
</p>
<span class="text-gray-600 underline">เหตุผล</span>&ensp;
{{ item.closure_approved_message }}
</div>
</div>
</div>
<p class="mt-4 px-2 text-xs">
<a class="text-blue-500 cursor-pointer" @click="showLogDialog = true">ดูประวัติ</a>
</p>
Expand All @@ -309,7 +305,7 @@
</template>

<script setup>
import {CheckCircleIcon, CheckIcon, PencilIcon, XMarkIcon} from "@heroicons/vue/20/solid";
import {CheckIcon, PencilIcon, XMarkIcon} from "@heroicons/vue/20/solid";
import AppLayout from '@/Layouts/AppLayout.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import InputError from '@/Jetstream/InputError.vue'
Expand All @@ -324,6 +320,7 @@ import Input from "@/Jetstream/Input.vue";
import Button from "@/Jetstream/Button.vue";
import ProjectClosureStatus from "@/Components/ProjectClosureStatus.vue";
import ClosureLogDialog from "@/Components/ClosureLogDialog.vue";
import ClosureStatusText from "@/Components/ClosureStatusText.vue";

const props = defineProps({
item: Object,
Expand All @@ -334,6 +331,7 @@ const form = useForm({
approve: '', // "yes" or "no"
reason: '',
approve_participants: [],
allow_resubmit: false,
});
const selectedParticipants = ref(props.item.participants.map(e => e.id));
const showLogDialog = ref(false);
Expand Down
12 changes: 10 additions & 2 deletions resources/js/Pages/ProjectApprovalIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,16 @@
{{ item.participants_count }}
</td>
<td class="px-2 py-2 md:px-4 text-gray-600 text-xs"
:class="{'bg-blue-300': item.status === 5, 'bg-green-300': item.status === 10,'bg-gray-300': item.status === 10,'bg-yellow-300': item.status === 1}">
{{ {1: 'ส่งปิดโครง', 5: 'ผู้เกี่ยวข้องยืนยัน', 10: 'อนุมัติ', '-1': 'ไม่อนุมัติ'}[item.status] ?? item.status }}
:class="{'bg-blue-300': item.status === 5, 'bg-green-300': item.status === 10,'bg-gray-300': item.status <= -1,'bg-yellow-300': item.status === 1}">
{{
{
1: 'ส่งปิดโครง',
5: 'ผู้เกี่ยวข้องยืนยัน',
10: 'อนุมัติ',
'-1': 'ไม่อนุมัติ',
'-2': 'ไม่อนุมัติ ให้แก้ไข'
}[item.status] ?? item.status
}}
</td>
</tr>
</tbody>
Expand Down
Loading

0 comments on commit a0ec328

Please sign in to comment.