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

S3 — Activity Report & Course Registration (PRC) — ER Diagram (Target Design)

TARGET DESIGN

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

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

ไดอะแกรมแยกเป็นหลายบล็อกตาม domain เพื่อให้อ่านง่าย — ตารางที่เป็นสะพานข้าม domain (เช่น Course, Batch, Member, Registration) จะปรากฏมากกว่าหนึ่งบล็อก

Auth & Identity (thin SSO mirror)

erDiagram
    OrgUnit  ||--o{ OrgUnit        : "ParentOrgUnitId"
    OrgUnit  ||--o{ AppUser        : "OrgUnitId"
    Trainer  ||--o{ AppUser        : "TrainerId"
    Member   ||--o{ AppUser        : "MemberId"

    AppUser  ||--o{ RoleAssignment : "AppUserId"
    Role     ||--o{ RoleAssignment : "RoleId"
    AppUser  ||--o{ RoleAssignment : "GrantedBy"

AppUser เป็น mirror จาก SSO กลาง (ไม่มีคอลัมน์ password) · สิทธิ์จริงมาจาก JWT — Role/RoleAssignment เป็น catalog/mirror ภายใน · AppUser อ้างกลับไป Trainer/Member (nullable) เมื่อ user เป็นวิทยากร/ศิษย์เก่า — เกิด cycle เชิงความหมายกับ Trainer.AppUserId ซึ่งจัดการได้เพราะทุกเส้นเป็น nullable

Course / Project / Trainer

erDiagram
    Province ||--o{ Course         : "ProvinceId"
    Trainer  ||--o{ Course         : "TrainerId"

    Project  ||--o{ ProjectCourse  : "ProjectId"
    Course   ||--o{ ProjectCourse  : "CourseId"
    Course   ||--o{ CoursePrerequisite : "CourseId"
    Course   ||--o{ CoursePrerequisite : "PrerequisiteCourseId"
    Project  ||--o{ BudgetCalculation : "ProjectId"

    OrgUnit  ||--o{ Trainer            : "OrgUnitId"
    AppUser  ||--o{ Trainer            : "AppUserId"
    Trainer  ||--o{ TrainerSpecialty   : "TrainerId"
    Trainer  ||--o{ TrainerEditRequest : "TrainerId"
    AppUser  ||--o{ TrainerEditRequest : "ReviewedBy"

ProjectCourse คือ junction N:M ระหว่าง Project กับ Course · CoursePrerequisite คือ self-reference N:M บน Course (หลักสูตร→หลักสูตรที่ต้องผ่านก่อน, REQ-008) · BudgetCalculation เป็น snapshot ค่าใช้จ่ายต่อหัวของ Project · TrainerEditRequest คือ workflow ร่าง → ADMIN อนุมัติ (ReviewedBy = ผู้พิจารณา)

Member / Enrollment / Registration

erDiagram
    MemberType ||--o{ Member       : "MemberTypeId"
    OrgUnit    ||--o{ Member       : "OrgUnitId"
    Province   ||--o{ Member       : "ProvinceId"

    Member     ||--o{ Enrollment   : "MemberId"
    Course     ||--o{ Enrollment   : "CourseId"
    Batch      ||--o{ Enrollment   : "BatchId"

    Course     ||--o{ Registration : "CourseId"
    MemberType ||--o{ Registration : "MemberTypeId"
    Member     ||--o{ Registration : "MemberId"
    AppUser    ||--o{ Registration : "ReviewedBy"

Enrollment คือ junction Member × Course (+Batch รุ่น) = ศิษย์เก่า/สมาชิกในหลักสูตร · Registration คือคำขอลงทะเบียน + คิวอนุมัติ — ReviewedBy = OFFICER_KSS ผู้พิจารณา, MemberId ผูกภายหลังอนุมัติ

Batch / Certificate

erDiagram
    Course      ||--o{ Batch              : "CourseId"

    Course      ||--o{ Certificate        : "CourseId"
    Member      ||--o{ Certificate        : "MemberId"
    Batch       ||--o{ Certificate        : "BatchId"
    Certificate ||--o{ CertificatePrintLog : "CertificateId"
    AppUser     ||--o{ CertificatePrintLog : "PrintedBy"

Batch = รุ่นของหลักสูตร · Certificate (e-cert) ออกให้ Member ต่อ Course/Batch · CertificatePrintLog เป็น append-only log บันทึกการพิมพ์ทุกครั้ง (ORIGINAL/COPY) — PrintedBy = ผู้พิมพ์

Equipment

erDiagram
    AppUser   ||--o{ Equipment                 : "ResponsibleUserId"
    Equipment ||--o{ EquipmentOwnershipHistory : "EquipmentId"
    AppUser   ||--o{ EquipmentOwnershipHistory : "FromUserId"
    AppUser   ||--o{ EquipmentOwnershipHistory : "ToUserId"
    AppUser   ||--o{ EquipmentOwnershipHistory : "ChangedBy"

Equipment = ครุภัณฑ์ (ผู้รับผิดชอบ ResponsibleUserId map ไป AppUser ได้, มี free-text fallback ResponsibleName) · EquipmentOwnershipHistory append-only บันทึกการเปลี่ยนผู้รับผิดชอบ (FromUserIdToUserId, ChangedBy = ผู้บันทึก)

Knowledge / Notification / Audit

erDiagram
    KnowledgeType ||--o{ KnowledgeReport   : "KnowledgeTypeId"
    OrgUnit       ||--o{ KnowledgeReport   : "OrgUnitId"
    Province      ||--o{ KnowledgeReport   : "ProvinceId"
    AppUser       ||--o{ KnowledgeReport   : "SubmittedBy"
    AppUser       ||--o{ KnowledgeReport   : "FlaggedBy"

    Registration  ||--o{ EmailNotification : "RegistrationId"
    EmailNotification ||--o{ EmailAttempt  : "EmailNotificationId"

    AppUser       ||--o{ AuditLog          : "ActorUserId"

KnowledgeReport คือรายงานการเผยแพร่ความรู้ — SubmittedBy = OFFICER_OTHER (data-scoping sees-own), FlaggedBy = ADMIN ผู้ flag · EmailNotification 1 ต่อ Registration (แจ้งผลลงทะเบียน), EmailAttempt append-only ทุกครั้งที่พยายามส่ง (รองรับ retry/backoff) · AuditLog append-only, denormalized — ActorUserId nullable (NULL = system)

Attachment (polymorphic — ไม่มี FK ไปยังเจ้าของ)

PRC.Attachment เป็นตารางแนบไฟล์แบบ polymorphic ตัวเดียว ใช้ร่วมหลาย entity ผ่านคู่คอลัมน์ OwnerEntityType + OwnerEntityId (OwnerEntityType ∈ COURSE/PROJECT/TRAINER/REGISTRATION/KNOWLEDGE_REPORT/MEMBER) · ไม่มี FK ระดับ DB ไปยังตารางเจ้าของ (เป็นความสัมพันธ์เชิงตรรกะ resolve ที่ชั้นแอป) จึงไม่วาดเส้นในไดอะแกรม · FK เดียวที่มีจริงคือ Attachment.UploadedByAppUser (ผู้อัปโหลด)

Foreign Keys (สรุป)

ตารางครบทุก FK ที่ประกาศใน schema.sql Section 2 (รวม audit CreatedBy/UpdatedBy) — schema PRC ทุกตาราง

Child Table Column Parent Table Parent Column
OrgUnit ParentOrgUnitId OrgUnit OrgUnitId
AppUser OrgUnitId OrgUnit OrgUnitId
AppUser TrainerId Trainer TrainerId
AppUser MemberId Member MemberId
AppUser CreatedBy AppUser AppUserId
AppUser UpdatedBy AppUser AppUserId
RoleAssignment AppUserId AppUser AppUserId
RoleAssignment RoleId Role RoleId
RoleAssignment GrantedBy AppUser AppUserId
Course ProvinceId Province ProvinceId
Course TrainerId Trainer TrainerId
Course CreatedBy AppUser AppUserId
Course UpdatedBy AppUser AppUserId
ProjectCourse ProjectId Project ProjectId
ProjectCourse CourseId Course CourseId
ProjectCourse CreatedBy AppUser AppUserId
CoursePrerequisite CourseId Course CourseId
CoursePrerequisite PrerequisiteCourseId Course CourseId
CoursePrerequisite CreatedBy AppUser AppUserId
Project OwnerUserId AppUser AppUserId
Project CreatedBy AppUser AppUserId
Project UpdatedBy AppUser AppUserId
BudgetCalculation ProjectId Project ProjectId
BudgetCalculation ConfirmedBy AppUser AppUserId
BudgetCalculation CreatedBy AppUser AppUserId
BudgetCalculation UpdatedBy AppUser AppUserId
Trainer OrgUnitId OrgUnit OrgUnitId
Trainer AppUserId AppUser AppUserId
Trainer CreatedBy AppUser AppUserId
Trainer UpdatedBy AppUser AppUserId
TrainerSpecialty TrainerId Trainer TrainerId
TrainerEditRequest TrainerId Trainer TrainerId
TrainerEditRequest RequestedBy AppUser AppUserId
TrainerEditRequest ReviewedBy AppUser AppUserId
TrainerEditRequest CreatedBy AppUser AppUserId
TrainerEditRequest UpdatedBy AppUser AppUserId
Member MemberTypeId MemberType MemberTypeId
Member OrgUnitId OrgUnit OrgUnitId
Member ProvinceId Province ProvinceId
Member CreatedBy AppUser AppUserId
Member UpdatedBy AppUser AppUserId
Enrollment MemberId Member MemberId
Enrollment CourseId Course CourseId
Enrollment BatchId Batch BatchId
Enrollment CreatedBy AppUser AppUserId
Enrollment UpdatedBy AppUser AppUserId
Registration CourseId Course CourseId
Registration MemberTypeId MemberType MemberTypeId
Registration MemberId Member MemberId
Registration ReviewedBy AppUser AppUserId
Registration CreatedBy AppUser AppUserId
Registration UpdatedBy AppUser AppUserId
Batch CourseId Course CourseId
Batch CreatedBy AppUser AppUserId
Batch UpdatedBy AppUser AppUserId
Certificate CourseId Course CourseId
Certificate MemberId Member MemberId
Certificate BatchId Batch BatchId
Certificate CreatedBy AppUser AppUserId
Certificate UpdatedBy AppUser AppUserId
CertificatePrintLog CertificateId Certificate CertificateId
CertificatePrintLog PrintedBy AppUser AppUserId
Equipment ResponsibleUserId AppUser AppUserId
Equipment CreatedBy AppUser AppUserId
Equipment UpdatedBy AppUser AppUserId
EquipmentOwnershipHistory EquipmentId Equipment EquipmentId
EquipmentOwnershipHistory FromUserId AppUser AppUserId
EquipmentOwnershipHistory ToUserId AppUser AppUserId
EquipmentOwnershipHistory ChangedBy AppUser AppUserId
KnowledgeReport KnowledgeTypeId KnowledgeType KnowledgeTypeId
KnowledgeReport OrgUnitId OrgUnit OrgUnitId
KnowledgeReport ProvinceId Province ProvinceId
KnowledgeReport SubmittedBy AppUser AppUserId
KnowledgeReport FlaggedBy AppUser AppUserId
KnowledgeReport CreatedBy AppUser AppUserId
KnowledgeReport UpdatedBy AppUser AppUserId
EmailNotification RegistrationId Registration RegistrationId
EmailNotification CreatedBy AppUser AppUserId
EmailNotification UpdatedBy AppUser AppUserId
EmailAttempt EmailNotificationId EmailNotification EmailNotificationId
AuditLog ActorUserId AppUser AppUserId
Attachment UploadedBy AppUser AppUserId