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

Fasáda Tickets

Všetko ide cez jednu fasádu, RoundlyConsulting\Tickets\Facades\Tickets (globálny alias Tickets), ktorá je skratkou nad singletonom TicketsManager. Tiket od založenia po vyriešenie:

use Illuminate\Support\Facades\Gate;
use RoundlyConsulting\Tickets\Facades\Tickets;

// Who is an agent is your call: is_support stands for your own agent flag. An undefined ability denies.
Gate::define('tickets.agent', fn ($user) => $user->is_support);

// A category, once: its defaults apply to every ticket filed under it.
$hardware = Tickets::categories()->make(['en' => 'Hardware', 'sk' => 'Hardvér'])
    ->defaults(priority: 'high', dueIn: 'P1D')
    ->create();

// The customer opens a ticket — numbered TCK-1, high priority, due in a day.
$ticket = Tickets::make()
    ->subject('Printer on floor 3 is jammed')
    ->description('Paper jam error E-102 since this morning.')
    ->requester($customer)
    ->category($hardware)
    ->create();

// An agent takes it, answers the customer and leaves a note only agents see.
Tickets::for($ticket)->by($agent)->claim()->start();
Tickets::for($ticket)->by($agent)->reply('A technician is on the way.', transition: 'wait');
Tickets::for($ticket)->by($agent)->note('Roller worn out, order a spare.');

// The customer's reply puts it back in the queue; the agent resolves it.
Tickets::for($ticket)->by($customer)->reply('Works again, thanks!');
Tickets::for('TCK-1')->by($agent)->resolve('Roller replaced.');

Tickets::for('TCK-1')->status();   // TicketStatus::Resolved
Tickets::for('TCK-1')->thread();   // the two public replies — never the note

Metódy manažéra

MetódaVraciaČo robí
make()TicketBuilderFluentný builder pre nový tiket.
create(CreateTicketData $data)TicketTá istá cesta kódu ako builder.
find(string $reference, bool $withTrashed = false)?TicketŽivý tiket podľa čísla (SUP-42); s withTrashed aj soft-deleted.
findOrFail(string $reference, bool $withTrashed = false)TicketInak TicketNotFoundException.
for(Ticket|string $ticket)TicketHandlePodľa modelu alebo čísla; neznáme či soft-deleted číslo vyhodí TicketNotFoundException.
visibleTo(Model $user)Builder<Ticket>Tikety, ktoré policy view povoľuje používateľovi (alebo hosťovi).
project(Model&TicketProject $project)ProjectTicketsTikety, kategórie, polia a štítky jedného projektu.
categories()Accessors\CategoriesStrom kategórií a ich predvolené hodnoty.
fields()Accessors\FieldsDefinície vlastných polí.
tags()Accessors\TagsGlobálne a projektové štítky.
guests()Accessors\GuestsHostia — resolve, find, update, convert, odkazy.
sweep()SweepResultKontrola termínov, ktorú spúšťa tickets:sweep.
Tickets::fake()TicketsFakeLen na fasáde — nahrávací fake, ktorý volania aj tak vykoná (pozrite Testovanie).

Na manažérovi je verejná aj metóda perform(), no je interná: je to jediný lievik, cez ktorý každý handle, pod-prístup, builder aj metóda modelu spúšťa svoju akciu, a háčik, cez ktorý zaznamenáva fake. Z kódu aplikácie ju nevolajte.

Handle for($ticket)

Tickets::for($ticket) vráti nemenný TicketHandle. Mutátory vracajú handle, takže ich môžete reťaziť; metódy, ktoré niečo vytvárajú alebo čítajú, vracajú svoj výsledok. Naviazaný Ticket sa aktualizuje na mieste, takže zreťazené volanie vidí účinok predošlého:

Tickets::for('SUP-42')->by($agent)->claim()->prioritize('urgent')->due(now()->addHours(4))->wait();
MetódaVraciaČo robí
by(?Model $actor) / asSystem()TicketHandleNový handle, ktorý nasledujúce volania pripíše aktérovi; by(null) = prihlásený používateľ, asSystem() = nikto.
ticket()TicketNaviazaná inštancia, každé volanie ju aktualizuje na mieste (nečíta sa znova).
update(UpdateTicketData $data)TicketHandlePredmet, popis, priorita, kategória, termín, žiadateľ, meta hostiteľa — len to, čo zadáte.
prioritize(BackedEnum|string $priority)TicketHandleZmení prioritu.
categorize(TicketCategory|int|null $category)TicketHandlePredvolené hodnoty kategórie sa znova neuplatnia; novo povinné polia potrebujú hodnotu.
due(?CarbonInterface $at)TicketHandleNastaví, posunie alebo zruší termín; posun znova aktivuje príznaky po termíne a tesne pred termínom.
changeRequester(Model|GuestData $requester)TicketHandleAutomatické sledovanie predošlého žiadateľa zanikne; odkazy predošlého žiadateľa-hosťa sa zneplatnia.
assign(Model $assignee) / unassign() / claim()TicketHandlePriradenie — pozrite Priradenie a sledujúci.
transition(string $name, ?string $reason = null, array $payload = [])TransitionResultĽubovoľný prechod nakonfigurovaného životného cyklu (výsledok z lifecycle).
start(), wait(), hold($reason), resume(), resolve($resolution), reopen($reason), close($reason), dismiss($reason)TicketHandlePrechody dodaného životného cyklu.
can(string $transition)boolČi je prechod práve teraz povolený pre tohto aktéra.
allowedTransitions()list<string>Všetky práve povolené prechody.
status()TicketStateAktuálny stav.
reply(string $body, ?string $transition = null, array $attachments = [], ?string $channel = null, ?string $reason = null, array $payload = [], array $meta = [])CommentVerejná odpoveď; prechod sa uplatní atomicky — a len ak sa odpoveď zverejní. Prílohy sú uploady alebo lokálne cesty.
note(string $body, array $attachments = [], ?string $channel = null, array $meta = [])CommentInterná poznámka — len agenti pre tento tiket a systém.
editComment(Comment $comment, string $body)CommentZaznamená sa ako comment_edited; komentár iného tiketu vyhodí TicketCommentNotFoundException.
deleteComment(Comment $comment)TicketHandleZaznamená sa ako comment_deleted.
thread(bool $internal = false)Collection<int, Comment>Od najstaršej; len verejné, alebo všetky správy.
activity(bool $internal = false)Collection<int, TicketActivity>Od najstaršieho; len verejné záznamy, alebo všetky — rovnako bezpečné predvolenie ako thread().
fields() / tags() / watchers() / links() / attachments()TicketFields / TicketTags / …Pod-prístupy — nižšie.
mergeInto(Ticket|string $target, ?string $reason = null)MergeResultZlúči tento tiket do cieľového (pozrite Zlučovanie tiketov).
guestLink(?Model $guest = null)stringPre žiadateľa-hosťa alebo hosťa medzi sledujúcimi, kým je zapnuté watchers.can_view; pre kohokoľvek iného vyhodí GuestAccessDeniedException.
revokeGuestAccess()TicketHandleZneplatní všetky doteraz vydané odkazy.
delete() / restore()TicketHandleSoft delete / obnovenie.

Pod-prístupy

PrístupMetódy
fields() → TicketFieldsset($key, $value), setMany(array), forget(array|string), get($key), all(), definitions(), ticket()
tags() → TicketTagsattach($tags), detach($tags), sync(array), all(), ticket()
watchers() → TicketWatchersadd($model), remove($model), watch(), unwatch(), has($model), all(), ticket()
links() → TicketLinksadd($other, LinkType), remove($other, LinkType), all(): list<TicketLinkView>, ticket()
attachments() → TicketAttachmentsadd($file): Media, remove(Media|string $media), all(), ticket()

Mutátory pod-prístupov vracajú samotný pod-prístup, takže sa reťazia v rámci svojej oblasti: ->tags()->attach(['vip'])->detach(['spam']). Každá zmena soft-deleted tiketu vyhodí TicketTrashedException (lifecycle jeho prechody odmietne cez SubjectTrashedException).

Rozsah je bezpečnostná hranica. Handle odmietne všetko, čo nepatrí jeho tiketu: komentár iného tiketu, prepojenie, ktorého tento tiket nie je súčasťou, médium iného vlastníka či odkaz pre hosťa pre niekoho, kto nie je žiadateľom-hosťom ani hosťom medzi sledujúcimi. ProjectTickets::for() odmietne tiket iného projektu.

Tickets::categories()

MetódaVraciaČo robí
make(array|string $name)CategoryBuilderdescription(), project(), under(), position(), active(), inactive(), defaults(priority:, assignee:, dueIn:), data(), create().
create(CategoryData $data)TicketCategoryDTO podoba buildera.
update(TicketCategory $category, UpdateCategoryData $data)TicketCategoryLen zadané polia; bez rodiča (použite move()) a bez projektu (je pevný).
move(TicketCategory $category, TicketCategory|int|null $parent, ?int $position = null)TicketCategoryRodič null ju presunie do koreňa.
delete(TicketCategory $category, TicketCategory|int|null $reassignTo = null)TicketCategorySoft delete; reassignTo je potrebné, kým ju používajú živé tikety.
restore(TicketCategory $category)TicketCategoryObnoví ju v pôvodnej podobe.
find(int $id)?TicketCategoryPodľa id.
tree(?Model $project = null)CollectionKorene s načítanými potomkami — globálny strom plus strom projektu.

Názov ako reťazec je v aktuálnom jazyku; pole je mapa jazykov (['en' => 'Hardware', 'sk' => 'Hardvér']).

Tickets::fields()

MetódaVraciaČo robí
make(string $key, FieldType $type)FieldBuilderlabel(), help(), options(), rules(), required(), encrypted(), default(), requesterVisible(), requesterEditable(), agentOnly(), position(), active(), inactive(), project(), category(), data(), define().
define(FieldData $data)TicketFieldDTO podoba buildera.
update(TicketField $field, UpdateFieldData $data)TicketFieldLen zadané atribúty.
delete(TicketField $field) / restore(TicketField $field)TicketFieldSoft delete hodnoty skryje; obnovenie ich vráti.
prune(bool $dryRun = false, int $chunk = 200)intPočet zabudnutých (alebo spočítaných) osirotených hodnôt.
find(int|string $idOrKey, ?Model $project = null)?TicketFieldŽivé pole podľa id alebo kľúča: s projektom jeho pole alebo globálne; bez projektu globálne.
for(Ticket|TicketCategory $subject, ?Model $project = null)array<string, FieldDefinition>Platná sada polí podľa kľúča, v poradí.

Tickets::tags()

MetódaVraciaČo robí
create(TagData|string $name, ?string $color = null, ?Model $project = null)TicketTagVytvorí štítok (opätovné vytvorenie zmazaného názvu ho obnoví, bez tiketov).
findOrCreate(string $name, ?Model $project = null)TicketTagSúbežné volania vytvoria jediný štítok.
rename(TicketTag $tag, string $name) / recolor(TicketTag $tag, ?string $color)TicketTagSlug nasleduje premenovanie.
update(TicketTag $tag, UpdateTagData $data)TicketTagNázov a farba.
delete(TicketTag $tag)TicketTagVráti zmazaný štítok a odoberie ho zo všetkých tiketov.
find(int|string $idOrSlug, ?Model $project = null)?TicketTagŽivý štítok podľa id alebo slugu (názov sa prevedie na slug): projektový, inak globálny.
all(?Model $project = null)CollectionGlobálne štítky plus štítky projektu, podľa názvu.

Tickets::guests()

MetódaVraciaČo robí
resolve(GuestData|array $fields)TicketGuestNájde alebo vytvorí hosťa z jeho polí — zvalidovaných a normalizovaných.
find(GuestData|array $fields)?TicketGuestZhoda len na čítanie podľa normalizovanej identifikačnej hodnoty — nikdy nič nevytvorí, nedoplní ani nezvaliduje; fake ju nezaznamená.
update(TicketGuest $guest, GuestData|array $fields)TicketGuestZmení polia hosťa.
convert(TicketGuest $guest, Model $user)intPresunie všetky tikety a sledovania na registrovaného používateľa; vráti počet presunutých tiketov.
linkFor(Ticket|string $ticket, ?TicketGuest $guest = null)stringTo isté ako Tickets::for($ticket)->guestLink().

Tickets::project($project)

MetódaVraciaČo robí
project()Model&TicketProjectProjekt.
make()TicketBuilderBuilder už nastavený na projekt.
tickets()BuilderTikety projektu ako dopyt.
find(string $reference)?TicketTiket tohto projektu.
for(Ticket|string $ticket)TicketHandleTiket iného projektu vyhodí výnimku.
categories() / tags()CollectionGlobálne plus projektové.
fields(TicketCategory|int|null $category = null)arraySada polí, ktorú dostane tiket tohto projektu (a kategórie).

Traity modelov

Traity delegujú na manažéra, takže všetko, čo robia, zaznamená aj fake:

TraitPrePridáva
Concerns\RequestsTicketspoužívateľ vašej aplikácierequestedTickets(), assignedTickets(), watchedTickets(), visibleTickets(), openTicket(): TicketBuilder
Concerns\HasTicketsprojekt vašej aplikácie (implementuje TicketProject)tickets(), ticketCategories(), ticketFields(), ticketTags(), ticketing(): ProjectTickets, openTicket()
Concerns\IsTicketAssigneeakýkoľvek riešiteľticketAssigneeName(), ticketNotifiables(), isEligibleTicketAssignee(Ticket)
Concerns\HasConfigurableKeyTicket a TicketGuest z balíka (na podtriede ho ponechajte)configuredKeyType(): KeyType, newUniqueId()

Dátové objekty

Každé DTO je final readonly:

DTOPolia
CreateTicketDatasubject, requester (Model|GuestData), ?description, ?category, ?priority, ?project, ?assignee, ?dueAt, ?channel, ?actor, system, fields, tags, watchers, attachments (uploady alebo lokálne cesty), meta (patrí hostiteľovi).
UpdateTicketDatasubject, description, priority, category, dueAt, requester, meta (nahradí celú mapu; [] ju vyprázdni) — každé predvolene Support\Optional (nezadané), takže null môže hodnotu zrušiť.
GuestDatafields: array<string, scalar|null>.
CategoryData / UpdateCategoryDataname, ?description, ?parent, ?project, position, active, ?defaultPriority, ?defaultAssignee, ?defaultDueIn (CarbonInterval|string). Pri úprave je každé pole Optional, bez parent a project.
FieldData / UpdateFieldDatakey, type, label, ?help, options, rules, required, encrypted, default, requesterVisible, requesterEditable, position, active, ?project, ?category. Pri úprave je každé Optional — key, project a category sú pevné.
TagData / UpdateTagDataname, ?color, ?project / name, color (Optional).
TransitionData, ReplyDatatransition, ?reason, payload / body, attachments, ?channel, meta.
MergeResultsource, target, comments (presunuté), attachments (presunuté).
TicketLinkViewlink (id), ticket (druhý koniec), type, label, direction (outgoing / incoming).
SweepResultoverdue, dueSoon — tikety, ktoré táto kontrola novo označila; total(): int ich sčíta.
FieldDefinitionVyhodnotené pole: id, key, type, label (mapa jazykov), options, rules, required, encrypted, default, requesterVisible, requesterEditable, categoryId; label(?string $locale), attributeDefinition(), validationRules(string $prefix).

PHP API dôveruje volajúcemu

Fasáda vynucuje doménové pravidlá — žiadateľ nesmie pridať poznámku, priradiť možno len vhodného riešiteľa, uzavretý tiket neprijme správu, zlučovanie vyžaduje oprávnenie merge, rozsah agentov rozhoduje, kto je pre tiket agentom —, nikdy sa však nepýta TicketPolicy. Policy je autoritou HTTP API; aplikácia, ktorá volá fasádu, rozhoduje, kto smie čo volať.

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.