NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Tickets for Laravel

Stavy a prechody

Stavy tiketov bežia na lifecycle-for-laravel: definícia v kóde, prechody s pravidlami, pečiatky, TTL, história a idempotencia. Tickets nikdy sám nezapisuje stĺpec status a jeho vlastná logika číta len päť kategórií stavov, nikdy konkrétny stav — preto sa dá životný cyklus vymeniť.

Stavy

StavKategóriaPoznámky
newOpenpočiatočný
openOpen—
in_progressActive—
pendingWaitingčaká na žiadateľa
on_holdWaitingčaká na tretiu stranu
resolvedResolvedzaznamená resolved_at; po tickets.auto_close.after sa automaticky uzavrie
closedClosedkoncový; zaznamená closed_at

Prechody

Prechod (konštanta)ZDoPravidlá
open (OPEN)newopenpovolené aj pre systém
start (START)new, open, pending, on_holdin_progress—
wait (WAIT)new, open, in_progresspending—
hold (HOLD)new, open, in_progresson_holdpovinný dôvod
resume (RESUME)pending, on_holdopenpovolené aj pre systém
resolve (RESOLVE)new, open, in_progress, pending, on_holdresolvedpayload resolution (voliteľný reťazec, ≤ 5000)
reopen (REOPEN)resolvedopenpovolené aj pre systém
close (CLOSE)resolvedclosedpovolené aj pre systém
dismiss (DISMISS)každý stav okrem closedclosedpovinný dôvod; povolené aj pre systém

dismiss pokrýva aj resolved, aby sa vyriešený tiket dal stále zlúčiť alebo zahodiť ako spam. S close zdieľa resolved → closed, preto lifecycle:validate hlási jedno upozornenie ambiguous_target, ktoré tickets:doctor vyhodnotí ako ok. Prechody vždy spúšťajte podľa názvu: na vyriešenom tikete nikdy nevolajte $ticket->transitionTo(TicketStatus::Closed) — pri dvoch možných prechodoch vyhodí AmbiguousTransitionException. Použite close() (alebo transition('close')), prípadne dismiss($reason).

Kategórie stavov

KategóriaNevyriešenéVýznam
Openánonová, ešte netriedená práca
Activeánoniekto na ňom pracuje
Waitingánočaká na žiadateľa alebo tretiu stranu — prekročenie termínu sa stále ráta
Resolvednie—
Closedniekonverzácia je zamknutá

Spúšťanie prechodov

Tickets::for($ticket)->by($agent)->transition('resolve', reason: null, payload: ['resolution' => 'Roller replaced.']);

// Named conveniences — the shipped lifecycle's transitions, each returning the handle
Tickets::for($ticket)->by($agent)->start()->wait();
Tickets::for($ticket)->by($agent)->hold('Waiting for the vendor');
Tickets::for($ticket)->by($agent)->resolve('Roller replaced.');
Tickets::for($ticket)->by($requester)->reopen();
Tickets::for($ticket)->by($agent)->dismiss('Spam');

Tickets::for($ticket)->can('close');           // bool, for this actor, now
Tickets::for($ticket)->allowedTransitions();   // ['wait', 'hold', 'resolve', 'dismiss', …]
Tickets::for($ticket)->status();               // TicketStatus::InProgress
  • transition() vracia TransitionResult z lifecycle; pomenované skratky vracajú handle.
  • Zamietnutie vyhodí TransitionDeniedException z lifecycle s kódmi zamietnutia — API odpovie 422 ticket_transition_denied, alebo 429 s Retry-After, keď zamietnutie uvádza, kedy to skúsiť znova.
  • Skratka, ktorú nakonfigurovaný životný cyklus nemá, vyhodí UnknownTicketTransitionException — použite transition() s názvami vášho cyklu. tickets:doctor vypíše, ktoré skratky podporuje.
  • Prechody soft-deleted tiketu sa odmietnu (SubjectTrashedException).
  • asSystem() beží bez aktéra; dodaný cyklus povolí systému len open, resume, reopen, close a dismiss, takže import či skript odovzdá pre čokoľvek iné aktéra.
  • PHP API uplatní to, čo lifecycle aktérovi povolí; kto sa smie pýtať, rozhodujete vy. Policy API povolí agentom akýkoľvek prechod a žiadateľom len tickets.authorization.requester_transitions (predvolene close a reopen; pri participants_can_transition aj sledujúcemu).

Automatizácie

Prechody, ktoré spúšťa balík, bežia ako systém (žiadateľa teda nikdy neposudzujú pravidlá aktérov životného cyklu) a v zázname aktivity sa pripíšu aktérovi, ktorý ich vyvolal. Nakonfigurovaný cyklus priradí každému háčiku jeden zo svojich prechodov, alebo null na vypnutie:

AutomatizáciaKedyDodaný prechodMusí opustiťMusí skončiť v
Assignedtiket v počiatočnom stave dostane riešiteľa (aj pri založení)openpočiatočný stavOpen, Active
RequesterRepliedžiadateľ alebo účastník odpovie počas stavu Waitingresumekaždý stav WaitingOpen, Active
RequesterReopenedžiadateľ alebo účastník odpovie počas stavu Resolved (tickets.comments.reply_reopens)reopenkaždý stav ResolvedOpen, Active, Waiting
AutoClosevyprší TTL vyriešeného tiketu (sweep z lifecycle)closekaždý stav ResolvedClosed
Mergedtiket sa zlúči do inéhodismisskaždý stav Open, Active, Waiting, ResolvedClosed

Každý priradený prechod musí povoľovať systémový kontext. Explicitné transition: pri odpovedi nahradí automatizáciu odpovede. Odpoveď, ktorú zadrží moderácia hostiteľa, nespustí ani jedno — žiadnu automatizáciu ani explicitný prechod.

Automatické uzavretie

  • Dodaný stav resolved má TTL tickets.auto_close.after (predvolene P7D, alebo off pre nikdy), ktoré vyprší cez close. Nezmyselná hodnota vyhodí InvalidTicketsConfigurationException.
  • TTL sa číta, keď tiket vstúpi do stavu Resolved, takže zmena konfigurácie ovplyvní len neskoršie vyriešenia.
  • Vypršanie uplatní lifecycle:sweep — naplánujte ho každú minútu. tickets:sweep nikdy nič neuzatvára.
  • Znovuotvorenie (alebo zlúčenie, ktoré tiket zahodí) čakajúce automatické uzavretie zruší.

Zámok konverzácie

Listener na LifecycleTransitioning z lifecycle (v transakcii prechodu) zamkne vlákno comments-for-laravel tiketu, keď je kategória cieľového stavu Closed, a odomkne ho, keď tiket stav Closed opustí. Funguje s akýmkoľvek životným cyklom bez kódu na vašej strane; rollbacky a adopcie zámok zosynchronizujú po potvrdení. Pri tickets.guests.revoke_on_close vstup do Closed zneplatní aj všetky odkazy pre hostí.

Udalosti stavov

Každý prechod zapíše záznam aktivity status_changed atomicky so stavom a — po potvrdení — spustí TicketStatusChanged a udalosti vstupu do kategórie: TicketResolved (vstup do Resolved), TicketClosed (vstup do Closed) a TicketReopened (odchod z Resolved alebo Closed do Open, Active či Waiting). Počiatočný riadok histórie žiadnu nespustí; pokrýva ho aktivita created.

Výmena životného cyklu

// config/tickets.php
'lifecycle' => App\Tickets\SupportLifecycle::class,

Najmenšia zmena je zdediť dodanú triedu (nie je final) a upraviť define(). Úplne vlastná definícia musí dediť od LifecycleDefinition z lifecycle a implementovať Contracts\TicketLifecycle (automation(Automation $hook): ?string), používať enum stavov s Contracts\TicketState (category(): StatusCategory), byť životným cyklom status modelu tiketu a každú automatizáciu, ktorá nie je null, priradiť existujúcemu prechodu, ktorý povoľuje systémový kontext a zodpovedá tabuľke vyššie:

use RoundlyConsulting\Lifecycle\Definition\LifecycleBuilder;
use RoundlyConsulting\Lifecycle\Definition\LifecycleDefinition;
use RoundlyConsulting\Tickets\Contracts\TicketLifecycle;
use RoundlyConsulting\Tickets\Enums\Automation;

final class SupportLifecycle extends LifecycleDefinition implements TicketLifecycle
{
    public function define(LifecycleBuilder $lifecycle): void
    {
        $lifecycle->states(SupportStatus::class)->initial(SupportStatus::Triage)->terminal(SupportStatus::Done);
        // … transitions, stamps, a Resolved-category TTL …
    }

    public function automation(Automation $hook): ?string
    {
        return match ($hook) {
            Automation::Assigned => 'accept',
            Automation::RequesterReplied => 'resume',
            Automation::RequesterReopened => null,     // off
            Automation::AutoClose => 'archive',
            Automation::Merged => 'discard',
        };
    }
}

Kontrakt sa overuje lenivo — pri prvom prechode alebo automatizácii, s cache na triedu —, takže chybný cyklus nikdy nerozbije artisan. Porušenie vyhodí InvalidTicketLifecycleException a tickets:doctor nahlási každý problém: not_a_definition, not_status_attribute, state_not_ticket_state, automation_unknown, automation_not_system, automation_source, automation_target plus vlastné problémy validácie lifecycle (lifecycle.<code>). withStatusCategory() aj každé pravidlo podľa kategórií sa riadia nakonfigurovaným enumom.

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.