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

Tomato Invoices Module Laravel Package

tomatophp/tomato-invoices-module

An invoices management module for the TomatoPHP ecosystem, adding invoice creation, tracking, and billing screens to your Filament admin panel. Integrates with other Tomato modules to handle customers, payments, and exports.

View on GitHub
Deep Wiki
Context7

Getting Started

Day-one install (scratch branch, non-production DB):

composer require tomatophp/tomato-invoices-module:^1.0
php artisan vendor:publish        # interactive list — read the tags before choosing
php artisan migrate

Publishing is the step that matters. Run the interactive publisher first and record every tag it exposes (config, migrations, lang, views, assets). Tag names vary across TomatoPHP modules — don't assume --tag=tomato-invoices-config exists until you've seen the list.

Then register the plugin with your Filament panel provider:

// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(\TomatoPHP\Invoices\InvoicesPlugin::class); // verify exact FQCN
}

Where to look first — in this order:

  1. vendor/tomatophp/tomato-invoices-module/composer.json — Laravel, PHP, Filament and sibling tomatophp/* constraints. A Filament v2/v3 mismatch is the single most common silent blocker here; check this before anything else.
  2. src/ — locate the service provider, then the plugin class. That tells you what gets registered globally (routes, views, migrations, translations) and whether anything can collide with existing invoice-ish routes.
  3. database/migrations/ — this is the real specification. The release notes for v1.0.0 are a link and nothing more, so the schema is your only source of truth: table names, column types, enum vs. string statuses, soft-delete columns, decimal precision, and whether numbering is stored or derived.
  4. src/Filament/ — the resources and forms. This is what you're actually adopting (the CRUD layer); read the form schema to see whether totals and tax are computed server-side or trusted from the request.
  5. resources/views/ + any PDF-related service — determine whether rendering is Blade→DomPDF/Snappy inline or queued.

First use case to prove out: in a throwaway branch, create one invoice with two line items through the admin panel, confirm the persisted rows and computed totals in the DB, then produce a PDF from it. If you cannot get a PDF and a correct persisted total inside a day, the module's value proposition is already eroding — stop and reassess before building on it.

Implementation Patterns

Wrap it behind an adapter on day one. This is the highest-leverage decision in the whole adoption, because the package is v1.0.0 with a single maintainer and no dependents. Application code should never reference the module's models directly.

final class BillingService
{
    public function createDraft(Customer $customer, array $lines): InvoiceDTO { /* ... */ }
    public function issue(InvoiceDTO $invoice): void { /* numbering, lock, event */ }
    public function void(InvoiceDTO $invoice, string $reason): void { /* ... */ }
    public function pdfUrl(InvoiceDTO $invoice): string { /* signed, versioned */ }
}

Everything — controllers, jobs, Filament actions, listeners — talks to BillingService. When you fork or replace the module, you rewrite one class instead of grepping the codebase.

Vendor the migrations immediately. Copy the published migrations into your own database/migrations/ with fresh timestamps on the first day, and then stop relying on upstream migrations. Invoice tables become your financial system of record; a future upstream column add or rename landing on a table you're reading directly is the most expensive failure mode in this package class. Forking at install time converts that into a non-event.

Compute money in exactly one place. Persist amounts as integer minor units (or decimal(12,2) with explicit casts) — never float. Do not trust totals submitted from the Filament form's line-item repeater: recalculate subtotal, tax and total in the adapter inside a transaction, and write them. If the module ships its own calculator, keep it behind your adapter so you can swap it for a jurisdiction-aware implementation later.

Solve invoice numbering yourself. Generic invoicing modules typically ship a naive max(id)+1 or count()+1 sequence, which duplicates under concurrency and leaves gaps on rollback — both are compliance incidents, not bugs. Use a dedicated sequence table with lockForUpdate() inside the issuing transaction, plus a unique index on (series, year, number) so the DB is the final referee:

DB::transaction(function () use ($invoice) {
    $seq = InvoiceSequence::where('series', 'INV')->where('year', $year)
        ->lockForUpdate()->firstOrCreate([...]);
    $invoice->forceFill(['number' => ++$seq->last, 'issued_at' => now(), 'status' => 'issued'])->save();
    event(new InvoiceIssued($invoice));
});

Enforce immutability on issued invoices with model events, not just UI. Block updates and deletes once status === 'issued' in a saving/deleting observer, and add a policy. Filament's edit page is one door; background jobs and tinker are others.

Filament integration tips. Don't subclass the module's resources — upstream refactors break subclasses at upgrade time. Instead: register your own resource for panels where behavior diverges, or use getEloquentQuery() overrides via a custom resource that extends Filament's base, plus table-level Actions for "Download PDF", "Mark as Paid", "Void". On list pages, ensure line items are eager-loaded (->with('items')) and pagination is on — Filament will otherwise N+1 the line-item repeater.

Queue PDF generation. A GenerateInvoicePdf job that writes to a stable, versioned disk path (invoices/{id}/{hash}.pdf), idempotent by content hash, dispatched on issue. Never render inside the Filament request — PDF rendering is CPU-bound and the first customer with 40 line items and a logo will time you out.

Order → invoice flow. Listen for your own domain event (OrderPaid) and call BillingService::createDraft(); keep the module out of checkout entirely. This preserves the boundary that it's an admin/records tool, not a payment system.

Testing. Factory the invoice via the adapter, not the model. Assert the job is dispatched (Queue::fake()), assert totals for edge cases (zero-rate tax, discounts, rounding on 3-decimal currencies), and assert a unique-constraint violation when two processes issue concurrently.

Gotchas and Tips

  • Filament major version is the gate. Confirm the supported Filament and Laravel majors from composer.json and, ideally, a CI matrix. There is no published compatibility table to fall back on.
  • Table-name collisions. The migrations almost certainly claim invoices (and likely invoice_items). Run php artisan migrate --pretend on a clone of your schema before touching a real database.
  • The v1.0.0 release notes are a link. Treat the tag as "first stable snapshot," not a reviewed release. Do not assume a changelog will tell you what changed between versions — diff src/ and database/ yourself, and pin the exact version in composer.json.
  • Single maintainer, zero dependents. There is no community trail to search when something breaks. Budget the ramp-up: a Laravel/Filament developer reads this codebase in a day, but keep a fork remote configured so you can patch rather than wait.
  • Deletes and retention. Check whether the module hard-deletes. If issued invoices are legally retained records, disable deletion at the policy and model level and prefer status-based voiding.
  • Decimal/enum portability. JSON columns and enum handling diverge between MySQL 5.7/8 and Postgres. If you're on Postgres, verify the migrations actually run before committing to the module.
  • PDF fidelity and RTL. If invoices go to Arabic-speaking customers, DomPDF's Arabic shaping is poor out of the box — you'll need a proper font registration and RTL Blade templates, or swap the renderer behind your adapter. Test with real Arabic product names, not lorem ipsum.
  • Seeded permissions are not your authorization layer. The module may ship roles/permissions for its resources; add your own policies on top rather than relying on them, especially around editing and voiding.
  • Publishing tags are undiscoverable in docs. Use the interactive php artisan vendor:publish menu and write down the tags in your onboarding notes; the next developer will otherwise re-run it blindly.
  • Extension points to check for before you build workarounds: a model key in the published config (lets you swap in your own Invoice extends ModuleInvoice), a configurable table prefix, and any events the module fires. If none exist, the adapter layer is your extension point — don't fight the package internals.
  • Write down two rules in the onboarding doc: "never touch module models directly — go through BillingService" and "never edit published migrations after forking." Both erode within two sprints if undocumented.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource