tomatophp/tomato-invoices
Tomato Invoices is a Laravel package for creating, managing, and tracking invoices in TomatoPHP and Filament admin panels. Generate professional invoices with clients, line items, taxes, totals, statuses, and PDF-ready output from a streamlined billing interface.
tomatophp/tomato-invoices is a Filament-oriented invoicing module from the TomatoPHP stack. It ships models, migrations, Filament resources, and (as of v1.0.5) an HTTP route surface for product/line-item handling.
Minimum path to a working install:
composer require tomatophp/tomato-invoices — pin the constraint ("^1.0.5") rather than tracking dev-main; this is a fast-moving 1.x package.php artisan vendor:publish --tag=tomato-invoices-migrations
php artisan vendor:publish --tag=tomato-invoices-config
php artisan migrate
->plugin(TomatoInvoicesPlugin::make())), then clear caches: php artisan optimize:clear.Where to look first: the published config (for table prefixes, model bindings, and disk settings), then the Filament resource classes for Invoice and Product. Those two resources define the day-to-day surface area far more than the models do.
First real use case: generate a draft invoice from a customer plus line items, render it to PDF, and store it on a configured disk. Get that working in a queue-safe way before building anything on top of it.
Treat invoices as an aggregate, not a CRUD row. The invoice header, its line items, and its computed totals need to move together. Wrap creation and mutation in a transaction and recalculate totals from the line items rather than trusting a form-supplied total:
DB::transaction(function () use ($data) {
$invoice = Invoice::create($header);
$invoice->items()->createMany($data['items']);
$invoice->recalculateTotals()->save();
});
Use observers for total recalculation. Hooking saved/deleted on the line-item model keeps totals correct regardless of whether the change came from Filament, a console command, or an import. This is the single highest-leverage integration point.
Model swapping over forking. TomatoPHP packages generally expose model class bindings in config. If you need tenant scoping or extra columns, bind your own subclass in config and add a migration for the extra fields — don't edit the vendor migrations, or every upgrade becomes a merge.
PDF generation belongs in a queued job. Rendering and uploading a PDF is slow and failure-prone (fonts, disk timeouts). Dispatch a job, store the file path on the invoice, and let the Filament action poll or notify. Keep the render path idempotent so retries don't produce duplicate files.
Extend Filament resources via configuration, not by copying. Where the package exposes form/table schema hooks, use them. Where it doesn't, subclass the resource and re-register it in your panel — this survives minor upgrades much better than editing the published resource in place.
Scope by tenant at the query level. If you're multi-tenant, apply a global scope to the invoice model early. Retrofitting tenancy after you have production invoice data is painful, and invoice numbering schemes usually need to become tenant-aware at the same time.
Keep an eye on the route layer. The v1.0.5 fix was a route correction for products, which means the HTTP surface is still settling. If you consume these routes directly (rather than through Filament), write a thin integration test that hits each endpoint you depend on, so a patch release that reshuffles a route name fails your CI rather than production.
route:clear (or optimize:clear) runs. On cached-route production deploys, this is the most common "the fix didn't work" report.migrate won't pick up new vendor migrations. Diff the package's database/migrations against your published set on each minor bump.deleted on some setups — use the appropriate model event or forceDeleted handling so totals don't silently go stale.storage_path permissions, and non-default disks are the usual culprits for blank or missing PDFs. Test the render path in the same container image you deploy, not just locally.APP_DEBUG=true and check whether the failure is in the resource or in a policy. Missing policies on new resources are a frequent source of 403s that look like route bugs.How can I help you explore Laravel packages today?