Audience: support engineers, admins Difficulty: admin only
What this covers
When a user says "I can't see X in the menu" or "I get 'Plan does not include feature' but I'm on Premium" — this page walks the chain from their plan through the entitlement snapshot, the org-level toggle, the route middleware, the policy, and the UI conditional. By the end you know which link in the chain broke.
The chain
client_subscription.plan_id → plan.feature_overrides
→ EntitlementResolver merges with feature catalog
→ feature_flags table (per-org override)
→ EnsureFinanceFeatureEnabled (route middleware)
→ Policy / @can / controller authorize()
→ Blade @feature / config('finance_features') in view
A user can be blocked at any of those seven steps. Walk it in this order — each step is cheaper to check than the one after.
Step 1 — Plan
/admin/clients > [client] > Subscription
- Confirm the active subscription is what the user thinks. The
subscription has
status='active'andbilling_cycle_ends_atin the future. - Confirm the plan is the right one (Free / Professional / Premium / Enterprise; or a bundle like Starter / Business / Enterprise).
- For bundles, confirm the constituent project plans (finance + inventory) are at the right tier each.
If wrong here. Update the subscription. Have the user sign out and back in to refresh the entitlement snapshot.
Step 2 — Plan feature overrides
/admin/projects > [project] > Plans > [tier] > Feature Overrides
- Each tier carries a map of
feature_key => value. - For boolean features the value is
true/false. - For limit features the value is a number or
null(= unlimited).
Reference: the seeder is database/seeders/ProjectPlanSeeder.php —
read tierBlueprints() and financeTrackerFeatureOverrides() for the
generated defaults.
If wrong here. Edit the override and save. The seeder writes
through plan_features and the resolver picks it up on next session.
Step 3 — Central feature catalog
config/features.catalog.php (parent app, 108 entries) is the
authoritative list of feature keys + types + defaults. The seeder
database/seeders/PlanFeatureSeeder.php reflects this catalog into
the plan_features table.
A feature missing from this catalog will not be persisted.
Audit-flagged: 7 keys are referenced as feature: middleware in
finance routes but are missing from the central catalog and the
finance local config:
finance.retainers
projects.ai_assistant
projects.core
projects.members
projects.tasks
projects.time_approval
projects.time_tracking
If a user's complaint involves any of these keys, the real fix is to
add them to config/features.catalog.php and finance_features.php.
Step 4 — Org-level toggle
Inside the tenant DB, the feature_flags table holds per-org
overrides. Owner / Manager can edit at Settings > Features in the
finance app.
A row keyed (organization_id, key) with enabled=false overrides the
plan's true. So even with a Premium plan, an org can have a feature
turned off.
How to check.
-- run in the tenant DB
SELECT key, enabled, value
FROM feature_flags
WHERE organization_id = ?
AND key = 'tracker.fixed_assets';
If wrong here. Owner toggles in Settings > Features, or support clears the row.
Step 5 — Route middleware
The feature: route middleware (EnsureFinanceFeatureEnabled) reads:
- The org's
feature_flagsrow, if any. - The plan's resolved value (from the entitlements snapshot).
- The default in
config/finance_features.php.
Whichever is most specific wins. If all three are missing, the middleware's behaviour on missing config keys was not fully audited — it likely soft-fails (treats as enabled or as disabled). For the 7 undeclared keys above, this means the route may be either always allowed or always blocked.
How to check. Reproduce the user's request, watch
storage/logs/laravel.log for "Feature not enabled" — the middleware
logs a line.
Step 6 — Policy / authorize() / perm:
Even if the feature gate passes, the user might not have the permission for the action. The two are independent.
feature:controls visibility of the area at all.perm:(or a policy) controls whether the user in that role can perform the specific action.
Example. A Free-plan org has tracker.purchase_orders=false. The
PO menu is hidden. Even an Owner cannot see it — feature gates
beat role permissions.
Conversely: a Premium org has tracker.purchase_orders=true, but a
Member without purchase_orders.create cannot create a PO. The PO
list is visible; the New Purchase Order button is hidden.
Reference. reference/permission-matrix.
Step 7 — UI conditional
Even if the route is reachable and the user has permission, the UI might not render the menu item. Two patterns:
@feature('tracker.foo')Blade directive — gates a sidebar entry.config('finance_features.features.foo.default')— used by the V2 menu (config/finance_menu_v2.php) to decide whether to show a panel.
The V2 menu file is the source of truth for what shows in the
sidebar. Solabooks V2 sidebar is enabled when FINANCE_UI_V2=true.
Common debug paths
"I'm on Premium but I see 'Plan does not include feature' for Sales Orders."
Walk:
- Subscription → confirm Premium tier on
financeproject. - Plan feature overrides → find
tracker.sales_orders— should betrue. Iffalse, fix it. - Note: Sales Orders also requires
sales.use_sales_orders=true(a second flag). Both must be on. - Org
feature_flags→ confirmsales.use_sales_ordersis not overridden tofalsefor the org.
"My new accountant can see invoices but the New button is missing."
This is a permission issue, not a feature issue. The Member finance
role has sales.invoices.view but not sales.invoices.create by
default. Add the permission per-user, or upgrade the user to Manager.
"I upgraded an hour ago and still see the Free limits."
Entitlements are snapshotted into the user's session at sign-in. Have
the user sign out and back in. If the issue persists, run the parent
command subscriptions:resync --client=<id>.
"Feature works for me but not for my colleague — same role, same org."
Check:
- Is the colleague's session stale? (Last seen >24h ago in User Monitor — sign out / in.)
- Does the colleague have a per-user permission override?
- Are they in a different organization than they think they are? Check the org switcher.
Audit-flagged mismatches
From the Phase 1 audit:
- 116 keys in
solavel-finance/config/finance_features.phpare not in the central catalog. Most are tracker-defaults that should live as plan overrides. Net effect: a feature defined here but absent from the central catalog is not seeded intoplan_features— the resolver falls back to this file'sdefault. - 33 keys in the central catalog are not in finance's
featuresmap. Limit-type entitlements (e.g.max_price_lists,receipt_ocr_scans) are usually fine here — they're enforced at controller level. Boolean ones could break if the route readsconfig('finance_features.features.<key>.default'). - 130 of the 261 finance permissions are not enforced by any
perm:middleware token at the route layer. They may be checked in policies,@candirectives, or controllers — but if a user is granted one and finds it has no effect, this is why.