Last active 4 days ago

Revision 7d62d7c72aadae10b20d1272190c4f144bf3ed1d

action-plan.docs.md Raw

Action Plans API

Base URL: {host}/api/v1

All responses wrapped in:

{ "statusCode": 200, "message": "...", "data": { ... }, "durationMs": 42, "_reference": [] }

Hierarchy & Member Counting

Activity
  └─ Task (many)
      └─ Subtask (many)
Level Has children? Member count source
Activity Tasks with subtasks Union of all subtask members
Activity Tasks without subtasks Union of all task members
Activity No tasks Activity's own members
Task Has subtasks Union of all subtask members
Task No subtasks Task's own members
Subtask Subtask's own members

Example:

Activity1 (10 members) ← Union(Task1, Task2)
  Task1 (5 members) ← Union(Subtask1, Subtask2)
    Subtask1 (3 members)
    Subtask2 (2 members)
  Task2 (5 members) ← Task scope, no subtasks

Leaf-Only Validation

Nodes with children reject direct progress/plans/members operations.

Operation Activity has tasks? Task has subtasks?
Progress ❌ 400 ❌ 400
Plans ❌ 400 ❌ 400
Members ❌ 400 ❌ 400

Subtask always valid.


Route Map

All routes under: api/projects/:projectId/action-plans

GET    /                                           list activities
POST   /                                           create activity
GET    /tree                                       tree view
GET    /tree/gantt                                 gantt
GET    /analytics                                  analytics
GET    /import/template/download                   excel template
POST   /import                                     import
GET    /:activityId                                get activity
PATCH  /:activityId                                update activity
DELETE /:activityId                                delete activity

// Activity leaf
POST   /:activityId/progress                       create progress
GET    /:activityId/progress                       list progress
GET    /:activityId/progress/:progressId           get progress
POST   /:activityId/progress/:progressId/respond   respond
GET    /:activityId/plans                          plans (S-curve)
PUT    /:activityId/plans                          set plans
POST   /:activityId/members                        add member
DELETE /:activityId/members/:userId                remove member
GET    /:activityId/members                        list members

// Activity progress feedbacks
POST   /:activityId/progress/:pid/feedbacks        create feedback
GET    /:activityId/progress/:pid/feedbacks        list feedbacks
PATCH  /:activityId/progress/:pid/feedbacks/:fid   edit feedback
DELETE /:activityId/progress/:pid/feedbacks/:fid   delete feedback

// Task CRUD + leaf
POST   /:activityId/tasks                          create task
GET    /:activityId/tasks                          list tasks
GET    /:activityId/tasks/:taskId                  get task
PATCH  /:activityId/tasks/:taskId                  update task
DELETE /:activityId/tasks/:taskId                  delete task
POST   /:activityId/tasks/:taskId/progress         task progress
GET    /:activityId/tasks/:taskId/progress         list
GET    /:activityId/tasks/:taskId/progress/:pid    get
POST   /:activityId/tasks/:taskId/progress/:pid/respond
GET    /:activityId/tasks/:taskId/plans            plans
PUT    /:activityId/tasks/:taskId/plans
POST   /:activityId/tasks/:taskId/members          add member
DELETE /:activityId/tasks/:taskId/members/:uid     remove member
GET    /:activityId/tasks/:taskId/members          list members

// Task progress feedbacks
POST   /:activityId/tasks/:tid/progress/:pid/feedbacks
GET    /:activityId/tasks/:tid/progress/:pid/feedbacks
PATCH  /:activityId/tasks/:tid/progress/:pid/feedbacks/:fid
DELETE /:activityId/tasks/:tid/progress/:pid/feedbacks/:fid

// Subtask CRUD + leaf
POST   /:activityId/tasks/:taskId/subtasks         create subtask
GET    /:activityId/tasks/:taskId/subtasks         list subtasks
GET    /:activityId/tasks/:taskId/subtasks/:subtaskId
PATCH  /:activityId/tasks/:taskId/subtasks/:subtaskId
DELETE /:activityId/tasks/:taskId/subtasks/:subtaskId
POST   /:activityId/tasks/:taskId/subtasks/:sid/members
DELETE /:activityId/tasks/:taskId/subtasks/:sid/members/:uid
GET    /:activityId/tasks/:taskId/subtasks/:sid/members
GET    /:activityId/tasks/:taskId/subtasks/:sid/plans
PUT    /:activityId/tasks/:taskId/subtasks/:sid/plans
POST   /:activityId/tasks/:taskId/subtasks/:sid/progress
GET    /:activityId/tasks/:taskId/subtasks/:sid/progress
GET    /:activityId/tasks/:taskId/subtasks/:sid/progress/:pid
POST   /:activityId/tasks/:taskId/subtasks/:sid/progress/:pid/respond

// Subtask progress feedbacks
POST   /:aId/tasks/:tId/subtasks/:sId/progress/:pId/feedbacks
GET    /.../feedbacks
PATCH  /.../feedbacks/:fId
DELETE /.../feedbacks/:fId

Enums

Subtask/Task/Activity Status

INCOMPLETE_DATA | NOT_STARTED | IN_PROGRESS | NEED_APPROVAL | COMPLETED

Progress Entry Status

PENDING | APPROVED | APPROVED_WITH_NOTES | REJECTED

ActionPlanMemberScope

ACTIVITY | TASK | SUBTASK


Notifications

Fire-and-forget. Does not block operation on failure.

Member Notifications

Trigger Type Recipient
Add to subtask action_plan_member_added Added user
Remove from subtask action_plan_member_removed Removed user
Add to task action_plan_member_added Added user
Remove from task action_plan_member_removed Removed user
Add to activity action_plan_member_added Added user
Remove from activity action_plan_member_removed Removed user

Progress Respond Notifications

Verb Type Recipient
APPROVE action_plan_progress_approve Submitter
APPROVE_WITH_NOTES action_plan_progress_approve_with_notes Submitter
REJECT action_plan_progress_reject Submitter

Request/Response Schemas

Create Activity

POST /

{
  "name": "Activity name",
  "tasks": [{ "name": "Task name", "workWeight": 100, "workVolume": 500, "workVolumeUnit": "m2", "startDate": "2026-01-01", "endDate": "2026-03-31",
    "subtasks": [{ "name": "Subtask name", "workVolume": 250, "workVolumeUnit": "m2", "workWeight": 50, "startDate": "2026-01-01", "endDate": "2026-02-15" }]
  }]
}

List Activities

GET / — Query: page, limit, search, sort_by (created_at/updated_at/name), sort_order (asc/desc) Response: { items: [...], total, page, limit }

Get Single Activity

GET /:id

{ "id":"uuid", "projectId":"uuid", "name":"...", "taskCount":2, "subtaskCount":5, "memberCount":10, "status":"IN_PROGRESS", "progress":45.5, "workWeight":150 }

Tree / Gantt

GET /tree — Query: status filter. Each node has totalUsersAssigned, accumulatedProgress. GET /tree/gantt — Same + derived startDate/endDate.

Progress Entry

Response shape (all scopes):

{
  "id":"uuid", "scopeType":"SUBTASK|TASK|ACTIVITY", "scopeId":"uuid", "scopeName":"name",
  "progress":75, "workVolume":187.5, "scopeWorkVolume":250, "scopeWorkVolumeUnit":"m2",
  "description":"...", "status":"APPROVED", "amendsId":null, "amendsOriginal":null,
  "amendedByCount":0, "amendedBy":[], "approvedBy":"uuid", "approvedByName":"Admin",
  "approvedAt":"ISO", "createdBy":"uuid", "createdByName":"Worker",
  "images":[{ "id":"uuid","key":"s3/path","originalName":"photo.jpg","url":"presigned-url" }],
  "sitemap":{ "id":"uuid","key":"s3/path","originalName":"site.pdf","url":"presigned-url" }
}

Progress Create (multipart/form-data)

POST /:../progress

Field Type
progress number (0-100)
workVolume number, optional
description string, max 5000
images file[], max 10
sitemap file, PDF

Progress Respond

POST /:../progress/:id/respond

{ "verb":"APPROVE|APPROVE_WITH_NOTES|REJECT", "progress":80, "workVolume":200, "description":"Adjusted" }

Plans (S-Curve)

GET /:../plans

{ "duration":{ "days":90, "startDate":"...", "endDate":"..." }, "planning":[...], "sCurve":{ "plan":[...], "actual":[...] } }

PUT /:../plans{ "plans":[{ "startDate":"...", "endDate":"...", "targetProgress":50 }] }

Members

POST/DELETE/GET /:../members{ "userId":"uuid" } / response: [{ "id":"uuid","userId":"uuid","displayName":"John Doe" }]

Feedbacks

POST /:../feedbacks{ "message":"text" } PATCH /:../feedbacks/:id{ "message":"updated" }

Analytics

GET /analytics — Query: layout (detailed/simplified), granularity (daily/weekly/monthly), search, startDate, endDate

Import

GET /import/template/download — Excel file POST /import (multipart) — file (.xlsx/.xls/.csv, max 5MB)


Constraint Summary

Constraint Value
Max tasks per activity 50
Max subtasks per task 50
Max images per progress 10
Max feedback/description length 5000 chars
Max import file size 5MB
Progress range 0-100
Work weight/volume Number >= 0

Editing Rules

  • Activity with tasks → only name editable
  • Task with subtasks → only name editable
  • Subtask in progress → workWeight/workVolume locked

Soft Delete

All deletes are soft (deletedAt). Cascades: deleting activity → all tasks + subtasks. Deleting task → all subtasks.