eCourt-Doc-Edit/docs/plans/field-editor.md
Suphonchai Phoonsawat 9bb4a48c00 Phase 10: field editor (FR-14)
เพิ่ม editor ต่อ template field — ผู้ดูแลเลือกรูปแบบช่องกรอกในฟอร์มสร้างเอกสารได้:
text → textbox/textarea, number/date → control ตาม type (presentation metadata
ล้วน — validation ยังยึด Type, ค่า field เป็น string ทั้ง flow, ไม่แตะ generate/sync/add-in)

- Backend: TemplateField.Editor (nullable, null = default ของ type) +
  migration AddTemplateFieldEditor + validate ตาม type ใน PUT templates
  (ค่าไม่รู้จัก/ไม่เข้ากับ type → 400, normalize-on-write, API คืน resolved เสมอ)
- Web: util fieldEditor.ts + editor select หน้าแก้ template (desktop+mobile,
  ซ่อนเมื่อ type มีทางเดียว) + ฟอร์มสร้างเอกสาร render ตาม editor +
  แก้ prefill re-generate ของ field date จาก ISO timestamp เต็ม (input[type=date]
  รับเฉพาะ yyyy-MM-dd เดิมแสดงว่าง)
- Docs: spec v6 (FR-14/AC-17) + plan Phase 10 + sub-plan field-editor.md +
  acceptance-review AC-17 + README/CLAUDE.md
- Tests: backend 60/60 (FieldEditorTests ใหม่ 8 กรณี) · web 46/46 + build ผ่าน

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-22 09:17:15 +07:00

11 KiB

Field Editor — FR-14 / AC-17 / Spec v6

Context

ฟอร์มสร้างเอกสารบนเว็บ (FR-10) render ทุก field เป็น <input> บรรทัดเดียว — field ข้อความยาว (เช่น รายละเอียดคดี, ที่อยู่) กรอกในช่องแคบไม่สะดวก ผู้ใช้ต้องการให้ผู้ดูแลเลือก editor (รูปแบบช่องกรอก) ต่อ field ได้ ขอบเขต (ยืนยันกับผู้ใช้แล้ว): ไม่เพิ่ม dropdown/checkbox — แค่:

  • text → textbox (default) หรือ textarea
  • number → number (ช่องตัวเลข — ทางเดียว)
  • date → date (date picker — ทางเดียว)

ข้อตกลง fixed:

  • Editor เป็น presentation metadata แยกจาก Type — validation ของค่า field (generate/sync) ยึด Type เท่านั้น ค่า field เป็น string ทั้ง flow (กติกา #5/#6)
  • คอลัมน์ nullable ไม่ backfill — null = "default ของ Type" (มิเรอร์ pattern AddVersionMessage) ทำให้ path auto-detect (POST create / PUT file) ไม่ต้องแต่
  • Normalize-on-write: ค่าที่ resolve แล้วเท่ากับ default → เก็บ null (DB ไม่มีค่า default ซ้ำซ้อน อนาคตเปลี่ยน default มีผลทุก row) · API คืนค่า resolved เสมอ
  • UI admin ซ่อน select เมื่อ type มี editor ทางเดียว (แสดง — muted แทน select หลอก) — backend ยัง validate ค่า number/date ให้ (round-trip ปลอดภัย)
  • Non-goal: ไม่แตะ add-in (ไม่มี UI แก้ค่า field) · ไม่แตะ DocumentFieldItem/หน้า detail (display-only) · ไม่แตะ docx/generate path
  • ตามกติกา repo: แก้ spec ก่อน แล้ว sync plan

ขั้นที่ 0 — บันทึกแผน + แก้เอกสาร requirement ก่อน

  1. บันทึกแผนนี้ที่ docs/plans/field-editor.md (ธรรมเนียมเดิม)
  2. specs/spec-officejs.md: changelog v6 บรรทัดบนสุด · FR-9 bullet admin config เพิ่ม editor (FR-14) · ### FR-14: Field editor หลัง FR-13 · section 7 TemplateField POCO เพิ่ม public string? Editor { get; set; } · section 10 เพิ่ม AC-17 (v6) · section 11 เพิ่ม task items (v6) ทั้ง backend + web list
  3. docs/plans/officejs-implementation-plan.md: header ข้อตกลง v5→v6 · ## Phase 10: Field editor — FR-14 (เพิ่มทีหลัง, v6) + checklist + แถว mapping | 10 (ทีหลัง, v6) | FR-14 | AC-17 | + AC-17 ในรายการ AC เพิ่มเติม
  4. docs/acceptance-review.md: "AC ทั้ง 16 ข้อ" → 17 + section AC-17 (🔶 browser click-through)

ขั้นที่ 1 — Backend (backend/WordVersioning.Api)

  1. Models/TemplateField.cs: public string? Editor { get; set; } หลัง Type + static EditorsByType (text → [textbox, textarea], number → [number], date → [date]) + DefaultEditor(type) (unknown type → "textbox")
  2. Data/AppDbContext.cs: ใน block TemplateField เพิ่ม entity.Property(x => x.Editor).HasMaxLength(16); (มิเรอร์ Type)
  3. Migration: dotnet ef migrations add AddTemplateFieldEditor → expect AddColumn<string>("Editor", "TemplateFields", nullable, maxLength 16) เดี่ยว ไม่ backfill (pattern เดียวกับ AddVersionMessage)
  4. Models/TemplateDtos.cs: TemplateFieldDefinition.Editor (string? หลัง Type — ไม่ส่ง/ว่าง = default) · TemplateFieldDto แทรก string Editor หลัง Type (คืนค่า resolved เสมอ)
  5. Controllers/TemplatesController.cs:
    • Update field loop หลัง type check: resolve null/whitespace → DefaultEditor(def.Type) · ไม่อยู่ใน EditorsByType[def.Type] → 400 ข้อความไทย (ครอบทั้งค่าไม่รู้จักและไม่เข้ากับ type) · persist normalize-on-write: row.Editor = editor == DefaultEditor(def.Type) ? null : editor
    • Create/ReplaceFile: ไม่แตะ (Editor null = default ถูกต้องอยู่แล้ว)
    • FieldDtos: Editor = f.Editor ?? TemplateField.DefaultEditor(f.Type) (จุด resolve เดียว)
  6. ไม่แตะ DocumentsController (validation ยึด Type), VersionDtos.cs, docx path, add-in

ขั้นที่ 2 — Web (web)

  1. types/api.ts: export type FieldEditor = 'textbox' | 'textarea' | 'number' | 'date' · TemplateFieldDto เพิ่ม editor: FieldEditor | string หลัง type (string-tolerant เหมือน type)
  2. สร้าง app/utils/fieldEditor.ts (pure util auto-import เหมือน fieldValidation): EDITORS_BY_TYPE · defaultEditor(type) · editorOptions(type) (text = 2 ตัว, number/date = 1 ตัว) · resolveEditor(field) (valid-for-type → ตัวมันเอง, null/unknown/incompatible → default — degrade อ่อนโยน) · dateInputValue(value) (ISO timestamp → yyyy-MM-dd 10 หลักแรก, อื่นๆ คงเดิม)
  3. templates/[id]/edit.vue: payload mapping เพิ่ม editor: f.editor · type <select> ทั้ง 2 ที่ เพิ่ม @change="f.editor = defaultEditor(f.type)" · editor select แสดงเฉพาะ editorOptions(f.type).length > 1 (mobile card + desktop <th>/<td>) ไม่งั้น — muted · hint ไทยใต้ subtitle
  4. documents/create/[templateId].vue: field loop แทน ternary ด้วย resolveEditor(f) switch — textarea → <textarea rows="4"> · number → input number step any (preserve) · date → input date (preserve) · else textbox · prefill ?from= ของ field editor date ผ่าน dateInputValue (input[type=date] รับเฉพาะ yyyy-MM-dd — ค่า timestamp เต็มจาก Word sync ทำให้ช่องแสดงว่าง)
  5. templates/new.vue: copy ปุ่มลิงก์ label/type/required → label/type/editor/required

ขั้นที่ 3 — Tests

Backend — ไฟล์ใหม่ FieldEditorTests.cs (pattern VersionMessageTests — fixture.CreateDb() + AsNoTracking + ใช้ TemplateTests.CreateDefaultTemplateAsync):

  1. Update_TextFieldEditorTextarea_PersistsAndEchoed (response + DB row = "textarea")
  2. Update_OmittedEditor_ResolvesDefaultPerType_StoresNull (text/number/date → echo textbox/number/date, DB null ทั้งหมด)
  3. Update_EditorEqualToDefault_NormalizedToNull (ส่ง "textbox" ชัดๆ → echo "textbox", DB null)
  4. Update_UnknownEditor_Returns400
  5. [Theory] Update_EditorIncompatibleWithType_Returns400 ((number,"textarea"), (text,"date"), (date,"textbox"))
  6. ReplaceFile_AddedTagGetsDefaultEditor (echo "textbox", DB null)

Mirror DTO: TestDtos.cs TemplateFieldItem เพิ่ม string Editor · extend TemplateTests — Create_DetectsAllSdtTags_WithDefaults assert Editor == "textbox" + Update_SavesFieldDefinitions_AndMetadata เพิ่ม editor ใน payload

Web — สร้าง test/fieldEditor.spec.ts: defaultEditor ทุก type + unknown · resolveEditor (explicit/undefined/incompatible/string-tolerant) · editorOptions (text=2, number/date=1) · dateInputValue (timestamp→date, plain คงเดิม, ค่าอื่นคงเดิม) · fieldValidation.spec.ts ไม่แตะ

Verification

# ลำดับ: docs → backend → web
cd backend/WordVersioning.Api && dotnet ef migrations add AddTemplateFieldEditor   # ตรวจ Up/Down
cd backend && dotnet test --filter FullyQualifiedName~FieldEditorTests   # ต้องมี Docker (Testcontainers)
cd backend && dotnet test
cd web && npx vitest run test/fieldEditor.spec.ts && npm test && npm run build

Manual E2E (หลัง docker compose up -d + backend + npm run dev):

  1. หน้าแก้ template: field type text → เลือก editor textarea ได้ · type number/date → แสดง — · เปลี่ยน type จาก text เป็น number → editor กลับเป็น default
  2. ฟอร์มสร้างเอกสาร: field textarea → กว้างหลายบรรทัด · number → ช่องตัวเลข · date → date picker (ตามเดิม)
  3. Re-generate เอกสารที่ field date เก็บ timestamp เต็ม → ช่อง date ต้องแสดงวันที่ ไม่ใช่ว่างเปล่า

Risks / gotchas

  • TemplateFieldDto เป็น positional record — แทรก Editor หลัง Type กระทบจุด construct เดียว (FieldDtos) + test mirror (TestDtos.TemplateFieldItem) — ตรวจ compile
  • Validation editor ต้องมาหลัง type check (allow-list keyed ตาม type — ลำดับใน loop เดิมรับประกันอยู่แล้ว)
  • editor select ใช้ label editor lowercase ตาม convention label/type/required ของหน้า — option แสดง raw value เหมือน type select
  • อย่าลืมอัปเดตตัวเลข test count ใน docs (52/36) หลังรันจริง

สถานะ: ✅ implement เสร็จ (2026-08-22)

ผลรันจริงทั้งชุด: backend dotnet test 60/60 (เพิ่ม FieldEditorTests 8 กรณี) · web npm test 46/46 + build ผ่าน (เพิ่ม fieldEditor.spec.ts 10 กรณี) · addin ไม่ถูกแตะ (50/50 คงเดิม) — รายละเอียด checkbox Phase 10 + หมายเหตุจากการทำจริงอยู่ที่ officejs-implementation-plan.md · ผล AC-17 ที่ acceptance-review.md · เหลือยืนยันด้วยมือ: browser click-through ของ admin select + textarea/date rendering ในฟอร์มสร้างเอกสาร