D
Engineering

Tickets Schema

Skema tabel tickets, ticket_messages, ticket_ads_accounts, ticket_activities, attachments. Ticket lifecycle via model booted hooks — auto notification on status change, broadcast via Reverb.

Skema entitas Ticketing Konco. Tabel tickets adalah master; ticket_messages menyimpan pesan chat; ticket_activities audit log perubahan.

tickets

Master tiket bantuan.

FieldTipeCatatan
idUUID (PK)
ticket_numberintegerNomor urut (auto-increment) — di-pad ke 4 digit untuk display
member_idUUID (FK)→ members.id (pemilik tiket)
admin_idUUID (FK, nullable)→ users.id (admin penanggung jawab)
subjectstringCast CleanHtmlInput (HTML purifier)
descriptiontextCast CleanHtmlInput
statusstringpending / open / closed
prioritystring (nullable)low / medium / high
reopened_atdatetime (nullable)Set saat tiket di-reopen
closed_atdatetime (nullable)
created_by, updated_byUUIDWithAuthor
timestamps—

Status derivasi

php
function getStatusInfoAttribute() {
    $status = strtolower($this->status ?? 'open');
    if ($status === 'open' && $this->admin_id) {
        $status = 'on progress';
    }
    // ...badge color mapping
}
Stored statusDisplay bila admin_id terisi
pendingpending (blue)
openon progress (warning) — bila ada admin_id
closedclosed (red)

Relasi

MethodTipeTarget
message()HasManyTicketMessage
ads_account()HasManyTicketAdsAccount
member()BelongsToMember
admin()BelongsToUser (guard admin)
activities()HasManyTicketActivity

Model booted hooks

EventTriggerSide effect
creatingTicket baruSet admin_id = null eksplisit
createdTicket tersimpanLog activity “Tiket #{number} telah dibuat”
updatedadmin_id berubah & tidak nullTicketHandledNotification + email email.ticket.ongoing + AdminTicketAssignedNotification
updatedstatus berubah ke closedTicketClosedNotification + email email.ticket.closed
updatedstatus berubah ke open & reopened_at setTicketReopenedNotification + email email.ticket.reopened
deletingHapus tiketCek guard admin / status pending, cascade hapus messages & ads_accounts

ticket_messages

Pesan tiket — live chat antara member & admin.

FieldTipeCatatan
idUUID (PK)
ticket_idUUID (FK)→ tickets.id
user_idUUID (FK)→ users.id (pengirim — member atau admin)
messagetextIsi pesan
readbooleanStatus sudah dibaca penerima
attachment_idUUID (FK, nullable)→ attachments.id
created_by, updated_byUUIDWithAuthor
timestamps—

Event on create

TicketMessageCreated — broadcast ke channel private-ticket.{ticket_id} via Reverb. Frontend dengar event ini untuk append pesan ke chat UI real-time.

Relasi

MethodTipeTarget
ticket()BelongsToTicket
user()BelongsToUser (pengirim)
attachment()BelongsToAttachment

ticket_ads_accounts

Relasi tiket ↔ akun iklan (tiket bisa melibatkan multiple akun).

FieldTipe
idUUID (PK)
ticket_idUUID (FK)
ads_account_idUUID (FK)
ads_idstring (cache ID Meta)
timestamps—

Accessor Ticket::getAdsAccountListAttribute() — return comma-separated ads_id list.

ticket_activities

Audit log perubahan tiket.

FieldTipeCatatan
idUUID (PK)
ticket_idUUID (FK)→ tickets.id
user_idUUID (FK)→ users.id (actor)
actionstringcreated / assigned / status_changed / closed / reopened
messagestringDeskripsi perubahan
metadataarray (JSON, nullable)Data tambahan
timestamps—

attachments

Lampiran pesan tiket.

FieldTipeCatatan
idUUID (PK)
user_idUUID (FK)Uploader
filenamestringNama file asli
pathstringPath di storage
mime_typestring
sizeintegerBytes
diskstringlocal / public / s3
timestamps—

Ticket auto-assignment

Jika config ticket_support_role (UUID role) diset, Ticket::getadmin() auto-pilih admin random dengan role tersebut:

php
static function getadmin() {
    $cs = Config::get_val('ticket_support_role');
    if (!Str::isUuid($cs)) return;
    return User::whereHas("roles", fn($q) => $q->where("id", $cs))
        ->admin()->active()->inRandomOrder()->pluck('id')->first();
}

Broadcast channels

php
// routes/channels.php
Broadcast::channel('ticket.{id}', function ($user, $id) {
    $ticket = Ticket::find($id);
    return $ticket && ($ticket->member_id === $user->member->id ?? null
        || $user->guard_name === 'admin');
});

Scopes

ScopeFungsi
scopeActive($q)whereIn('status', ['pending', 'open'])
scopeUnread($q)Filter pesan belum dibaca — logic berbeda untuk admin vs member

Langkah berikutnya