209 lines
7.8 KiB
Markdown
209 lines
7.8 KiB
Markdown
# 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.
|