Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

13 KiB

PRD — Teams V2: Organizational Hierarchy + Operational Teams

  • Slug: teams-v2
  • Date: 2026-02-26
  • Status: Draft

Summary

Split the existing single "teams" concept into two distinct structures:

  1. "Reports To" (Organizational Hierarchy) — A reports_to field on each user forming a manager-subordinate tree. Drives org chart display, timesheet approval chains, and scheduling scope.
  2. "Teams" (Functional/Operational Groups) — Repurpose existing teams as cross-functional groups that can be assigned to tickets and project tasks as a unit. Team lead auto-becomes primary assignee. Members are expanded into ticket_resources.

All new UI elements are gated behind a teams-v2 PostHog feature flag. Schema migrations are additive and not gated.

Problem

The current team system is a settings-only feature disconnected from day-to-day workflows:

  • Teams cannot be assigned to tickets or tasks — work assignment is individual-only
  • Timesheet approval is tightly coupled to team manager, but there's no formal org hierarchy — if a user's manager changes, you have to restructure teams
  • There's no org chart visualization
  • Manager-of-manager cannot approve subordinates' timesheets (no transitive authority)
  • Several API endpoints reference non-existent database tables and crash at runtime

Goals

  1. Introduce a reports_to relationship on users to model organizational hierarchy
  2. Auto-seed reports_to from existing team/manager data (zero customer effort)
  3. Enable timesheet approval via the reports_to chain (transitive — manager of manager can approve)
  4. Provide org chart visualization in User Management
  5. Enable assigning an entire team to a ticket or project task
  6. When a team is assigned, auto-set team lead as primary assignee and expand members into ticket resources
  7. Provide a new UserAndTeamPicker component that shows both users and teams
  8. Show team badge on the "Agent team" card in ticket detail
  9. Enhance the "Assigned To" ticket list filter to include teams
  10. Gate all new UI behind a feature flag for safe, gradual rollout

Non-goals

  • Team logos or custom team icons (use generic team icon only)
  • Team-based reporting or analytics dashboards
  • Team notifications on assignment
  • Team-based dispatch filtering
  • Workflow automation with team routing rules
  • "My Team" ticket queue view
  • Cleaning up broken API endpoints (deferred to Phase 3)
  • Removing the existing team-manager approval path (kept for backwards compatibility)

Users and Primary Flows

Personas

  • MSP Admin — Sets up org structure and teams. Manages user profiles.
  • Team Manager / Lead — Approves timesheets for direct reports. Leads a functional team.
  • Technician / Agent — Works on assigned tickets. May belong to one or more teams.

Primary Flows

Flow 1: Admin sets up "Reports To" hierarchy

  1. Admin opens User Management
  2. Edits a user profile
  3. Sets the "Reports To" dropdown to select their manager
  4. Views the org chart tab to verify the hierarchy looks correct

Flow 2: Manager approves timesheets via reporting chain

  1. Manager opens timesheet approval dashboard
  2. Sees timesheets from all direct and indirect reports (via reports_to chain)
  3. Approves or requests changes as usual

Flow 3: Agent assigns a team to a ticket

  1. Agent opens a ticket
  2. In the "Assigned To" field, the UserAndTeamPicker shows users and teams
  3. Agent selects "Network Team"
  4. System sets assigned_team_id, makes team lead the primary assignee (if empty), and adds all other members as additional agents
  5. Team badge appears on the "Agent team" card

Flow 4: Agent removes a team from a ticket

  1. Agent clicks "x" on the team badge
  2. Confirmation dialog appears with options:
    • Remove all team members from assignment
    • Keep all team members as individual agents
    • Select individual members to keep/remove
  3. Agent makes selection, assigned_team_id is cleared

Flow 5: Filtering tickets by team

  1. Agent opens ticket list
  2. Uses the "Assigned To" filter which now shows teams alongside users
  3. Selects a team to see all tickets assigned to that team

UX / UI Notes

Reports To

  • "Reports To" dropdown on the user edit form — standard dropdown selecting from all users in the tenant
  • Org chart tab in User Management — tree view visualization of reporting lines (read-only)

Team Assignment

  • UserAndTeamPicker — new component replacing UserPicker via feature flag swap:

    {featureFlag('teams-v2')
      ? <UserAndTeamPicker ... />
      : <UserPicker ... />}
    
    • Users section first, teams section below with separator
    • Teams show member count and lead name
    • Generic team icon for teams vs. user avatar for individuals
  • Team badge on "Agent team" card — chip/badge following existing Additional Agents pattern (initials/icon + name + x button). Uses same chip styling as the existing MultiUserPicker chips (the bg-gray-100 rounded-full pill pattern).

  • Team removal dialog — confirmation dialog with radio buttons:

    • "Remove all team members"
    • "Keep all team members as individual agents"
    • Individual checkboxes for each team member
  • "Assigned To" filter enhancement — existing MultiUserPicker filter in TicketingDashboard.tsx enhanced to show teams in the dropdown alongside users when flag is on.

  • All new UI must follow established component variants, color schemes, and interaction patterns.

Requirements

Functional Requirements

Phase 1: Reports To + Org Hierarchy

  • FR-1.1: Add nullable reports_to UUID column to users table referencing users(user_id)
  • FR-1.2: Auto-seed reports_to from existing team membership data in the migration (team member → team manager_id)
  • FR-1.3: Validate that setting reports_to doesn't create circular chains
  • FR-1.4: Add "Reports To" dropdown field on user edit form (behind feature flag)
  • FR-1.5: Build org chart tree view in User Management (behind feature flag)
  • FR-1.6: Add isInReportsToChain() function for transitive approval check
  • FR-1.7: Extend canApprove logic: existing team-manager check (always active) + reports_to chain check (behind feature flag)
  • FR-1.8: Update fetchTimeSheetsForApproval() to include timesheets from reports_to subordinates when flag is on
  • FR-1.9: Update AvailabilitySettings to additionally scope by reports_to when flag is on
  • FR-1.10: Update SchedulePage to additionally scope by reports_to when flag is on

Phase 2: Operational Teams

  • FR-2.1: Add role TEXT DEFAULT 'member' column to team_members table
  • FR-2.2: Migration: set role = 'lead' for existing team members whose user_id matches teams.manager_id
  • FR-2.3: Add nullable assigned_team_id UUID column to tickets table referencing teams(team_id)
  • FR-2.4: Add nullable assigned_team_id UUID column to project_tasks table referencing teams(team_id)
  • FR-2.5: Implement team assignment action: sets assigned_team_id, assigns lead as primary (if assigned_to is NULL), expands other members into ticket_resources with role = 'team_member'
  • FR-2.6: When assigning team and assigned_to is already set: add all team members as resources EXCEPT whoever is already assigned_to
  • FR-2.7: Team lead must NOT be added as ticket_resources when they are the primary assignee (DB constraint: assigned_to != additional_user_id)
  • FR-2.8: Build UserAndTeamPicker component showing users and teams in grouped sections (behind feature flag)
  • FR-2.9: Feature flag swap: render UserAndTeamPicker instead of UserPicker on ticket/task detail when teams-v2 is enabled
  • FR-2.10: Show team badge (chip: generic icon + team name + x) on "Agent team" card when assigned_team_id is set (behind feature flag)
  • FR-2.11: Clicking "x" on team badge shows confirmation dialog with removal options
  • FR-2.12: "Remove all" option removes ticket_resources with role = 'team_member' and clears assigned_team_id
  • FR-2.13: "Keep all" option clears assigned_team_id only, resources stay
  • FR-2.14: Individual selection option lets user choose which team members to keep/remove
  • FR-2.15: Primary assigned_to is never automatically removed when clearing team assignment
  • FR-2.16: Adding/removing individual agents does NOT affect team assignment. Team badge and individual agent chips coexist independently.
  • FR-2.17: Team membership is a snapshot at assignment time — later team roster changes don't affect existing ticket resources
  • FR-2.18: Enhance "Assigned To" filter in ticket list to show teams alongside users when flag is on
  • FR-2.19: Selecting a team in the filter → filters by tickets.assigned_team_id
  • FR-2.20: Extend tickets.create workflow action to support assignee: { type: 'team', id: uuid } (matching projects.create_task pattern)
  • FR-2.21: Update ITicket and IProjectTask interfaces to include assigned_team_id
  • FR-2.22: Update tenant export to include new columns (reports_to, assigned_team_id, role)

Non-functional Requirements

  • NFR-1: All new UI elements gated behind teams-v2 PostHog feature flag
  • NFR-2: Schema migrations are additive (nullable columns) and not feature-flag-gated
  • NFR-3: Existing team-manager timesheet approval logic remains active and unchanged
  • NFR-4: Multi-tenant isolation preserved — all queries tenant-scoped
  • NFR-5: isInReportsToChain() must handle arbitrarily deep hierarchies without stack overflow (use iterative/CTE approach)

Data / API / Integrations

Schema Changes

-- Phase 1
ALTER TABLE users ADD COLUMN reports_to UUID REFERENCES users(user_id);

-- Auto-seed
UPDATE users u SET reports_to = t.manager_id
FROM team_members tm
JOIN teams t ON t.team_id = tm.team_id AND t.tenant = tm.tenant
WHERE u.user_id = tm.user_id AND u.tenant = tm.tenant AND u.user_id != t.manager_id;

-- Phase 2
ALTER TABLE team_members ADD COLUMN role TEXT NOT NULL DEFAULT 'member';
UPDATE team_members tm SET role = 'lead'
FROM teams t WHERE tm.team_id = t.team_id AND tm.tenant = t.tenant AND tm.user_id = t.manager_id;

ALTER TABLE tickets ADD COLUMN assigned_team_id UUID REFERENCES teams(team_id);
ALTER TABLE project_tasks ADD COLUMN assigned_team_id UUID REFERENCES teams(team_id);

Affected Interfaces

  • IUser — add reports_to?: string
  • ITeam — existing, no change needed (manager_id stays as team lead reference)
  • ITeamMember — add role: 'member' | 'lead'
  • ITicket — add assigned_team_id?: string
  • IProjectTask — add assigned_team_id?: string
  • ITicketResourcerole field will use 'team_member' value for team-assigned resources

API Considerations

  • Existing team CRUD endpoints unchanged
  • New server actions needed: assignTeamToTicket(), removeTeamFromTicket(), getReportsToChain()
  • Existing addTicketResource() reused for individual member expansion

Security / Permissions

  • reports_to changes: require user edit permission
  • Team assignment on tickets: require ticket edit permission (same as changing assigned_to)
  • Timesheet approval via reports_to: require timesheet:approve permission (existing)
  • Cycle prevention on reports_to: server-side validation, not client-only
  • All queries continue to be tenant-scoped via RLS policies

Rollout / Migration

Migration Safety

  • All schema changes are additive nullable columns — no data loss, no breaking changes
  • reports_to auto-seeded in migration from existing team data (verified safe for all active tenants)
  • All UI behind feature flag — invisible until enabled
  • Existing approval logic untouched — works in parallel

Rollout Plan

  1. Deploy migrations (schema + data seed) — invisible to users
  2. Enable teams-v2 flag on staging for internal testing
  3. Enable flag on Nine Minds tenant for dogfooding
  4. Gradual per-tenant rollout to customers
  5. Once stable, remove flag gates and make generally available

Open Questions

None — all questions resolved during analysis phase.

Acceptance Criteria (Definition of Done)

  1. reports_to column exists on users and is auto-seeded from existing team data
  2. Setting reports_to rejects circular chains
  3. Org chart view displays the reporting hierarchy correctly
  4. Managers can approve timesheets for direct and indirect reports via reports_to chain
  5. A team can be assigned to a ticket, setting team lead as primary and expanding members into resources
  6. Team badge appears on "Agent team" card with working "x" removal
  7. Team removal dialog offers all three options and executes correctly
  8. UserAndTeamPicker shows users and teams, swaps correctly via feature flag
  9. "Assigned To" filter includes teams when flag is on
  10. All new UI is invisible when teams-v2 flag is off
  11. Existing team-manager approval continues to work unchanged
  12. No regressions in existing ticket assignment, resource management, or timesheet approval