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-341).
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-202). 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-183), 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.
| Model | Tabel | Kolom di $fillable | $casts |
|---|---|---|---|
Project | projects | name, description, ticket_prefix, color, start_date, end_date, pinned_date | start_date→date, end_date→date, pinned_date→datetime (app/Models/Project.php:16-306) |
Ticket | tickets | project_id, ticket_status_id, priority_id, name, description, start_date, due_date, uuid, epic_id, created_by | start_date→date, due_date→date (app/Models/Ticket.php:16-327) |
Epic | epics | project_id, name, description, start_date, end_date, sort_order | start_date→date, end_date→date (app/Models/Epic.php:14-368) |
TicketStatus | ticket_statuses | project_id, name, sort_order, color, is_completed | is_completed→boolean (app/Models/TicketStatus.php:14-349) |
TicketPriority | ticket_priorities | name, color | tanpa $casts (app/Models/TicketPriority.php:13-2110) |
ExternalAccess | external_access | project_id, access_token, password, is_active, last_accessed_at, migration_generated | last_accessed_at→datetime, is_active→boolean, password→encrypted (app/Models/ExternalAccess.php:14-341) |
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.Project menghubungkan ticketStatuses(), tickets(), members() dan users() lewat pivot project_members, epics(), serta notes() (app/Models/Project.php:47-7711). 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-2012). 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-11013). Terakhir, externalAccess() adalah hasOne, dan generateExternalAccess() menghapus record lama sebelum memanggil ExternalAccess::generateForProject (app/Models/Project.php:112-12214). 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-2315). Migration berikutnya menambah uuid unik dan nullable (database/migrations/2025_03_13_154334_add_uuid_to_ticket_table.php:14-1616), 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.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-6023). 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-10024). 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-1925). Tiga migration menambah sort_order bertipe integer default 0 (database/migrations/2025_05_06_220233_add_sort_order_to_ticket_statuses_table.php:14-1626), 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-4229). 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-3030); sort_order ditambahkan belakangan dengan default 0 (database/migrations/2025_10_17_215303_add_sort_order_to_epics_table.php:14-1631).
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-1932). 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-4933). 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-3334). 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-5235).
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-3836).
| Index | Tabel | Kolom |
|---|---|---|
idx_tickets_project_status | tickets | project_id, ticket_status_id |
idx_tickets_status_created | tickets | ticket_status_id, created_at |
idx_tickets_due_date | tickets | due_date |
idx_ticket_statuses_project_sort | ticket_statuses | project_id, sort_order |
idx_ticket_users_ticket_user | ticket_users | ticket_id, user_id |
idx_project_members_project_user | project_members | project_id, user_id |
idx_projects_pinned | projects | pinned_date |
idx_projects_dates | projects | start_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-3836).
⚠️
2025_06_24_212838_drop_user_id_column_from_ticketsmenghapus foreign key dan kolomuser_iddaritickets(database/migrations/2025_06_24_212838_drop_user_id_column_from_tickets.php:14-1719). Jalankan2025_06_24_212750_migrate_existing_user_id_datalebih dulu; kalau tidak, penugasan lama tidak pernah masuk keticket_users.
⚠️
down()pada migration enkripsi menulis password kembali sebagai plaintext (database/migrations/2026_09_17_050054_encrypt_external_access_passwords.php:41-5637).rollbackkarena itu menurunkan proteksi kredensial klien.