# Shot / Task / Version & Approval Status Workflow ## Core Entities & Status Fields | Entity | Status Field | Enum | |---|---|---| | **Shot** | `status` (derived) | `ShotStatus` | | **Shot** | `shotApprovalStatus` | `ShotApprovalStatus` | | **Task** | `status` | `TaskStatus` | | **Version** | `approvalStatus` | `ApprovalStatus` | | **Version** | `reviewStatus` | `ReviewStatus` (lightly used) | --- ## Data Model & Enums | Enum | States | Purpose | |------|--------|---------| | **ShotStatus** | `WAITING`, `IN_PROGRESS`, `INTERNAL_REVIEW`, `READY_FOR_CLIENT`, `CLIENT_REVIEW`, `REVISIONS`, `COMPLETE` | Derived shot state (read-only, calculated) | | **ShotApprovalStatus** | `PENDING`, `INTERNALLY_APPROVED`, `CLIENT_APPROVED` | Shot-level approval stage (manually set) | | **TaskStatus** | `TODO`, `IN_PROGRESS`, `INTERNAL_REVIEW`, `CLIENT_REVIEW`, `CHANGES`, `DONE` | Task execution state | | **ApprovalStatus** | `PENDING_REVIEW`, `APPROVED`, `REJECTED`, `NEEDS_CHANGES` | Version review decision | | **ReviewStatus** | `PENDING`, `INTERNAL_APPROVED`, `CLIENT_APPROVED`, `NEEDS_CHANGES`, `FINAL_APPROVED` | Version review progression (legacy/parallel) | ### Core Relationships ``` Project → Shot (1:N) ↓ Shot → Task (1:N) ↓ Task → Version (1:N) ↓ Version → Approval (1:N, one per reviewer) ``` --- ## Shot Status is Derived, Not Set Directly `ShotStatus` is computed by `deriveShotStatus()` in `lib/shot-status.ts` from task states and approval fields. It is recalculated via `recalcShotStatus()` whenever a task, version, or approval changes. Priority rules (highest wins): ``` REVISIONS ← any task is CHANGES COMPLETE ← shotApprovalStatus === CLIENT_APPROVED CLIENT_REVIEW ← INTERNALLY_APPROVED + sharedWithClient = true READY_FOR_CLIENT ← INTERNALLY_APPROVED + sharedWithClient = false IN_PROGRESS ← any task is TODO or IN_PROGRESS INTERNAL_REVIEW ← tasks exist, none blocking (approval still PENDING) WAITING ← no tasks ``` `recalcShotStatus()` is automatically triggered on: - Task creation (added to shot) - Task status update - Approval submission --- ## Version → Approval → Task Pipeline ``` Artist uploads Version → all prior versions: isLatest = false → new Version: approvalStatus = PENDING_REVIEW, isLatest = true → Task auto-moves → INTERNAL_REVIEW → Shot recalculated → INTERNAL_REVIEW → Notifications sent to supervisors/producers ↓ Reviewer submits Approval (creates Approval record) → Version.approvalStatus updated → APPROVED → Task → DONE → REJECTED → Task → CHANGES → NEEDS_CHANGES → Task → CHANGES → Shot recalculated → Notifications sent (artist, Slack webhook) ``` ### Approval Status Mapping | `Version.approvalStatus` | `Task.status` | `Shot.status` (if isolated) | |---|---|---| | `PENDING_REVIEW` | `INTERNAL_REVIEW` | `INTERNAL_REVIEW` | | `APPROVED` | `DONE` | depends on other tasks | | `REJECTED` | `CHANGES` | `REVISIONS` | | `NEEDS_CHANGES` | `CHANGES` | `REVISIONS` | ### Role-Based Access for Approvals - Only ADMIN, PRODUCER, SUPERVISOR, CLIENT can approve - Client can only approve if shot is shared (`sharedWithClient = true`) - Approval records are **append-only** — each review creates a new `Approval` row; the latest one wins --- ## Task Status Transitions ``` TODO ↓ (artist starts work / uploads version) IN_PROGRESS / INTERNAL_REVIEW ↓ (approval submitted) ├─→ DONE (if APPROVED) └─→ CHANGES (if REJECTED or NEEDS_CHANGES) ↓ CLIENT_REVIEW (when shot is shared with client) ``` | Action | Trigger | Effect | |--------|---------|--------| | `createTask()` | Producer creates task | Creates in TODO; calls `recalcShotStatus()` | | `updateTaskStatus()` | Manual status change | Updates task; triggers notifications + shot recalc | | Task → `INTERNAL_REVIEW` | Version uploaded | Auto-triggered in `actions/versions.ts` | | Task → `CLIENT_REVIEW` | Shot shared with client | Auto-triggered in `actions/shots.ts` | | Task → `DONE` | Version approved | Auto-triggered in `actions/approvals.ts` | | Task → `CHANGES` | Version rejected/needs changes | Auto-triggered in `actions/approvals.ts` | **Permissions:** ADMIN, PRODUCER, SUPERVISOR can create/update tasks. --- ## Shot-Level Approval Lifecycle ``` PENDING (initial) ↓ internallyApproveShot() [ADMIN / PRODUCER / SUPERVISOR] INTERNALLY_APPROVED → Shot status: READY_FOR_CLIENT ↓ shareWithClient() sharedWithClient = true → Shot status: CLIENT_REVIEW latest versions: isClientVisible = true, sharedAt = now ↓ ↓ clientApproveShot() clientRequestsChanges() shotApprovalStatus = shotApprovalStatus = PENDING CLIENT_APPROVED sharedWithClient = false Shot status: COMPLETE isClientVisible = false (all task versions) Shot status: REVISIONS (if tasks have CHANGES) ``` ### State Details | State | `shotApprovalStatus` | `sharedWithClient` | `Shot.status` | |---|---|---|---| | Initial | `PENDING` | `false` | `WAITING` / `IN_PROGRESS` / `INTERNAL_REVIEW` | | Internally approved | `INTERNALLY_APPROVED` | `false` | `READY_FOR_CLIENT` | | Shared with client | `INTERNALLY_APPROVED` | `true` | `CLIENT_REVIEW` | | Client approved | `CLIENT_APPROVED` | `true` | `COMPLETE` | | Revisions requested | `PENDING` | `false` | `REVISIONS` | --- ## Key Implementation Files | Concern | File | |---|---| | Status derivation engine | `lib/shot-status.ts` | | Shot actions (share/approve/revise) | `actions/shots.ts` | | Approval creation + cascades | `actions/approvals.ts` | | Version upload + task auto-transition | `actions/versions.ts` | | Task creation / status updates | `actions/tasks.ts` | | Shot status UI badges | `components/shots/ShotCard.tsx` | | Task status UI badges | `components/tasks/TaskCard.tsx` | --- ## Notable Design Choices - **`ShotStatus` is never written directly** — always a computed projection of task + approval state, preventing inconsistency. - **`ReviewStatus` on Version is largely redundant** with `approvalStatus`; it appears to be a legacy/parallel field not driving much logic currently. - **Approval records are append-only** — each review creates a new `Approval` row; the latest one wins for updating `Version.approvalStatus`. - **Client visibility is gated at two levels**: `Shot.sharedWithClient` and `Version.isClientVisible`, both toggled atomically when sharing. - **`shareWithClient()` is atomic**: all task versions are updated in a single transaction. --- ## Unapprove / Undo Approval ### Version-Level Unapprove (`unapproveVersion`) Available to: **ADMIN, PRODUCER, SUPERVISOR** Shown as: **"Undo Approval"** in the version dropdown (only visible when `approvalStatus === APPROVED`) Location: `VersionList.tsx` → `actions/approvals.ts` ``` Version.approvalStatus === APPROVED ↓ unapproveVersion() Version.approvalStatus → PENDING_REVIEW Task.status (if DONE) → INTERNAL_REVIEW Audit: new Approval record created with status = PENDING_REVIEW, notes = "Approval undone" Shot recalculated ``` ### Shot-Level Unapprove (`unapproveShot`) Available to: **ADMIN, PRODUCER, SUPERVISOR** Shown as: **"Undo Client Approval"** button on the shot detail page (only when `shotApprovalStatus === CLIENT_APPROVED`) Location: `shots/[shotId]/page.tsx` → `actions/shots.ts` ``` shotApprovalStatus === CLIENT_APPROVED (Shot status: COMPLETE) ↓ unapproveShot() shotApprovalStatus → INTERNALLY_APPROVED Shot.status → CLIENT_REVIEW (if still sharedWithClient) → READY_FOR_CLIENT (if sharedWithClient = false) ``` Neither unapprove action modifies the `sharedWithClient` flag or version visibility — only the approval state is rolled back.