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
| Stav | Kategória | Poznámky |
|---|---|---|
new | Open | počiatočný |
open | Open | — |
in_progress | Active | — |
pending | Waiting | čaká na žiadateľa |
on_hold | Waiting | čaká na tretiu stranu |
resolved | Resolved | zaznamená resolved_at; po tickets.auto_close.after sa automaticky uzavrie |
closed | Closed | koncový; zaznamená closed_at |
Prechody
| Prechod (konštanta) | Z | Do | Pravidlá |
|---|---|---|---|
open (OPEN) | new | open | povolené aj pre systém |
start (START) | new, open, pending, on_hold | in_progress | — |
wait (WAIT) | new, open, in_progress | pending | — |
hold (HOLD) | new, open, in_progress | on_hold | povinný dôvod |
resume (RESUME) | pending, on_hold | open | povolené aj pre systém |
resolve (RESOLVE) | new, open, in_progress, pending, on_hold | resolved | payload resolution (voliteľný reťazec, ≤ 5000) |
reopen (REOPEN) | resolved | open | povolené aj pre systém |
close (CLOSE) | resolved | closed | povolené aj pre systém |
dismiss (DISMISS) | každý stav okrem closed | closed | povinný 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ória | Nevyriešené | Význam |
|---|---|---|
| Open | áno | nová, ešte netriedená práca |
| Active | áno | niekto na ňom pracuje |
| Waiting | áno | čaká na žiadateľa alebo tretiu stranu — prekročenie termínu sa stále ráta |
| Resolved | nie | — |
| Closed | nie | konverzá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ácia | Kedy | Dodaný prechod | Musí opustiť | Musí skončiť v |
|---|---|---|---|---|
Assigned | tiket v počiatočnom stave dostane riešiteľa (aj pri založení) | open | počiatočný stav | Open, Active |
RequesterReplied | žiadateľ alebo účastník odpovie počas stavu Waiting | resume | každý stav Waiting | Open, Active |
RequesterReopened | žiadateľ alebo účastník odpovie počas stavu Resolved (tickets.comments.reply_reopens) | reopen | každý stav Resolved | Open, Active, Waiting |
AutoClose | vyprší TTL vyriešeného tiketu (sweep z lifecycle) | close | každý stav Resolved | Closed |
Merged | tiket sa zlúči do iného | dismiss | každý stav Open, Active, Waiting, Resolved | Closed |
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 kryptomienOdoslaní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.