ข้ามไปที่เนื้อหา

S1 — LCS — Backend Implementation Blueprint

TARGET DESIGN

พิมพ์เขียวสำหรับทีมพัฒนา backend ของ S1 ตาม schema schema.sql · ดู index.md (traceability + mockup→target)

สรุปการตัดสินใจสถาปัตยกรรม (ตอบ: "สร้าง backend ใหม่ หรือใช้ร่วมกับ rlpdjs?")

rlpdjs = ไลบรารี ไม่ใช่ backend — package @rlpdjs/observability เป็น shared NestJS library (logger / tracing / Prometheus / HTTP client / DB query log) Prisma stub ของมันเป็น postgresql เพื่อ generate type ของไลบรารีเท่านั้น README ระบุชัด "consumer apps define their own schema with real models."

แนวทางที่ถูกต้อง:

  1. สร้าง backend ใหม่ใน repos/lcs/be (ปัจจุบันเป็น placeholder NestJS ว่าง) — นี่คือ S1 backend จริง (lcs-api.rlpd.go.th)
  2. backend ตัวนี้ npm install @rlpdjs/observability เป็น dependency แล้วเรียกใช้ใน AppModule (LoggerModule/PrometheusModule/HttpModule/DatabaseModule) — ได้ structured log → ELK ฟรี (NFR-M02)
  3. อย่า เขียนโค้ดแอป S1 ไว้ใน repo rlpdjs — มันเป็นไลบรารีกลางที่ทุกระบบ (S1…S9) ใช้ร่วม
  4. Schema กับ backend คนละชั้น — schema นี้ (design_p2/s1_lcs) เป็น contract ฐานข้อมูล ใช้ได้ไม่ว่า backend จะ deploy ที่ไหน
repos/
  lcs/
    fe/   ← React mockup (มีแล้ว) — contract ของ schema นี้
    be/   ← NestJS S1 backend (สร้างใหม่ที่นี่)
          └─ import @rlpdjs/observability
          └─ Prisma (provider = sqlserver) → schema LCS
  rlpdjs/ ← @rlpdjs/observability (ไลบรารีกลาง — ห้ามใส่ business logic ของ S1)

Prisma + MSSQL + observability (สำคัญ)

  • rlpdjs/prisma/schema.prisma มี provider = "postgresql" แต่เป็น stub ของไลบรารีเอง — ไม่กระทบ consumer
  • S1 backend ตั้ง Prisma ของตัวเอง ที่ lcs/be/prisma/schema.prisma:
    datasource db { provider = "sqlserver"; url = env("DATABASE_URL") }
    // DATABASE_URL=sqlserver://PSDBPRDDB...;schema=LCS
    // models map ตรงกับตารางใน schema.sql (@@schema("LCS"))
    
  • ส่ง new PrismaClient() เข้า DatabaseModule.forRoot({ prisma }) ของ @rlpdjs/observability → ได้ query log อัตโนมัติ
  • ทางเลือก: ถ้าทีมถนัด TypeORM/raw มากกว่า Prisma ก็ได้ — schema.sql เป็น source of truth ; observability lib รองรับ Prisma เป็นหลัก (DB module) แต่ logger/http/metrics ใช้ได้กับทุก ORM

REQ → Endpoint → Service → ตาราง

REQ Endpoint (ตัวอย่าง REST) Service / Logic ตารางหลัก
001 POST /consultant-applications · POST /:id/review · POST /:id/approve สร้างคำขอ → ตรวจคุณสมบัติ → DIRECTOR อนุมัติ → สร้าง Consultant + ConsultantTerm (3 ปี) ConsultantApplication, Consultant, ConsultantSpecialty, ConsultantProvince, ConsultantTerm, Attachment, ApprovalStep
002 GET /me/consultant · GET /me/duty self-service: สถานะ + วาระ + ตารางเวร + ประวัติ (อ่านจาก JWT sub → Consultant.AppUserId) Consultant, ConsultantTerm, DutySlot, DutySession
003 POST /imports (multipart) · POST /imports/:id/commit parse .xls/.xlsx/.csv → validate ราย row → preview error → commit ImportBatch, ImportError, Consultant/DutyRoster
004 POST /consultants/:id/revoke · job age75-auto-revoke เพิกถอน + เหตุผล + แนบไฟล์ ; cron ตรวจอายุ 75 → auto ConsultantRevocation, Consultant, Notification, Attachment
005 POST /users/:id/roles · DELETE /users/:id/roles/:role grant/revoke (บังคับ Reason) → mirror SSO + เขียน PermissionLog RoleAssignment, PermissionLog, Role
006 POST /requests · POST /requests/:id/assign บันทึกคำขอจากช่องทาง + มอบหมาย (แสดงเฉพาะที่ปรึกษา ACTIVE + ว่างจาก DutySession/ConsultantAvailability) ConsultationRequest, RequestAssignment, Attachment
007 POST /requests/:id/result · POST /:id/qa บันทึกผล (versioned) → QA pass/fail → fail = เปิดแก้ + RequestStatusHistory + Notification ConsultationResult, QAReview, RequestStatusHistory, Notification
008 POST /requests/:id/escalate ส่ง Web Service → OCIPA (ผ่าน HttpModule ของ rlpdjs) → เก็บ ExternalRefId Escalation, ConsultationRequest
009 POST /me/availability · POST /rosters · POST /duty/:id/cancel แจ้งวันว่าง / อัปโหลดเวร / ยกเลิก + ผู้แทน (สถิติ) ConsultantAvailability, DutyRoster, DutySlot, DutySession, DutyCancellation
010 POST /compensations/calculate · GET /compensations คำนวณ 8 ชม.=1,000 จาก DutySession (scope จังหวัดจาก JWT) → export → SENT_S5 CompensationCalculation, CompensationItem, DutySession
011 GET /dashboard/* · GET /reports/export aggregate query (Top 5 topic drill-down, channel, demographic) + export (query) ConsultationRequest/Channel/ConsultationTopic ; export→AuditLog
012 GET /public/track/:referenceNo public (ไม่ต้อง login) — map StatusPublicStatus ผ่าน SystemConfig ConsultationRequest, RequestStatusHistory, SystemConfig
013 (middleware/interceptor) NestJS interceptor เขียน AuditLog ทุก mutation (before/after JSON) AuditLog, PermissionLog

Cross-cutting (NestJS pattern)

  • AuthN: Keycloak JWT guard (ThaiD/AD federate) → req.user.sub → upsert AppUser (auto-provision) [OPEN-AUTH]
  • AuthZ: RBAC guard อ่าน role จาก JWT claim — ไม่เชื่อ RoleAssignment ในการตัดสินใจ (เป็น mirror เท่านั้น)
  • Province scope: interceptor ผูก ProvinceId จาก JWT → จำกัด query (REQ-010: จังหวัดเห็นเฉพาะตน, ส่วนกลางเห็นหมด)
  • Audit: global interceptor → AuditLog (REQ-013) ; การกระทำที่ requiresReason (role.update/lawyer.revoke/qa.reject) บังคับ field reason
  • Attachment: upload → object storage (เข้ารหัส AES-256) → เก็บ metadata ใน Attachment (polymorphic) [NFR-S02]
  • Validation: class-validator DTO ; เลขบัตร 13 หลัก, รูปแบบ ReferenceNo
  • Notification: event-driven → Notification (in-app) + อีเมล/LINE (ภายหลัง)
  • Observability: ทุก service ใช้ @rlpdjs/observability (LoggerService/PrometheusService) → trace + metric + structured JSON log → ELK (NFR-M02), Swagger/OpenAPI 3.0 (NFR-M03)

ลำดับงานพัฒนา (suggested)

  1. รัน schema.sql บน dev DB (schema LCS) → generate Prisma models (prisma db pull) หรือเขียน models map @@schema("LCS")
  2. Scaffold lcs/be NestJS + wire @rlpdjs/observability + Keycloak guard
  3. Module ตามโดเมน: consultant/ (001-004), auth-admin/ (005), request/ (006-008,012), roster/ (009), compensation/ (010), report/ (011), audit/ (013)
  4. seed lookup (Section 4 ของ schema.sql) → ต่อ frontend lcs/fe แทน mock data.jsx ทีละหน้าจอ
  5. Integration: OCIPA Web Service (008), S5 handoff (010, [OPEN-S5]), ThaiD (auth)

ลำดับ frontend cutover

เริ่มจากหน้าจอที่ mock ตรง schema มากสุด — lawyer-db (Consultant), inbox/create-request (ConsultationRequest), qa-pending (QAReview) — แล้วค่อยทำ roster/compensation ที่ logic ซับซ้อนกว่า