7.8 KiB
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
Approvalrow; 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
ShotStatusis never written directly — always a computed projection of task + approval state, preventing inconsistency.ReviewStatuson Version is largely redundant withapprovalStatus; it appears to be a legacy/parallel field not driving much logic currently.- Approval records are append-only — each review creates a new
Approvalrow; the latest one wins for updatingVersion.approvalStatus. - Client visibility is gated at two levels:
Shot.sharedWithClientandVersion.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.