# Groups (Manage) — Agency Multi-Tenancy

**Portal:** Manage · **Routes:** `manage.people.groups.*` · **Nav:** the pinned **Setting** entry → **Lead Distribution** tab of [`SettingTabs.vue`](/resources/js/Components/SettingTabs.vue) → **Groups** on the `lead-distribution` section strip (Operations suite; the Distribution Setting button on the Leads page is a shortcut to the same place). Groups is the agency structure the distribution rules hand leads across, so it sits beside them on that strip rather than as its own Setting tab; the New Project / Subsale suites still link it directly

## What it does
Agency-level **groups** (whitelabel tenancy): a platform Admin creates a group together with its **first Group Super Admin (GSA)**; the GSA then runs the agency — builds **teams**, adds leaders/agents (via the group-scoped [Admins](/docs/modules_handbook/manage/people/admins/readMe.md) page and this page's Members tab), connects WhatsApp channels / Meta accounts and launches FLG campaigns. Everything the group produces — **leads, FLG projects/campaigns, connected channels & Meta accounts, and (via propagation) WhatsApp inbox + Zoom** — stays visible inside the group only. Platform staff (no group) see everything.

## How it works
- **Membership lives on `admins`** (`group_id`, `team_id`); `Src\People\Group` + `Src\People\Team` (a team optionally records a `leader_id`, informational). `User::groupId()` / `teamId()` / `isGroupSuperAdmin()` are the accessors.
- **GSA is an ADD-ON role** (2026-07-08): a person keeps their main role (Admin / leader / agent / custom) and `group-super-admin` stacks on top — permissions union via spatie multi-role. `User::mainRole()`/`roleName()` resolve the main role (highest `Role::LEVELS`, GSA excluded; GSA-only legacy accounts fall back to GSA). The Admins form exposes it as an **"is group super admin" toggle** (only honoured when the account has a group); `UserRepository::create/update` take the flag, `changeRole()` preserves the add-on, and `grantGroupSuperAdmin()`/`revokeGroupSuperAdmin()` back the promotion flows. Removing a member from a group strips the add-on; a GSA-only member is blocked from removal until given a main role. Super admins are excluded from the GSA picker.
- **Four visibility levels** (see [Roles & Permissions](/docs/modules_handbook/manage/people/roles/readMe.md)): All / **Group** (`leads.group_id` stamp OR assigned to any group member) / **Team** (assigned to `admins.team_id` members — assignment-based, self-heals on membership changes) / Own. Resolved centrally in `LeadVisibility` / `AccountVisibility`; group/team permission without membership degrades to Own.
- **Ingestion stamping** (`group_id` set at creation, never moved later): FLG webhook sync (campaign's group, falling back to the project's for legacy rows), manual lead create (actor's group), WhatsApp CTA capture + proactive flows (channel's group), owner-listing auto-leads (project's group), channel/Meta connects (connector's group), FLG project create (actor's group).
- **Lead assignment rights follow origin** (2026-07-08): assignment never changes `group_id`. A company lead (no stamp) assigned to a group member stays company-owned — the member and their GSA can see it (group level covers member-assigned leads) but only platform staff can (re)assign it; a group's own lead is freely assignable by the GSA within the group. Cross-group assignment is rejected. Enforced in `LeadsController::assign` + surfaced as `can_assign` on lead rows.
- **Group-scoped Admins**: `AdminsController` base query filters by the viewer's group; a GSA may only hand out the group sales roles (leader/agent, forced into their own group; GSA itself comes from the toggle) and never reaches platform users (404). Admin-account writes moved from the `super-admin` middleware to `permission:manage-admins` (plain admins don't hold it — unchanged effective access).
- **FLG partition — shared platform projects** (2026-07-08): strict `GroupScope::apply/allows` guards every write (project update/archive/destroy, brochures, creatives, floor plans, owner listing); the **shared** variants `applyShared/allowsShared` open platform projects (`group_id` null) to group users as **read-only launch parents**. Campaigns carry their own `flg_campaigns.group_id` (stamped `launcher's group ?? project's group`), so each group sees only its own campaigns/leads under a shared project (`Projects Show` scopes the campaigns/flgLeads props; `canEdit` gates asset UI). The FLG leads list follows `LeadVisibility`, with filter options + counts constrained to the viewer's partition.
- Role/permission surface: `group-super-admin` role (level 40, seeded group-level defaults incl. `manage-admins`), `view-*-team` levels, `view-groups` / `manage-groups` pair. Leaders seed at **team** level, agents at own.

## Related files

**Backend**
- [src/People/Group.php](/src/People/Group.php), [src/People/Team.php](/src/People/Team.php) — models + `memberUserIds()` resolver subqueries.
- [src/People/Repositories/GroupRepository.php](/src/People/Repositories/GroupRepository.php), [TeamRepository.php](/src/People/Repositories/TeamRepository.php) (+ facades).
- [app/Http/Controllers/Manage/People/GroupsController.php](/app/Http/Controllers/Manage/People/GroupsController.php) — index/store(+first GSA)/show/update/destroy, teams CRUD, member add/move/remove.
- [app/Http/Requests/Manage/People/Groups/](/app/Http/Requests/Manage/People/Groups/) — GroupQueryRequest, Store/Update, StoreTeam, Member requests.
- Scope resolvers: [LeadVisibility](/src/Auth/Support/LeadVisibility.php), [AccountVisibility](/src/Auth/Support/AccountVisibility.php), [GroupScope](/src/Auth/Support/GroupScope.php).
- Stamping: [SyncFlgLeadToCrmAction](/app/Actions/SyncFlgLeadToCrmAction.php), [LeadRepository](/src/Lead/Repositories/LeadRepository.php) (`create` / `firstOrCreateForPhone(..., $groupId)` / `stampGroup`), [ProcessInboundWhatsAppWebhook](/app/Jobs/Whatsapp/ProcessInboundWhatsAppWebhook.php), [StartProactiveFlow](/app/Jobs/Whatsapp/StartProactiveFlow.php), [ProcessOwnerListingJob](/app/Jobs/FacebookLeadGenerator/ProcessOwnerListingJob.php), [ChannelsController](/app/Http/Controllers/Manage/Whatsapp/ChannelsController.php), [FacebookIntegrationRepository](/src/Facebook/Repositories/FacebookIntegrationRepository.php), [FLG ProjectsController](/app/Http/Controllers/Manage/FacebookLeadGenerator/ProjectsController.php).

**Frontend (Vue)**
- [Pages/Manage/People/Groups/Index.vue](/resources/js/Pages/Manage/People/Groups/Index.vue), [Show.vue](/resources/js/Pages/Manage/People/Groups/Show.vue), [Partials/GroupFormModal.vue](/resources/js/Pages/Manage/People/Groups/Partials/GroupFormModal.vue), [Partials/Tabs/TeamsTab.vue](/resources/js/Pages/Manage/People/Groups/Partials/Tabs/TeamsTab.vue), [MembersTab.vue](/resources/js/Pages/Manage/People/Groups/Partials/Tabs/MembersTab.vue).
- [Admins AdminForm.vue](/resources/js/Pages/Manage/People/Admins/Partials/AdminForm.vue) — Group/Team selects.

**Migrations**
- [2026_07_06_100001_create_groups_table](/database/migrations/2026_07_06_100001_create_groups_table.php), [100002_create_teams_table](/database/migrations/2026_07_06_100002_create_teams_table.php), [100003_add_group_to_admins_table](/database/migrations/2026_07_06_100003_add_group_to_admins_table.php), [100004…100007 group_id on leads / whatsapp_channels / facebook_integrations / flg_projects](/database/migrations).

**Routes / Seeder / Tests**
- [routes/web.php](/routes/web.php) — `manage.people.groups.*` (`view-groups`; writes `manage-groups`; teams/members `manage-groups|manage-admins`).
- [database/seeds/RolesSeeder.php](/database/seeds/RolesSeeder.php) + [SeedCommonRolesAction](/app/Actions/SeedCommonRolesAction.php).
- [2026_07_08_100001_add_group_id_to_flg_campaigns_table](/database/migrations/2026_07_08_100001_add_group_id_to_flg_campaigns_table.php) — campaign-level group ownership (backfilled from projects).
- [tests/Feature/Group/](/tests/Feature/Group/) — GroupVisibilityTest, GroupAdminsTest, GroupsCrudTest, GroupStampingTest, GsaAddOnRoleTest, FlgSharedProjectsTest (+ shared GroupTestHelpers).

## Known limits
- **Shared WhatsApp contacts**: contacts are deduped by phone across all channels; conversations partition by channel/lead, but a contact's name/tags are shared platform-wide.
- **Existing data stays platform-scoped** (`group_id` null) — no backfill; new groups start clean.
- A leader needs a `team_id` or their team-level grant degrades to Own (set it on the Admins form / Members tab).
- Per-group role matrices (spatie `teams` mode) are NOT enabled — roles are global; groups partition data only.
