Tasks — Developer Guide
Mirror of the Leads developer guide. Prefer copying Leads patterns over inventing new ones.
Backend layout
| Piece | Path |
|---|---|
| Models | app/Models/Task.php, TaskTag, TaskNote, TaskNoteMention, TaskActivity |
| Enums | app/Enums/Tenant/TaskStatusEnum (includes waiting; Open label To Do), TaskPriorityEnum, TaskActivityTypeEnum |
| Service | app/Services/Tenant/TaskService.php (+ ScopesToAssignee), TaskTagService.php |
| Controller | app/Http/Controllers/Tenant/Api/V1/TaskController.php, TaskTagController.php |
| Requests | app/Http/Requests/Tenant/Api/V1/Task/* |
| Resources | app/Http/Resources/Tenant/Api/V1/Task/* |
| Policy | app/Policies/TaskPolicy.php (changeDueDate → tasks.change_due_date), TaskTagPolicy.php |
| Events | app/Events/Task*.php (includes TaskTagCreated, TaskTagsSynced) |
| Subscriber | app/Listeners/TaskEventSubscriber.php (audit + notifications) |
| Notifications | app/Notifications/Tenant/Task/* — assign/complete/reopen: database + optional mail (+ webpush on assign); mentions: database + webpush + optional mail; due/overdue: database; daily digest: mail only (TaskDueDigestNotification) |
| Mentions | App\Support\NoteMentions, NoteMentionService; wired from TaskNoteAdded in TaskEventSubscriber |
| Digest delivery | task_digest_deliveries + TaskDigestDeliveryService; TrackTaskDueDigestDelivery on NotificationSent / NotificationFailed |
| Scheduled due | crm:send-due-notifications every 5 minutes (onOneServer); tenant setting task_reminder_time (Daily Reminder Time) gated in workspace timezone — see Workspace timezone convention |
| Tests | tests/Feature/Tenant/Task/TaskTest.php, TaskTagTest.php, tests/Feature/Tenant/Notification/TaskDueDigestNotificationTest.php, NoteMentionNotificationTest.php, tests/Unit/NoteMentionsTest.php |
Domain notes
- Comment bodies may include
@[Display Name](user:ID)mention tokens (composer UI shows@Namechips). OnTaskNoteAdded,NoteMentionServicepersiststask_note_mentionsand sendstask.mentioned(skip self; idempotent viadedupe_key). Mail is optional viaemail_notifications.task_mentioned(default off). - Assignee scoping via
ScopesToAssigneewithtasks.assign. - Task assignee pickers use
filterTaskAssigneeOptions(omit suspended only). They do not reusefilterLeadAssigneeOptions—exclude_from_lead_auto_assignand workspace owners remain assignable on Tasks. Backend create/update/assign useEligibleTaskAssignee(User::isEligibleAssignee— rejects suspended users; allows keeping the current assignee when suspended so they can be cleared). - Updating
due_atafter create requirestasks.change_due_date(enforced inTaskService/ policy). Initialdue_aton create is allowed without that permission. due_atis a UTC instant (UtcDateTime/UtcIso). Overdue / due-today / due-this-week SQL usesApp\Support\UtcInstantso non-UTC workspace timezones do not mark upcoming tasks overdue. SPA create/edit usesappLocalInputToIso/isoToAppLocalInput(Settings → General timezone), not rawdatetime-local/ ISO slice.- Board columns are one per
TaskStatusEnumcase. - Optional soft
project_id(nullable FK →projects,nullOnDelete) validated byLinkableProject— Projects module must be entitled and the project must be visible to the actor. List/show embedproject(id,uuid,title,status) when loaded. Catalog version 1.4.0 (attachments + upload-policy enforcement). See Projects developer guide. - Attachments:
task_attachmentsandtask_note_attachmentson the workspace uploads disk (tenants/{uuid}/tasks/). Types/sizes from workspacestorage.upload_policyviaWorkspaceUploadPolicy. Multi-file batches assert total bytes against remaining Storage quota before any object is written. Auth’d download/delete; bytes count toward Storage used. Production checklist: upload policy + task media readiness.
Permissions
config/tenant-permissions.php:
tasks.view | create | update | delete | assign | complete | change_due_dateRoutes use module:tasks then can:tasks.* / policies.
API (tenant)
Base: /api/tenant/v1 — full reference tenant-v1-tasks.md.
| Method | Path | Permission |
|---|---|---|
| GET | /tasks | view |
| GET | /tasks/stats | view |
| GET | /tasks/board | view |
| POST | /tasks | create |
| GET | /tasks/{task} | view |
| PUT | /tasks/{task} | update (+ change_due_date when changing due_at) |
| DELETE | /tasks/{task} | delete |
| POST | /tasks/{task}/assign | assign |
| POST | /tasks/{task}/complete | complete |
| POST | /tasks/{task}/reopen | complete |
| POST | /tasks/{task}/notes | update |
| GET | /tasks/{task}/timeline | view |
| GET | /task-tags | view |
| POST | /task-tags | create |
| PUT | /tasks/{task}/tags | update |
Auth login/me include modules: string[] for SPA gating.
Colored tags are create-only for MVP (no tag update/delete/reorder routes). Assign on store/update via tag_ids[] or PUT …/tags; filter list/board with tag_id.
Frontend
| Piece | Path |
|---|---|
| Page | src/pages/tasks/tasks-page.tsx (board default + list; list row menu Complete/Reopen via tasks.complete) |
| Form | task-form-dialog.tsx |
| Detail | task-detail-sheet.tsx (Comments + History; Comments use MentionComposer; board DnD auto-saves status on the list page) |
| Shared board | src/components/crm/kanban-board.tsx (per-column vertical scroll + contained horizontal scroll; titles stay fixed; touch pan on column lists) |
| Mentions UI | src/components/crm/mention-composer.tsx (shows @Name chips; emits @[Name](user:id); keep typing after pick; Backspace/Delete removes chips), src/lib/note-mentions.ts (formatNoteMentionsForDisplay in record pages + latest-note-follow-up.tsx list/board previews) |
| Notification registry | src/notifications/modules/tasks.ts (task.mentioned) |
| Service | taskService in src/api/services.ts |
| Nav | permission: tasks.view, module: 'tasks' |
Tests
bash
# Backend
php artisan test --compact tests/Feature/Tenant/Task/TaskTest.php
php artisan test --compact tests/Feature/Tenant/Task/TaskTagTest.php
php artisan test --compact tests/Unit/UtcInstantTest.php
# Frontend E2E
npm run test:e2e:tasksLogging
- Spatie
LogsActivityonTask(log nametasks) - Domain
task_activitiestimeline PlatformAuditServiceviaTaskEventSubscriber(includestask_tag_created,task_tags_synced)
Intentional differences from Leads
| Leads | Tasks |
|---|---|
| Stages + independent status | Status + priority enums (no stages) |
| Follow-ups | None |
| Assignment history table | Timeline only |
export / convert | complete / change_due_date |
| Board by stage | Board by status |
AI tools
Ask EloSync Task tools (get_task, confirmed status/assign/note writes, plus existing list reads and create_task) are registered in AIToolRegistry and confirmed via PendingAiActionService. See AI tools and AI Task triage production readiness.