Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ticket Bundle Laravel Package

nti/ticket-bundle

nti/ticket-bundle provides a lightweight ticketing layer for PHP applications, letting teams create, assign, and track support requests with statuses, priorities, and comments. It fits into existing Symfony/Laravel projects for simple helpdesk and issue-management workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

v3.2.0 is the first release worth onboarding against — multiple boards, a repaired inbox sync command, and ticket document storage change the day-to-day shape of the package enough that you should start here rather than with earlier versions.

  1. Install and publish:
    composer require <vendor>/nti-tickets:^3.2
    php artisan vendor:publish --provider="Nti\Tickets\TicketsServiceProvider"
    php artisan migrate
    
    Review the published config/nti.php (or config/tickets.php) before migrating — v3.2 introduces a boards collection where earlier versions had a single board block.
  2. Where to look first:
    • The published config: board definitions, credentials, and the document disk.
    • Nti\Tickets\Console\Commands\SynchronizeBoardsInboxCommand — the command name (nti:ticket:synchronize-boards-inbox) tells you the intended entry point.
    • The Board / Ticket models and their relationships — these define what you actually query in your app.
  3. First use case: register at least one board in config, then run a single sync and read back the result:
    php artisan nti:ticket:synchronize-boards-inbox --board=default -v
    
    Then confirm tickets landed (Ticket::where('board_id', ...)->latest()->first()) and that any attachments written to storage/app/... look right. That one loop — config → sync → query → inspect document — exercises every moving part.

Implementation Patterns

Board-scoped sync as the default mental model. With multi-board support, treat the command as per-board work rather than a global job. Schedule one entry per board so a slow or failing board doesn't stall the rest:

// routes/console.php
Schedule::command('nti:ticket:synchronize-boards-inbox')
    ->everyFiveMinutes()
    ->withoutOverlapping()
    ->runInBackground();

If the command supports a board argument/filter, prefer explicit per-board scheduling over a single sweeping run — it gives you per-board cadence (some inboxes need 1 minute, others hourly) and per-board failure isolation.

Wrap syncs in a queued job. Artisan commands are fine for cron, but a job gives you retries, backoff, and a place to hang observability:

class SyncBoardInbox implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [30, 120, 600];

    public function __construct(public int|string $boardId) {}

    public function handle(TicketSynchronizer $sync): void
    {
        $sync->forBoard($this->boardId)->run();
    }
}

Keep the job payload to a board identifier, never a hydrated model — boards get renamed/deleted and serialized models go stale.

Idempotent upserts keyed on the external ID. Every sync pattern in this package family relies on a stable remote identifier. Upsert on (board_id, external_id) rather than trusting local auto-increment IDs, so re-runs and overlapping schedules are harmless:

Ticket::upsert($rows, ['board_id', 'external_id'], ['subject', 'status', 'updated_at']);

Documents: decide ownership up front. v3.2 persists ticket documents. Two viable patterns:

  • Mirror — download on sync into a configured disk, store the path on the ticket/document row. Good for retention, offline access, and serving through your own auth.
  • Reference — store only the remote URL/ID and stream on demand. Cheaper, but you inherit the remote system's auth and availability.

Mirroring is usually right for anything user-visible; reference-only is fine for audit exports. Either way, route all writes through Storage::disk(config('nti.documents.disk')) so tests and local dev can swap to a fake.

Per-board credential isolation. Multi-board almost always means multi-tenant in practice. Keep credentials in config/env keyed by board slug, not on the board model, and resolve the client per board through the container rather than a singleton — a shared client is how you accidentally post board A's ticket to board B.

Test against a fake HTTP layer. Use Http::fake() with fixtures captured from one real sync per board shape. Assert on the upsert result and on Storage::disk(...)->assertExists(...) for the document path; that's the pair most likely to regress.

Gotchas and Tips

  • The multi-board change is the migration risk, not the feature. Expect a board_id column added to tickets and a config key that previously held a single board now holding a collection. Backfill board_id for existing rows before adding a NOT NULL constraint, and check whether old flat config keys still work — v3.2 notes "code cleaning," which often removes deprecated accessors silently. Grep your codebase for direct config reads (config('nti.board_id')) rather than going through the package's resolver.
  • The sync command was fixed, which means it was broken. Anything you built as a workaround around the old nti:ticket:synchronize-boards-inbox behaviour — custom loops, manual pagination, post-processing of half-synced inboxes — should be re-evaluated, not assumed compatible. Re-read the command's options; a bug fix often changes option semantics or exit codes.
  • Exit codes and output format. If you drive the command from a scheduler or CI smoke test, verify it still returns non-zero on failure after the fix. A command that "no longer errors" can also be one that swallows errors.
  • Document storage failures are silent by default. A ticket can save while its attachment download fails. Log document failures distinctly (ticket id + remote document id + reason) and consider a separate retry path just for documents — re-downloading an entire inbox to recover one PDF is wasteful.
  • Disk and path conventions. Confirm whether the package writes under a board-scoped prefix. If it doesn't, two boards with the same remote filename will collide. Adding a boards/{slug}/ prefix via a custom path resolver is the cheap fix.
  • Size and mime limits. Large attachments will blow memory if streamed into a string. Prefer streaming to disk and set a max-size guard in config; also validate mime types if documents are ever served back to users.
  • withoutOverlapping() is not optional. Inbox syncs are typically paginated and slow; overlapping runs plus upserts on the same key is a deadlock recipe on busy boards. If you can't use the scheduler lock, use a cache lock per board.
  • Rate limits scale with board count. Multi-board multiplies your request volume against the same upstream API. Even if the package handles throttling, budget per-board concurrency and watch for 429s after you add the third or fourth board.
  • Watch for N+1s in your own code, not the package's. Boards → tickets → documents is three levels; Board::with('tickets.documents') on any dashboard listing. The package's "code cleaning" won't fix your queries.
  • Extension points worth knowing: the config-driven board resolver, the document storage path/disk config, and the synchronizer binding in the service provider. Overriding the synchronizer binding is the cleanest way to inject per-board custom mapping without forking.
  • Debugging recipe: run one board with -v, tail the log channel the package writes to, and compare Ticket::count() before/after. If counts match but fields are stale, the problem is in your upsert key, not the transport.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect