# 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