tomatophp/tomato-branches-module
Tomato Branches is a module for the TomatoPHP ecosystem that adds branch and location management to your Laravel app, letting you organize records and users by branch, typically through a Filament admin panel.
tomatophp/tomato-admin), Filament v3, and the nwidart/laravel-modules loader. Dropping it into a bare Laravel app will install fine and then fail on missing providers. Confirm the stack before you composer require.composer require tomatophp/tomato-branches-module
php artisan module:list # module registered + enabled?
php artisan migrate
php artisan config:clear && php artisan cache:clear
Then open the admin panel and look for the Branches screen. If it's absent, suspect enablement/publishing before you suspect code.php artisan vendor:publish --tag=tomato-branches-module-config
php artisan vendor:publish --tag=tomato-branches-module-lang
Tag names drift between modules — verify against the service provider's publishes() array rather than trusting the README.database/migrations/ (the actual ground truth of the schema — names, address fields, is_active), src/Models/ (the Branch model and its relations), src/Filament/ (resource, form and table schemas you'll copy into your own resources), config/, then routes/.branch_id. The module's CRUD is table stakes; the value on day one is branch as a first-class dimension on your records.CurrentBranch singleton in the container and resolve it in a fixed order: session → user's default branch → first active branch. Everything else (scopes, Filament dropdowns, report filters) reads from that singleton. This is the single design decision that determines whether branch scoping stays sane or turns into session()->get('branch_id') scattered across forty files.set() / runAs($branchId, fn () => ...) method so background work sets the branch deliberately instead of silently picking the first active one.BelongsToBranch trait + global scope is the workhorse. On each branch-owned model: add a branch_id column (your own migration, not theirs), apply the trait, and let the global scope filter by current branch. Add a withoutBranchScope() escape hatch for admin/global views — you will need it within a week.belongsTo vs belongsToMany up front. Single branch per user is belongsTo and trivially simple. Multi-branch staff needs a user_branch pivot and a branch-switcher UI, plus a rule for which branch is "current." Retrofitting this later means touching every scope and every policy.team_id. Map them one-to-one instead of maintaining two parallel scoping mechanisms. Commit to this early — merging two permission-scoping systems after the fact is genuinely painful.PanelProvider alongside the module, register the plugin/resource in the provider — don't rely on discovery across module boundaries.BranchResource extends \TomatoPHP\...\BranchResource (or use the schema-modification hooks if the module exposes them) so a composer update can't revert your work. Same for the form/table schema additions.$this->redirect(request()->header('Referer')) — full reload is fine here and avoids stale scoped data in cached Filament tables.Branch model (filtered to is_active) through a repository, not the module's routes. You stay insulated from route-name churn and get is_active filtering for free.BranchSeeder in your application, with 2–3 realistic records. Tests and local dev should never depend on an empty branch table or on the package's demo data.runAs(B) sees only B.config/tomato.php / modules_statuses.json. After enabling, always php artisan config:clear && php artisan cache:clear — cached config is the usual culprit, and it costs people hours.Class not found on a form component, with no obvious link to the version. Pin filament/filament and this module together in composer.json.add_branch_id_to_* migrations in the app, and inspect the shipped schema with vendor:publish --tag=...-migrations (read it, then delete it) before writing yours. Watch foreign-key ordering: your branch_id FK requires the branches table to exist.config/ are a common upgrade surprise.branch_id is nullOnDelete or cascadeOnDelete. Use is_active = false to retire a branch in practice; hard-deleting a branch with attached orders is the kind of thing you only do once.composer update within ^1.0 is safe; no data migration or republish expected. If you're on 1.0.0 there's no urgency, but the absence of release notes is itself a signal about maintenance rigor: validate in a spike, don't assume.php artisan about (module + Filament versions) → php artisan module:list → the panel provider's discoverResources/plugin list → php artisan route:list | grep branch (did the routes load at all?). That sequence resolves most "it's broken" reports before you open a single PHP file.Branch (audit who renamed or deactivated what), and resource subclassing. Never patch vendor PHP — this ecosystem moves fast and local edits are the first casualty of the next update.Branch entity, and you can fork or vendor it in an afternoon if needed.How can I help you explore Laravel packages today?