roundly-consulting/tickets-for-laravel
A Laravel package that adds ticketing support to your application, providing models, migrations, and helpers for creating, tracking, and managing support tickets out of the box. Integrates with Eloquent and Blade views for quick setup and customization.
config/tickets.php omits routes.enabled or sets it to null, the API no longer loads. This turns "do we expose a tickets API to our app, integrations, or mobile clients?" into a deliberate launch decision (TICKETS_ROUTES=on) rather than an accident of how the config was published. It also shrinks the default attack surface if you adopt the domain layer now and add HTTP endpoints later.guestLink(), Tickets::guests()->linkFor()) means you can support a "contact us without an account" funnel with attribution. The 1.0.1 fix to assertGuestLinkIssued() also makes this flow testable, which lowers the cost of iterating on that funnel.Tickets::guests()->convert() turns a guest into a real user at signup. The 1.0.1 fix (watch-list collisions no longer abort and roll back the conversion) directly de-risks a signup-adjacent conversion step, which is typically a high-traffic, high-blast-radius path.duplicates semantics, plus the ability to merge a resolved ticket into another, support deduplication/triage features. The corrected DELETE /tickets/{ticket}/links/{link} behavior makes merge/unlink UI trustworthy — previously the wrong direction could be removed, corrupting history and activity logs.requester_can_edit plus requester_editable fields, and the corrected TicketPolicy::update() with no attributes passed, let you answer "can this person edit anything at all?" correctly in UI (show/hide the edit affordance) — a common source of confused support contacts.filter[created_to], filter[due_to], and datetime custom fields are now inclusive of the whole named day, while true datetime bounds stay exact. This is a decision-relevant correctness fix for any dashboard, backlog report, or SLA/overdue computation built on those filters.close vs dismiss both lead resolved → closed) constrain workflow design — you must transition by name on a resolved ticket. Plan state-machine/UX work around named transitions rather than assuming a single "close" verb.tickets:install and tickets:doctor give you a supported setup and health-check path, including a clean baseline on an untouched install. As of 1.0.3, tickets:doctor and php artisan about agree on the API switch — when config is published without routes.enabled, about reports Routes OFF and the doctor warns the HTTP API is off rather than falsely claiming it is served. That means the one command your on-call and support engineers already run tells the truth about whether ticket endpoints are live.Consider it when:
User model, auth, policies, and notifications — instead of syncing with an external helpdesk.requester_can_edit, requester_editable).Tickets::fake() with link/guest assertions is a meaningful velocity argument.Look elsewhere, or budget extra discovery, when:
User model. The install path requires Contracts\Assignee with Concerns\IsTicketAssignee; without it claim() and assign() throw InvalidAssigneeException. In a locked-down or multi-tenant identity setup, validate this early — it's the single most likely first-day blocker.transitionTo() on a resolved ticket throws. If your process can't be expressed as named transitions, expect friction.package-toolkit ^1.4.0, so your dependency tree moves with it.routes.enabled. Pre-1.0.3 that config loaded the API implicitly; now it does not. If any integration, mobile build, or partner relies on those endpoints, you must opt in explicitly.For executives (one paragraph): This package gives us a production-ready ticketing core inside our existing Laravel application, so we can ship support workflows — guest contact forms, agent assignment, duplicate/merge handling, requester self-service edits, and date-bounded reporting — without buying a separate helpdesk or building the domain model ourselves. The 1.0.x line is a stabilization series with no breaking changes to the domain model: 1.0.1 fixed correctness bugs that would otherwise have produced wrong merge results, off-by-one reporting windows, incorrect permission answers in the UI, and a failed guest-to-account conversion; 1.0.3 tightens the HTTP API so it stays off unless we deliberately turn it on, and makes our ops tooling (php artisan about, tickets:doctor) report that state consistently. Net effect: a smaller default surface area, fewer "is the API up?" false alarms, and no surprises for anyone who didn't intend to expose ticket endpoints. Cost remains one required integration on our User model plus our own UI.
For engineering (what to verify and plan for):
Contracts\Assignee on the user model with Concerns\IsTicketAssignee. claim() and assign() fail hard without it (InvalidAssigneeException). Note the is_support flag in the printed Gate line is our own agent flag, not a package concept.config/tickets.php that omits routes.enabled, or sets it to null, no longer serves the HTTP API. Before upgrading, grep your config and environment for TICKETS_ROUTES / routes.enabled. If you do serve the API, set TICKETS_ROUTES=on explicitly (or set routes.enabled to true) and assert it in a smoke test; if you don't, treat the closed default as the desired state and keep it that way.about and doctor as one source of truth: post-1.0.3 both read the switch identically. The doctor's missing-key warning now correctly states the API is off (and points at TICKETS_ROUTES=on) instead of claiming it is served — if anyone had muted or worked around that warning, un-mute it during the upgrade.package-toolkit ^1.4.0. Run a full composer update in a branch, check the lockfile diff, and confirm nothing else in the app pins an older toolkit.TicketPolicy::update() answer when called without attributes, resilient guest conversion with watch-list merge, clean tickets:doctor baseline on untouched installs, and the fail-closed API switch.tickets:install output/README for the assignee step; re-publish/merge config/tickets.php and decide the API switch explicitly rather than inheriting it; re-run tickets:doctor and confirm the shipped lifecycle's lifecycle.ambiguous_target reports ok. Custom lifecycles still warn — address that note before rollout, and use named transitions (e.g., transitionTo(TicketStatus::Closed)) rather than assuming a single close verb on a resolved ticket.duplicates links, assertGuestLinkIssued() for links created without an explicit guest, date-only vs datetime filter bounds, @can('update', $ticket) without attributes, guest conversion racing an existing watch, and an explicit assertion of the API's enabled/disabled state so a future config publish can't silently flip it.How can I help you explore Laravel packages today?