Skip to content

Scope owner inputs to admin businesses; keep membership names readable - #4296

Open
gilgardosh wants to merge 2 commits into
mainfrom
claude/owner-selection-filtering-pjnvl4
Open

Scope owner inputs to admin businesses; keep membership names readable#4296
gilgardosh wants to merge 2 commits into
mainfrom
claude/owner-selection-filtering-pjnvl4

Conversation

@gilgardosh

Copy link
Copy Markdown
Collaborator

What

Two related fixes around "which businesses am I acting as?".

1. Owner inputs offer only businesses the user owns

Every "Owner" input listed all businesses or all financial entities — including counterparties that can never be an owner. They now read allAdminBusinesses, which is what the charges filter already did:

Input Was Now
Business ledger filters — Owners useGetBusinesses useGetAdminBusinesses
Trial balance filters — Owners useGetBusinesses useGetAdminBusinesses
Documents filters — Owners useGetFinancialEntities useGetAdminBusinesses
Depreciation report — Owner useGetFinancialEntities useGetAdminBusinesses
Dynamic report toolbar — owner picker hard-coded [{ id: adminBusinessId, name: username }] useGetAdminBusinesses
PCN874 validation — report issuer useGetBusinesses useGetAdminBusinesses
Shaam-6111 — business picker useGetBusinesses useGetAdminBusinesses

Where there is only one option, the input is disabled and shows it rather than pretending to offer a choice. Single-select owner fields (VAT monthly, balance report, depreciation, PCN874, Shaam-6111, Green Invoice sync) also pre-select that value, so the submitted filter matches what is displayed. The charges filter's Owners field switches from being hidden on a single owner to being shown and locked, for consistency.

useGetAdminBusinesses gains soleAdminBusinessId, which is the single option's id or null.

The Green Invoice sync modal also loses its hard-coded owner UUID default (6a20aa69-…) in favour of the sole admin business, and the field is now required.

2. Business switcher no longer degrades to bare UUIDs

Query.userContext.memberships resolves each business name through financial_entities, which RLS narrows to the request's X-Business-Scope. So on the first (unscoped) fetch the user menu showed names, and on every fetch afterwards the memberships outside the chosen scope came back nameless — the very list a user needs in order to leave a narrow scope became unreadable.

New migration 2026-08-26T10-00-00.rls-membership-financial-entities: a permissive FOR SELECT policy on financial_entities letting a user always read the row of a business they are a member of. It ORs with tenant_isolation, so it only ever adds visibility, and only for the caller's own memberships. get_current_user_id() is NULL for API-key requests, which leaves the subquery empty and those requests unchanged.

Testing

  • yarn lint — 0 errors
  • yarn workspace @accounter/client build (tsc && vite build) — clean
  • yarn test — 3776 passed; the only failing suite is packages/migrations/src/__tests__/rls-all-tables.test.ts, which exits on missing Postgres env vars in this environment and is unrelated to the diff
  • yarn migration:check-conflicts — passed
  • The new RLS policy has not been exercised against a live database here; it needs a run of the migrations plus a manual check of the business switcher with a scope selected.

Generated by Claude Code

Every "Owner" input listed all businesses or financial entities, including
counterparties that can never be an owner. They now read allAdminBusinesses,
as the charges filter already did. Where there is a single option the input is
disabled and shows it instead of pretending to offer a choice; single-select
owner fields also pre-select that value so the submitted filter matches what is
displayed. The Green Invoice sync modal loses its hard-coded owner UUID.

Also fixes the user-menu business switcher rendering out-of-scope memberships
as bare UUIDs: membership names resolve through financial_entities, which RLS
narrows to the requested X-Business-Scope, so after the first scoped request
the names of the user's other businesses — the very list needed to leave that
scope — became unreadable. A permissive SELECT policy lets a user always read
the financial_entities row of a business they are a member of; API-key
requests, which carry no user id, are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfgxehTempoyjd5hewfdYm
@gilgardosh
gilgardosh deployed to accounter-fullstack August 26, 2026 14:08 — with GitHub Actions Active
@gilgardosh
gilgardosh deployed to accounter-fullstack August 26, 2026 14:08 — with GitHub Actions Active
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@accounter/client 0.1.0-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/green-invoice-graphql 0.8.7-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/hashavshevet-mesh 0.2.13-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/israeli-vat-scraper 0.1.13-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/modern-poalim-scraper 0.11.0-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/payper-mesh 0.2.13-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/scraper-app 0.0.3-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/server 0.2.0-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/shaam-uniform-format-generator 0.2.7-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎
@accounter/shaam6111-generator 0.1.9-alpha-20260826143211-737d372de7c376288a44cdbce576ea6a53346625 npm ↗︎ unpkg ↗︎

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The Documents Owners multi-select can display the sole admin business while react-hook-form still submits a different/empty ownerIDs value because the form state isn’t synchronized when the field is disabled.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR tightens “Owner”/business selection across the client so users can only act as owners for businesses they actually administer, and it fixes business-switcher readability under scoped RLS by allowing membership-business name resolution regardless of the current X-Business-Scope.

Changes:

  • Client: switch multiple “Owner” pickers from all businesses/financial entities to admin businesses; add “sole admin business” handling to lock and (in most cases) preselect when only one option exists.
  • Server/DB: add an RLS FOR SELECT policy on financial_entities to allow reading names for businesses the user is a member of (even when scoped), preventing membership names from degrading to UUIDs.
  • UX/API safety: remove hard-coded Green Invoice owner default and make the owner field required.
File summaries
File Description
packages/server/src/modules/common/resolvers/user-context.resolver.ts Adds resolver comment clarifying membership name loads rely on the new RLS policy.
packages/migrations/src/run-pg-migrations.ts Registers the new migration in the migration runner.
packages/migrations/src/actions/2026-08-26T10-00-00.rls-membership-financial-entities.ts Introduces membership_business_visibility RLS policy to read financial_entities for membership businesses.
packages/client/src/hooks/use-get-admin-businesses.ts Adds soleAdminBusinessId to support “single option means locked input” behavior.
packages/client/src/components/screens/reports/shaam-6111-report/shaam-6111-filters.tsx Switches owner/business picker to admin businesses; auto-selects and locks when sole option.
packages/client/src/components/screens/reports/depreciation-report/depreciation-report-filters.tsx Switches Owner selector to admin businesses; auto-selects/locks for sole option.
packages/client/src/components/screens/reports/balance-report/balance-report-filters.tsx Uses soleAdminBusinessId to preselect/lock owner when only one admin business exists.
packages/client/src/components/screens/documents/all-documents/documents-filters.tsx Changes Owners filter to use admin businesses (instead of all financial entities).
packages/client/src/components/reports/vat-monthly-report/vat-monthly-report-filters.tsx Uses soleAdminBusinessId to preselect/lock issuer.
packages/client/src/components/reports/validations/validate-reports-filter.tsx Switches “report issuer” to admin businesses; preselect/lock when sole option.
packages/client/src/components/reports/trial-balance-report/trial-balance-report-filters.tsx Switches Owners multi-select to admin businesses and locks when sole option.
packages/client/src/components/reports/dynamic-report/toolbar.tsx Adds ownerDisabled support to render owner select read-only when sole option.
packages/client/src/components/reports/dynamic-report/index.tsx Replaces hard-coded owner list with useGetAdminBusinesses; locks/report-filtering uses sole owner when applicable.
packages/client/src/components/common/modals/sync-green-invoice-documents-modal.tsx Removes hard-coded owner UUID default; requires owner; preselect/lock when sole option.
packages/client/src/components/charges/charges-filters/sections/entities-section.tsx Changes Owners section to show (and lock) even for single-owner tenants.
packages/client/src/components/charges/charges-filters/negatable-multi-select-field.tsx Adds disabled prop passthrough for MultiSelectField.
packages/client/src/components/business-ledger/business-ledger-filters.tsx Switches Owners multi-select to admin businesses and locks when sole option.
.changeset/owner-selects-admin-businesses.md Adds a changeset documenting the owner scoping + membership-name/RLS fix.
Review details
  • Files reviewed: 18/18 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

A disabled field never fires onChange, so the locked owner shown in the
documents, business-ledger and trial-balance filters was display-only: the
submitted filter kept whatever ownerIds/ownerIDs the form was seeded with,
which need not be the sole admin business (the default derives from the active
read scope, not from businesses_admin). Pre-select it via setValue so the input
and the submitted filter agree.

Addresses review feedback on packages/client/src/components/screens/documents/all-documents/documents-filters.tsx.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfgxehTempoyjd5hewfdYm
@gilgardosh
gilgardosh deployed to accounter-fullstack August 26, 2026 14:30 — with GitHub Actions Active
@gilgardosh
gilgardosh deployed to accounter-fullstack August 26, 2026 14:30 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants