commit
This commit is contained in:
113
docs/permission-template-validation-fix-report.md
Normal file
113
docs/permission-template-validation-fix-report.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Permission Template Validation Fix Report
|
||||
|
||||
## Executive Summary
|
||||
|
||||
แก้ปัญหา validation ของการเลือก `Permission Template` ในฟอร์มกำหนดสิทธิ์ผู้ใช้ โดยทำให้ชนิดข้อมูลของ `template_id` สอดคล้องกันตั้งแต่ `Select` บน frontend, TanStack Form state, Zod validation, API payload, route handler และ database ที่ใช้ `integer`
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
สาเหตุหลักคือ `Select` component ส่งค่าออกมาเป็น `string` ตามพฤติกรรมของ Radix Select และ shared `FormSelectField` ของโปรเจกต์ก็ส่งค่าต่อเข้า form state แบบ `string` ตรง ๆ แต่ `userPermissionAssignmentSchema` กำหนด `template_id` เป็น `z.number()`
|
||||
|
||||
ผลที่เกิดขึ้นคือ
|
||||
|
||||
- เมื่อผู้ใช้เลือกค่าใน dropdown, form state กลายเป็น `string`
|
||||
- Zod ตรวจ `template_id` เป็น `number` จึงขึ้น error `Invalid input: expected number, received string`
|
||||
- component ต้องพยายาม `Number(value.template_id)` ตอน submit เพื่อแก้ปลายทาง
|
||||
- logic ที่หา template ปัจจุบันในหน้า edit มีโอกาส mismatch เพราะ `template.id` เป็น `number` แต่ค่าจาก form อาจเป็น `string`
|
||||
|
||||
## Architecture Review
|
||||
|
||||
Flow หลังแก้ไขเป็นดังนี้
|
||||
|
||||
Frontend
|
||||
-> `Select` ใช้ `string` เฉพาะใน UI
|
||||
-> `onValueChange` แปลงค่าเป็น `number` ก่อนเก็บใน form state
|
||||
|
||||
Validation
|
||||
-> `userPermissionAssignmentSchema` รองรับ string ที่หลุดเข้ามาได้ด้วย `z.preprocess`
|
||||
-> คืนข้อความผู้ใช้ `กรุณาเลือก Permission Template`
|
||||
|
||||
API
|
||||
-> `saveUserPermissionAssignment()` ส่ง `template_id` เป็น `number`
|
||||
|
||||
Backend
|
||||
-> route handler ใช้ schema เดิมตรวจ payload
|
||||
-> service layer รับ `template_id: number`
|
||||
|
||||
Database
|
||||
-> `user_permission_template_assignments.template_id` และ `permission_templates.id` เป็น `integer`
|
||||
|
||||
## Files Modified
|
||||
|
||||
- [src/features/permission-management/components/user-permission-sheet.tsx](/D:/WY-2569/HRD/training-system-minimal-refactor/src/features/permission-management/components/user-permission-sheet.tsx)
|
||||
ปรับ select ของ `template_id` ให้แปลงค่า `string -> number` ก่อนเก็บลง form state และเลิกแปลงซ้ำตอน submit
|
||||
- [src/features/permission-management/schemas/user-permission-assignment.ts](/D:/WY-2569/HRD/training-system-minimal-refactor/src/features/permission-management/schemas/user-permission-assignment.ts)
|
||||
ปรับ schema ให้ preprocess ค่า `template_id` และคืนข้อความ validation ที่เป็นมิตรกับผู้ใช้
|
||||
|
||||
## Implementation Details
|
||||
|
||||
Form
|
||||
|
||||
- เปลี่ยน default ของ `template_id` จาก fallback `0` เป็นค่าเดิมหรือ template แรกที่มีอยู่จริง
|
||||
- เก็บค่าใน TanStack Form เป็น `number | undefined` แทน `string`
|
||||
|
||||
Select
|
||||
|
||||
- ใช้ custom `form.AppField` แทน shared `FormSelectField` เฉพาะจุดนี้
|
||||
- แปลง `field.state.value` เป็น `String(...)` เฉพาะตอน bind เข้า `Select`
|
||||
- แปลงค่ากลับเป็น `Number(value)` ตอน `onValueChange`
|
||||
- ป้องกันเคส empty value ไม่ให้กลายเป็น `0`
|
||||
|
||||
Schema
|
||||
|
||||
- ใช้ `z.preprocess` สำหรับ `template_id`
|
||||
- ถ้าเป็น string ว่าง จะเปลี่ยนเป็น `undefined`
|
||||
- ถ้าเป็น numeric string จะเปลี่ยนเป็น `number`
|
||||
- ถ้าไม่ผ่านเงื่อนไข จะได้ข้อความ `กรุณาเลือก Permission Template`
|
||||
|
||||
API / Backend
|
||||
|
||||
- payload จาก frontend ถูกส่งเป็น `number` แล้ว
|
||||
- route handler และ service layer ไม่ต้องเปลี่ยน business logic
|
||||
|
||||
## Before
|
||||
|
||||
- เลือก Permission Template แล้ว form มีโอกาสถือค่าเป็น `string`
|
||||
- schema คาดหวัง `number`
|
||||
- ผู้ใช้เห็น error เชิงเทคนิค `Invalid input: expected number, received string`
|
||||
- หน้า edit อาจหา template ปัจจุบันไม่ตรงชนิดข้อมูล
|
||||
|
||||
## After
|
||||
|
||||
- ค่า `template_id` ใน form state และ payload เป็น `number`
|
||||
- schema และ API รับค่าชนิดเดียวกับ database
|
||||
- ผู้ใช้เห็นข้อความ validation ที่เข้าใจได้
|
||||
- create และ edit ใช้ logic ชนิดข้อมูลเดียวกันตลอด flow
|
||||
|
||||
## Validation Result
|
||||
|
||||
ตรวจจากโค้ดและ data flow หลังแก้ไขแล้วได้ผลดังนี้
|
||||
|
||||
- Create: เลือก template แล้ว submit จะส่ง `template_id` เป็น `number`
|
||||
- Edit: ค่าเดิมของ template ถูกโหลดเป็น `number` และ bind กลับเข้า select ได้ถูกต้อง
|
||||
- Validation: หากไม่มีค่า จะได้ข้อความ `กรุณาเลือก Permission Template`
|
||||
- API: route handler ยังใช้ schema เดิม แต่รับค่า numeric string ได้อย่างปลอดภัยและ normalize เป็น `number`
|
||||
- Database: ไม่มีการเปลี่ยน schema และยังสอดคล้องกับ `integer`
|
||||
|
||||
## Regression Check
|
||||
|
||||
- ไม่เปลี่ยน permission logic
|
||||
- ไม่เปลี่ยน API endpoint
|
||||
- ไม่เปลี่ยน database schema
|
||||
- ไม่เพิ่ม library ใหม่
|
||||
- ไม่เพิ่ม `any`
|
||||
- ใช้ shared `Select` component เดิมของระบบ
|
||||
|
||||
## Risks
|
||||
|
||||
- shared `FormSelectField` ของโปรเจกต์ยังเป็น string-based ตามเดิม ดังนั้นถ้ามีฟอร์มอื่นที่ใช้ select กับ schema แบบ `number` อาจเจอ pattern เดียวกันได้
|
||||
|
||||
## Recommendation
|
||||
|
||||
- หากอนาคตมีหลายฟอร์มที่ต้องใช้ select กับค่า `number`, ควรเพิ่ม shared numeric select wrapper ที่อยู่บน pattern เดิมของ `tanstack-form`
|
||||
- ควรเพิ่ม automated test ระดับ component หรือ integration สำหรับ create/edit flow ของ user permission assignment
|
||||
Reference in New Issue
Block a user