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-fragxPrisma stub ของมันเป็นpostgresqlเพื่อ generate type ของไลบรารีเท่านั้น "consumer apps define their own schema with real models."
แนวทางที่ถูกต้อง:
- สร้าง backend ใหม่ใน
repos/pjos/be(ปัจจุบันเป็น placeholder NestJS ว่าง) — นี่คือ S2 backend จริง (pjos.rlpd.go.th) - backend ตัวนี้
npm install @rlpdjs/observabilityเป็น dependency แล้ว wire ในAppModule(ConfigModule/LoggerModule/PrometheusModule/HttpModule/DatabaseModule) — ได้ structured log → ELK ฟรี (NFR-M02) + Prometheus metrics + trace context อัตโนมัติ (zero boilerplate) - อย่า เขียนโค้ดแอป S2 ไว้ใน repo
rlpdjs— มันเป็นไลบรารีกลาง การแก้เพื่อ S2 จะกระทบทุกระบบ ; ถ้าต้องการความสามารถใหม่จาก lib → เสนอเป็น PR กลางที่ generic - 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) AppModuleimport โมดูลจาก 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→ upsertAppUser(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)¶
- รัน
schema.sqlบน dev DB (schemaPJOS) → generate Prisma models (prisma db pull) หรือเขียน models map@@schema("PJOS") - Scaffold
pjos/beNestJS + wire@rlpdjs/observability+ S9 SSO JWT guard - Module ตามโดเมน:
intake/(001),case/(003,009),assignment/(004),outreach/(002),plan/(005-006),implementation/(007),referral/(008),report/(010-011),admin/(012) - seed lookup (Section 4 ของ schema.sql) → ต่อ frontend
pjos/feแทน mockdata.jsxทีละหน้าจอ - 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 ซับซ้อนกว่า