S4 — eMediation — Backend Implementation Blueprint¶
ขอบเขตเอกสาร
map schema DELTA (schema.sql) → backend repo repos/emediation/be (NestJS) + ลำดับพัฒนา · backend = สร้างใหม่ ทับ V2 (Webify Node/Express เดิม) ด้วย NestJS โครงเดียวกับ repos/pjos/be · ดู spine: index.md
1. Architecture — V3 = รวม V1 + V2 ขึ้น stack ใหม่¶
S4 ถูกสั่งให้ สร้างใหม่โดยรวม V1 (PHP/CI, .41) + V2 (Node/Vue, .87) เป็นระบบเดียว (ดู systems/s4_combine_summary.md) → "version 3" = backend NestJS ใหม่ที่:
- คงฟีเจอร์เดิมครบ — อ่าน/เขียนตาราง
cms_*เดิมทั้ง 95 ตาราง (REUSE) บน schemaMEDIATE(PSDBPRDDB) - เพิ่มฟีเจอร์ใหม่ — 23 ตาราง NEW + 7 ALTER ตาม mockup
fe+ SRS v4.0.0 - UI เดียว — React
fe(mockupbranch) แทนทั้ง PHP SSR (V1) และ Vue SPA (V2)
| ส่วน | เทคโนโลยี | อ้างอิง |
|---|---|---|
| Framework | NestJS (TypeScript, microservice-oriented, Docker, Linux VM ใหม่) | emediation/be, pjos/be README |
| ORM | Prisma datasource provider = "sqlserver" → schema MEDIATE |
— |
| Observability | @rlpdjs/observability — ConfigModule/LoggerModule/PrometheusModule/HttpModule/DatabaseModule.forRoot({ prisma }) |
rlpdjs/README.md |
| AuthN | ThaiD / Keycloak SSO → JWT (อ่าน role จาก token) | design_p2 D8 |
| AuthZ | RBAC จาก cms_user_role/cms_user_permission (sync → Keycloak client roles) |
REQ-MED-007 |
| API docs | OpenAPI /api/docs |
pjos/be convention |
// emediation/be — AppModule (รูปแบบเดียวกับ pjos/be, ใช้ rlpdjs)
@Module({
imports: [
ConfigModule.forRoot(),
LoggerModule.forRoot({ serviceName: 'emediation', enabled: true }),
PrometheusModule.forRoot(),
HttpModule.forRoot({ timeout: 30000 }),
DatabaseModule.forRoot({ prisma: new PrismaClient() }), // Prisma → sqlserver MEDIATE
// domain modules:
WorkflowModule, MediationModule, MediatorModule, CenterModule,
TrainingModule, CompensationModule, AdminModule, PublicModule,
],
})
export class AppModule implements NestModule {
configure(c: MiddlewareConsumer) { c.apply(LoggerMiddleware).forRoutes('*'); }
}
Prisma introspect ของจริงก่อนเขียน model
cms_* 95 ตารางมีจริงบน MEDIATE แล้ว → prisma db pull (introspect) เพื่อ generate model จาก DB จริง
แล้วจึง apply DELTA (schema.sql) เพิ่ม 23 ตาราง NEW + ALTER · อย่าเขียน model มือจาก 0
2. REQ → Endpoint → Service → Table¶
| REQ | Endpoint (ตัวอย่าง REST) | Service / Logic | ตารางหลัก (R/M/N) |
|---|---|---|---|
| 001 | GET/POST /courses · POST /exams · POST /exam-attempts/:id/submit (auto-grade) · POST /courses/:id/checkin |
จัดการหลักสูตร → สุ่มข้อสอบจาก category+question_count → ตรวจอัตโนมัติ score/total≥pass_pct (retake ≤3) → ออก cert |
R cms_training_topic,cms_training_register,cms_training_management_checkin,cms_training_certificate · N cms_exam_question,cms_exam,cms_exam_attempt,cms_trainer_eval_category/subject |
| 002 | POST /mediators · POST /:id/renew · POST /:id/edit-request · POST /:id/ending |
ขึ้นทะเบียน→วาระ+expiry ; ต่ออายุ 90 วันล่วงหน้า ; ≤3 สังกัด ; bookbank/K-COP ลงนามก่อน | R cms_mediator_applicant,cms_mediator_registra,cms_mediator_appointment,cms_mediator_cert,cms_mediator_request_edit,cms_mediator_ending,cms_profile_ktbcorp* |
| 003 | POST /centers · POST /:id/participants |
ตั้งศูนย์ + คณะทำงาน 5 ตำแหน่ง ; ตรวจ idcard 4 ตำแหน่งหลักไม่ซ้ำข้ามศูนย์ ; ≥1 ผ่านอบรม |
R cms_people_center,cms_people_center_participant,cms_master_center_position |
| 004 | POST /centers/:id/committees · POST /assessments/:id/score |
ตั้งคณะกรรมการ (สาย workflow) → ให้คะแนนรายบุคคล → ถ่วงน้ำหนัก 4 ด้าน (20/20/25/35) → cert รายปี (ไม่ทับปีเก่า) | N cms_assessment_committee(_member/_role) · M cms_master_assessment_category(+weight) · R cms_people_center_assessment(_detail/_result) |
| 005 | POST /mediations · POST /:id/appoint · POST /:id/sessions · POST /:id/agreement/sign |
รับคำร้อง→แต่งตั้ง→นัดหมาย+สอบถามความสมัครใจ (eSign) ; รายงาน 1–4 ; ข้อตกลงครั้งที่ 5 eSign (thaid/manual) ; บังคับ all_parties_replied_date |
R cms_mediation,cms_mediation_mediator/witness/docs,cms_master_civil_dispute · M cms_mediation,cms_mediation_books |
| 006 | POST /mediations/:id/certify · POST /compensation-claims |
นายทะเบียนรับรอง/ไม่รับรอง/ส่งกลับ ; เบิกค่าตอบแทน (เฉพาะหลังเสร็จ ; matrix เดินทาง) | R cms_compensation_main/history/requisition_form/payment_receipt,cms_comment_certificate,cms_mediation_compensation(_rate) |
| 007 | GET /wf/tasks (inbox) · POST /wf/instances/:id/approve\|return\|skip · POST /wf/delegations · GET /wf/audit |
engine: เดินตาม cms_wf_step ตามสาย 7 ระดับ ; skip ต้องมี skip_reason ; delegation override assignee ; ทุก action → cms_wf_audit |
N cms_wf_* (9) · M cms_user_role(+kind/base_role) · R cms_user_permission,cms_menu |
| 008 | GET/PUT /integrations/:id/toggle · POST /news (draft→publish) |
เปิด/ปิดราย integration (ThaID/eSign/K-COP/ศาล) ; authoring ข่าว/เอกสาร publish_state |
N cms_integration_setting · M cms_news,cms_people_center_announcement |
| 009 | GET /dashboard/* · GET /reports/* (export) |
aggregate ราย จังหวัด/ปีงบ — query/view ไม่มีตารางเก็บ | views บน R |
| 010 | (scheduled job) evaluateCenterTermination() |
ประเมินไม่ผ่าน → consecutive_fails++ ; ≥2 → เปิด cms_wf_instance สายยุติศูนย์ |
M cms_people_center(+consecutive_fails) · N cms_wf_instance |
| 011 | GET/POST /document-templates · POST /:id/render |
template + {var} → merge ข้อมูลจริง → PDF |
N cms_document_template · R cms_master_document_type |
| 012 | (scheduled job) notifyExpiring() |
เทียบ expiry/registra date กับ today+90 → เขียน cms_notify |
R cms_notify,cms_mediator_registra |
| 013 | POST /public/training-signup · GET /public/training/lookup?idcard&dob |
2 รอบ: สมัคร(stage='สมัครอบรม')→ขึ้นทะเบียน('ขึ้นทะเบียน') ; ตรวจตนด้วยบัตร+วันเกิด |
M cms_training_register(+stage/pre_member_no/selection_result) |
| 015 | POST /center-process-costs |
รายการค่าใช้จ่าย + ใบเสร็จ (≤10MB) → เพดานงบ → สาย 7 ระดับ | N cms_center_process_cost(_item) · R cms_wf_instance |
| 016 | GET/POST /award-criteria · POST /centers/:id/award |
เกณฑ์รายปีงบ (รวม 100%) → คะแนน → cert PDF + QR token | N cms_award_criteria,cms_center_award · M cms_people_center(+is_model_org) |
| 017 | GET /centers/:id/performance |
sync จาก REQ-005 ราย ปีงบ → รายงาน PDF/Excel | R cms_people_center_report/result,cms_master_performance |
3. Cross-cutting¶
- AuthN — SSO ทุกผู้ใช้ — V2 เริ่ม SSO ไว้แล้ว (
ssoController+.env API_SSO_URL, Bearer JWT = active path ปัจจุบัน) → V3 ทำต่อให้เป็น SSO ล้วน: Keycloak (realmrlpd, federate AD) + ThaiD (ประชาชน/ผู้ไกล่เกลี่ย) → JWT · ตัด legacy ของ V2 ทิ้ง: LDAP (ldapController→AD.15, last มิ.ย. 2025, แทบไม่ใช้) + local password (comment ออกแล้ว) +users.dn·cms_users= thin mirror auto-provision ตอน login ครั้งแรก (ไม่มีคอลัมน์ password/lockout — central SSO จัดการ) · บทบาทกลางที่ S9 Portal → sync Keycloak client roles - AuthZ — guard อ่าน role จาก JWT → เทียบ
cms_user_permission(action_*รายmenu×role) ;MEDIATOR/CITIZENscope = self,OFFICERscope = province (cms_wf_instance.province_id,cms_*.province_id) - Workflow — service กลาง
WorkflowService.start(requestType, subject)/.act(instanceId, action, actor)เรียกจากทุก domain (center-reg / compensation / eval-committee / process-cost) — subject ผูก polymorphic ผ่านsubject_table+subject_id - Audit —
@rlpdjs/observabilitylogger +cms_wf_audit(workflow) +cms_user_logs(login) + audit septet (created_by/updated_by/*_ip) ทุก mutation [NFR] - Attachments — คง pattern เดิม
*_ext(ชื่อไฟล์ใน DB, binary บนดิสก์/blob) ; ไฟล์ ≤10–50MB ; PDF/JPG - PII / PDPA —
idcard/bank/signature เข้ารหัส at-rest AES-256 ; ThaID ดึงข้อมูลแล้ว lock field - eSign —
cms_mediation_books.sign_method=thaid(ETDA link) |manual(manual_sign_json= เจ้าหน้าที่ยืนยัน)
4. ลำดับพัฒนา (suggested order)¶
- Foundation —
prisma db pullintrospectMEDIATE→ apply DELTA (NEW + ALTER) → AppModule + rlpdjs + Keycloak guard - Wave A (ของเดิมให้ทำงานเหมือนเดิม) — Mediation (005) · Mediator (002) · Center (003) · Training (001 ส่วน reuse) · Compensation (006) — port logic จาก V2
mssqlController*.jsขึ้น NestJS service - Wave B (ฟีเจอร์ใหม่ตาม mockup/SRS) — Workflow Engine (007) ก่อน (ฐานของ 004/010/015) → Exam bank (001) → Assessment Committee (004) → Document Template (011) → Process Cost (015) → Award (016) → Integration toggles (008)
- Wave C — Dashboard/reports (009/017 = views) · scheduled jobs (010/012) · public portal (013) · UX/bug/format (014)
- Data merge (นอก TOR — ดู §5) — reconcile mediate2020 (.43, 55K) ↔ MEDIATE (.149, 12K) → migration scripts → validate
5. V1 + V2 merge — สิ่งที่ schema นี้ "รองรับ" vs "ต้องทำต่อ"¶
ยืนยันจาก source V2 จริง (SSH 10.136.27.87 · 2026-06-17)
mediation-backend2025 (Express, active) = dual-DB / dual-API:
(1) MSSQL cms_* (MEDIATE) ผ่าน generic /mssql/:table (getall/get/join/add/update/...
+ uploadSignature/uploadPDF/updateEvidence/updateDocMoney) — mockup เรียกแบบนี้ตรง ๆ ;
(2) MySQL (mediation@localhost:3306, Sequelize) ผ่าน /model/:model — online mediation
(Webex) / eKYC / eSign / exam / online training · ldapjs+jsonwebtoken = auth · webexController/smsController/mailController/ssoController = integrations จริงที่มีอยู่
| ด้าน | schema DELTA นี้ครอบคลุม | ต้องทำต่อ (นอกขอบ schema) |
|---|---|---|
| โครงสร้าง MSSQL | ✅ ใช้ MEDIATE (.149) เป็นฐาน + DELTA |
diff คอลัมน์ vs mediate2020 (.43) — idcard varchar(20 vs 13), binary cols (evidence/doc_money/certificate ✅ ยืนยันจาก endpoint) |
| ฐาน MySQL (V2 Phase-1) | ✅ A9 (5 ตาราง online mediation) + cms_exam* = ปลายทาง migrate |
เขียน migration MySQL→MSSQL: meetings/meetingAttendances/signatures/signs/webex_auths/meetingReports/meeting_memos/exams/questions/trainings/registers/trainingAttendances/mediationsetting |
| ฟีเจอร์เดิม | ✅ REUSE 95 ตาราง + A9 = ทำงานเหมือนเดิม | port logic จาก mssqlController*.js (1,827 บรรทัด) + modelController ขึ้น NestJS · reverse-engineer ฟอร์ม PDF V1 PHP (K-CoP/ใบเสร็จ/ค่าเดินทาง) ที่ไม่มีใน V2 |
| ข้อมูล | — (schema ไม่ migrate ข้อมูล) | reconcile 55K(.43) vs 12K(.149) + ข้อมูล MySQL · migration script + validate |
| integration จริง | ✅ cms_integration_setting + cms_webex_auth |
port webexController/ssoController/smsController/mailController/ldapController |
| ฟีเจอร์ใหม่ | ✅ 28 ตาราง NEW + 7 ALTER ตาม mockup + SRS v4 | — |
5.1 MySQL → single DB migration map (ครบทั้ง 21 ตาราง — "ย้ายให้หมด")¶
นโยบาย V3 = single DB ไม่เหลือ MySQL · ทุกตารางใน mediation@3306 มีปลายทางใน MEDIATE:
| MySQL (V2) | rows/สถานะ | ปลายทางใน MEDIATE (single DB) |
วิธี |
|---|---|---|---|
meetings |
291 🟢 | cms_online_meeting (A9) |
migrate ตรง + reconcile กับ cms_mediation_booking_online (R) |
meetingAttendances |
1,677 🟢 | cms_meeting_attendance (A9, +eKYC) |
migrate ตรง |
signatures |
1,442 🟢 | cms_meeting_signature (A9) |
blob data → ไฟล์/signature_ext |
signs |
2,684 🟢 | cms_meeting_signature (A9, รวม sign_status) |
merge เข้ากับ signatures ตาม meetingAttendances_id |
meetingReports |
0 ⚪ | cms_meeting_report (A9) |
สร้าง schema รอ (ว่าง) |
meetingReportContents |
0 ⚪ | cms_meeting_report (คอลัมน์ content / child) |
fold-in |
meeting_memos |
6 🟡 | cms_meeting_report (report_type='memo') หรือ cms_mediation_books (R) |
migrate + map |
memo_users |
12 🟡 | cms_meeting_report (parties) / cms_mediation_witness (R) |
map คู่กรณี |
internalMeetings |
0 ⚪ | cms_online_meeting (category='internal') |
fold-in (ว่าง) |
webex_auths |
1 🟡 | cms_webex_auth (A9) |
migrate (secret → vault) |
meetingCategories |
2 🟡 | cms_setting (R) หรือ master เล็ก |
lookup |
users |
121 🟢 | cms_users (R, thin mirror) + cms_member (R) |
reconcile/merge ตาม id_card_number (ระวังซ้ำกับ MSSQL cms_users 3,185) · เลิก password/dn (LDAP) → identity จาก SSO (Keycloak+ThaiD) |
loginLogs |
276 🟢 | cms_user_logs (R) |
migrate ตรง |
images |
543 🟢 | ไฟล์ + *_ext (R, แบบ MSSQL) |
blob → file store |
mediationSettings |
1 🟡 | cms_setting (R, mediator_fee) |
key/value |
exams |
1 ⚪ | cms_exam (A3) |
stub — สร้างใหม่ตาม SRS (ไม่ต้อง migrate data) |
questions |
4 ⚪ | cms_exam_question (A3) |
stub |
courses |
1 ⚪ | cms_training_topic (R) |
stub |
trainings |
13 ⚪ | cms_training_topic (R, +webex cols ถ้าจำเป็น) |
stub/เก่า 2022 |
trainingAttendances |
4 ⚪ | cms_training_management_checkin (R) |
stub |
registers |
2 ⚪ | cms_training_register (R) |
stub |
🟢 ต้อง migrate data จริง · 🟡 migrate น้อย/lookup · ⚪ stub/ว่าง (สร้าง schema ใหม่ตาม SRS, ไม่ต้องยกข้อมูล) · หลัง migrate ครบ → ปิด MySQL + ชี้ backend NestJS ไป MEDIATE อย่างเดียว
ขอบเขต merge = Scope Change (ดู s4_combine_summary.md)
การรวม V1+V2 ไม่อยู่ใน TOR 7.14 (ระบุแค่ "ปรับปรุง V2") + มี 2 ฐานข้อมูลในตัว V2 เอง (MSSQL+MySQL) → effort +67–97 man-days · เอกสารนี้เตรียม schema target (V3) พร้อม แต่ reverse-engineer V1 / MySQL→MSSQL migration / cutover เป็นงานแยกที่ต้องขอ Change Request · แนะนำ Phased: ส่งมอบ DELTA ตาม TOR ตรงเวลา → รวมข้อมูลจริง (รวม MySQL store) เฟส 2