From 42fc9fa502dedf6f6a821d9bfbb10c706a163a89 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 24 Mar 2026 14:39:09 +0700 Subject: [PATCH] 260324:1439 Refactor RFA :correct ci-deploy #03 --- .gitea/workflows/ci-deploy.yml | 8 +- .../app/(dashboard)/rfas/[uuid]/edit/page.tsx | 2 +- frontend/components/rfas/form.tsx | 2 +- specs/01-requirements/01-04-user-stories.md | 52 +++--- .../01-05-acceptance-criteria.md | 85 ++++++--- .../01-06-edge-cases-and-rules.md | 108 ++++++++++-- specs/01-requirements/01-07-ui-wireframes.md | 164 ++++++++++++++++-- 7 files changed, 340 insertions(+), 81 deletions(-) diff --git a/.gitea/workflows/ci-deploy.yml b/.gitea/workflows/ci-deploy.yml index dbd03f6..8d3d77c 100644 --- a/.gitea/workflows/ci-deploy.yml +++ b/.gitea/workflows/ci-deploy.yml @@ -16,15 +16,15 @@ jobs: - name: 📥 Checkout uses: actions/checkout@v4 - - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 - - - name: 🟢 Setup Node + - name: � Setup Node uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" + - name: � Install pnpm + uses: pnpm/action-setup@v4 + - name: 📦 Install deps run: pnpm install --frozen-lockfile diff --git a/frontend/app/(dashboard)/rfas/[uuid]/edit/page.tsx b/frontend/app/(dashboard)/rfas/[uuid]/edit/page.tsx index 1359506..f16bf8c 100644 --- a/frontend/app/(dashboard)/rfas/[uuid]/edit/page.tsx +++ b/frontend/app/(dashboard)/rfas/[uuid]/edit/page.tsx @@ -34,7 +34,7 @@ export default function RFAEditPage() { const updateMutation = useUpdateRFA(); const currentRevision = - rfa?.revisions?.find((r) => r.isCurrent) ?? rfa?.revisions?.[0]; + rfa?.revisions?.find((r: { isCurrent: boolean }) => r.isCurrent) ?? rfa?.revisions?.[0]; const form = useForm({ resolver: zodResolver(editRfaSchema), diff --git a/frontend/components/rfas/form.tsx b/frontend/components/rfas/form.tsx index f5735eb..eddd082 100644 --- a/frontend/components/rfas/form.tsx +++ b/frontend/components/rfas/form.tsx @@ -24,7 +24,7 @@ import { correspondenceService } from '@/lib/services/correspondence.service'; const rfaSchema = z.object({ projectId: z.string().min(1, 'Project is required'), // ADR-019: UUID contractId: z.string().min(1, 'Contract is required'), - disciplineId: z.number({ required_error: 'Discipline is required' }).min(1, 'Discipline is required'), + disciplineId: z.number({ message: 'Discipline is required' }).min(1, 'Discipline is required'), rfaTypeId: z.number().min(1, 'Type is required'), subject: z.string().min(5, 'Subject must be at least 5 characters'), description: z.string().optional(), diff --git a/specs/01-requirements/01-04-user-stories.md b/specs/01-requirements/01-04-user-stories.md index 1c8f6f9..6cb5892 100644 --- a/specs/01-requirements/01-04-user-stories.md +++ b/specs/01-requirements/01-04-user-stories.md @@ -1,4 +1,4 @@ -# 📖 User Stories — LCBP3-DMS v1.8.0 +# 📖 User Stories — LCBP3-DMS v1.8.1 --- @@ -187,6 +187,7 @@ So that ดำเนินการได้ทันที (สร้าง Cir - List แสดง Received/Sent แยก | PDF Viewer ในแอป (Streaming) - ดาวน์โหลดไฟล์แนบได้ (ถ้ามีสิทธิ์) +- อ้างอิงเอกสารด้วย UUID (ADR-019) --- @@ -202,7 +203,7 @@ So that จัดกลุ่มเอกสารและ Navigate ข้า **Done When:** -- ค้นหาและเลือก Reference Documents | Link ระหว่างเอกสาร (คลิก Navigate) +- ค้นหาและเลือก Reference Documents | Link ระหว่างเอกสาร (คลิก Navigate ด้วย UUID) - กำหนด Tag หลาย Tag | ค้นหาได้จาก Tag --- @@ -314,8 +315,11 @@ So that ส่งเอกสารเป็นชุด ที่ปรึก **Done When:** - Transmittal → เลือก RFA หลายฉบับ | ออกเลขเองเป็น Correspondence +- สถานะ → FAP (For Approval) | Workflow Instance ถูกสร้าง +- ผู้รับ → Notification | สถานะ SUBMITTED +- สร้าง Workflow Instance อัตโนมัติ +- อ้างอิงเอกสารด้วย UUID (ADR-019) - RFA แต่ละฉบับ → ออกเลขตาม Format `LCBP3-RFA-{DISCIPLINE}-{SEQ}` -- ที่ปรึกษา → Notification --- @@ -331,9 +335,10 @@ So that Contractor ได้รับผลพิจารณาและดำ **Done When:** -- PDF Viewer Streaming | ปุ่ม: Approved / Approved w/Comments / Rejected -- Comment บังคับสำหรับ Approved w/Comments และ Rejected +- PDF Viewer Streaming | ปุ่ม: Approved (1A) / Approved w/Comments (1B) / Rejected (4X) +- Comment บังคับสำหรับ 1B และ 4X - Originator → Notification | Workflow History บันทึกครบ +- อ้างอิงเอกสารด้วย UUID (ADR-019) --- @@ -408,6 +413,7 @@ So that รู้ทันทีว่าเอกสารอยู่ขั้ - Workflow Diagram แสดง State ปัจจุบัน (Highlight) - คลิก Step ที่ผ่านมา → Audit Log ย่อย (ใคร/เมื่อไหร่/Comment) - Step ที่ยังไม่ถึง → Disabled Style +- อ้างอิงเอกสารด้วย UUID (ADR-019) --- @@ -425,7 +431,8 @@ So that Workflow เดินหน้าหรือส่งกลับตา - เห็นปุ่ม Action เฉพาะ Step ที่เป็นของฉัน - Wrong Role → ปุ่มซ่อน / 403 ถ้าเรียก API ตรงๆ -- ทุก Action → Workflow History + Timestamp +- ทุก Action → Workflow History + Timestamp + Audit Log +- อ้างอิงเอกสารด้วย UUID (ADR-019) --- @@ -481,6 +488,7 @@ So that งานถูก Assign ชัดเจนและมีหลัก - กำหนด Main / Action / Information Assignees (หลายคน) - Assignees → In-App + Email Notification - Internal Only (ไม่เห็นข้ามองค์กร) +- อ้างอิงเอกสารด้วย UUID (ADR-019) --- @@ -499,6 +507,7 @@ So that ไม่พลาดงาน และ Track สถานะได้ - Dashboard My Tasks แสดง Deadline + Overdue Badge - ปิด Circulation → สถานะ CLOSED + Timestamp - My Tasks ลบรายการที่ปิดแล้ว +- อ้างอิงเอกสารด้วย UUID (ADR-019) --- @@ -534,7 +543,7 @@ So that ไม่พลาดงานโดยไม่ต้องเช็ค **Done When:** - Email ถูกส่งภายใน 5 นาที หลัง Event (BullMQ Queue) -- Bell Icon Unread Count | คลิก → Navigate ไปเอกสาร +- Bell Icon Unread Count | คลิก → Navigate ไปเอกสาร (ใช้ UUID) - Retry 3 ครั้ง ถ้าส่งไม่ได้ → Dead Letter Queue --- @@ -579,24 +588,25 @@ So that รู้สถานะงานโดยไม่ต้องไล่ ## 📊 Story Map Summary -| Epic | Stories | 🔴 Must | 🟠 Should | 🟡 Could | -| ---------------- | ---------- | ------- | --------- | -------- | -| Auth & Users | US-001~006 | 4 | 1 | 1 | -| Correspondence | US-007~011 | 2 | 2 | 1 | -| RFA | US-012~015 | 2 | 2 | 0 | -| Drawing | US-016~017 | 0 | 2 | 0 | -| Workflow | US-018~021 | 1 | 1 | 2 | -| Circulation | US-022~023 | 0 | 2 | 0 | -| Search & Notify | US-024~025 | 0 | 2 | 0 | -| File & Dashboard | US-026~027 | 0 | 2 | 0 | -| **รวม** | **27** | **9** | **14** | **4** | +| Epic | Stories | 🔴 Must | 🟠 Should | 🟡 Could | +| ---------------- | --------------- | ------- | --------- | -------- | +| Auth & Users | US-001~006 | 4 | 1 | 1 | +| Correspondence | US-007~011 | 2 | 2 | 1 | +| RFA | US-012~015, 012a~012c | 4 | 3 | 0 | +| Drawing | US-016~017 | 0 | 2 | 0 | +| Workflow | US-018~021 | 1 | 1 | 2 | +| Circulation | US-022~023 | 0 | 2 | 0 | +| Search & Notify | US-024~025 | 0 | 2 | 0 | +| File & Dashboard | US-026~027 | 0 | 2 | 0 | +| **รวม** | **30** | **11** | **15** | **4** | -> **MVP Sprint Focus:** US-001~006, US-007~008, US-012~014, US-019 — ครอบคลุม Core Happy Path ทั้งหมด +> **MVP Sprint Focus:** US-001~006, US-007~008, US-012~015, US-012a~012c, US-019 — ครอบคลุม Core Happy Path ทั้งหมด --- ## 📝 Document Control -- **Version:** 1.0.0 | **Status:** DRAFT -- **Created:** 2026-03-11 | **Owner:** Nattanin Peancharoen +- **Version:** 1.8.1 | **Status:** Updated +- **Created:** 2026-03-11 | **Updated:** 2026-03-24 | **Owner:** Nattanin Peancharoen +- **Changes:** Added US-012a~012c (Edit/Cancel/Search RFA), Updated Epic 3 story count, Added RBAC filtering details - **Classification:** Internal Use Only diff --git a/specs/01-requirements/01-05-acceptance-criteria.md b/specs/01-requirements/01-05-acceptance-criteria.md index 510eb7e..81416ee 100644 --- a/specs/01-requirements/01-05-acceptance-criteria.md +++ b/specs/01-requirements/01-05-acceptance-criteria.md @@ -1,16 +1,17 @@ -# ✅ Acceptance Criteria — LCBP3-DMS MVP (UAT) +# ✅ Acceptance Criteria — LCBP3-DMS MVP (UAT) v1.8.1 --- title: 'Acceptance Criteria & UAT Test Scenarios' -version: 1.8.0 -status: DRAFT — Pending Stakeholder Sign-off +version: 1.8.1 +status: updated owner: Nattanin Peancharoen (Product Owner) -last_updated: 2026-03-11 +last_updated: 2026-03-24 related: - specs/01-Requirements/01-01-objectives.md -- specs/01-Requirements/01-03-modules/ +- specs/01-Requirements/01-04-user-stories.md +- specs/01-Requirements/01-06-edge-cases-and-rules.md - specs/01-Requirements/01-02-business-rules/01-02-01-rbac-matrix.md - specs/01-Requirements/01-02-business-rules/01-02-04-non-functional-rules.md - specs/06-Decision-Records/ADR-001-unified-workflow-engine.md @@ -245,7 +246,7 @@ related: | **When** | กด Submit | | **Then** | ✅ เลขที่ RFA ถูกออกอัตโนมัติตาม Format (`LCBP3-RFA-STR-0001`) | | | ✅ เลขไม่ reset ตามปี (RFA = No reset policy) | -| | ✅ สถานะ = `SUBMITTED` | +| | ✅ สถานะ = `SUBMITTED` → สร้าง Transmittal อัตโนมัติ | | | ✅ Workflow Routing เริ่มทำงาน (ส่งไปยัง Reviewer) | ### AC-RFA-003 — Review RFA (Approved) @@ -256,7 +257,7 @@ related: | --------- | ---------------------------------------------------- | | **Given** | RFA อยู่ในสถานะรอ Review ที่องค์กรเรา | | **When** | Engineer คลิก "Approved" | -| **Then** | ✅ สถานะ RFA เปลี่ยนเป็น `APPROVED` | +| **Then** | ✅ สถานะ RFA เปลี่ยนเป็น `APPROVED` (1A) | | | ✅ Originator (Contractor) ได้รับแจ้งเตือน | | | ✅ Workflow History บันทึก Action + Timestamp + User | @@ -268,7 +269,7 @@ related: | --------- | ----------------------------------------------------- | | **Given** | RFA อยู่ในสถานะรอ Review | | **When** | Engineer คลิก "Approved with Comments" + กรอก Comment | -| **Then** | ✅ สถานะ = `APPROVED_WITH_COMMENTS` | +| **Then** | ✅ สถานะ = `APPROVED_WITH_COMMENTS` (1B) | | | ✅ Comment ถูกบันทึกใน Workflow History | | | ✅ Originator เห็น Comment ได้ | @@ -280,7 +281,7 @@ related: | --------- | --------------------------------------------- | | **Given** | RFA อยู่ในสถานะรอ Review | | **When** | Engineer คลิก "Rejected" + ระบุเหตุผล | -| **Then** | ✅ สถานะ = `REJECTED` | +| **Then** | ✅ สถานะ = `REJECTED` (4X) | | | ✅ เหตุผลการ Reject บันทึกครบถ้วน | | | ✅ Contractor สามารถยื่น RFA Revision ใหม่ได้ | @@ -296,6 +297,45 @@ related: | | ✅ เลขที่เอกสารเดิม — Revision Code เปลี่ยน (เช่น `-B`) | | | ✅ Rev.A ยังอ่านได้ (ไม่ถูกลบ) | +### AC-RFA-007 — Edit Draft RFA + +**Priority:** 🔴 Blocker | **Role:** Document Control (Contractor) + +| | Description | +| --------- | ---------------------------------------------- | +| **Given** | RFA อยู่ในสถานะ DRAFT (EC-RFA-001 enforced) | +| **When** | Document Control แก้ไข Subject/Body/Remarks | +| **Then** | ✅ RFA ถูกอัปเดตในสถานะ DRAFT | +| | ✅ หากสถานะไม่ใช่ DFT → 403 Forbidden | +| | ✅ Audit Log บันทึก UPDATE + user + timestamp | +| | ✅ 1 Shop Drawing Revision = 1 RFA เท่านั้น | + +### AC-RFA-008 — Cancel Draft RFA + +**Priority:** 🟠 Critical | **Role:** Document Control (Contractor) + +| | Description | +| --------- | ---------------------------------------------- | +| **Given** | RFA อยู่ในสถานะ DRAFT | +| **When** | กด Cancel | +| **Then** | ✅ สถานะเปลี่ยนเป็น CANCELLED | +| | ✅ Shop Drawing Revision ถูกปลดผูก (available) | +| | ✅ Audit Log บันทึก CANCELLED + reason | +| | ✅ ไม่สามารถกด Cancel ได้ถ้าสถานะไม่ใช่ DFT | + +### AC-RFA-009 — Search/Filter RFA + +**Priority:** 🔴 Blocker | **Role:** Document Control/Engineer + +| | Description | +| --------- | ---------------------------------------------------- | +| **Given** | มี RFA ในระบบหลายฉบับ | +| **When** | Filter ด้วย Project/Status หรือค้นหา Keyword | +| **Then** | ✅ ผลลัพธ์ถูกต้องตาม Filter | +| | ✅ DFT → เห็นเฉพาะ originator org (RBAC) | +| | ✅ Pagination ทำงาน (20 items/page) | +| | ✅ ค้นหาจากเลขเอกสาร/Subject ได้ | + --- ## 📐 Module 5: Drawing Management @@ -642,10 +682,10 @@ related: | | Description | | --------- | --------------------------------------------------------------------------- | -| **Given** | ดำเนินการ Action ต่างๆ ในระบบ (Create, Update, Submit, Approve) | +| **Given** | ดำเนินการ Action ต่างๆ ในระบบ (Create, Update, Submit, Approve, Cancel) | | **When** | ตรวจสอบ Audit Log | -| **Then** | ✅ ทุก Action มีบันทึกใน `audit_logs` | -| | ✅ บันทึกมี: user_id, action, entity_type, entity_id, ip_address, timestamp | +| **Then** | ✅ ทุก Action มีบันทึกใน `audit_logs` (severity INFO/WARN/ERROR/CRITICAL) | +| | ✅ บันทึกมี: user_id, action, entity_type, entity_uuid, details_json, ip_address, timestamp | | | ✅ ไม่สามารถแก้ไขหรือลบ Audit Log ได้ (Read-only) | --- @@ -678,10 +718,10 @@ related: **Priority:** 🔴 Blocker | **Role:** QA (Security Test) -| | Description | -| --------- | --------------------------------------------------------- | -| **Given** | User A รู้ Document ID ของ User B | -| **When** | User A เรียก `GET /correspondences/:id` ของ User B โดยตรง | +| | Description | +| --------- | -------------------------------------------------------------- | +| **Given** | User A รู้ Document UUID ของ User B (ADR-019) | +| **When** | User A เรียก `GET /correspondences/:uuid` ของ User B โดยตรง | | **Then** | ✅ ได้รับ 403 Forbidden (ไม่ใช่ 404) | | | ✅ ข้อมูลของ User B ไม่ถูกเปิดเผย | @@ -724,7 +764,7 @@ related: | | ✅ P90 Search Query < 500ms | | | ✅ File Upload 50MB < 30 seconds | -> **Test Tool:** k6 หรือ Apache JMeter +> **Test Tool:** k6 หรือ Apache JMeter > **Test Script:** `specs/05-Engineering-Guidelines/performance-test-script.js` (TODO: สร้าง) ### AC-PERF-002 — Concurrent Users @@ -793,7 +833,7 @@ related: | 1 | AC-AUTH-001 ~ AC-AUTH-005 ผ่านทั้งหมด | ⬜ | | 2 | AC-ADMIN-001 ~ AC-ADMIN-005 ผ่านทั้งหมด | ⬜ | | 3 | AC-CORR-001 ~ AC-CORR-002 (Happy Path) ผ่าน | ⬜ | -| 4 | AC-RFA-001 ~ AC-RFA-003 (Submit + Approve) ผ่าน | ⬜ | +| 4 | AC-RFA-001 ~ AC-RFA-003, AC-RFA-007 ~ AC-RFA-009 (RFA Core) ผ่าน | ⬜ | | 5 | AC-WF-001 ~ AC-WF-003 (Workflow Engine Core) ผ่าน | ⬜ | | 6 | AC-DN-001 (Concurrent Number) ผ่าน | ⬜ | | 7 | AC-STOR-001 (Two-Phase Storage + ClamAV) ผ่าน | ⬜ | @@ -818,15 +858,16 @@ related: ## 📝 Document Control -- **Version:** 1.0.0 -- **Status:** DRAFT — Pending Stakeholder Sign-off -- **Created:** 2026-03-11 +- **Version:** 1.8.1 +- **Status:** updated +- **Created:** 2026-03-11 | **Updated:** 2026-03-24 - **Owner:** Nattanin Peancharoen (Product Owner / System Architect) +- **Changes:** Added AC-RFA-007~009 (Edit/Cancel/Search RFA), Updated status codes, Added UUID references (ADR-019), Linked edge cases - **Next Review:** Prior to UAT Start - **Classification:** Internal Use Only --- > [!NOTE] -> เอกสารนี้ต้องได้รับการ Sign-off จาก Stakeholder ทุกฝ่ายก่อนเริ่ม UAT +> เอกสารนี้ต้องได้รับการ Sign-off จาก Stakeholder ทุกฝ่ายก่อนเริ่ม UAT > ดู Gap 5 (Stakeholder Sign-off) ใน `po-analysis.md` สำหรับ Process การอนุมัติ diff --git a/specs/01-requirements/01-06-edge-cases-and-rules.md b/specs/01-requirements/01-06-edge-cases-and-rules.md index f64ca70..01e6904 100644 --- a/specs/01-requirements/01-06-edge-cases-and-rules.md +++ b/specs/01-requirements/01-06-edge-cases-and-rules.md @@ -1,24 +1,26 @@ -# 🛡️ Module Edge Cases & Business Rules — LCBP3-DMS v1.8.0 +# 🛡️ Module Edge Cases & Business Rules — LCBP3-DMS v1.8.1 --- title: 'Edge Cases, Business Rules, and Anti-Bug Specifications' -version: 1.0.0 -status: DRAFT +version: 1.8.1 +status: updated owner: Nattanin Peancharoen (Product Owner / System Architect) -last_updated: 2026-03-11 +last_updated: 2026-03-24 related: +- specs/01-Requirements/01-04-user-stories.md - specs/01-Requirements/01-05-acceptance-criteria.md - specs/01-Requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md - specs/06-Decision-Records/ADR-001-unified-workflow-engine.md - specs/06-Decision-Records/ADR-016-security-authentication.md +- specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md - specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql --- > [!IMPORTANT] -> เอกสารนี้ระบุ **Edge Cases ที่ต้อง Implement และ Test อย่างชัดเจน** เพื่อป้องกัน Bug ในระบบ Prod +> เอกสารนี้ระบุ **Edge Cases ที่ต้อง Implement และ Test อย่างชัดเจน** เพื่อป้องกัน Bug ในระบบ Prod > ทุก Edge Case มี **Expected Behavior** ที่ Developer และ QA ต้องยึดถือ --- @@ -147,7 +149,7 @@ WHERE status = 'RESERVED' AND expires_at < NOW(); **Severity:** 🔴 Critical | **Type:** Concurrency, Business Rule -**Scenario:** Workflow มี Parallel Approval (Engineer A **และ** Engineer B ต้อง Approve พร้อมกัน) +**Scenario:** Workflow มี Parallel Approval (Engineer A **และ** Engineer B ต้อง Approve พร้อมกัน) Engineer A Approve พร้อมกับ Engineer B Approve ใน millisecond เดียวกัน **Expected Behavior:** @@ -170,7 +172,7 @@ Engineer A Approve พร้อมกับ Engineer B Approve ใน millisecon **Severity:** 🔴 Critical | **Type:** Security, Business Rule -**Scenario A:** Reviewer พยายาม Approve เอกสารที่ถูก Cancel แล้ว +**Scenario A:** Reviewer พยายาม Approve เอกสารที่ถูก Cancel แล้ว **Scenario B:** Reviewer Approve เอกสารที่ Approve ไปแล้ว (Double-click) **Expected Behavior (A):** @@ -389,6 +391,62 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet --- +### EC-RFA-005 — Edit Draft RFA Validation + +**Severity:** 🔴 Critical | **Type:** Business Rule, Data Integrity + +**Scenario:** User พยายามแก้ไข RFA ที่ไม่ใช่สถานะ DRAFT หรือพยายามแก้ไข Shop Drawing Revision + +**Expected Behavior:** + +- ถ้าสถานะ ≠ DRAFT → 403 Forbidden: "สามารถแก้ไขได้เฉพาะในสถานะ Draft" +- ถ้าสถานะ = DRAFT → อนุญาตแก้ไข Subject, Body, Remarks, Description, Due Date +- Shop Drawing Revision ที่ผูกอยู่ **ไม่สามารถเปลี่ยนได้** (EC-RFA-001 enforced) +- Audit Log บันทึก UPDATE + user + timestamp ทุกครั้ง +- Details JSON schema_version คงที่ (ไม่อนุญาตเปลี่ยน version) + +**Reference:** US-012a, AC-RFA-007 + +--- + +### EC-RFA-006 — Cancel Draft RFA Cascade Effects + +**Severity:** 🔴 Critical | **Type:** Business Rule, Data Integrity + +**Scenario:** User ยกเลิก RFA ที่อยู่ในสถานะ DRAFT หรือพยายามยกเลิกที่ไม่ใช่ DRAFT + +**Expected Behavior:** + +- ถ้าสถานะ ≠ DRAFT → 403 Forbidden: "สามารถยกเลิกได้เฉพาะในสถานะ Draft" +- ถ้าสถานะ = DRAFT → RFA status เปลี่ยนเป็น CANCELLED +- Shop Drawing Revision ถูกปลดผูก (available = true) ทันที +- Audit Log บันทึก CANCELLED + reason + user + timestamp +- ไม่สามารถ Undo การ Cancel ได้ (ต้องสร้าง RFA ใหม่) + +**Reference:** US-012b, AC-RFA-008 + +--- + +### EC-RFA-007 — Search Results RBAC Filtering + +**Severity:** 🔴 Critical | **Type:** Security, Business Rule + +**Scenario:** Contractor A ค้นหา RFA แต่พยายามเห็น RFA ของ Contractor B โดยใช้ Advanced Filter + +**Expected Behavior:** + +- RFA ในสถานะ DRAFT → เห็นเฉพาะ originator organization (เจ้าของ RFA) +- RFA ในสถานะอื่น (SUBMITTED, FAP, APPROVED, REJECTED) → เห็นตามสิทธิ์ปกติ (project/contract scope) +- Elasticsearch query filter ด้วย `visible_to_organizations` array field +- Frontend ไม่แสดงผลลัพธ์ที่ไม่มีสิทธิ์ (return empty ไม่ใช่ error) +- API Response ไม่เปิดเผย entity_uuid ของเอกสารที่ไม่มีสิทธิ์ + +**Implementation:** Backend filter ใน service layer + Elasticsearch query filter + +**Reference:** US-012c, AC-RFA-009, ADR-019 + +--- + ## Module 5: Authentication & Session Edge Cases ### EC-AUTH-001 — Token Refresh Race Condition @@ -469,14 +527,17 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated'); **Severity:** 🔴 Critical | **Type:** Security -**Scenario:** User A รู้ ID ของเอกสาร User B (เช่น `/correspondences/12345`) แล้วเรียกตรงๆ +**Scenario:** User A รู้ UUID ของเอกสาร User B (เช่น `/correspondences/550e8400-e29b-41d4-a716-446655440000`) แล้วเรียกตรงๆ **Expected Behavior:** -- CASL AbilityGuard ตรวจสอบทั้ง Action และ Resource Owner +- CASL AbilityGuard ตรวจสอบทั้ง Action และ Resource Owner (ADR-019) - ถ้าไม่มีสิทธิ์ → **403 Forbidden** (ไม่ใช่ 404 — เพราะ 404 บอกว่ามีอยู่แต่หาไม่เจอ) - **Exception:** ถ้าต้องการซ่อน Existence ของ Document → Return 404 - ทุก API ต้องผ่าน Permission Check โดยไม่มีข้อยกเว้น +- ParseUuidPipe ตรวจสอบ UUID format ก่อน query database + +**Implementation:** UUID-based routing + CASL permissions --- @@ -681,14 +742,14 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated'); | Document Numbering | 2 | 2 | 1 | 5 | | Workflow Engine | 2 | 1 | 2 | 5 | | File Storage | 2 | 2 | 1 | 5 | -| RFA & Drawing | 1 | 2 | 1 | 4 | +| RFA & Drawing | 4 | 2 | 1 | 7 | | Auth & Session | 3 | 0 | 1 | 4 | | Permission & RBAC | 2 | 0 | 1 | 3 | | Correspondence | 1 | 0 | 2 | 3 | | Circulation | 0 | 1 | 2 | 3 | | Search | 1 | 0 | 2 | 3 | | Notifications | 0 | 1 | 1 | 2 | -| **รวม** | **14** | **9** | **14** | **37** | +| **รวม** | **17** | **9** | **14** | **40** | --- @@ -697,6 +758,21 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated'); ### สำหรับ Unit Tests (Backend) ```typescript +// ตัวอย่าง: EC-RFA-005 — Edit Draft RFA Validation +describe('RFAService - Edit Draft Validation', () => { + it('should allow edit when status is DRAFT', async () => { + const rfa = await service.createRFA({ status: 'DRAFT' }); + const updated = await service.updateRFA(rfa.uuid, { subject: 'Updated' }); + expect(updated.subject).toBe('Updated'); + }); + + it('should reject edit when status is not DRAFT', async () => { + const rfa = await service.createRFA({ status: 'SUBMITTED' }); + await expect(service.updateRFA(rfa.uuid, { subject: 'Updated' })) + .rejects.toThrow('403'); + }); +}); + // ตัวอย่าง: EC-DN-001 — Concurrent Number Generation describe('DocumentNumberingService - Concurrency', () => { it('should generate unique numbers for concurrent requests', async () => { @@ -713,19 +789,23 @@ describe('DocumentNumberingService - Concurrency', () => { - EC-DN-001: k6 Load Test Script (50 VUs, `/document-numbering/reserve`) - EC-AUTH-001: Cypress Multi-tab Token Refresh Test -- EC-PERM-001: API Test Suite — Direct Object Reference สำหรับทุก Resource +- EC-PERM-001: API Test Suite — Direct Object Reference สำหรับทุก Resource (UUID-based) +- EC-RFA-005~007: RFA CRUD operations test with different user roles ### สำหรับ Manual UAT - EC-WF-001: Test Parallel Approval ด้วย 2 Browser Session พร้อมกัน - EC-STOR-002: Upload EICAR Test File (ClamAV Test Virus) - EC-RFA-001: สร้าง RFA สำหรับ Revision เดิมที่มี Active RFA → Assert Block +- EC-RFA-006: Cancel Draft RFA → Verify Shop Drawing Revision ถูกปลดผูก +- EC-RFA-007: Contractor A ค้นหา → Assert ไม่เห็น RFA ของ Contractor B --- ## 📝 Document Control -- **Version:** 1.0.0 | **Status:** DRAFT -- **Created:** 2026-03-11 | **Owner:** Nattanin Peancharoen +- **Version:** 1.8.1 | **Status:** updated +- **Created:** 2026-03-11 | **Updated:** 2026-03-24 | **Owner:** Nattanin Peancharoen +- **Changes:** Added EC-RFA-005~007 (Edit/Cancel/Search RFA), Updated UUID references (ADR-019), Sync with US-012a~012c and AC-RFA-007~009 - **Next Review:** Pre-UAT (T-2 สัปดาห์ก่อน Go-Live) - **Classification:** Internal Use Only — Developer & QA Reference diff --git a/specs/01-requirements/01-07-ui-wireframes.md b/specs/01-requirements/01-07-ui-wireframes.md index d04a302..defbeb1 100644 --- a/specs/01-requirements/01-07-ui-wireframes.md +++ b/specs/01-requirements/01-07-ui-wireframes.md @@ -1,22 +1,25 @@ -# 🖼️ UI/UX Wireframes & Screen Inventory — LCBP3-DMS v1.8.0 +# 🖼️ UI/UX Wireframes & Screen Inventory — LCBP3-DMS v1.8.1 --- title: 'UI/UX Screen Inventory, Navigation Map, and Wireframes' -version: 1.0.0 -status: DRAFT +version: 1.8.1 +status: updated owner: Nattanin Peancharoen (Product Owner) -last_updated: 2026-03-11 +last_updated: 2026-03-24 related: - specs/01-Requirements/01-02-business-rules/01-02-03-ui-ux-rules.md - specs/01-Requirements/01-04-user-stories.md - specs/01-Requirements/01-05-acceptance-criteria.md +- specs/01-Requirements/01-06-edge-cases-and-rules.md +- specs/06-Decision-Records/ADR-012-ui-components.md +- specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md --- > [!NOTE] -> Wireframes ในเอกสารนี้เป็น **Low-fidelity ASCII/Text Wireframes** เพื่อสื่อสาร Layout และ Component Hierarchy +> Wireframes ในเอกสารนี้เป็น **Low-fidelity ASCII/Text Wireframes** เพื่อสื่อสาร Layout และ Component Hierarchy > สำหรับ High-fidelity Design ให้ใช้ Figma หรือ Shadcn/UI Components ตาม ADR-012 --- @@ -36,27 +39,29 @@ related: │ ├── /correspondences → รายการ Correspondence │ ├── /correspondences/new → สร้างใหม่ -│ └── /correspondences/:id → รายละเอียด + Workflow +│ └── /correspondences/:uuid → รายละเอียด + Workflow │ ├── /rfas → รายการ RFA │ ├── /rfas/new → สร้างใหม่ -│ └── /rfas/:id → รายละเอียด + Workflow +│ ├── /rfas/:uuid → รายละเอียด + Workflow +│ ├── /rfas/:uuid/edit → แก้ไข Draft RFA (ใหม่!) +│ └── /rfas/search → ค้นหาและกรอง RFA (ใหม่!) │ ├── /transmittals → รายการ Transmittal │ ├── /transmittals/new → สร้างใหม่ (รวม RFAs) -│ └── /transmittals/:id → รายละเอียด +│ └── /transmittals/:uuid → รายละเอียด │ ├── /drawings → Drawing Management │ ├── /drawings/contract → Contract Drawings │ │ ├── /drawings/contract/new → Upload ใหม่ -│ │ └── /drawings/contract/:id → รายละเอียด +│ │ └── /drawings/contract/:uuid → รายละเอียด │ └── /drawings/shop → Shop Drawings │ ├── /drawings/shop/new → Upload ใหม่ -│ └── /drawings/shop/:id → รายละเอียด + RFA History +│ └── /drawings/shop/:uuid → รายละเอียด + RFA History │ ├── /circulations → Circulation Sheets (Internal) │ ├── /circulations/new → สร้างใหม่ -│ └── /circulations/:id → รายละเอียด + Assignees +│ └── /circulations/:uuid → รายละเอียด + Assignees │ ├── /search → Full-text Search │ @@ -122,13 +127,16 @@ Mobile: Sidebar → Collapsible Hamburger Drawer (ตาม UI-Rule 5.11) | SCR-003 | `/dashboard` | Dashboard | ทุก Role | 🔴 Must | | SCR-004 | `/correspondences` | Correspondence List | Doc Control | 🔴 Must | | SCR-005 | `/correspondences/new` | Create Correspondence | Doc Control | 🔴 Must | -| SCR-006 | `/correspondences/:id` | Correspondence Detail + Workflow | ทุก Role | 🔴 Must | +| SCR-006 | `/correspondences/:uuid` | Correspondence Detail + Workflow | ทุก Role | 🔴 Must | | SCR-007 | `/rfas` | RFA List | Doc Control | 🔴 Must | | SCR-008 | `/rfas/new` | Create RFA | Doc Control | 🔴 Must | -| SCR-009 | `/rfas/:id` | RFA Detail + Workflow | ทุก Role | 🔴 Must | +| SCR-009 | `/rfas/:uuid` | RFA Detail + Workflow | ทุก Role | 🔴 Must | +| SCR-008b | `/rfas/:uuid/edit` | Edit Draft RFA | Doc Control | 🔴 Must | +| SCR-008c | — (action modal) | Cancel Draft RFA | Doc Control | 🔴 Must | +| SCR-008d | `/rfas/search` | RFA Search & Filter | Doc Control | 🔴 Must | | SCR-010 | `/transmittals` | Transmittal List | Doc Control | 🟠 Should | | SCR-011 | `/transmittals/new` | Create Transmittal | Doc Control | 🟠 Should | -| SCR-012 | `/transmittals/:id` | Transmittal Detail | ทุก Role | 🟠 Should | +| SCR-012 | `/transmittals/:uuid` | Transmittal Detail | ทุก Role | 🟠 Should | | SCR-013 | `/drawings/contract` | Contract Drawing List | Doc Control | 🟠 Should | | SCR-014 | `/drawings/shop` | Shop Drawing List | Doc Control | 🟠 Should | | SCR-015 | `/drawings/shop/:id` | Shop Drawing Detail | ทุก Role | 🟠 Should | @@ -144,7 +152,7 @@ Mobile: Sidebar → Collapsible Hamburger Drawer (ตาม UI-Rule 5.11) | SCR-025 | `/admin/doc-numbering` | Document Number Config | Superadmin | 🟠 Should | | SCR-026 | `/admin/audit-logs` | Audit Log Viewer | Org Admin+ | 🟠 Should | -**รวม:** 26 หน้า (9 Must / 13 Should / 1 Could) +**รวม:** 29 หน้า (12 Must / 14 Should / 1 Could) --- @@ -404,6 +412,122 @@ Workflow Step Popup (คลิก Step ที่ผ่านแล้ว): --- +### SCR-008b: Edit Draft RFA + +``` +┌─ แก้ไข RFA (Draft) ───────────────────────────────────────────────┐ +│ LCBP3-RFA-STR-0042 | สถานะ: 🟡 DRAFT | แก้ไขล่าสุด: 13:28:45 │ +│ │ +│ ⚠️ สามารถแก้ไขได้เฉพาะในสถานะ Draft เท่านั้น │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Subject* │ │ +│ │ [ขออนุมัติแก้ไขแบบ Shop Drawing ส่วน Foundation...] │ │ +│ │ │ │ +│ │ Body/Description* │ │ +│ │ [เนื่องจากมีการเปลี่ยนแปลงรายละเอียด Connection Plate...] │ │ +│ │ │ │ +│ │ Remarks │ │ +│ │ [_____________________________________________] │ │ +│ │ │ │ +│ │ 📋 RFA Details (JSON Schema v1) — ไม่สามารถแก้ไขได้ │ │ +│ │ Drawing Count: 1 | Discipline: Structural | Due: 20 มี.ค. │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📎 Shop Drawing (ไม่สามารถเปลี่ยนได้): │ +│ ☑️ CD-STR-001-Foundation-RevA.pdf (2.3MB) ✅ Scan OK │ +│ │ +│ [บันทึก Draft] [Submit →] [ยกเลิก] │ +└─────────────────────────────────────────────────────────────────────┘ + +Validation: +- ถ้าสถานะ ≠ DRAFT → Redirect ไปหน้า detail พร้อม error toast +- Shop Drawing Revision ไม่สามารถเปลี่ยน (EC-RFA-001) +- Auto-save ทุก 2 วินาที +``` + +--- + +### SCR-008c: Cancel Draft RFA (Modal) + +``` +┌─ ยกเลิก RFA (Draft) ───────────────────────────────────────────────┐ +│ LCBP3-RFA-STR-0042 | สถานะ: 🟡 DRAFT │ +│ │ +│ ⚠️ การยกเลิกจะทำให้ Shop Drawing Revision นี้สามารถสร้าง RFA ใหม่ได้ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ เหตุผลการยกเลิก* │ │ +│ │ [_____________________________________________] │ │ +│ │ │ │ +│ │ เลือกเหตุผล: │ │ +│ │ ○ ข้อมูลไม่ครบถ้วย │ │ +│ │ ○ แก้ไขข้อมูลผิดพลาดหลายครั้ง │ │ +│ │ ○ ไม่ต้องการส่งออกแล้ว │ │ +│ │ ○ อื่นๆ (ระบุ) │ │ +│ │ │ │ +│ │ [คำอธิบายเพิ่มเติม...] │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ 🔔 ผู้ที่เกี่ยวข้องจะได้รับแจ้งเตือน: │ +│ - Document Control ที่สร้าง RFA (Email + In-App) │ +│ - Admin ขององค์กร (ถ้ามีการตั้งค่า) │ +│ │ +│ [← กลับ] [✅ ยืนยันการยกเลิก] │ +└─────────────────────────────────────────────────────────────────────┘ + +Post-cancel Flow: +- RFA status → CANCELLED +- Shop Drawing Revision.available → true +- Audit Log: CANCELLED + reason + user + timestamp +- Redirect ไปหน้า RFA List พร้อม success toast +``` + +--- + +### SCR-008d: RFA Search & Filter + +``` +┌─ ค้นหาและกรอง RFA ───────────────────────────────────────────────────┐ +│ 🔍 [ค้นหา RFA...] [ค้นหา] [ตั้งค่ากรอง] │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ 📁 Project: [LCBP3 ▼] │ │ +│ │ 📊 Status: [ทั้งหมด ▼] 🟡DRAFT 🔵SUBMITTED 🔄FAP ✅APPROVED ❌REJECTED │ │ +│ │ 📅 Revision: [ทั้งหมด ▼] CURRENT OLD ALL │ │ +│ │ 🏢 Originator: [ทั้งหมด ▼] [สค.] [กทท.] [ผรม.] [คคง.] │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ พบ 15 รายการ (แสดงตามสิทธิ์ของคุณ) │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ ☐ │ เลขที่ │ Subject | สถานะ | วันที่ │ │ │ +│ ├──┼─────────────┼──────────────────────┼────────┼──────────┤ │ +│ │ ☐ │ RFA-STR-0042 │ Foundation Plan Rev.A│ 🟡DRAFT │ 10 มี.ค. │[✏️]│ │ +│ │ ☐ │ RFA-STR-0043 │ Column Detail Rev.B │ 🔵SUBMIT│ 12 มี.ค. │[👁️]│ │ +│ │ ☐ │ RFA-STR-0041 │ Beam Design Rev.A │ ✅APPROV│ 08 มี.ค. │[👁️]│ │ +│ │ ☐ │ RFA-STR-0040 │ Slab Detail Rev.A | ❌REJECT│ 05 มี.ค. │[✏️]│ │ +│ └────────┴─────────────┴──────────────────────┴────────┴──────────┘ │ +│ │ +│ [< 1 2 >] แสดง 10/15 [ส่งออก Excel] │ +│ │ +│ ⚠️ หมายเหตุ RBAC: │ +│ - DRAFT → เห็นเฉพาะ originator organization │ +│ - สถานะอื่น → เห็นตาม project/contract scope │ +└─────────────────────────────────────────────────────────────────────┘ + +Advanced Filters (Collapsible): +┌─ กรองขั้นสูง ───────────────────────────────────────────────────┐ +│ 📅 วันที่สร้าง: [____] ถึง [____] │ +│ 👤 ผู้สร้าง: [ค้นหา...] │ +│ 📝 มีคำว่า: [ค้นหา...] │ +│ 🏷️ Tags: [foundation] [column] [beam] │ +│ [ค้นหา] [ล้าง] │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + ### SCR-009: RFA Detail + Workflow ``` @@ -548,8 +672,9 @@ User Edit Drawer (Slide in from right): /* Status Colors */ --status-draft: hsl(48, 96%, 53%); /* Yellow */ --status-submitted: hsl(217, 91%, 60%); /* Blue */ ---status-review: hsl(24, 95%, 53%); /* Orange */ +--status-fap: hsl(24, 95%, 53%); /* Orange */ --status-approved: hsl(142, 71%, 45%); /* Green */ +--status-approved-wc: hsl(142, 71%, 35%); /* Green Dark */ --status-rejected: hsl(0, 84%, 60%); /* Red */ --status-cancelled: hsl(215, 14%, 55%); /* Gray */ --status-overdue: hsl(0, 84%, 60%); /* Red (same as rejected) */ @@ -579,6 +704,8 @@ font-family: 'Inter', 'Noto Sans Thai', sans-serif; | Component | Default | Hover | Active | Disabled | Error | | -------------- | ---------------- | ---------------- | ------------------- | --------------- | -------------- | | Button Primary | bg-primary | bg-primary-hover | scale-95 | opacity-50 | — | +| Button Secondary| bg-surface | bg-muted | scale-95 | opacity-50 | — | +| Button Danger | bg-red-500 | bg-red-600 | scale-95 | opacity-50 | — | | Input | border-gray-300 | border-primary | border-primary ring | border-gray-200 | border-red-500 | | Table Row | bg-surface | bg-muted | — | opacity-60 | bg-red-50 | | Badge | per status color | — | — | — | — | @@ -630,8 +757,9 @@ User พิมพ์ใน Form → debounce 2 วินาที → บัน ## 📝 Document Control -- **Version:** 1.0.0 | **Status:** DRAFT -- **Created:** 2026-03-11 | **Owner:** Nattanin Peancharoen +- **Version:** 1.8.1 | **Status:** updated +- **Created:** 2026-03-11 | **Updated:** 2026-03-24 | **Owner:** Nattanin Peancharoen +- **Changes:** Added SCR-008b~008d (Edit/Cancel/Search RFA), Updated routes to use UUID (ADR-019), Added new status colors and button variants, Sync with US-012a~012c and AC-RFA-007~009 - **Next Step:** สร้าง High-fidelity Mockup ใน Figma ตามโครงสร้างนี้ - **Figma Link:** [TBD — สร้างใน Figma Community หรือ Self-hosted Penpot] - **Classification:** Internal Use Only