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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
145 lines
8.1 KiB
Markdown
145 lines
8.1 KiB
Markdown
# Time Entry Delegation (Enter/Edit Time for Other Users)
|
||
|
||
## Summary
|
||
Enable authorized users (billing admins, system admins, and team managers) to **create and edit time entries on behalf of another user** so invoicing and reporting are not blocked by missing time. This includes the ability to add time into **any existing time period** (including previously approved periods) while preserving billing integrity:
|
||
|
||
- **Billing/system admins** can act tenant-wide and can **reopen + approve** time sheets as needed.
|
||
- **Managers** can act **only for users in teams they manage**.
|
||
- **No changes are allowed for already-invoiced time** (cannot add/edit entries that would affect an invoiced period).
|
||
|
||
This work also clarifies “actor vs subject”:
|
||
- **Actor**: the logged-in user performing the action.
|
||
- **Subject**: the user whose time is being entered/edited.
|
||
|
||
## Problem Statement
|
||
When generating invoices, admins frequently find missing time (e.g., a technician forgot to enter time). Today, the Time Entry UI and server actions implicitly operate only on the current user, making it impossible (or unsafe) for an authorized admin/manager to record time for someone else.
|
||
|
||
## Goals
|
||
- Allow an authorized actor to:
|
||
- select a subject user
|
||
- view that subject’s time periods/time sheets
|
||
- create/edit/delete time entries for the subject within a selected time sheet
|
||
- submit/reopen/approve time sheets as needed to reach an **APPROVED** state for billing
|
||
- Enforce permissions and relationship constraints:
|
||
- billing/system admins: tenant-wide capability (RBAC-driven)
|
||
- managers: limited to their managed teams
|
||
- Preserve billing integrity:
|
||
- disallow any action that would alter invoiced time (time entries marked `invoiced=true` or time sheets containing invoiced entries)
|
||
- Add auditability:
|
||
- record who created/updated entries on behalf of others
|
||
|
||
## Non-Goals
|
||
- Supporting retroactive billing changes for already-invoiced time (credits/reinvoicing workflows).
|
||
- A full “missing time” workflow embedded inside the invoicing UI (this can be added later).
|
||
- Role semantics beyond RBAC (roles are labels; permissions drive behavior).
|
||
|
||
## Users / Personas
|
||
- **Billing admin / finance**: needs to ensure all billable work is recorded and approved so invoices can be generated.
|
||
- **System admin**: needs global corrective capabilities and oversight.
|
||
- **Team manager**: can correct/complete time sheets for their direct reports.
|
||
|
||
## Primary User Flows
|
||
### Flow A: Billing Admin enters missing time (open period)
|
||
1. Billing admin opens Time Entry.
|
||
2. Selects a subject user (any internal user in tenant).
|
||
3. Selects a time period.
|
||
4. Adds or edits entries (subject’s time).
|
||
5. Submits and approves the time sheet.
|
||
|
||
### Flow B: Billing Admin enters missing time (previously approved period, not invoiced)
|
||
1. Billing admin opens the subject’s already-approved time sheet.
|
||
2. Clicks “Reopen for edits” (reverse approval) → time sheet becomes editable.
|
||
3. Adds missing entries.
|
||
4. Re-submits and approves.
|
||
|
||
### Flow C: Team manager enters missing time for a report
|
||
1. Manager opens Time Entry.
|
||
2. Selects a subject user from within teams they manage.
|
||
3. Selects a time period and edits time.
|
||
4. Submits and approves.
|
||
|
||
### Flow D: Attempt to change invoiced time (blocked)
|
||
1. Actor attempts to edit an invoiced time entry or reopen a time sheet with invoiced entries.
|
||
2. System blocks the action with a clear error message.
|
||
|
||
## UX / UI Notes
|
||
- Add a **“User” selector** to the Time Entry landing page (`/msp/time-entry`) for users who can act on behalf of others.
|
||
- Default selection: current user.
|
||
- For non-authorized users, hide the selector (or show it disabled).
|
||
- The delegated time-entry UI (subject selector + delegated timesheet editing UX) is gated behind the PostHog feature flag **`delegated-time-entry`** (UI-only).
|
||
- When disabled: the UI only supports working with the current user’s own time sheets; delegated sheets (if accessed directly) are read-only and show an explanation.
|
||
- Bundled tickets (tickets that are part of a ticket bundle) cannot have time logged directly; the UI should:
|
||
- show bundled tickets as **disabled/greyed out** in ticket pickers
|
||
- explain why (e.g. “Bundled ticket — log time on the master ticket”)
|
||
- Timesheet view (`/msp/time-entry/timesheet/[id]`) must clearly indicate:
|
||
- “Time Sheet for {Subject Name}”
|
||
- Optional small text: “Edited by {Actor Name}” for transparency (audit UI).
|
||
- Add a **“Reopen for edits”** action for authorized actors when a time sheet is `APPROVED` and contains no invoiced entries.
|
||
- Reopen should set the time sheet status to `CHANGES_REQUESTED` (editable) and restore time entry statuses accordingly.
|
||
|
||
## Technical Design Notes
|
||
### Current-State Notes (as observed in repo)
|
||
- Time entry CRUD is handled via Next.js server actions in:
|
||
- `packages/scheduling/src/actions/timeEntryCrudActions.ts`
|
||
- Time sheet operations/time-period listing are in:
|
||
- `packages/scheduling/src/actions/timeSheetOperations.ts`
|
||
- Timesheet approval flows exist in:
|
||
- `packages/scheduling/src/actions/timeSheetActions.ts`
|
||
- `packages/scheduling/src/components/time-management/approvals/ManagerApprovalDashboard.tsx`
|
||
- Billing engine uses only `APPROVED` + `invoiced=false` time entries:
|
||
- `server/src/lib/billing/billingEngine.ts`
|
||
- Known blocker/bug to address:
|
||
- `saveTimeEntry` currently forces `user_id` to the actor (`user.user_id`), which prevents “on behalf of” and can corrupt ownership on updates.
|
||
|
||
### Proposed Architecture
|
||
#### 1) Add explicit “subject user” support across time entry actions
|
||
- Introduce a `subjectUserId` parameter (or derive subject via time sheet) for:
|
||
- fetching time periods
|
||
- fetching/creating time sheets
|
||
- saving/deleting time entries
|
||
- Ensure the persisted time entry uses:
|
||
- `time_entries.user_id = subjectUserId`
|
||
- audit columns track actor (see below)
|
||
|
||
#### 2) Centralize authorization checks for on-behalf access
|
||
Define a shared helper used by all relevant server actions:
|
||
- If subject is self → allow (subjectUserId === actor.user_id).
|
||
- Else require:
|
||
- actor has `timesheet:approve`, and
|
||
- (actor has `timesheet:read_all` for tenant-wide) OR (actor manages a team that includes subject).
|
||
|
||
Important: manager/team checks must be validated server-side (do not trust client-supplied team ids).
|
||
|
||
#### 3) Support approved periods by reopening (reverse approval)
|
||
- Provide a “reopen” operation that:
|
||
- requires `timesheet:reverse` (and the same on-behalf rules above)
|
||
- blocks if any associated time entries are invoiced
|
||
- sets time sheet to `CHANGES_REQUESTED` (editable)
|
||
- sets time entries to `CHANGES_REQUESTED`
|
||
|
||
#### 4) Add audit columns for actor tracking
|
||
Add columns on `time_entries`:
|
||
- `created_by` (nullable, references users)
|
||
- `updated_by` (nullable, references users)
|
||
|
||
Behavior:
|
||
- On create: set `created_by = actor.user_id`, `updated_by = actor.user_id`
|
||
- On update: set `updated_by = actor.user_id`
|
||
|
||
#### 5) Enforce invoicing and boundary constraints
|
||
- Disallow update/delete when `time_entries.invoiced = true`.
|
||
- Disallow reopening a time sheet if any related time entry is invoiced.
|
||
- Add server-side validation that new/edited entry timestamps are within the time period boundaries for the time sheet.
|
||
|
||
## Risks / Considerations
|
||
- Multiple implementations exist (server actions vs `server/src/lib/api/services/TimeSheetService.ts`). We should keep behavior consistent for the UI paths we update and avoid accidental divergence.
|
||
- Need to ensure “subject timezone” is used when computing `work_date`/`work_timezone` so the entry is attributed to the correct day for the subject.
|
||
|
||
## Acceptance Criteria
|
||
- A billing/system admin can create/edit time entries for any internal user and approve/reopen as needed (unless invoiced).
|
||
- A manager can create/edit time entries only for users in teams they manage.
|
||
- A user without these capabilities cannot view or edit another user’s time sheets/entries (even if they know IDs).
|
||
- Attempting to modify invoiced time is blocked with a clear error.
|
||
- Time entry records capture “who entered/edited” via audit fields.
|
||
- Bundled tickets are visibly non-selectable in the UI with clear messaging about logging time on the master ticket.
|