# Projects, Cycles & Milestones

Group work into projects, time-box it with cycles, and track milestones


Projects, sprints (cycles), and milestone progress rollups in the Operation plugin.

Backend code lives under `backend/plugins/operation_api/src/modules/{project,cycle,milestone}/`.

## Data models

- **`operation_projects`** ([model](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/operation_api/src/modules/project/db/definitions/project.ts)): `{ name, description, status: Int, priority: Int, icon, teamIds: [ObjectId] (required), tagIds, startDate, targetDate, leadId, memberIds, createdBy, convertedFromId }`. `convertedFromId` links a project back to the task it was converted from; creating a second project from the same task fails with "Project has been converted already."
- **`operation_cycles`**: `{ name, description, startDate, endDate, teamId, isCompleted, isActive, statistics, donePercent, unFinishedTasks: [ObjectId], number }`.
- **`operation_milestones`**: `{ name, description, targetDate, projectId (required), createdBy }`.

## Queries and mutations

### Projects

| Operation | Kind | Permission |
| --- | --- | --- |
| `getProject(_id)` / `getProjects(filter: IProjectFilter)` | query | `projectRead` |
| `getProjectProgress` / `getProjectProgressByMember` / `getProjectProgressByTeam` / `getProjectProgressChart` (`_id`) | query | `projectRead` |
| `getConvertedProject(convertedFromId)` | query | `projectRead` |
| `cpGetProjects` | query | `forClientPortal` + `cpUserRequired`; Client Portal users only |
| `createProject` / `updateProject` / `removeProject(_id)` | mutation | `projectCreate` / `projectUpdate` / `projectRemove` |

`IProjectFilter` accepts `_ids`, `name`, `status`, `priority`, `teamIds`, `tagIds`, `leadId`, `memberIds`/`memberId`, `userId`, `active`, `taskId`, date bounds, and cursor params.

### Cycles

| Operation | Kind | Permission |
| --- | --- | --- |
| `getCycle(_id)` / `getCycles(teamId, …)` / `getCyclesActive(teamId, taskId, …)` | query | `cycleRead` |
| `getCycleProgress` / `getCycleProgressChart` / `getCycleProgressByMember` / `getCycleProgressByProject` (`_id`, `assigneeId?`) | query | `cycleRead` |
| `createCycle(input: CycleInput)` / `updateCycle(input)` / `removeCycle(_id)` | mutation | `cycleCreate` / `cycleUpdate` / `cycleRemove` |
| `endCycle(_id)` | mutation | `cycleEnd` |

### Milestones

| Operation | Kind | Permission |
| --- | --- | --- |
| `getMilestone(_id)` / `milestones(projectId, searchValue, …)` | query | `milestoneRead` |
| `milestoneProgress(projectId)` | query | `milestoneRead`; per-milestone `totalScope`, `totalStartedScope`, `totalCompletedScope` |
| `createMilestone` / `updateMilestone` / `removeMilestone` | mutation | `milestoneCreate` / `milestoneUpdate` / `milestoneRemove` |

## Cycle lifecycle

Cycles are per-team sprints with date boundaries:

- `createCycle`/`updateCycle` reject date ranges that overlap another cycle of the same team ("New cycle overlaps with an existing cycle"), and a completed cycle cannot be updated.
- `startCycle` activates a cycle only when no other cycle of that team is active.
- `endCycle` snapshots `statistics` (chart data + progress by member and project), marks the cycle completed, and activates the team's next uncompleted cycle (or auto-creates one with the same duration: `Cycle <date>`, `number + 1`). Unfinished tasks move into it via `Task.moveCycle` and their ids are recorded in `unFinishedTasks` ([Cycle model](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/operation_api/src/modules/cycle/db/models/Cycle.ts)).
- The `operations-daily-cycles-check` BullMQ scheduler (hourly `0 * * * *` UTC) runs `checkCycle` per tenant at midnight in the tenant's `TIMEZONE` core config: it ends cycles whose `endDate` fell yesterday and activates cycles whose `startDate` is today. On SaaS (`VERSION=saas`) it fans out per organization; otherwise it runs for the `os` tenant ([dailyCheckCycles.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/operation_api/src/worker/dailyCheckCycles.ts)).

> [!WARNING]
> **Cycle errors**
>
> `startCycle` fails with "Previous cycle is active" if the team already has an active cycle; end it first. If daily rollover does not happen, `checkCycle` only acts at hour 0 in the tenant `TIMEZONE` core config, so make sure the `operations-daily-cycles-check` queue is running and the config is set.

## Progress rollups

`getProjectProgress*` aggregate task scope/estimate over the project's tasks, grouped by member and team, plus a time-series chart. `getCycleProgress*` does the same per cycle with an optional `assigneeId` filter. `milestoneProgress` returns per-milestone totals (`totalScope`, `totalStartedScope`, `totalCompletedScope`) for one project.

Subscriptions `operationProjectChanged(_id)` and `operationProjectListChanged(filter)` publish project updates; task updates that affect rollups publish on the task channels (see [Tasks & Statuses](https://erxes.io/docs/operation-tasks)).

## Segments and automations

Projects are a declared segment content type (`operation:project.projects`). The declaration registers the content type and dependent modules but answers no field producers. Automation triggers "Project", "Project completed" (every task done), "Milestone reached", and "Team work completed" plus the "Create project" action are described in [GitHub Integration & Automations](https://erxes.io/docs/operation-github-integration).

