Files
alla-tms/docs/permission-template-validation-fix-report.md
2026-07-16 09:53:14 +07:00

114 lines
7.2 KiB
Markdown

# 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