Lompat ke konten
dokumentasi.id
Commit a2522f5
v1
Star645

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

11

Import dan Export Ticket

Ada tiga jalur Excel di aplikasi ini: impor ticket dari berkas .xlsx/.xls, ekspor ticket ke .xlsx, dan unduhan template impor. Ketiganya memakai facade Excel dari maatwebsite/excel dan menutup prosesnya dengan satu Notification Filament. Aturan status, prioritas, dan epic yang dipakai saat impor dijelaskan di Manajemen Ticket; tombol ekspor sendiri dipasang di Papan Kanban dan Timeline.

Peta kelas dan titik masuk

ImportTicketsAction::make() mengembalikan Action bernama import_tickets dengan label "Import from Excel", ikon heroicon-m-arrow-up-tray, dan warna success app/Filament/Actions/ImportTicketsAction.php:21-26. Di repository ini tidak ada pemanggil ImportTicketsAction::make(); kelasnya hanya muncul sebagai baris use di resource project app/Filament/Resources/Projects/ProjectResource.php:28.

Aksi impor yang benar-benar terpasang adalah versi inline di TicketsRelationManager dengan nama aksi dan struktur form yang sama, tetapi project diambil dari owner record, bukan dari Select app/Filament/Resources/Projects/RelationManagers/TicketsRelationManager.php:242-276.

ExportTicketsAction::make() dipasang pada header actions ProjectBoard dan hanya tampil bila sebuah project sudah dipilih dan user memegang role super_admin app/Filament/Pages/ProjectBoard.php:387-388.

DownloadTicketTemplateAction berdiri sendiri dan tidak dipanggil di tempat lain. Unduhan template tersedia sebagai aksi bersarang download_template di dalam form impor app/Filament/Actions/ImportTicketsAction.php:54-79. Kelas mandiri itu memilih project lewat Select sendiri app/Filament/Actions/DownloadTicketTemplateAction.php:22-38, lalu menyusun nama berkas dari nama project dan now()->format('Y-m-d') sebelum memanggil Excel::download app/Filament/Actions/DownloadTicketTemplateAction.php:40-53.

Batas form impor

Tabel berikut merangkum batas yang dipasang pada form impor app/Filament/Actions/ImportTicketsAction.php:31-52 dan pada unggahan berkasnya app/Filament/Actions/ImportTicketsAction.php:81-90.

Bagian formBatas
Select project_idhanya project yang punya member dengan user_id milik user aktif, super_admin melihat semua project, required, searchable, live
Pergantian projectexcel_file direset ke null lewat afterStateUpdated
FileUpload excel_filetipe application/vnd.openxmlformats-officedocument.spreadsheetml.sheet dan application/vnd.ms-excel, maxSize(5120) alias 5 MB, disk('local'), direktori temp-imports, visibility('private')

Alur impor

Handler aksi mengambil Project::findOrFail($data['project_id']), mengubah nama berkas unggahan menjadi path absolut lewat Storage::disk('local')->path(), lalu menjalankan Excel::import(new TicketsImport($project), $filePath) app/Filament/Actions/ImportTicketsAction.php:93-106. Setelah impor selesai, handler membaca getImportedCount(), errors(), dan failures(), menghapus berkas sementara dari disk local, baru kemudian mengirim notifikasi. getImportedCount() hanya mengembalikan penghitung $importedCount yang dinaikkan per baris sukses app/Imports/TicketsImport.php:191-194.

sequenceDiagram
    autonumber
    participant U as Admin
    participant A as ImportTicketsAction
    participant X as Excel facade
    participant I as TicketsImport
    participant DB as tickets table
    participant N as Notification
    U->>A: pilih project_id lalu unggah excel_file
    U->>A: klik unduh template
    A->>X: Excel::download TicketTemplateExport
    X-->>U: berkas xlsx tiga sheet
    U->>A: submit form impor
    A->>X: Excel::import TicketsImport dengan path lokal
    loop setiap baris berheading
        I->>I: model row, lewati baris tanpa title
        I->>DB: Ticket::create
        I->>DB: assignees sync email member
        I->>I: importedCount bertambah
    end
    A->>I: getImportedCount lalu errors lalu failures
    A->>A: hapus berkas sementara dari disk local
    alt importedCount lebih besar 0
        A->>N: Import Completed success
    else tidak ada baris masuk
        A->>N: Import Failed warning persistent
    end
    Note over A,N: Exception memicu Import Error danger persistent
%% fig: Siklus satu request impor ticket
%% desc: Form mengunggah berkas, TicketsImport membuat ticket per baris, lalu handler mengirim notifikasi sukses atau gagal setelah berkas sementara dihapus.
Fig. 1: Siklus satu request impor ticketForm mengunggah berkas, TicketsImport membuat ticket per baris, lalu handler mengirim notifikasi sukses atau gagal setelah berkas sementara dihapus.

TicketsImport mengimplementasikan ToModel, WithHeadingRow, WithValidation, SkipsOnError, dan SkipsOnFailure, serta menyimpan Project dan penghitung impor app/Imports/TicketsImport.php:25-35. Karena WithHeadingRow aktif, kunci setiap baris diambil dari heading yang di-slug, misalnya Due Date YYYY-MM-DD menjadi due_date_yyyy_mm_dd.

model() melewati baris tanpa title dan baris contoh template, lalu mencari TicketStatus pada project; bila status tidak ditemukan, baris dikembalikan null sehingga dilewati app/Imports/TicketsImport.php:37-58. priority dicari di tabel ticket_priorities, sedangkan epic dicari pada relasi epics() project; keduanya opsional app/Imports/TicketsImport.php:60-70.

Tanggal due_date_yyyy_mm_dd menerima serial numerik Excel maupun enam format teks (Y-m-d, m/d/Y, d/m/Y, Y/m/d, d-m-Y, m-d-Y), lalu jatuh ke Carbon::parse() bila semua gagal app/Imports/TicketsImport.php:72-102. start_date_yyyy_mm_dd memakai logika yang sama untuk kolom start_date app/Imports/TicketsImport.php:104-130.

Ticket dibuat lewat Ticket::create() dengan project_id, ticket_status_id, priority_id, epic_id, start_date, due_date, dan created_by dari auth()->id() app/Imports/TicketsImport.php:132-166. Email pada kolom assignee difilter: hanya email valid yang benar-benar member project yang masuk assignees()->sync($userIds).

Template impor dan pemetaan kolom

Template adalah satu workbook dengan tiga sheet, yaitu Ticket Template, Project Members, dan Reference Data app/Exports/TicketTemplateExport.php:27-34.

graph TD
    T["TicketTemplateExport"] --> S1["TicketTemplateSheet<br/>sheet Ticket Template"]
    T --> S2["ProjectMembersSheet<br/>sheet Project Members"]
    T --> S3["ReferenceDataSheet<br/>sheet Reference Data"]
    S1 --> H1["8 heading tetap<br/>Title sampai Due Date YYYY-MM-DD"]
    S1 --> H2["1 baris contoh<br/>dibuang importer"]
    S2 --> M1["Name, Email, Role<br/>dari members project"]
    S3 --> R1["status project<br/>seluruh ticket_priorities<br/>epic project"]
    style T fill:#f59e0b,color:#000
    style S2 fill:#8b5cf6,color:#fff
    style S3 fill:#8b5cf6,color:#fff
    style H2 fill:#dc2626,color:#fff
    style M1 fill:#2d7d46,color:#fff
%% fig: Struktur workbook template impor
%% desc: Satu TicketTemplateExport merangkai tiga sheet, dan hanya sheet Ticket Template yang dibaca baris per baris oleh TicketsImport.
Fig. 2: Struktur workbook template imporSatu TicketTemplateExport merangkai tiga sheet, dan hanya sheet Ticket Template yang dibaca baris per baris oleh TicketsImport.

Sheet Ticket Template memakai delapan heading tetap dari Title sampai Due Date YYYY-MM-DD app/Exports/TicketTemplateExport.php:51-63 dan menyertakan satu baris contoh yang justru dibuang oleh importer app/Exports/TicketTemplateExport.php:65-81. Sheet Project Members mengisi Name, Email, dan Role dari $project->members() agar email assignee bisa dicocokkan app/Exports/TicketTemplateExport.php:122-145. Sheet Reference Data menampilkan tiga kolom: status milik project, seluruh ticket_priorities, dan epic milik project app/Exports/TicketTemplateExport.php:186-213.

Aturan validasi dikumpulkan pada rules() app/Imports/TicketsImport.php:168-189, dan tabel berikut memasangkannya dengan heading template.

Heading templateKunci barisWajibPerilaku importer
Titletitleyarequired, string, max:255, nilai disimpan ke name ticket
Descriptiondescriptiontidaknullable, string
StatusstatusyaRule::exists('ticket_statuses', 'name') dibatasi project_id
Priorityprioritytidakexists:ticket_priorities,name
EpicepictidakRule::exists('epics', 'name') dibatasi project_id
Assignees Comma Separated Emailsassignees_comma_separated_emailstidakhanya email valid dan member project yang di-sync
Start Date YYYY-MM-DDstart_date_yyyy_mm_ddtidaknullable, date, parser tanggal fleksibel
Due Date YYYY-MM-DDdue_date_yyyy_mm_ddtidaknullable, date, parser tanggal fleksibel

Notifikasi hasil impor

Berkas sementara selalu dihapus lebih dulu, baik pada jalur sukses maupun pada blok catch. Tiga cabang notifikasi ada di handler aksi app/Filament/Actions/ImportTicketsAction.php:108-119, app/Filament/Actions/ImportTicketsAction.php:120-157, dan app/Filament/Actions/ImportTicketsAction.php:159-181.

KondisiJudulTipeIsi
importedCount lebih dari 0Import Completedsuccess"Successfully imported {N} ticket(s) to project '{nama}'." ditambah " Some rows had errors and were skipped." bila ada errors() atau failures()
0 baris masuk, ada failures()Import Failedwarning, persistent()"Validation errors found in N row(s):" lalu "Row {baris}: {pesan}"
0 baris masuk, ada errors()Import Failedwarning, persistent()"Processing errors:" lalu tiap pesan galat
0 baris masuk tanpa keduanyaImport Failedwarning, persistent()lima butir saran: format template, kolom Title dan Status, nama status, YYYY-MM-DD, email assignee
ExceptionImport Errordanger, persistent()$e->getMessage() plus saran sesuai kata kunci file, database, atau SQL

⚠️ Baris tanpa title dan baris contoh template dikembalikan null tanpa pesan apa pun app/Imports/TicketsImport.php:39-48. Untuk kasus itu failures() tetap kosong, sehingga berkas yang hanya berisi header berakhir pada notifikasi Import Failed berisi lima saran umum app/Filament/Actions/ImportTicketsAction.php:139-149, bukan daftar baris bermasalah.

Versi inline di TicketsRelationManager memakai teks berbeda: blok **Validation Errors:** per baris dan blok **Possible causes:** bila tidak ada catatan error app/Filament/Resources/Projects/RelationManagers/TicketsRelationManager.php:304-343.

Alur ekspor

ExportTicketsAction menyusun form CheckboxList columns dengan sepuluh opsi, default enam kolom (uuid, name, status, assignee, due_date, created_at), required(), dan minItems(1) app/Filament/Actions/ExportTicketsAction.php:17-45. Aksi itu tidak mengekspor sendiri: ia memanggil $livewire->exportTickets($data['columns'] ?? []) app/Filament/Actions/ExportTicketsAction.php:46-48.

graph LR
    A["ExportTicketsAction<br/>CheckboxList columns"] --> B["ProjectBoard::exportTickets"]
    B --> C{"kolom kosong atau tickets kosong?"}
    C -- ya --> E["Notification Export Failed<br/>warning atau danger"]
    C -- tidak --> D["TicketsExport<br/>map dan headings"]
    D --> F["Excel::store ke exports/<br/>disk public"]
    F --> G["asset storage/exports lalu fetch<br/>di browser"]
    G --> H["Notification Export Successful"]
    style A fill:#f59e0b,color:#000
    style F fill:#336791,color:#fff
    style G fill:#8b5cf6,color:#fff
    style H fill:#2d7d46,color:#fff
    style E fill:#dc2626,color:#fff
%% fig: Alur data dari form ekspor ke unduhan
%% desc: Aksi hanya meneruskan kolom terpilih, ProjectBoard menyusun koleksi ticket, TicketsExport memetakan sel, lalu berkas diunduh lewat fetch dari disk public.
Fig. 3: Alur data dari form ekspor ke unduhanAksi hanya meneruskan kolom terpilih, ProjectBoard menyusun koleksi ticket, TicketsExport memetakan sel, lalu berkas diunduh lewat fetch dari disk public.

exportTickets() menolak daftar kolom kosong, lalu mengambil ticket project terpilih bersama relasi assignees, status, project, dan epic berurutan created_at menurun; bila tidak ada project terpilih, ticket dikumpulkan dari ticketStatuses app/Filament/Pages/ProjectBoard.php:478-516. Daftar yang kosong memicu notifikasi Export Failed bertipe warning dan method berhenti tanpa menulis berkas.

Berkas disimpan lewat Excel::store($export, 'exports/'.$fileName, 'public') sehingga unduhan memakai URL asset('storage/exports/'.$fileName) app/Filament/Pages/ProjectBoard.php:518-523. Proses unduh berjalan di browser: fetch() mengambil blob, a.click() memicu unduhan, lalu URL.revokeObjectURL() membersihkan URL sementara.

⚠️ Karena berkas ditulis ke disk public di direktori exports, tombol ekspor bergantung pada symlink storage menuju storage/app/public. Tanpa symlink itu, URL hasil asset() tidak menunjuk berkas yang sama.

Setelah berkas tersimpan, handler mengirim Export Successful app/Filament/Pages/ProjectBoard.php:540-544; seluruh blok dibungkus try/catch yang mengubah exception menjadi Export Failed bertipe danger berisi $e->getMessage() app/Filament/Pages/ProjectBoard.php:546-552.

Kolom ekspor dan isi sel

TicketsExport mendefinisikan sebelas kolom di $availableColumns, termasuk start_date app/Exports/TicketsExport.php:19-31. Form ekspor hanya menawarkan sepuluh di antaranya, sehingga start_date tidak dapat dipilih dari CheckboxList.

Heading diambil dari $availableColumns sesuai urutan kolom terpilih app/Exports/TicketsExport.php:44-53, sedangkan isi sel dihasilkan map() app/Exports/TicketsExport.php:55-100.

KunciHeadingNilai sel
uuidTicket ID$ticket->uuid
nameTitle$ticket->name
descriptionDescriptionstrip_tags($ticket->description ?? '')
statusStatus$ticket->status?->name atau No Status
assigneeAssigneenama assignee di-implode(', ')
projectProjectnama project atau No Project
epicEpicnama epic atau No Epic
start_dateStart Dateformat('Y-m-d') atau string kosong
due_dateDue Dateformat('Y-m-d') atau string kosong
created_atCreated Atformat('Y-m-d H:i:s')
updated_atUpdated Atformat('Y-m-d H:i:s')

Baris pertama lembar diberi latar FF366092 dengan teks putih tebal app/Exports/TicketsExport.php:103-125.