Lompat ke konten
dokumentasi.id
Commit a2522f5
v1
Star645

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

6

Manajemen Ticket

Ticket adalah unit kerja utama aplikasi ini. Semua pengelolaannya melewati satu resource Filament, TicketResource, yang menunjuk model App\Models\Ticket app/Filament/Resources/Tickets/TicketResource.php:33-43. Resource itu mendaftarkan empat route halaman: index, create, view, dan edit app/Filament/Resources/Tickets/TicketResource.php:391-399.

Halaman ini membahas pembuatan Ticket, pembentukan uuid berprefiks proyek, penugasan multi user, dan tenggat waktu. Status, prioritas, dan epic dibahas di Status, Prioritas, dan Epic. Komentar serta riwayat perubahan status dibahas di Komentar dan Riwayat Ticket.

Siklus hidup satu pembuatan Ticket

Diagram berikut menelusuri satu request pembuatan ticket, dari submit form sampai baris ticket_users terisi.

sequenceDiagram
    actor U as User
    participant CT as CreateTicket
    participant T as Ticket
    participant P as Project
    participant DB as ticket_users
    U->>CT: submit form
    CT->>CT: mutateFormDataBeforeCreate isi created_by
    CT->>T: parent::handleRecordCreation
    T->>P: Project::find project_id
    T->>T: hook creating bangun uuid
    T-->>CT: record Ticket tersimpan
    CT->>P: members() per assignee
    alt semua assignee adalah member
        CT->>DB: assignees()->sync(validAssignees)
    else sebagian assignee bukan member
        CT->>U: warning Some assignees removed
        CT->>DB: assignees()->sync(validAssignees)
    else tidak ada assignee valid
        CT->>U: info Auto-assigned
        CT->>DB: assignees()->sync(auth()->id())
    end
%% fig: Siklus pembuatan satu Ticket
%% desc: Form CreateTicket divalidasi resource, hook creating membentuk uuid, lalu handleRecordCreation memeriksa keanggotaan proyek sebelum sync ke ticket_users.
Fig. 1: Siklus pembuatan satu TicketForm CreateTicket divalidasi resource, hook creating membentuk uuid, lalu handleRecordCreation memeriksa keanggotaan proyek sebelum sync ke ticket_users.

CreateTicket menimpa mutateFormDataBeforeCreate agar created_by selalu diisi user yang login app/Filament/Resources/Tickets/Pages/CreateTicket.php:56-61, sehingga nilai itu tidak bisa dipalsukan dari form. Setelah baris tickets tersimpan, handleRecordCreation memeriksa setiap assignees terhadap $project->members() dan memisahkannya menjadi validAssignees dan invalidAssignees app/Filament/Resources/Tickets/Pages/CreateTicket.php:63-122. Tabel berikut merangkum cabang keputusannya.

KondisiAksiNotifikasi
Semua assignee adalah member proyekassignees()->sync($validAssignees)—
Sebagian assignee bukan memberhanya validAssignees yang di-syncwarning Some assignees removed
Tidak ada assignee valid, user adalah membersync([auth()->id()])info Auto-assigned
Form dikirim tanpa assignees, user adalah membersync([auth()->id()])—

Cabang Auto-assigned dipakai saat tidak satu pun assignee lolos validasi, dan user yang sedang login langsung ditugaskan app/Filament/Resources/Tickets/Pages/CreateTicket.php:96-107. Setelah itu getRedirectUrl mengembalikan /admin/project-board-page bila header referer memuat project-board-page, dan selebihnya ke halaman index resource app/Filament/Resources/Tickets/Pages/CreateTicket.php:124-133.

Pembentukan uuid berprefiks proyek

Nilai uuid di sini bukan UUID RFC, melainkan string pendek berpola {prefix}-{6 karakter acak}. Hook creating mengambil Project::find($ticket->project_id), memakai $project->ticket_prefix sebagai prefiks, dan jatuh ke TKT bila proyek tidak ditemukan; enam karakter terakhir berasal dari Str::upper(Str::random(6)) app/Models/Ticket.php:34-49. Hook yang sama juga mengisi created_by bila masih kosong dan ada user yang login, sehingga insert langsung lewat model tetap meninggalkan jejak pembuat app/Models/Ticket.php:34-49.

Kolom uuid ditambahkan migration sebagai string('uuid')->after('id')->unique()->nullable() database/migrations/2025_03_13_154334_add_uuid_to_ticket_table.php:14-16. Prefiksnya bersumber dari kolom ticket_prefix di tabel projects yang wajib diisi database/migrations/2025_03_02_200055_create_projects_table.php:18.

⚠️ Hook hanya mengisi uuid ketika kolomnya masih kosong dan tidak memeriksa bentrokan nilai. Karena kolom itu ber-index unique, dua ticket dari proyek sama yang memperoleh enam karakter acak identik akan ditolak database; tidak ada retry otomatis di hook.

Pada alur salin, uuid justru dibuang dari data form sebelum form diisi, bersama id, created_at, updated_at, dan created_by, sehingga ticket hasil salinan mendapat uuid baru dari hook app/Filament/Resources/Tickets/Pages/CreateTicket.php:16-54.

Ketergantungan field pada project_id

Form ticket bertumpu pada satu field induk: project_id bersifat live, dan setiap perubahannya mengosongkan ticket_status_id, assignees, serta epic_id app/Filament/Resources/Tickets/TicketResource.php:64-91.

graph LR
    P["Select project_id (live)"] -->|afterStateUpdated| RS["reset ticket_status_id"]
    P --> RA["reset assignees"]
    P --> RE["reset epic_id"]
    P --> S["Select ticket_status_id"]
    P --> E["Select epic_id"]
    P --> A["Select assignees (multiple)"]
    A -->|project_id kosong| X["whereRaw 1 = 0"]
    A -->|member proyek| OK["sync ke ticket_users"]
    style P fill:#f59e0b,color:#000
    style S fill:#f59e0b,color:#000
    style E fill:#f59e0b,color:#000
    style A fill:#f59e0b,color:#000
    style X fill:#dc2626,color:#fff
    style OK fill:#2d7d46,color:#fff
%% fig: Ketergantungan field form Ticket pada project_id
%% desc: Mengubah project_id mengosongkan status, assignee, dan epic; opsi status, epic, dan assignee baru terisi setelah project_id ada.
Fig. 2: Ketergantungan field form Ticket pada project_idMengubah project_id mengosongkan status, assignee, dan epic; opsi status, epic, dan assignee baru terisi setelah project_id ada.
FieldKomponenAturan
project_idSelectrequired, searchable, preload, live, disabledOn('ticket_on_board'), dehydrated
ticket_status_idSelectrequired, opsi dari TicketStatus::where('project_id', ...)
priority_idSelectnullable, opsi dari TicketPriority::pluck
epic_idSelectnullable, opsi dari Epic::where('project_id', ...), hidden bila project_id kosong
nameTextInputrequired, maxLength(255)
descriptionRichEditorlampiran ke disk public, direktori attachments
assigneesSelect multiplerelasi assignees, hanya member proyek
start_dateDatePickerdefault now(), nullable
due_dateDatePickernullable
created_bySelectdisabled, hiddenOn(['create', 'ticket_on_board'])

Opsi project_id berasal dari seluruh Project untuk super_admin, dan dari auth()->user()->projects() untuk peran lain app/Filament/Resources/Tickets/TicketResource.php:64-91. Opsi status dan epic mengikuti proyek terpilih app/Filament/Resources/Tickets/TicketResource.php:93-133. Sisanya, termasuk name, description, dan created_by, didefinisikan pada sisa skema form app/Filament/Resources/Tickets/TicketResource.php:135-190.

Field created_by hanya tampil di halaman edit: Select itu memakai relasi creator, disabled, dan disembunyikan pada konteks create serta ticket_on_board app/Filament/Resources/Tickets/TicketResource.php:185-189. Konteks ticket_on_board dipakai ProjectBoard saat membuat ticket dari papan kanban; bahasan papan ada di Papan Kanban dan Timeline.

Penugasan multi user

Relasi assignees adalah belongsToMany(User::class, 'ticket_users') app/Models/Ticket.php:72-75. Tabel pivotnya dibuat dengan foreign key cascade dan unique(['ticket_id', 'user_id']), dan migration yang sama menambahkan kolom created_by nullable ke tickets database/migrations/2025_06_24_212547_add_ticket_users_table_and_created_by_column.php:14-26.

Model menyediakan empat helper: assignUser memakai syncWithoutDetaching, unassignUser memakai detach, assignUsers memakai sync, dan isAssignedTo memeriksa keberadaan baris pivot app/Models/Ticket.php:102-120.

Pada halaman edit, penyaringan assignee dilakukan di mutateFormDataBeforeSave dan sync dijalankan di afterSave app/Filament/Resources/Tickets/Pages/EditTicket.php:25-68.

⚠️ mutateFormDataBeforeSave menyaring $data['assignees'] menjadi validAssignees, tetapi afterSave melakukan sync($this->data['assignees']) dari state form apa adanya. Penyaringan itu tidak otomatis terbawa ke sync, sehingga user non-member masih bisa masuk ke ticket_users lewat halaman edit app/Filament/Resources/Tickets/Pages/EditTicket.php:62-68.

Tenggat waktu

start_date dan due_date adalah dua DatePicker terpisah; start_date diberi default now() dan keduanya nullable app/Filament/Resources/Tickets/TicketResource.php:177-184. Keduanya di-cast date pada model app/Models/Ticket.php:16-32, dan kolom start_date ditambahkan lewat migration tersendiri database/migrations/2025_08_17_041650_add_start_date_to_tickets_table.php:11-13.

Daftar ticket bisa disaring rentang tenggat lewat Filter::make('due_date') dengan dua input, due_from dan due_until, yang masing-masing menjadi whereDate('due_date', '>=', ...) dan whereDate('due_date', '<=', ...) app/Filament/Resources/Tickets/TicketResource.php:342-357. Di halaman detail, due_date diberi warna danger bila isPast() dan success bila belum lewat app/Filament/Resources/Tickets/Pages/ViewTicket.php:218-222.

Daftar, detail, dan edit

getEloquentQuery membatasi ticket untuk pengguna non-super_admin pada tiga jalur: sebagai assignees, sebagai created_by, atau sebagai member proyek app/Filament/Resources/Tickets/TicketResource.php:45-62. TicketPolicy::hasAccessToTicket mencerminkan aturan yang sama agar sebuah permission tidak membuka ticket di luar proyek pengguna app/Policies/TicketPolicy.php:22-39; rincian peran ada di Kontrol Akses dan Peran.

Kolom tabel menampilkan uuid yang copyable, status.name sebagai badge HTML berwarna dari $record->status?->color dengan fallback #6B7280, priority.name sebagai badge hasil match (High, Medium, Low), assignees.name yang di-limitList(2) dan expandableLimitedList, serta start_date dan due_date berformat date() app/Filament/Resources/Tickets/TicketResource.php:197-269. Navigasi resource memakai badge berisi hasil hitung getEloquentQuery app/Filament/Resources/Tickets/TicketResource.php:401-406.

FilterTipePerilaku
ProjectSelectFilteropsi mengikuti peran super_admin atau auth()->user()->projects()
StatusSelectFilteropsi dibaca dari tableFilters.project_id
EpicSelectFilteropsi dibaca dari tableFilters.project_id
PrioritySelectFilteropsi dari TicketPriority::pluck
AssigneeSelectFilter multiplerelasi assignees
Created BySelectFilterrelasi creator
Due DateFilterrentang due_from sampai due_until

Seluruh filter itu didefinisikan dalam satu blok ->filters([...]) dengan defaultSort('created_at', 'desc') app/Filament/Resources/Tickets/TicketResource.php:276-358.

Setiap baris punya tiga aksi: ViewAction, EditAction, dan Action::make('copy') yang mengalihkan ke route create dengan parameter copy_from app/Filament/Resources/Tickets/TicketResource.php:360-374. Hapus massal lewat DeleteBulkAction hanya tersedia untuk super_admin app/Filament/Resources/Tickets/TicketResource.php:376-380.

Di ViewTicket, tombol EditAction hanya muncul untuk super_admin, pembuat, atau assignee ticket, dan ada tombol back menuju ProjectBoard proyek terkait app/Filament/Resources/Tickets/Pages/ViewTicket.php:144-161.

⚠️ Action::make('copy') hanya mengoper copy_from, tanpa memeriksa ulang hak akses di sisi klien; penjagaan tetap terjadi di CreateTicket::fillForm, yang memakai query ber-scope sama seperti getEloquentQuery untuk pengguna non-super_admin app/Filament/Resources/Tickets/Pages/CreateTicket.php:16-54.