Lompat ke konten
dokumentasi.id
Commit a2522f5
v1
Star645

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

4

Model Data dan Migration

Halaman ini memetakan tabel yang dibentuk migration ke model Eloquent yang membungkusnya. Seluruh data inti berada di satu database relasional, dan nama tabel mengikuti konvensi Laravel kecuali ExternalAccess yang menetapkan protected $table = 'external_access' (app/Models/ExternalAccess.php:14-34).

Dari migration ke model

Tabel projects dibuat dengan kolom id, name, description, ticket_prefix, dan timestamps (database/migrations/2025_03_02_200055_create_projects_table.php:14-20). Tiga migration lanjutan menambah kolom pada tabel yang sama: start_date dan end_date bertipe date (database/migrations/2025_05_10_202453_add_start_date_end_date_to_project.php:14-18), pinned_date bertipe timestamp (database/migrations/2025_07_16_182905_add_pinned_to_projects_table.php:14-17), serta color bertipe string panjang 7 (database/migrations/2025_11_04_181918_add_color_to_projects_table.php:14-17). Model Project mendaftarkan tujuh kolom itu di $fillable dan meng-cast start_date serta end_date ke date, lalu pinned_date ke datetime (app/Models/Project.php:16-30).

Ringkasan tiap model ada di tabel berikut.

ModelTabelKolom di $fillable$casts
Projectprojectsname, description, ticket_prefix, color, start_date, end_date, pinned_datestart_date→date, end_date→date, pinned_date→datetime (app/Models/Project.php:16-30)
Ticketticketsproject_id, ticket_status_id, priority_id, name, description, start_date, due_date, uuid, epic_id, created_bystart_date→date, due_date→date (app/Models/Ticket.php:16-32)
Epicepicsproject_id, name, description, start_date, end_date, sort_orderstart_date→date, end_date→date (app/Models/Epic.php:14-36)
TicketStatusticket_statusesproject_id, name, sort_order, color, is_completedis_completed→boolean (app/Models/TicketStatus.php:14-34)
TicketPriorityticket_prioritiesname, colortanpa $casts (app/Models/TicketPriority.php:13-21)
ExternalAccessexternal_accessproject_id, access_token, password, is_active, last_accessed_at, migration_generatedlast_accessed_at→datetime, is_active→boolean, password→encrypted (app/Models/ExternalAccess.php:14-34)

Relasi antar entitas

erDiagram berikut diturunkan dari method relasi di model dan dari foreign key yang dibuat migration.

erDiagram
    PROJECTS ||--o{ TICKET_STATUSES : "status"
    PROJECTS ||--o{ TICKETS : "ticket"
    PROJECTS ||--o{ EPICS : "epic"
    PROJECTS ||--o{ PROJECT_MEMBERS : "anggota"
    PROJECTS ||--o| EXTERNAL_ACCESS : "akses klien"
    PROJECTS ||--o{ PROJECT_NOTES : "catatan"
    TICKET_STATUSES ||--o{ TICKETS : "status saat ini"
    TICKET_PRIORITIES ||--o{ TICKETS : "prioritas"
    EPICS ||--o{ TICKETS : "epic_id"
    TICKETS ||--o{ TICKET_USERS : "assignee"
    TICKETS ||--o{ TICKET_HISTORIES : "riwayat"
    TICKETS ||--o{ TICKET_COMMENTS : "komentar"
    USERS ||--o{ PROJECT_MEMBERS : "anggota"
    USERS ||--o{ TICKET_USERS : "ditugaskan"
    USERS ||--o{ TICKETS : "created_by"
    PROJECTS {
        bigint id
        varchar name
        varchar ticket_prefix
        varchar color
        date start_date
        date end_date
        timestamp pinned_date
    }
    TICKETS {
        bigint id
        varchar uuid
        varchar name
        longtext description
        date start_date
        date due_date
    }
    EXTERNAL_ACCESS {
        bigint id
        varchar access_token
        varchar password
        boolean is_active
        timestamp last_accessed_at
    }
%% fig: Relasi tabel inti
%% desc: Setiap ticket menggantung pada satu project, satu status, satu prioritas opsional, dan satu epic opsional; akses klien satu per project.
Fig. 1: Relasi tabel intiSetiap ticket menggantung pada satu project, satu status, satu prioritas opsional, dan satu epic opsional; akses klien satu per project.

Project menghubungkan ticketStatuses(), tickets(), members() dan users() lewat pivot project_members, epics(), serta notes() (app/Models/Project.php:47-77). Pivot project_members menyimpan project_id, user_id, timestamps, dan unique ['project_id', 'user_id'] (database/migrations/2025_03_02_200109_create_project_members_table.php:14-20). Dua nilai turunan dihitung di model: getRemainingDaysAttribute mengembalikan 0 bila end_date sudah lewat, dan getProgressPercentageAttribute membagi jumlah ticket berstatus is_completed dengan total ticket lalu membulatkan satu desimal (app/Models/Project.php:95-110). Terakhir, externalAccess() adalah hasOne, dan generateExternalAccess() menghapus record lama sebelum memanggil ExternalAccess::generateForProject (app/Models/Project.php:112-122). Pemakaian relasi ini dibahas di Manajemen Proyek dan Anggota dan Portal Eksternal Klien.

Ticket dan evolusi skemanya

Tabel tickets lahir dengan project_id, ticket_status_id, name, description, user_id, due_date, dan timestamps; dua foreign key pertama memakai onDelete('cascade') (database/migrations/2025_03_02_200246_create_tickets_table.php:14-23). Migration berikutnya menambah uuid unik dan nullable (database/migrations/2025_03_13_154334_add_uuid_to_ticket_table.php:14-16), lalu created_by yang onDelete('set null') bersama tabel pivot ticket_users dengan unique ['ticket_id', 'user_id'] (database/migrations/2025_06_24_212547_add_ticket_users_table_and_created_by_column.php:14-26). Isi user_id disalin ke ticket_users sebelum kolomnya dihapus (database/migrations/2025_06_24_212750_migrate_existing_user_id_data.php:16-28), dan penghapusan itu terjadi di migration terpisah (database/migrations/2025_06_24_212838_drop_user_id_column_from_tickets.php:14-17). priority_id ditambahkan sebagai nullable dengan onDelete('set null') (database/migrations/2025_07_04_164558_add_priority_id_to_tickets_table.php:14-20), description diubah menjadi longText (database/migrations/2025_06_29_052227_change_tickets_description_to_longtext.php:14-16), dan start_date ditambahkan setelah description (database/migrations/2025_08_17_041650_add_start_date_to_tickets_table.php:11-13).

Urutan perubahan itu berjalan satu arah, dari tabel kosong menuju skema dengan index.

graph LR
    T1["2025_03_02 create_tickets_table"] --> T2["2025_03_13 add_uuid"]
    T2 --> T3["2025_03_27 create_epics_table"]
    T3 --> T4["2025_06_24 ticket_users + created_by"]
    T4 --> T5["2025_06_24 migrate user_id"]
    T5 --> T6["2025_06_24 drop user_id"]
    T6 --> T7["2025_06_29 description longtext"]
    T7 --> T8["2025_07_04 priority_id"]
    T8 --> T9["2025_08_17 start_date"]
    T9 --> T10["2025_09_16 performance indexes"]
    style T1 fill:#336791,color:#fff
    style T2 fill:#336791,color:#fff
    style T3 fill:#336791,color:#fff
    style T4 fill:#336791,color:#fff
    style T5 fill:#f59e0b,color:#000
    style T6 fill:#dc2626,color:#fff
    style T7 fill:#336791,color:#fff
    style T8 fill:#336791,color:#fff
    style T9 fill:#336791,color:#fff
    style T10 fill:#8b5cf6,color:#fff
%% fig: Evolusi skema tabel tickets
%% desc: Setiap migration menambah atau mengubah satu bagian skema tickets, dengan satu langkah destruktif di tengah urutan.
Fig. 2: Evolusi skema tabel ticketsSetiap migration menambah atau mengubah satu bagian skema tickets, dengan satu langkah destruktif di tengah urutan.

Model Ticket menyimpan hook di booted(): saat creating, uuid diisi dengan "{$prefix}-{$randomString}" dari ticket_prefix project (fallback TKT) dan enam karakter acak; saat updating, perubahan ticket_status_id menulis baris baru ke TicketHistory (app/Models/Ticket.php:34-60). Relasinya adalah project(), status() lewat ticket_status_id, assignees() lewat pivot ticket_users, creator() lewat created_by, histories(), comments(), epic(), dan priority() lewat priority_id (app/Models/Ticket.php:62-100). Perilaku status dan riwayat dibahas di Status, Prioritas, dan Epic serta Komentar dan Riwayat Ticket.

TicketStatus

ticket_statuses hanya berisi id, project_id (onDelete('cascade')), name, dan timestamps saat dibuat (database/migrations/2025_03_02_200213_create_ticket_statuses_table.php:14-19). Tiga migration menambah sort_order bertipe integer default 0 (database/migrations/2025_05_06_220233_add_sort_order_to_ticket_statuses_table.php:14-16), color dengan default #3490dc (database/migrations/2025_05_06_221002_add_sort_color_to_ticket_statuses_table.php:14-17), dan is_completed bertipe boolean default false (database/migrations/2025_08_23_214007_add_is_completed_to_ticket_statuses_table.php:11-13). Model TicketStatus memakai kelima kolom itu dan meng-cast is_completed ke boolean (app/Models/TicketStatus.php:14-34).

TicketPriority dan Epic

ticket_priorities dibuat dengan name unik dan color panjang 7 default #6B7280, lalu langsung diisi tiga baris: Low dengan #10B981, Medium dengan #F59E0B, dan High dengan #EF4444 (database/migrations/2025_07_04_164429_create_ticket_priorities_table.php:15-42). Epics tinggal di tabel epics dengan project_id cascadeOnDelete, name, description, start_date, end_date, dan foreign key epic_id pada tickets yang nullOnDelete (database/migrations/2025_03_27_065113_create_epics_table.php:14-30); sort_order ditambahkan belakangan dengan default 0 (database/migrations/2025_10_17_215303_add_sort_order_to_epics_table.php:14-16).

ExternalAccess

external_access menyimpan project_id (onDelete('cascade')), access_token unik, password, is_active default true, dan last_accessed_at nullable (database/migrations/2025_08_05_030102_create_external_access_table.php:11-19). Migration lanjutan menambah kolom penanda migration_generated dan membuat kredensial untuk project yang belum punya akses, dengan token 32 karakter dan password 8 karakter (database/migrations/2025_08_05_031001_generate_client_access_for_existing_projects_safe.php:19-49). Password lama kemudian dienkripsi: migration melewati nilai kosong, mencoba Crypt::decryptString, lalu hanya mengenkripsi baris yang belum terenkripsi (database/migrations/2026_09_17_050054_encrypt_external_access_passwords.php:15-33). Di model, $casts menandai password sebagai encrypted, generateForProject membuat token 32 karakter dan password 8 karakter, dan updateLastAccessed mengisi last_accessed_at (app/Models/ExternalAccess.php:36-52).

Index dan migration destruktif

add_performance_indexes menambah index komposit untuk query papan dan daftar, misalnya idx_tickets_project_status, idx_ticket_statuses_project_sort, idx_project_members_project_user, dan idx_projects_pinned (database/migrations/2025_09_16_051002_add_performance_indexes.php:15-38).

IndexTabelKolom
idx_tickets_project_statusticketsproject_id, ticket_status_id
idx_tickets_status_createdticketsticket_status_id, created_at
idx_tickets_due_dateticketsdue_date
idx_ticket_statuses_project_sortticket_statusesproject_id, sort_order
idx_ticket_users_ticket_userticket_usersticket_id, user_id
idx_project_members_project_userproject_membersproject_id, user_id
idx_projects_pinnedprojectspinned_date
idx_projects_datesprojectsstart_date, end_date

Semua index di tabel itu dibuat lewat $this->indexExists agar migration aman diulang (database/migrations/2025_09_16_051002_add_performance_indexes.php:15-38).

⚠️ 2025_06_24_212838_drop_user_id_column_from_tickets menghapus foreign key dan kolom user_id dari tickets (database/migrations/2025_06_24_212838_drop_user_id_column_from_tickets.php:14-17). Jalankan 2025_06_24_212750_migrate_existing_user_id_data lebih dulu; kalau tidak, penugasan lama tidak pernah masuk ke ticket_users.

⚠️ down() pada migration enkripsi menulis password kembali sebagai plaintext (database/migrations/2026_09_17_050054_encrypt_external_access_passwords.php:41-56). rollback karena itu menurunkan proteksi kredensial klien.