Skip to content

System-Wide Entity-Relationship Diagram

Auto-generated by c4docgen on 2026-04-27. Do not edit manually — regenerate with ./gradlew generateSystemErd.

erDiagram

    %% ── Attendance ──
    School ||--o{ AttendanceRecord : "has many"
    Student ||--o{ AttendanceRecord : "has many"
    Classroom ||--o{ AttendanceRecord : "has many"
    PickupPerson ||--o{ AttendanceRecord : "has many"
    School ||--o{ AttendanceSetting : "has many"
    School ||--o{ PickupPerson : "has many"
    School ||--o{ SectionSchedule : "has many"
    Section ||--o{ SectionSchedule : "has many"
    Student ||--o{ StudentPickupPerson : "has many"
    PickupPerson ||--o{ StudentPickupPerson : "has many"

    %% ── Canteen ──
    Student ||--o{ CanteenOrder : "has many"
    MealTicket ||--o{ CanteenOrder : "has many"
    Menu ||--o{ CanteenOrder : "has many"
    School ||--o{ CanteenOrder : "has many"
    School ||--o{ CanteenSetting : "has many"
    CateringService }o--o{ CanteenUser : "linked via"
    School }o--o{ CanteenUser : "linked via"
    School ||--o{ CateringService : "has many"
    CateringService ||--o{ Item : "has many"
    ItemCategory ||--o{ Item : "has many"
    School ||--o{ Item : "has many"
    School ||--o{ ItemCategory : "has many"
    Item ||--o{ ItemVariant : "has many"
    Menu ||--o{ MealTicket : "has many"
    School ||--o{ MealTicket : "has many"
    School ||--o{ Menu : "has many"
    Menu }o--o{ MenuAudience : "linked via"
    Campus }o--o{ MenuAudience : "linked via"
    Classroom }o--o{ MenuAudience : "linked via"
    MenuSection ||--o{ MenuSchedule : "has many"
    Item ||--o{ MenuSchedule : "has many"
    Menu ||--o{ MenuSection : "has many"
    Item ||--o{ OrderItem : "has many"
    MenuSection ||--o{ OrderItem : "has many"
    MealTicket }o--o{ TicketPrice : "linked via"

    %% ── Sample ──

    %% ── School ──
    AcademicYear ||--o{ AcademicHoliday : "has many"
    AcademicPeriod ||--o{ AcademicHoliday : "has many"
    AcademicYear ||--o{ AcademicPeriod : "has many"
    School ||--o{ AcademicYear : "has many"
    Campus ||--o{ AcademicYear : "has many"
    Section ||--o{ AcademicYear : "has many"
    School ||--o{ Campus : "has many"
    Section ||--o{ Classroom : "has many"
    School ||--o{ Family : "has many"
    Family ||--o{ Guardian : "has many"
    School ||--o{ SchoolStaff : "has many"
    Campus ||--o{ SchoolStaff : "has many"
    Campus ||--o{ Section : "has many"
    School ||--o{ Student : "has many"
    Classroom ||--o{ Student : "has many"
    Family ||--o{ Student : "has many"
    Student ||--o{ StudentGuardian : "has many"
    Guardian ||--o{ StudentGuardian : "has many"

    %% ── Wallet ──
    Student }o--o{ WalletBalance : "linked via"
    School }o--o{ WalletBalance : "linked via"

    %% ── Attendance entities ──
    AttendanceRecord {
        UUID id PK
        UUID schoolId FK
        UUID studentId FK
        UUID classroomId FK
        LocalDate date
        Instant checkInTime
        UUID checkInPerformedById FK
        String checkInPerformedByName
        CheckMethod checkInMethod "SCAN, MANUAL"
        Instant checkOutTime
        UUID checkOutPerformedById FK
        String checkOutPerformedByName
        CheckMethod checkOutMethod "SCAN, MANUAL"
        UUID dropoffPersonId FK
        String dropoffPersonName
        UUID pickupPersonId FK
        String pickupPersonName
        ArrivalClassification arrivalClassification "EARLY, ON_TIME, LATE"
        DepartureClassification departureClassification "EARLY, ON_TIME, LATE"
        AttendanceStatus status "PRESENT, ABSENT, LATE, EXCUSED"
        String excuseReason
        boolean isUnaccompanied
        Instant createdAt
        Instant updatedAt
    }

    AttendanceSetting {
        UUID id PK
        UUID schoolId FK
        boolean parentSelfCheckInEnabled
        AttendanceMode attendanceMode "SCAN, MANUAL, BOTH"
        LocalTime selfCheckInStartTime
        LocalTime selfCheckInEndTime
        Instant createdAt
        Instant updatedAt
    }

    PickupPerson {
        UUID id PK
        UUID schoolId FK
        String fullName
        String phoneNumber
        String relationship
        String photoUrl
        Instant createdAt
        Instant updatedAt
    }

    SectionSchedule {
        UUID id PK
        UUID schoolId FK
        UUID sectionId FK
        LocalTime startTime
        LocalTime endTime
        int lateThresholdMinutes
        int pickupLateThresholdMinutes
        int earlyDepartureThresholdMinutes
        Instant createdAt
        Instant updatedAt
    }

    StudentPickupPerson {
        UUID id PK
        UUID studentId FK
        UUID pickupPersonId FK
        AuthorisationMode authorisationMode "GENERAL, DAY_SPECIFIC"
        PickupPermissionType permissionType "DROPOFF_ONLY, PICKUP_ONLY, BOTH"
        boolean monday
        boolean tuesday
        boolean wednesday
        boolean thursday
        boolean friday
        Instant createdAt
        Instant updatedAt
    }

    %% ── Canteen entities ──
    CanteenOrder {
        UUID id PK
        String orderNumber
        UUID studentId FK
        UUID mealTicketId FK
        OrderType orderType "TICKET, PRE_ORDER, POS"
        BigDecimal totalPrice
        PaymentMode paymentMode "SIKA_ID, MOBILE_MONEY, CARD, CREDIT, CASH, ALLOCATION"
        OrderStatus status "DRAFT, PENDING_PAYMENT, PAID, SERVED, PARTIALLY_SERVED, CANCELLED"
        LocalDateTime scheduledDate
        UUID batchId FK
        UUID menuId FK
        UUID createdBy
        Instant redeemedAt
        Instant cancelledAt
        UUID cancelledBy
        UUID schoolId FK
        String invoiceId
        BigDecimal serviceFee
        Instant paidAt
        Instant createdAt
        Instant updatedAt
    }

    CanteenSetting {
        UUID id PK
        RedemptionMode redemptionMode "AUTO_REDEEM, AUTO_REDEEM_PRESENT, SCAN_TO_REDEEM"
        UUID schoolId FK
        Instant createdAt
        Instant updatedAt
    }

    CanteenUser {
        UUID id PK
        UUID userId FK
        UUID cateringServiceId FK
        CanteenRole role "CANTEEN_ADMIN, CANTEEN_MANAGER, POS_OPERATOR"
        boolean active
        UUID schoolId FK
        Instant createdAt
        Instant updatedAt
    }

    CateringService {
        UUID id PK
        String name
        String logo
        String email
        String phone
        UUID schoolId FK
        SettlementAccountType settlementAccountType "BANK, MOBILE_MONEY"
        String settlementAccountName
        String settlementAccountNumber
        GhanaBank settlementBank "ABSA_BANK_GHANA, ACCESS_BANK_GHANA, AGRICULTURAL_DEVELOPMENT_BANK, BANK_OF_AFRICA_GHANA, CALBANK, CONSOLIDATED_BANK_GHANA, ECOBANK_GHANA, FBN_BANK_GHANA, FIDELITY_BANK_GHANA, FIRST_ATLANTIC_BANK, FIRST_NATIONAL_BANK_GHANA, GCB_BANK, GUARANTY_TRUST_BANK_GHANA, NATIONAL_INVESTMENT_BANK, OMNIBSIC_BANK_GHANA, PRUDENTIAL_BANK, REPUBLIC_BANK_GHANA, SOCIETE_GENERALE_GHANA, STANBIC_BANK_GHANA, STANDARD_CHARTERED_BANK_GHANA, UNITED_BANK_FOR_AFRICA_GHANA, UNIVERSAL_MERCHANT_BANK, ZENITH_BANK_GHANA"
        MobileMoneyProvider settlementMobileMoneyProvider "MTN, TELECEL, AIRTELTIGO"
        BigDecimal platformServiceFeeOverride
        Instant createdAt
        Instant updatedAt
    }

    Item {
        UUID id PK
        UUID cateringServiceId FK
        UUID itemCategoryId FK
        String name
        String image
        String description
        String allergens
        int maxQuantityPerDay
        PricingStyle pricingStyle "FIXED, VARIANT, NO_PRICE"
        BigDecimal defaultPrice
        String currency
        UUID schoolId FK
        UUID createdBy
        Instant createdAt
        Instant updatedAt
    }

    ItemCategory {
        UUID id PK
        String name
        String description
        String themeColor
        UUID schoolId FK
        Instant createdAt
        Instant updatedAt
    }

    ItemVariant {
        UUID id PK
        UUID itemId FK
        String variantName
        BigDecimal price
        AudienceScope audience "ALL, SPECIFIC"
        List targetCampusIds
        List targetClassroomIds
        Instant createdAt
        Instant updatedAt
    }

    MealTicket {
        UUID id PK
        UUID menuId FK
        String name
        UUID academicTermId FK
        PricingMethod pricingMethod "DAILY, MONTHLY, TERMLY"
        PricingScheme pricingScheme "UNIFORM, DIFFERENTIAL"
        BigDecimal uniformPrice
        boolean active
        UUID schoolId FK
        Instant createdAt
        Instant updatedAt
    }

    Menu {
        UUID id PK
        String name
        String description
        MenuPurpose purpose "INFO_ONLY, PARENTS_CAN_ORDER"
        PaymentType paymentType "ORDER_AND_PAY_ITEM_PRICING, ORDER_ONLY_NO_PAYMENT, ORDER_AND_PAY_TICKET_PRICING"
        LocalDate startDate
        LocalDate endDate
        DeadlineType orderingDeadlineType "DAILY, WEEKLY"
        LocalTime dailyCutoffTime
        int dailyCutoffDays
        DayOfWeek weeklyCutoffDay "MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY"
        LocalTime weeklyCutoffTime
        int orderCancellationHours
        boolean published
        AudienceScope audience "ALL, SPECIFIC"
        List targetCampusIds
        List targetSectionIds
        List targetClassroomIds
        List excludedStudentIds
        int visibilityDays
        UUID schoolId FK
        UUID updatedBy
        String updatedByName
        Instant createdAt
        Instant updatedAt
    }

    MenuAudience {
        UUID id PK
        UUID menuId FK
        UUID campusId FK
        UUID classroomId FK
        Instant createdAt
        Instant updatedAt
    }

    MenuSchedule {
        UUID id PK
        UUID menuSectionId FK
        UUID itemId FK
        LocalDate date
        UUID scheduledViaCategoryId FK
        int stockAvailable
        SchedulePattern recurrenceRule "SINGLE_DAY, EVERY_DAY, EVERY_WEEKDAY, SPECIFIC_DAYS"
        Instant createdAt
        Instant updatedAt
    }

    MenuSection {
        UUID id PK
        UUID menuId FK
        String name
        LocalTime servingStartTime
        LocalTime servingEndTime
        SelectionMode selectionMode "SINGLE, MULTIPLE"
        String themeColor
        Instant createdAt
        Instant updatedAt
    }

    OrderItem {
        UUID id PK
        UUID orderId FK
        UUID itemId FK
        UUID variantId FK
        UUID menuSectionId FK
        int quantity
        BigDecimal unitPrice
        boolean served
        Instant servedAt
        long servedBy
        String servedByName
        Instant createdAt
        Instant updatedAt
    }

    OrderStatusHistory {
        UUID id PK
        UUID orderId FK
        OrderStatus fromStatus "DRAFT, PENDING_PAYMENT, PAID, SERVED, PARTIALLY_SERVED, CANCELLED"
        OrderStatus toStatus "DRAFT, PENDING_PAYMENT, PAID, SERVED, PARTIALLY_SERVED, CANCELLED"
        UUID changedBy
        Instant changedAt
    }

    TicketPrice {
        UUID id PK
        UUID mealTicketId FK
        UUID targetGroupId FK
        BigDecimal price
        Instant createdAt
        Instant updatedAt
    }

    %% ── Sample entities ──
    Todo {
        UUID id PK
        String title
        String description
        TodoStatus status "PENDING, IN_PROGRESS, COMPLETED"
        Instant createdAt
        Instant updatedAt
    }

    %% ── School entities ──
    AcademicHoliday {
        UUID id PK
        UUID academicYearId FK
        UUID academicPeriodId FK
        String name
        LocalDate startDate
        LocalDate endDate
        String description
        Instant createdAt
        Instant updatedAt
        long legacyHolidayId
    }

    AcademicPeriod {
        UUID id PK
        UUID academicYearId FK
        String name
        LocalDate startDate
        LocalDate endDate
        Instant createdAt
        Instant updatedAt
        long legacyPeriodId
    }

    AcademicYear {
        UUID id PK
        UUID schoolId FK
        UUID campusId FK
        UUID sectionId FK
        String name
        LocalDate startDate
        LocalDate endDate
        AcademicPeriodType periodType "TERM, SEMESTER, QUARTER, TRIMESTER"
        boolean isActive
        Instant createdAt
        Instant updatedAt
        long legacyYearId
    }

    Campus {
        UUID id PK
        UUID schoolId FK
        String name
        String code
        String address
        String phone
        String email
        String principalName
        boolean isActive
        Instant createdAt
        Instant updatedAt
        long legacyCampusId
        Map legacyConfiguration
    }

    Classroom {
        UUID id PK
        UUID sectionId FK
        String name
        String code
        String academicYear
        int capacity
        UUID classTeacherId FK
        String roomNumber
        boolean isActive
        Instant createdAt
        Instant updatedAt
        long legacyGradeId
    }

    Family {
        UUID id PK
        UUID schoolId FK
        String familyName
        String familyCode
        String primaryAddress
        String primaryPhone
        String primaryEmail
        String notes
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    Guardian {
        UUID id PK
        UUID familyId FK
        GuardianType guardianType "FATHER, MOTHER, STEPFATHER, STEPMOTHER, GRANDFATHER, GRANDMOTHER, UNCLE, AUNT, OLDER_SIBLING, LEGAL_GUARDIAN, FOSTER_PARENT, RELATIVE, FAMILY_FRIEND, OTHER"
        String firstName
        String middleName
        String lastName
        String email
        String phonePrimary
        String phoneSecondary
        String occupation
        String employer
        String address
        String nationalId
        String profilePhotoUrl
        boolean isEmergencyContact
        boolean canPickupStudent
        boolean isPrimaryContact
        long legacyUserId
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    School {
        UUID id PK
        String name
        String code
        String logoUrl
        String address
        String phone
        String email
        String website
        String accreditationNumber
        String defaultCurrency
        long legacySchoolId
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    SchoolStaff {
        UUID id PK
        UUID schoolId FK
        UUID campusId FK
        String staffIdNumber
        String firstName
        String middleName
        String lastName
        String email
        String phone
        LocalDate dateOfBirth
        Gender gender "MALE, FEMALE, OTHER"
        String profilePhotoUrl
        StaffRole role "TEACHER, ADMINISTRATOR, HEADMASTER, SUPPORT, OTHER"
        String department
        EmploymentType employmentType "FULL_TIME, PART_TIME, CONTRACT"
        LocalDate hireDate
        StaffStatus status "ACTIVE, INACTIVE, ON_LEAVE, TERMINATED"
        long legacyUserId
        Instant createdAt
        Instant updatedAt
    }

    Section {
        UUID id PK
        UUID campusId FK
        String name
        String code
        int levelOrder
        String description
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    Student {
        UUID id PK
        UUID schoolId FK
        UUID classroomId FK
        UUID familyId FK
        String studentIdNumber
        String firstName
        String middleName
        String lastName
        LocalDate dateOfBirth
        Gender gender "MALE, FEMALE, OTHER"
        String profilePhotoUrl
        String nationality
        String religion
        String bloodGroup
        String medicalNotes
        LocalDate enrollmentDate
        StudentStatus status "ACTIVE, INACTIVE, GRADUATED, WITHDRAWN, SUSPENDED"
        Instant createdAt
        Instant updatedAt
        long legacyStudentId
    }

    StudentGuardian {
        UUID studentId PK
        UUID guardianId PK
        boolean isPrimaryContact
        boolean isEmergencyContact
        boolean canPickupStudent
        String relationshipNotes
    }

    %% ── Wallet entities ──
    WalletBalance {
        UUID id PK
        UUID studentId FK
        UUID schoolId FK
        BigDecimal balance
        String currency
        Instant createdAt
        Instant updatedAt
    }