# Tasks & Statuses

Tasks, statuses, activities, notes, and triage


Tasks, the per-team triage queue, statuses, notes, and activity records in the Operation plugin.

Backend code lives under `backend/plugins/operation_api/src/modules/{task,status,note,activity}/`. The task module also contains the triage model and the task segment definitions in [`task/meta/segments/`](https://github.com/erxes/erxes/tree/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/operation_api/src/modules/task/meta/segments).

## Task data model

`operation_tasks` ([`modules/task/db/definitions/task.ts`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/operation_api/src/modules/task/db/definitions/task.ts)):

| Field | Type | Notes |
| --- | --- | --- |
| `name`, `description` | string | `name` required |
| `status` | ObjectId | references a team `Status`; required |
| `teamId` | ObjectId | required; permission scope field |
| `priority`, `estimatePoint` | number | `priority` defaults 0; `estimatePoint` defaults 0 |
| `assigneeId`, `createdBy` | string | Core user ids (indexed, sparse) |
| `labelIds`, `tagIds` | string[] | Core labels/tags |
| `cycleId`, `projectId`, `milestoneId` | ObjectId | optional rollups |
| `startDate`, `targetDate`, `statusChangedDate` | Date | `statusChangedDate` determines "completed" reporting |
| `number`, `statusType` | number | `statusType` mirrors the status's type |
| `githubIssueNumber`, `githubIssueUrl`, `githubRepoName` | | set by the GitHub sync |
| `propertiesData` | mixed | `operation:task` property values, keyed by field id |
| `segmentIds` | string[] | written only by the segmentation worker |

> [!WARNING]
> **Task _id is an ObjectId**
>
> `taskSchema` is a plain `new Schema(...)` without `schemaWrapper`, so `_id` stays a Mongo `ObjectId`. Segment and tRPC code always goes through the Mongoose model so string ids cast correctly. Do not bypass it with a raw driver handle.

## Statuses

`Status` documents are team-scoped (`{ name, description, color, type, teamId, order }`). `type` is one of `STATUS_TYPES` ([`status/constants/types.ts`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/operation_api/src/modules/status/constants/types.ts)): `STARTED=1`, `UNSTARTED=2`, `BACKLOG=3`, `COMPLETED=4`, `CANCELLED=5`, `TRIAGE=6`. Creating a team seeds the five default statuses (`backlog`, `todo`, `in progress`, `done`, `cancelled`); see `status/utils.ts`.

## Triage queue

Triage items (`operation_triage`) are inbox work for a team: `{ name, description, teamId, createdBy, priority, status, number }` plus GitHub issue fields. `operationConvertTriageToTask` creates a task in the same team (optionally looking up `status` by status *type*), carries over GitHub linkage, stores the `reason` as a note on the new task, and deletes the triage record.

> [!WARNING]
> **operationCancelTriage is not implemented**
>
> `operationCancelTriage(_id)` is declared in the triage GraphQL schema but has no resolver; calling it errors at runtime. To retire a triage item, update its `status` with `operationUpdateTriage` or convert it with `operationConvertTriageToTask` (which deletes the record).

## Queries and mutations

| Operation | Kind | Permission |
| --- | --- | --- |
| `getTasks(filter: ITaskFilter)` / `getTask(_id)` | query | `taskRead` |
| `createTask(…)` / `updateTask(…)` / `removeTask(_id)` | mutation | `taskCreate` / `taskUpdate` / `taskRemove` |
| `operationGetTriage(_id)` / `operationGetTriageList(filter)` | query | `triageRead` |
| `operationAddTriage` / `operationUpdateTriage` | mutation | `triageCreate` / `triageUpdate` |
| `operationConvertTriageToTask(_id, status: Int, reason)` | mutation | `triageConvert` |
| `getStatus(_id)` / `getStatusesByType(type, teamId)` / `getStatusesChoicesByTeam(teamId)` | query | `statusRead` |
| `addStatus` / `updateStatus` / `deleteStatus` | mutation | `statusCreate` / `statusUpdate` / `statusRemove` |
| `getOperationActivities(contentId, …)` | query | `taskRead` |
| `getNote(_id)` / `createNote` / `updateNote` / `deleteNote` | query/mutation | `noteRead` / `noteCreate` / `noteUpdate` / `noteRemove` |

`ITaskFilter` accepts `status`, `priority`, `assigneeId`, `createdBy`, `cycleId`, `projectId`, `milestoneId`, `teamId`, `labelIds`, `tagIds`, `estimatePoint`, `statusType`, `cycleFilter` (`noCycle`, `anyPastCycle`, `previousCycle`, `currentCycle`, `upcomingCycle`, `anyFutureCycle`), `projectStatus`/`projectPriority`/`projectLeadId`, date bounds, and cursor pagination params.

Example `createTask` call:

```graphql
mutation {
  createTask(
    name: "Prepare release checklist"
    teamId: "team_1"
    status: "status_todo"
    assigneeId: "user_1"
    priority: 3
    estimatePoint: 5
  ) {
    _id
    name
  }
}
```

## Notes and activity

`Note` (`{ content, contentId, createdBy, mentions, statusId }`) attaches to a task or project by `contentId`; mentions trigger a `note` notification to the mentioned users, and `removeNote` is author-only. `OperationActivity` (`{ action, contentId, module, metadata: {newValue, previousValue}, createdBy }`) is the audit trail; subscribe to `operationActivityChanged(contentId)` for live updates.

## Subscriptions

`operationTaskChanged(_id)`, `operationTaskListChanged(filter: ITaskFilter)` (filtered server-side via `withFilter`), and `operationActivityChanged(contentId)` publish on create/update through `graphqlPubsub`.

## tRPC

Merged in `src/trpc/init-trpc.ts`:

| Procedure | Type | Notes |
| --- | --- | --- |
| `task.tag({ tagIds, targetIds, type, action })` | mutation | Bulk-set `tagIds` on tasks |
| `task.findOne({ _ids })` | query | First task among the ids (`{ _id, name, teamId }` or `null`); non-ObjectId ids skipped |
| `task.createFromSource({ userId, doc })` | mutation | Creates a task as `userId`; reads `teamId` from `doc.status` (must be a status ObjectId); publishes the task subscriptions; returns `{ _id }` |

`createFromSource` is how `frontline` converts a conversation into a task. It checks **no permission**; the caller must enforce `taskCreate` for the acting user.

> [!WARNING]
> **"Status not found" from createFromSource**
>
> `doc.status` must be a status `_id` (ObjectId), not a status type number. Only `operationConvertTriageToTask` accepts a type.

## Segments and import/export

- `operation:task.tasks` is a segment content type with 19 filterable fields, member listing/counting, materialised `segmentIds`, and relations `user.assignedTasks` / `user.createdTasks` (the field-joined relations rely on the `assigneeId`/`createdBy` indexes). The content type must be exactly `operation:task.tasks`; the event dispatcher keys on it.
- Import type `operation:task.task` (`taskImportManage`) and export types `operation:task.task` / `operation:project.project` (`taskExportManage` / `projectExportManage`) are registered via `meta/import-export`. Repeating property groups export as numbered `<Group> <n> / <Field>` columns.
- Notifications: `taskAssignee` (task assigned) and `taskStatus` (status changed).

## Permissions

Modules `task`, `triage`, `note`, `status` (and `project`, `cycle`, `milestone`, `team` elsewhere) use scoped permissions: `own` (records you created), `group` (records in your teams via `scopeField: 'teamId'`), `all`. Read actions are `always: true`; write actions are grantable. `taskAssign` is a custom action for assignment control.

