Files
alla-tms/plans/online-lesson-returned-rejected-permission-logic-plan.md
2026-07-16 09:53:14 +07:00

1113 lines
37 KiB
Markdown

# Prompt: ตรวจสอบและแก้ไข Permission Logic สำหรับบทเรียนออนไลน์สถานะ Returned/Rejected
## บทบาท
ให้คุณทำหน้าที่เป็น Senior Full-Stack Developer, Authorization Reviewer และ Code Auditor ตรวจสอบระบบสิทธิ์และ Workflow ของโมดูลบทเรียนออนไลน์ก่อนดำเนินการแก้ไข
ต้องตรวจสอบ Source Code, Permission Definitions, Route Guards, Action Menu, Form Submission, API Authorization และ Workflow Transition ที่ระบบใช้อยู่จริง
ห้ามคาดเดาชื่อสถานะ Permission Route หรือ Function ขึ้นมาเอง ให้ยืนยันจากโค้ดจริงก่อนทุกครั้ง
---
# ปัญหาปัจจุบัน
ในหน้าบทเรียนออนไลน์ของเจ้าหน้าที่ เมื่อบทเรียนถูกผู้ตรวจสอบส่งกลับมาแก้ไข รายการมีสถานะประเภท:
```text
returned
rejected
revision_required
changes_requested
```
หรือสถานะจริงที่ระบบใช้อยู่
เจ้าหน้าที่ซึ่งเป็นเจ้าของหรือผู้สร้างบทเรียนควรสามารถแก้ไขงานของตนเองและส่งตรวจสอบใหม่ได้ แต่ปัจจุบันระบบกลับตรวจสอบสิทธิ์ด้วย Permission:
```text
online_lesson:revision
```
ทำให้เจ้าหน้าที่ทั่วไปที่ไม่มี Permission ดังกล่าว:
- ไม่เห็นปุ่มแก้ไข
- ไม่สามารถเข้าหน้าแก้ไข
- หรือเปิดหน้าแก้ไขได้แต่ไม่สามารถบันทึก
- ไม่สามารถส่งตรวจสอบใหม่
- ถูกปฏิเสธจาก Frontend หรือ Backend Authorization
จากการวิเคราะห์เบื้องต้น ปัญหาไม่น่าเกิดจากตัว Form โดยตรง แต่เกิดจากการตีความ Permission ผิดหน้าที่
---
# หลักการ Permission ที่ถูกต้อง
ให้แยกความหมายของ Permission อย่างชัดเจนดังนี้
## `online_lesson:revision`
Permission นี้ควรเป็นสิทธิ์ของฝั่งผู้ตรวจสอบหรือผู้อนุมัติ สำหรับการดำเนินการ:
```text
ส่งบทเรียนกลับให้เจ้าหน้าที่แก้ไข
```
ตัวอย่าง Action:
```text
pending_review → returned
```
หรือ Transition จริงของระบบ
Permission นี้ไม่ควรถูกใช้เป็นเงื่อนไขหลักในการอนุญาตให้เจ้าของบทเรียนแก้ไขรายการที่ถูกส่งกลับ
---
## `online_lesson:edit_draft`
Permission นี้ควรเป็นสิทธิ์ของฝั่งผู้สร้างหรือเจ้าหน้าที่ สำหรับการ:
- แก้ไขบทเรียนฉบับร่างของตนเอง
- แก้ไขบทเรียนของตนเองที่ถูกส่งกลับ
- บันทึกการแก้ไข
- ส่งตรวจสอบใหม่
ตัวอย่างสถานะที่ควรแก้ไขได้:
```text
draft
returned
```
หรือสถานะจริงของระบบ เช่น:
```text
draft
revision_required
rejected
```
ต้องตรวจสอบ Workflow จริงก่อนกำหนดรายการสถานะ
---
# เป้าหมาย
ปรับ Authorization Logic ให้เจ้าของหรือผู้สร้างบทเรียนที่ถูกส่งกลับสามารถแก้ไขงานของตนเองได้ด้วย Permission ฝั่งผู้สร้าง เช่น:
```text
online_lesson:edit_draft
```
โดยยังคงให้:
```text
online_lesson:revision
```
เป็น Permission สำหรับผู้ตรวจสอบในการส่งบทเรียนกลับให้แก้ไขเท่านั้น
หลังแก้ไขแล้วต้องได้พฤติกรรมดังนี้:
1. เจ้าหน้าที่ที่มี `online_lesson:edit_draft` สามารถแก้ไขบทเรียนของตนเองในสถานะ Draft
2. เจ้าหน้าที่ที่มี `online_lesson:edit_draft` สามารถแก้ไขบทเรียนของตนเองในสถานะ Returned
3. เจ้าหน้าที่ไม่จำเป็นต้องมี `online_lesson:revision` เพื่อแก้ไขงานที่ถูกส่งกลับ
4. ผู้ตรวจสอบยังต้องมี `online_lesson:revision` เพื่อกดส่งกลับ
5. ผู้ใช้งานต้องไม่สามารถแก้ไขบทเรียนของบุคคลอื่น เว้นแต่มี Permission ระดับจัดการที่ระบบกำหนดไว้
6. บทเรียนสถานะ Pending, Approved, Published หรือสถานะล็อกอื่น ต้องยังไม่สามารถแก้ไขได้ตาม Business Rule เดิม
7. Frontend และ Backend ต้องใช้กฎเดียวกัน
---
# ขอบเขตการตรวจสอบ
ตรวจสอบ Flow ทั้งหมดต่อไปนี้:
```text
Permission Definitions
→ Role / Permission Template
→ Navigation / Action Visibility
→ Action Menu
→ Edit Button
→ Edit Page Guard
→ Form Initialization
→ Update API / Server Action
→ Workflow Service
→ Status Transition
→ Ownership Check
→ Audit Log
→ Resubmission
```
ค้นหาไฟล์หรือ Function ที่เกี่ยวข้อง เช่น:
```text
permissions.ts
permission-constants.ts
online-lesson-permissions.ts
authorization.ts
requirePermission.ts
canEditOnlineLesson.ts
isOnlineLessonEditable.ts
OnlineLessonActionMenu
OnlineLessonCellAction
OnlineLessonEditPage
OnlineLessonForm
updateOnlineLesson
submitOnlineLessonForReview
returnOnlineLessonForRevision
```
ชื่อจริงอาจแตกต่าง ให้ตรวจสอบจาก Repository
---
# Phase 1: ตรวจสอบ Permission Definition
ตรวจสอบว่า Permission ต่อไปนี้ถูกประกาศไว้ที่ใด:
```text
online_lesson:create
online_lesson:edit_draft
online_lesson:submit
online_lesson:review
online_lesson:approve
online_lesson:revision
online_lesson:publish
online_lesson:manage
```
ให้ยืนยันความหมายจริงจาก:
- Permission Seed
- Permission Template
- Permission Description
- Role Mapping
- UI Permission Matrix
- Authorization Helper
- Existing Tests
- Documentation
ตรวจสอบว่า `online_lesson:revision` ถูกใช้ในตำแหน่งใดบ้าง และมีจุดใดนำไปใช้ผิดวัตถุประสงค์ เช่น:
```ts
canEdit =
hasPermission("online_lesson:revision") && lesson.status === "returned";
```
หรือ:
```ts
requirePermission("online_lesson:revision");
```
ภายใน Update API
ให้จัดทำรายการ Usage ทั้งหมดก่อนแก้ไข
---
# Phase 2: ตรวจสอบสถานะ Workflow จริง
ตรวจสอบ Enum หรือ Constant ของสถานะบทเรียนออนไลน์จาก Source of Truth จริง เช่น:
```ts
draft;
pending_review;
returned;
approved;
rejected;
scheduled;
published;
archived;
```
ต้องแยกให้ชัดเจนว่า:
- สถานะใดหมายถึง “ส่งกลับให้แก้ไข”
- สถานะใดหมายถึง “ปฏิเสธถาวร”
- สถานะใดอนุญาตให้ส่งใหม่
- สถานะใดไม่อนุญาตให้แก้ไขอีก
ห้ามเหมารวม `returned` และ `rejected` ว่าแก้ไขได้เหมือนกันโดยอัตโนมัติ
หาก Workflow ปัจจุบันกำหนดว่า:
```text
returned = แก้ไขและส่งใหม่ได้
rejected = ปิดรายการ ไม่สามารถส่งใหม่ได้
```
ให้รองรับเฉพาะ `returned`
หากระบบใช้ `rejected` ในความหมายว่า “ส่งกลับให้แก้ไข” จริง ให้ยึดตาม Business Rule ปัจจุบัน แต่ควรบันทึกข้อสังเกตไว้ในรายงาน
---
# Phase 3: สร้างหรือปรับกฎกลางสำหรับการแก้ไข
ควรมี Authorization Helper กลางสำหรับตรวจสอบสิทธิ์การแก้ไข เช่น:
```ts
canEditOnlineLesson({
actor,
lesson,
});
```
หรือใช้ Function เดิมที่มีอยู่แล้ว
แนวคิดที่ควรได้:
```ts
type CanEditOnlineLessonInput = {
actorUserId: string;
actorEmployeeId?: string;
permissions: string[];
lesson: {
createdBy: string;
status: OnlineLessonStatus;
};
};
export function canEditOnlineLesson(input: CanEditOnlineLessonInput): boolean {
const isOwner = input.lesson.createdBy === input.actorUserId;
const hasEditDraftPermission = input.permissions.includes(
"online_lesson:edit_draft",
);
const isEditableStatus = [
OnlineLessonStatus.DRAFT,
OnlineLessonStatus.RETURNED,
].includes(input.lesson.status);
return isOwner && hasEditDraftPermission && isEditableStatus;
}
```
ตัวอย่างนี้เป็นเพียงแนวคิด ต้องใช้ Field และ Type จริงของระบบ
หากระบบมีสิทธิ์ระดับจัดการ เช่น:
```text
online_lesson:manage
online_lesson:edit_all
```
ให้รองรับตาม Business Rule เดิม เช่น:
```ts
const canManageAll = hasPermission("online_lesson:manage");
```
แต่ห้ามเพิ่มสิทธิ์ใหม่โดยไม่จำเป็น
---
# หลักการ Ownership
ผู้สร้างหรือเจ้าของบทเรียนควรแก้ไขได้เฉพาะบทเรียนของตนเอง เว้นแต่มี Permission ระดับจัดการทั้งหมด
ต้องตรวจสอบ Field ที่ใช้ระบุเจ้าของจริง เช่น:
```ts
createdBy;
created_by;
ownerId;
owner_id;
authorId;
submittedBy;
employeeId;
```
ห้ามตรวจ Ownership ด้วยชื่อหรืออีเมล หากระบบมี ID ที่ชัดเจน
ตัวอย่างแนวคิด:
```ts
const isOwner = lesson.createdByUserId === session.user.id;
```
หรือ:
```ts
const isOwner = lesson.createdByEmployeeId === currentEmployee.id;
```
ให้ใช้ Identifier เดียวกับระบบปัจจุบัน
---
# จุดที่ต้องแก้ไขให้สอดคล้องกัน
## 1. Action Menu และปุ่มแก้ไข
ตรวจสอบเงื่อนไขการแสดงปุ่มแก้ไข
ตัวอย่างปัญหา:
```ts
const canEdit =
lesson.status === "draft" || hasPermission("online_lesson:revision");
```
หรือ:
```ts
const canEditReturned = hasPermission("online_lesson:revision");
```
ให้เปลี่ยนไปใช้ Helper กลาง เช่น:
```ts
const canEdit = canEditOnlineLesson({
actor,
lesson,
});
```
ปุ่มแก้ไขต้องแสดงเมื่อ:
- เป็นเจ้าของบทเรียน
- มี `online_lesson:edit_draft`
- สถานะอยู่ในรายการที่แก้ไขได้
---
## 2. Edit Page Guard
ตรวจสอบ Guard ของหน้าแก้ไข เช่น:
```ts
if (
lesson.status !== "draft" &&
!hasPermission("online_lesson:revision")
) {
redirect(...);
}
```
ให้ปรับเป็นกฎเดียวกับ Action Menu
ตัวอย่าง:
```ts
if (!canEditOnlineLesson({ actor, lesson })) {
forbidden();
}
```
ห้ามให้ Frontend แสดงปุ่มได้ แต่ Route Guard ปฏิเสธ
---
## 3. Update API หรือ Server Action
Backend ต้องตรวจสอบกฎเดียวกันเสมอ
ห้ามอาศัยเฉพาะการซ่อนปุ่มฝั่ง Frontend
ตรวจสอบ Logic เช่น:
```ts
requirePermission("online_lesson:revision");
```
ภายใน Update API สำหรับสถานะ Returned
ให้เปลี่ยนเป็น:
```ts
requirePermission("online_lesson:edit_draft");
```
ร่วมกับ:
```ts
requireOwnership(...)
```
และ Status Check
ตัวอย่างแนวคิด:
```ts
const lesson = await findOnlineLessonById(id);
if (!lesson) {
throw new NotFoundError();
}
const allowed = canEditOnlineLesson({
actor,
lesson,
});
if (!allowed) {
throw new ForbiddenError("คุณไม่มีสิทธิ์แก้ไขบทเรียนนี้");
}
```
---
## 4. Form Initialization
ตรวจสอบว่า Form ไม่ได้ Disable Field ทั้งหมดเมื่อสถานะไม่ใช่ Draft
ตัวอย่างปัญหา:
```ts
const isReadOnly = lesson.status !== "draft";
```
ควรเปลี่ยนเป็น:
```ts
const isReadOnly = !canEdit;
```
หาก Form รับ Prop เช่น:
```tsx
<OnlineLessonForm readOnly={...} />
```
ให้ส่งค่าจาก Authorization Helper กลาง
---
## 5. ปุ่มบันทึกฉบับร่าง
เมื่อบทเรียนสถานะ Returned ถูกแก้ไขแล้วกดบันทึกฉบับร่าง ต้องกำหนด Transition ตาม Workflow เดิม
แนวทางที่แนะนำ:
```text
returned → save draft → returned
```
เหตุผลคือยังเป็นงานที่ถูกส่งกลับและยังไม่ส่งเข้าตรวจสอบใหม่
แต่หากระบบปัจจุบันกำหนดว่า:
```text
returned → save draft → draft
```
ให้ยึด Business Rule ที่ผ่านการอนุมัติแล้ว
ห้ามเปลี่ยน Status โดยไม่ตรวจสอบ Workflow จริง
---
## 6. ปุ่มส่งตรวจสอบใหม่
เมื่อเจ้าหน้าที่แก้ไขเสร็จและส่งตรวจสอบอีกครั้ง ควรเกิด Transition เช่น:
```text
returned → pending_review
```
ต้องใช้ Permission ฝั่งผู้สร้าง เช่น:
```text
online_lesson:submit
```
หรือ Permission จริงของระบบ
ห้ามใช้:
```text
online_lesson:revision
```
สำหรับการส่งตรวจสอบใหม่
---
## 7. Action “ส่งกลับให้แก้ไข”
ฝั่งผู้ตรวจสอบต้องยังใช้ Permission:
```text
online_lesson:revision
```
สำหรับ Transition เช่น:
```text
pending_review → returned
```
ต้องตรวจสอบ:
- ผู้ตรวจสอบมี Permission
- สถานะปัจจุบันถูกต้อง
- มีเหตุผลหรือ Comment ตาม Validation เดิม
- บันทึกผู้ส่งกลับ
- บันทึกวันที่
- Audit Log
- Notification ไปยังเจ้าของบทเรียน
การแก้ครั้งนี้ห้ามลดระดับความปลอดภัยของ Action ฝั่งผู้ตรวจสอบ
---
# Permission Matrix ที่คาดหวัง
ตรวจสอบและปรับให้ได้พฤติกรรมตามแนวคิดนี้ โดยใช้ชื่อ Permission จริงของระบบ
| Action | เจ้าของบทเรียน | ผู้ตรวจสอบ | Permission หลัก |
| ----------------------- | -------------: | ------------: | -------------------------- |
| สร้างบทเรียน | ได้ | ตามสิทธิ์ | `online_lesson:create` |
| แก้ไข Draft ของตนเอง | ได้ | ไม่เกี่ยวข้อง | `online_lesson:edit_draft` |
| แก้ไข Returned ของตนเอง | ได้ | ไม่เกี่ยวข้อง | `online_lesson:edit_draft` |
| ส่งตรวจสอบ | ได้ | ไม่เกี่ยวข้อง | `online_lesson:submit` |
| ตรวจสอบบทเรียน | ไม่ได้ | ได้ | `online_lesson:review` |
| ส่งกลับให้แก้ไข | ไม่ได้ | ได้ | `online_lesson:revision` |
| อนุมัติ | ไม่ได้ | ได้ | `online_lesson:approve` |
| เผยแพร่ | ตาม Workflow | ตามสิทธิ์ | `online_lesson:publish` |
ตารางนี้เป็นแนวทาง ต้องยึด Permission จริงในระบบ
---
# แยก Permission กับ Status Transition
ห้ามตรวจเพียง Permission อย่างเดียว
การอนุญาต Action ต้องประกอบด้วย:
```text
Permission
AND Ownership
AND Current Status
AND Allowed Transition
```
ตัวอย่าง:
```ts
const canEdit =
hasPermission("online_lesson:edit_draft") && isOwner && isEditableStatus;
```
ตัวอย่างการส่งกลับ:
```ts
const canReturnForRevision =
hasPermission("online_lesson:revision") && lesson.status === "pending_review";
```
---
# ตรวจสอบการใช้ Permission ที่ผิดทั้งหมด
ค้นหาทั้ง Repository ด้วยคำ:
```text
online_lesson:revision
```
ตรวจสอบทุกตำแหน่งว่าเป็นการใช้งานสำหรับ:
```text
ผู้ตรวจสอบส่งกลับให้แก้ไข
```
จริงหรือไม่
ตำแหน่งที่อาจผิด:
- ปุ่มแก้ไข Returned
- Edit Page Guard
- Update API
- Form Read-only
- Save Draft Action
- Resubmit Action
- Navigation
- Permission Matrix Description
- Automated Tests
- Seed Data
- UI Description
หากพบว่า Permission Description อธิบายคลุมเครือ เช่น:
```text
แก้ไขบทเรียน
```
ให้ปรับคำอธิบายให้ชัดเจน เช่น:
```text
ส่งบทเรียนกลับให้ผู้สร้างแก้ไข
```
โดยไม่เปลี่ยน Permission Key หากไม่จำเป็น
---
# การจัดการ Rejected
ต้องตรวจสอบให้ชัดเจนว่าสถานะ `rejected` หมายถึงอะไร
## กรณี Rejected คือปฏิเสธถาวร
ห้ามให้เจ้าของแก้ไขหรือส่งใหม่โดยอัตโนมัติ
พฤติกรรม:
```text
rejected → locked
```
อาจต้องสร้างรายการใหม่แทน ตาม Business Rule
## กรณี Rejected ถูกใช้แทน Returned
ให้พิจารณารองรับการแก้ไข แต่ต้องรายงานว่า Naming ของสถานะอาจไม่สอดคล้องกับความหมายทางธุรกิจ
ห้ามเปลี่ยนชื่อ Enum หรือทำ Database Migration ภายในงานนี้ เว้นแต่ได้รับคำสั่งเพิ่มเติม
---
# Cache และ UI Refresh
หลังแก้ไขหรือส่งตรวจสอบใหม่ ต้องอัปเดตหน้ารายการทันที
ตรวจสอบ Data Fetching Pattern จริง เช่น:
- TanStack Query
- SWR
- Server Component
- Server Action
หลัง Mutation สำเร็จ ต้อง Invalidate หรือ Revalidate Query ที่เกี่ยวข้อง เช่น:
```text
online-lessons
online-lesson-detail
online-lesson-counts
review-queue
notifications
```
ห้ามใช้:
```ts
window.location.reload();
```
ห้ามใช้ `setTimeout()` เพื่อแก้ปัญหา Refresh
---
# Error Message
ปรับข้อความ Error ให้ตรงกับกฎจริง
ตัวอย่างข้อความที่ควรหลีกเลี่ยง:
```text
สามารถแก้ไขได้เฉพาะสถานะฉบับร่างเท่านั้น
```
หาก Returned แก้ไขได้แล้ว
ควรเป็น:
```text
สามารถแก้ไขได้เฉพาะบทเรียนฉบับร่างหรือบทเรียนที่ถูกส่งกลับให้แก้ไข
```
หรือใช้ข้อความตามมาตรฐานระบบ
กรณีไม่ใช่เจ้าของ:
```text
คุณไม่มีสิทธิ์แก้ไขบทเรียนนี้
```
กรณีสถานะไม่อนุญาต:
```text
ไม่สามารถแก้ไขบทเรียนในสถานะปัจจุบันได้
```
ไม่ควรเปิดเผยรายละเอียด Permission ภายในเกินจำเป็นต่อผู้ใช้ทั่วไป
---
# Audit Log
ตรวจสอบว่าการแก้ไข Returned และการส่งตรวจสอบใหม่บันทึก Audit Log ตามมาตรฐานเดิม เช่น:
```text
online_lesson.updated
online_lesson.resubmitted
```
ควรบันทึก:
- Lesson ID
- ผู้ดำเนินการ
- สถานะก่อนหน้า
- สถานะหลัง
- วันที่เวลา
- Field ที่แก้ไข หากระบบรองรับ
- Revision Number หากมี
ห้ามลบประวัติการส่งกลับหรือเหตุผลเดิม
---
# Notification
เมื่อเจ้าหน้าที่ส่งบทเรียนที่แก้ไขแล้วเข้าตรวจสอบใหม่ ให้ตรวจสอบ Notification ตาม Workflow เดิม เช่น:
- แจ้งผู้ตรวจสอบ
- แจ้ง HRD Manager
- อัปเดต In-app Notification
- Email หากระบบรองรับ
ห้ามส่ง Notification ซ้ำจากการกดปุ่มหลายครั้ง
---
# Test Cases
## Test Case 1: เจ้าของแก้ไข Draft
1. Login เป็นเจ้าหน้าที่ที่มี `online_lesson:edit_draft`
2. เปิดบทเรียน Draft ที่ตนเองสร้าง
3. ต้องเห็นปุ่มแก้ไข
4. เข้าหน้าแก้ไขได้
5. บันทึกได้สำเร็จ
---
## Test Case 2: เจ้าของแก้ไข Returned
1. ผู้ตรวจสอบส่งบทเรียนกลับ
2. Login เป็นเจ้าของบทเรียน
3. เจ้าของไม่มี `online_lesson:revision`
4. เจ้าของมี `online_lesson:edit_draft`
5. ต้องเห็นปุ่มแก้ไข
6. เข้าหน้าแก้ไขได้
7. บันทึกได้
8. ไม่ได้รับ Forbidden Error
---
## Test Case 3: เจ้าของไม่มี Edit Draft Permission
1. Login เป็นเจ้าของบทเรียน
2. ไม่มี `online_lesson:edit_draft`
3. บทเรียนอยู่สถานะ Returned
4. ต้องไม่เห็นปุ่มแก้ไข
5. เข้าหน้า Edit โดย URL ต้องถูกปฏิเสธ
6. เรียก Update API โดยตรงต้องได้ Forbidden
---
## Test Case 4: ผู้ใช้พยายามแก้บทเรียนคนอื่น
1. Login เป็นเจ้าหน้าที่ A
2. มี `online_lesson:edit_draft`
3. เปิดบทเรียน Returned ของเจ้าหน้าที่ B
4. ต้องไม่เห็นปุ่มแก้ไข
5. Route Guard ต้องปฏิเสธ
6. API ต้องปฏิเสธ
---
## Test Case 5: ผู้ตรวจสอบส่งกลับ
1. Login เป็นผู้ตรวจสอบ
2. มี `online_lesson:revision`
3. เปิดบทเรียน Pending Review
4. สามารถส่งกลับให้แก้ไขได้
5. สถานะเปลี่ยนเป็น Returned
6. เจ้าของได้รับ Notification
---
## Test Case 6: เจ้าหน้าที่ไม่มี Revision แต่แก้ Returned ได้
1. เจ้าหน้าที่มี:
- `online_lesson:edit_draft`
- ไม่มี `online_lesson:revision`
2. บทเรียนเป็นของตนเอง
3. สถานะ Returned
4. ต้องแก้ไขและบันทึกได้
Test Case นี้เป็น Acceptance Test หลักของงาน
---
## Test Case 7: ส่งตรวจสอบใหม่
1. เจ้าของแก้ไข Returned
2. กดส่งตรวจสอบใหม่
3. ใช้ Permission ฝั่ง Submit
4. สถานะเปลี่ยนเป็น Pending Review
5. รายการออกจาก Returned Filter
6. ไปปรากฏใน Pending Review
7. ผู้ตรวจสอบได้รับ Notification
---
## Test Case 8: Pending Review ต้องแก้ไม่ได้
1. เจ้าหน้าที่เป็นเจ้าของบทเรียน
2. มี `online_lesson:edit_draft`
3. สถานะ Pending Review
4. ต้องไม่เห็นปุ่มแก้ไข
5. Route และ API ต้องปฏิเสธ
---
## Test Case 9: Approved/Published ต้องแก้ไม่ได้
ทดสอบสถานะ:
```text
approved
published
scheduled
archived
```
หรือสถานะจริงของระบบ
เจ้าของที่มี `online_lesson:edit_draft` ต้องไม่สามารถแก้ไขได้ เว้นแต่มี Business Rule หรือ Permission ระดับจัดการที่รองรับอยู่แล้ว
---
## Test Case 10: Rejected
ตรวจสอบตามความหมายจริง:
- หาก Rejected ถาวร ต้องแก้ไม่ได้
- หาก Rejected หมายถึงส่งกลับ ต้องแก้ได้ตามกฎเดียวกับ Returned
ให้รายงานผลชัดเจน
---
## Test Case 11: Direct API Access
ทดสอบเรียก API โดยไม่ผ่าน UI:
- เจ้าของ + Edit Draft + Returned → สำเร็จ
- เจ้าของ + ไม่มี Edit Draft → Forbidden
- ไม่ใช่เจ้าของ + Edit Draft → Forbidden
- Revision อย่างเดียวแต่ไม่ใช่ผู้สร้าง → ต้องไม่สามารถใช้ Update API ของเจ้าของได้ เว้นแต่มี Manage Permission
---
## Test Case 12: Permission Regression
ตรวจสอบว่า:
- ผู้ตรวจสอบยังส่งกลับได้
- เจ้าหน้าที่ยังส่งตรวจสอบได้
- ผู้อนุมัติยังอนุมัติได้
- ผู้เผยแพร่ยังเผยแพร่ได้
- ไม่มี Permission ใดได้รับสิทธิ์เกินเดิม
---
# Automated Tests
หาก Repository มี Test Framework ให้เพิ่มหรือปรับปรุง Test ตาม Pattern เดิม
อย่างน้อยต้องครอบคลุม Authorization Matrix:
```text
owner + edit_draft + draft = allow
owner + edit_draft + returned = allow
owner + edit_draft + pending = deny
owner + revision + returned แต่ไม่มี edit_draft = deny edit
reviewer + revision + pending = allow return
non-owner + edit_draft + returned = deny
manage permission + supported status = follow existing rule
```
เพิ่ม Test สำหรับ:
- Action visibility
- Route Guard
- Update API
- Resubmit Action
- Workflow Transition
- Permission Description หากมี Snapshot หรือ Seed Test
---
# Validation ก่อนส่งงาน
รันคำสั่งจริงจาก `package.json` เช่น:
```bash
pnpm typecheck
pnpm lint
pnpm test
pnpm build
```
ห้ามแก้ Type Error ด้วย:
```ts
any
@ts-ignore
@ts-expect-error
eslint-disable
```
เว้นแต่มีเหตุผลจำเป็นและอธิบายในรายงาน
---
# Acceptance Criteria
งานถือว่าเสร็จเมื่อ:
- เจ้าของบทเรียน Returned สามารถแก้ไขได้ด้วย `online_lesson:edit_draft`
- เจ้าของไม่จำเป็นต้องมี `online_lesson:revision`
- `online_lesson:revision` ใช้สำหรับ Action ส่งกลับของผู้ตรวจสอบ
- Action Menu แสดงปุ่มถูกต้อง
- Edit Page Guard ใช้กฎเดียวกัน
- Form ไม่ถูก Read-only ผิดพลาด
- Update API ใช้กฎเดียวกัน
- Ownership ถูกตรวจสอบ
- Draft และ Returned แก้ไขได้ตามกฎ
- Pending, Approved และ Published ยังถูกล็อก
- Rejected ทำงานตาม Business Rule จริง
- สามารถบันทึกฉบับร่างหลังถูกส่งกลับ
- สามารถส่งตรวจสอบใหม่
- Cache และหน้ารายการอัปเดตทันที
- Audit Log ถูกต้อง
- Notification ไม่ซ้ำ
- Permission ฝั่งผู้ตรวจสอบไม่ถูกลดระดับ
- ไม่มีการเปิดให้แก้ไขบทเรียนของคนอื่น
- Type Check ผ่าน
- Lint ผ่าน
- Test ผ่าน
- Build ผ่าน
---
# รูปแบบรายงานผลหลังแก้ไข
หลังดำเนินการเสร็จ ให้รายงานตามหัวข้อต่อไปนี้
## 1. สาเหตุของปัญหา
ระบุจุดที่ใช้ `online_lesson:revision` ผิดหน้าที่ พร้อม Path และ Function ที่เกี่ยวข้อง
## 2. ความหมาย Permission หลังตรวจสอบ
สรุปว่าแต่ละ Permission ใช้ทำอะไร โดยเฉพาะ:
```text
online_lesson:edit_draft
online_lesson:revision
online_lesson:submit
```
## 3. สถานะที่แก้ไขได้
ระบุสถานะจริง เช่น:
```text
draft
returned
```
และอธิบาย Rejected ว่ามีความหมายอย่างไร
## 4. Ownership Rule
ระบุ Field ที่ใช้ตรวจสอบเจ้าของบทเรียน
## 5. ไฟล์ที่แก้ไข
ระบุ Path และหน้าที่ของแต่ละไฟล์
## 6. Authorization Helper
อธิบาย Helper กลางที่สร้างหรือปรับปรุง และตำแหน่งที่นำไปใช้
## 7. Frontend Changes
อธิบาย:
- Action Menu
- Edit Button
- Form Read-only
- Error Message
- Cache Refresh
## 8. Backend Changes
อธิบาย:
- API Authorization
- Status Check
- Ownership Check
- Workflow Transition
## 9. Permission Matrix
สรุปผลลัพธ์ของแต่ละ Role หรือ Permission Combination
## 10. ผลการทดสอบ
รายงาน:
- Manual Test
- Authorization Test
- Automated Test
- Type Check
- Lint
- Build
## 11. Regression Check
ยืนยันว่า:
- ผู้ตรวจสอบยังส่งกลับได้
- เจ้าหน้าที่แก้เฉพาะงานตนเอง
- Pending และ Published ยังล็อก
- Permission อื่นไม่เปลี่ยน
- หน้า List และ Action Menu อัปเดตถูกต้อง
## 12. ประเด็นคงค้าง
หากพบว่า Naming ของ `rejected` หรือ Permission Description ยังคลุมเครือ ให้รายงานแยก ห้ามทำ Migration หรือเปลี่ยน Permission Key โดยไม่ได้รับคำสั่ง
---
# ข้อกำหนดสุดท้าย
- ตรวจสอบโค้ดจริงก่อนแก้ไข
- แยกสิทธิ์ “แก้ไขงานของผู้สร้าง” ออกจากสิทธิ์ “ส่งกลับของผู้ตรวจสอบ”
- ใช้ `online_lesson:edit_draft` สำหรับเจ้าของบทเรียน Draft/Returned
- ใช้ `online_lesson:revision` สำหรับผู้ตรวจสอบส่งกลับ
- ต้องตรวจ Ownership ทุกครั้ง
- Frontend และ Backend ต้องใช้กฎเดียวกัน
- ห้ามแก้เฉพาะการซ่อนหรือแสดงปุ่ม
- ห้ามอาศัย Frontend Authorization เพียงอย่างเดียว
- ห้ามให้ `revision` กลายเป็นสิทธิ์แก้ไขบทเรียนของบุคคลอื่น
- ห้ามเพิ่ม Permission ใหม่หากของเดิมรองรับอยู่แล้ว
- ห้ามเปลี่ยน Database Schema หรือ Permission Key โดยไม่จำเป็น
- ห้ามใช้ `window.location.reload()` เพื่อแก้ UI
- ต้องมี Test ครอบคลุม Permission Matrix
- ต้องยืนยันว่าเจ้าหน้าที่ที่ไม่มี `online_lesson:revision` แต่มี `online_lesson:edit_draft` สามารถแก้ไขบทเรียน Returned ของตนเองได้จริง