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

S1 — Legal Consultation System (LCS) — ER Diagram (Target Design)

TARGET DESIGN

ความสัมพันธ์ตาม schema.sql (FK ทุกเส้นถูกประกาศจริงใน DDL — solid line) · เป็น greenfield design modeled จาก mockup repos/lcs/fe — ยังไม่ลงฐานข้อมูลจริง · ดู data_dict.md และ index.md

ทุกตาราง business มี audit columns CreatedBy/UpdatedBy (int FK → AppUser) — ละไว้ในไดอะแกรมเพื่อความชัด วาดเฉพาะ FK เชิงความหมาย (ownership / actor) · audit/soft-delete ละไว้ทั้งหมด · รายการ FK ครบถ้วนอยู่ที่ตารางสรุปท้ายเอกสาร

ไดอะแกรมแยกตาม domain — ตารางที่เป็นสะพานข้าม domain (Consultant, ConsultationRequest, Province, DutySession) จะปรากฏมากกว่าหนึ่งบล็อก

Auth & Identity (thin SSO mirror)

erDiagram
    Province ||--o{ AppUser        : "ProvinceId"
    AppUser  ||--o{ RoleAssignment : "AppUserId"
    Role     ||--o{ RoleAssignment : "RoleId"
    Province ||--o{ RoleAssignment : "ProvinceId (scope)"
    AppUser  ||--o{ RoleAssignment : "GrantedBy"
    AppUser  ||--o{ PermissionLog  : "TargetUserId"

AppUser เป็น mirror จาก SSO กลาง (ไม่มี password) · PermissionLog เป็น append-only บันทึกการ GRANT/CHANGE/REVOKE สิทธิ์ (บังคับเหตุผล, REQ-005)

Consultant Registry (ทะเบียนที่ปรึกษา + วาระ + เพิกถอน)

erDiagram
    ImportBatch          ||--o{ ImportError           : "ImportBatchId"
    ImportBatch          ||--o{ ConsultantApplication : "SourceImportBatchId"
    ImportBatch          ||--o{ ConsultantTerm        : "SourceImportBatchId"

    ConsultantApplication }o--o| Consultant           : "ConsultantId (on approve)"
    AppUser              ||--o{ ConsultantApplication : "ReviewedBy / ApprovedBy"

    Consultant           ||--o{ ConsultantSpecialty   : "ConsultantId"
    Specialty            ||--o{ ConsultantSpecialty   : "SpecialtyId"
    Consultant           ||--o{ ConsultantProvince    : "ConsultantId"
    Province             ||--o{ ConsultantProvince    : "ProvinceId"
    Consultant           ||--o{ ConsultantTerm        : "ConsultantId"
    Consultant           ||--o{ ConsultantRevocation  : "ConsultantId"
    AppUser              ||--o| Consultant            : "AppUserId (self-service)"

ConsultantApplication คือ workflow ลงทะเบียน (ตรวจคุณสมบัติ → อนุมัติ) → เมื่ออนุมัติสร้าง/ผูก Consultant · spec[] ของ mockup → junction ConsultantSpecialty · "1 คนหลายจังหวัด" → junction ConsultantProvince · ConsultantTerm = วาระ 3 ปี (รอบนำเข้าใหม่ = วาระใหม่) · ConsultantRevocation = append-only การเพิกถอน

Duty Roster & Attendance (แผนเวร vs ปฏิบัติจริง)

erDiagram
    Province    ||--o{ DutyRoster            : "ProvinceId"
    ImportBatch ||--o{ DutyRoster            : "SourceImportBatchId"
    DutyRoster  ||--o{ DutySlot              : "DutyRosterId"
    Consultant  ||--o{ DutySlot              : "PlannedConsultantId"
    Consultant  ||--o{ ConsultantAvailability : "ConsultantId"

    DutySlot    ||--o{ DutySession           : "DutySlotId"
    Province    ||--o{ DutySession           : "ProvinceId"
    Consultant  ||--o{ DutySession           : "ConsultantId (actual)"
    Consultant  ||--o{ DutySession           : "ReplacedConsultantId"
    DutySlot    ||--o{ DutyCancellation      : "DutySlotId"
    Consultant  ||--o{ DutyCancellation      : "ConsultantId / ReplacementConsultantId"

DutyRoster/DutySlot = แผน (อัปโหลด Template) · DutySession = ชั่วโมงจริง (แหล่งคำนวณค่าตอบแทน, รองรับผู้มาแทน + flag นับสถิติ) · DutyCancellation = สถิติการยกเลิก + ผู้แทน (REQ-009)

Consultation Requests (คำขอ → มอบหมาย → ผล → QA → Escalation)

erDiagram
    Channel            ||--o{ ConsultationRequest   : "ChannelId"
    Province           ||--o{ ConsultationRequest   : "ProvinceId"
    CaseType           ||--o{ ConsultationRequest   : "CaseTypeId"
    ConsultationTopic  ||--o{ ConsultationRequest   : "TopicId"
    ConsultationTopic  ||--o{ ConsultationTopic     : "ParentTopicId"

    ConsultationRequest ||--o{ RequestAssignment    : "RequestId"
    Consultant          ||--o{ RequestAssignment    : "ConsultantId"
    AppUser             ||--o{ RequestAssignment    : "AssignedBy / AssigneeUserId"

    ConsultationRequest ||--o{ ConsultationResult   : "RequestId"
    Consultant          ||--o{ ConsultationResult   : "ConsultantId"
    ConsultationRequest ||--o{ QAReview             : "RequestId"
    ConsultationResult  ||--o{ QAReview             : "ResultId"
    AppUser             ||--o{ QAReview             : "ReviewerId"
    QAReview            ||--o{ QAReviewItem         : "QAReviewId"
    ConsultationRequest ||--o{ Escalation           : "RequestId"
    ConsultationRequest ||--o{ RequestStatusHistory : "RequestId"

ConsultationResult เป็น versioned (แก้ไข/บันทึกย้อนหลังได้, REQ-007) · QAReview = pass/fail + ตีกลับ · Escalation ส่งต่อ OCIPA ผ่าน Web Service (เก็บ ExternalRefId) · RequestStatusHistory = append-only รองรับ public tracking + audit

Compensation (ค่าตอบแทน)

erDiagram
    Province                 ||--o{ CompensationCalculation : "ProvinceId"
    Consultant               ||--o{ CompensationCalculation : "ConsultantId"
    CompensationCalculation  ||--o{ CompensationItem        : "CompensationId"
    DutySession              ||--o{ CompensationItem        : "DutySessionId"

CompensationCalculation = รอบคำนวณต่อที่ปรึกษา/จังหวัด (8 ชม.=1,000, ไม่ครบไม่จ่าย) · CompensationItem โยงแต่ละบรรทัดกลับ DutySession (ชั่วโมงจริง) เพื่อตรวจสอบย้อนกลับ · Status='SENT_S5' + S5Reference = ส่งต่อระบบ S5 [OPEN-S5]

สรุป FK ทั้งหมด (รวม audit CreatedBy/UpdatedBy)

ทุกตาราง business มี CreatedBy/UpdatedByLCS.AppUser(AppUserId) (nullable — รองรับ system/import rows) นอกเหนือจาก FK เชิงความหมายที่วาดด้านบน · ดู Section 2 ของ schema.sql สำหรับรายการ ALTER TABLE ... ADD CONSTRAINT FK_* ครบทุกเส้น (รวม 50+ FK)

ตาราง FK เชิงความหมายหลัก
AppUser → Province
RoleAssignment → AppUser, Role, Province, AppUser(GrantedBy)
ConsultantApplication → ImportBatch, Consultant, AppUser(ReviewedBy/ApprovedBy)
Consultant → AppUser
ConsultantSpecialty / ConsultantProvince → Consultant + Specialty / Province
ConsultantTerm → Consultant, ImportBatch
ConsultantRevocation → Consultant, AppUser(RevokedBy)
ImportError → ImportBatch
DutyRoster → Province, ImportBatch
DutySlot → DutyRoster, Consultant(Planned)
DutySession → DutySlot, Province, Consultant, Consultant(Replaced)
DutyCancellation → DutySlot, Consultant, Consultant(Replacement)
ConsultantAvailability → Consultant
ConsultationRequest → Channel, Province, CaseType, ConsultationTopic
RequestAssignment → ConsultationRequest, Consultant, AppUser(Assignee/By)
ConsultationResult → ConsultationRequest, Consultant
QAReview → ConsultationRequest, ConsultationResult, AppUser(Reviewer)
QAReviewItem → QAReview
Escalation → ConsultationRequest, AppUser(SentBy)
RequestStatusHistory → ConsultationRequest, AppUser(ChangedBy)
CompensationCalculation → Province, Consultant
CompensationItem → CompensationCalculation, DutySession
Notification → AppUser(Recipient)
PermissionLog → AppUser(Target)
ConsultationTopic → ConsultationTopic(Parent, self-ref)