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

7.2 KiB

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

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