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

S2 — PJOS — Backend Implementation Blueprint

TARGET DESIGN

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

สรุปการตัดสินใจสถาปัตยกรรม (สร้าง backend ใหม่ + ใช้ shared lib rlpdjs)

rlpdjs = ไลบรารี ไม่ใช่ backend — package @rlpdjs/observability (repos/rlpdjs) เป็น shared NestJS library ที่ใช้ร่วมกันทุกระบบ S1…S9 ให้ structured JSON logging, distributed tracing (traceID/spanID ผ่าน AsyncLocalStorage), Prometheus metrics, HTTP client instrumentation, database query logging และ config management — format เข้ากันได้กับ go-fragx Prisma stub ของมันเป็น postgresql เพื่อ generate type ของไลบรารีเท่านั้น "consumer apps define their own schema with real models."

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

  1. สร้าง backend ใหม่ใน repos/pjos/be (ปัจจุบันเป็น placeholder NestJS ว่าง) — นี่คือ S2 backend จริง (pjos.rlpd.go.th)
  2. backend ตัวนี้ npm install @rlpdjs/observability เป็น dependency แล้ว wire ใน AppModule (ConfigModule/LoggerModule/PrometheusModule/HttpModule/DatabaseModule) — ได้ structured log → ELK ฟรี (NFR-M02) + Prometheus metrics + trace context อัตโนมัติ (zero boilerplate)
  3. อย่า เขียนโค้ดแอป S2 ไว้ใน repo rlpdjs — มันเป็นไลบรารีกลาง การแก้เพื่อ S2 จะกระทบทุกระบบ ; ถ้าต้องการความสามารถใหม่จาก lib → เสนอเป็น PR กลางที่ generic
  4. Schema กับ backend คนละชั้น — schema นี้ (design_p2/s2_pjos) เป็น contract ฐานข้อมูล ใช้ได้ไม่ว่า backend จะ deploy ที่ไหน
repos/
  pjos/
    fe/   ← React mockup (มีแล้ว) — contract ของ schema นี้
    be/   ← NestJS S2 backend (สร้างใหม่ที่นี่)
          └─ import @rlpdjs/observability   (logger / trace / metrics / db log / http)
          └─ Prisma (provider = sqlserver) → schema PJOS
  rlpdjs/ ← @rlpdjs/observability (ไลบรารีกลาง — ห้ามใส่ business logic ของ S2)

Wire @rlpdjs/observability ใน pjos/be

  • ติดตั้ง peer deps: @nestjs/common @nestjs/core reflect-metadata rxjs @prisma/client (+ -D prisma)
  • AppModule import โมดูลจาก lib:
    import { ConfigModule, LoggerModule, PrometheusModule, HttpModule, DatabaseModule } from '@rlpdjs/observability';
    // LoggerModule → JSON log + traceID/spanID อัตโนมัติ (NFR-M02)
    // PrometheusModule → /metrics
    // HttpModule → instrumented client สำหรับเรียก Service Center / S1 / S4 / S6 (REQ-008)
    // DatabaseModule.forRoot({ prisma }) → query log อัตโนมัติ
    
  • S2 ตั้ง Prisma ของตัวเอง ที่ pjos/be/prisma/schema.prisma:
    datasource db { provider = "sqlserver"; url = env("DATABASE_URL") }
    // DATABASE_URL=sqlserver://PSDBPRDDB...;schema=PJOS
    // models map ตรงกับตารางใน schema.sql (@@schema("PJOS"))
    
    แล้วส่ง new PrismaClient() เข้า DatabaseModule.forRoot({ prisma })
  • ทางเลือก ORM: schema.sql เป็น source of truth — ถ้าทีมถนัด TypeORM/raw ก็ได้ ; observability lib รองรับ Prisma เป็นหลัก (DB module) แต่ logger/http/metrics ใช้ได้กับทุก ORM

REQ → Endpoint → Service → ตาราง

REQ Endpoint (ตัวอย่าง REST) Service / Logic ตารางหลัก
001 POST /intakes (sync) · POST /intakes/:id/confirm · POST /cases/:id/rights รับ 4 ช่องทาง → dedup score → ยืนยันสร้าง Case → แจ้งสิทธิ 6 ข้อ ภายใน 24 ชม. IntakeRecord, Case, CaseRightsCheck
002 POST /cases/:id/reports · POST /reports/:id/submit · POST /reports/:id/approve รายงาน 2 รอบ (24 ชม./15 วัน) → route ตาม ApprovalStep (รอบ2 = หัวหน้างาน→ผู้บริหาร) OutreachReport, ApprovalAction, Notification
003 POST /cases · POST /cases/:id/victims บันทึกเคส + multi-victim + media consent + เลือกประเภทช่วยเหลือ/สน. จาก Master Case, CaseVictim, HelpType, PoliceStation
004 POST /cases/:id/assignees · PATCH /cases/:id/assignees มอบหมายหลายคน ; เปลี่ยนได้เฉพาะ SUPERVISOR/EXECUTIVE → เก็บประวัติ + PermissionLog(REASSIGN, บังคับเหตุผล) CaseAssignment, PermissionLog, OfficerProfile
005 POST /cases/:id/triggers · POST /cases/:id/plans · admin /rights-area-items ตั้ง gate 4 ด้าน (ไม่ครบ→NoPlanFlag) → เปิดฟอร์มแผนเฉพาะด้านที่เลือก ; admin CRUD รายการย่อย CaseTrigger, RightsArea, RightsAreaItem, AssistancePlan, PlanItem
006 POST /plans/:id/submit · POST /plans/:id/approve · POST /plans/:id/reject พิจารณาลำดับชั้น (หัวหน้า→ผอ.) + Comment ; REJECT=REWORK→สร้าง Version ใหม่ (SUPERSEDED ตัวเก่า) / TERMINATE AssistancePlan (versioned), ApprovalAction
007 POST /cases/:id/results · POST /cases/:id/terminate บันทึกผล Rich Text + บังคับแนบหลักฐาน + สถานะยุติ (DONE/NOT_DONE/UNABLE)+เหตุผล ; ขอยุติโปรแกรม ImplementationResult, Attachment, ProgramTermination
008 POST /cases/:id/referrals ส่งต่อ S1/S4/S6 ผ่าน HttpModule ของ rlpdjs (เก็บ ExternalRefId) / ภายนอก = Manual + เอกสาร Referral, Unit, Attachment
009 GET /cases?from&to&assignee&province · GET /cases/:id สืบค้น + scope จาก JWT (สยจ.=จังหวัดตน ; เชิงรุก=metadata-only ถ้าไม่ได้รับมอบหมาย) Case, CaseStatusHistory (query+scope)
010 GET /dashboard/* aggregate: เรื่อง (Case) vs ราย (CaseVictim), SLA 24 ชม./15 วัน, เพศ/อายุ/ฐานความผิด/สน. (query) Case/CaseVictim/OffenseBase
011 GET /reports/export · GET /cases/:id/export export รายบุคคล (รวมทุกขั้นตอน) + Dashboard เป็น .xls/.xlsx/.pdf/.csv (UI อ้าง OCIPA) (report engine) ; export→AuditLog
012 admin /master/* · (interceptor) Master Data CRUD + RBAC 5 บทบาท + global audit + PDPA masking/log SystemConfig, ApprovalStep, lookups, AuditLog, PermissionLog

Cross-cutting (NestJS pattern)

  • AuthN: JWT guard จาก S9 Web Portal SSO (ThaiD/AD federate) → req.user.sub → upsert AppUser (auto-provision) [OPEN-AUTH] — ไม่มี login เอง (ระบบไม่เปิดให้ประชาชน)
  • AuthZ: RBAC guard อ่าน role จาก JWT claim — ไม่เชื่อ RoleAssignment ในการตัดสินใจ (เป็น mirror เท่านั้น) ; การเปลี่ยนผู้รับมอบหมายจำกัด SUPERVISOR/EXECUTIVE
  • Province / case-visibility scope: interceptor ผูก ProvinceId + CaseAssignment จาก JWT → จำกัด query (REQ-009: สยจ.เห็นจังหวัดตน ; เจ้าหน้าที่เชิงรุกเห็นเคสไม่ได้รับมอบหมายแบบ metadata-only ปกปิด PII) — logic เดียวกับ canSeeDetail/maskPII ใน mockup
  • SLA engine: cron/job ตรวจ ContactDueAt/ReportDueAt → set *SlaBreached + Notification(SLA_WARNING) ทันทีเมื่อครบเวลา (NFR-P06)
  • Approval engine: อ่าน ApprovalStep (config) → สร้าง ApprovalAction ราย step ; REJECT บังคับ Comment + Decision (REWORK→Version ใหม่ / TERMINATE→ปิดเคส)
  • Audit + PDPA: global interceptor → AuditLog ทุก mutation (before/after JSON) ; การเปิดดูรายละเอียดผู้เสียหายเขียน Action='VIEW_PII' (NFR-S04) ; reassign/plan-reject บังคับ Reason
  • Attachment: upload → Object Storage (เข้ารหัส AES-256, ≥50MB/ไฟล์) → metadata ใน Attachment (polymorphic) ; กฎ "บันทึกผลต้องแนบหลักฐาน" บังคับที่ DTO (REQ-007) [NFR-S02/P04]
  • Integration (Inbound): Service Center (Line OA + ข่าว) → IntakeRecord (sync ≤5 นาที, NFR-P05) ; Master Data sync จาก OCIPA (HelpType/PoliceStation)
  • Validation: class-validator DTO ; เลขบัตร 13 หลัก, รูปแบบ CaseNo, RoundNo ∈ {1,2}
  • Observability: ทุก service ใช้ @rlpdjs/observability (Logger/Prometheus/Http/Database) → trace + metric + structured JSON log → ELK (NFR-M02), Swagger/OpenAPI 3.0 ครบทุก endpoint (NFR-M03)

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

  1. รัน schema.sql บน dev DB (schema PJOS) → generate Prisma models (prisma db pull) หรือเขียน models map @@schema("PJOS")
  2. Scaffold pjos/be NestJS + wire @rlpdjs/observability + S9 SSO JWT guard
  3. Module ตามโดเมน: intake/ (001), case/ (003,009), assignment/ (004), outreach/ (002), plan/ (005-006), implementation/ (007), referral/ (008), report/ (010-011), admin/ (012)
  4. seed lookup (Section 4 ของ schema.sql) → ต่อ frontend pjos/fe แทน mock data.jsx ทีละหน้าจอ
  5. Integration: Service Center inbound (001), OCIPA Master sync, S1/S4/S6 outbound (008), S9 SSO (auth)

ลำดับ frontend cutover

เริ่มจากหน้าจอที่ mock ตรง schema มากสุด — case-intake (Case+CaseVictim), rights-inbox (IntakeRecord), assignment (CaseAssignment) — แล้วค่อยทำ assistance-plan/plan-review (trigger gate + versioned plan + approval) ที่ logic ซับซ้อนกว่า