- Remove Preload("Status") from worker handler and repositories
- Update seeds to use in_progress boolean instead of status_id
- Remove task_taskstatus table creation from lookup seeds
- Update documentation to reflect in_progress boolean pattern
Fixes notification worker error:
"Status: unsupported relations for schema Task"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
317 lines
14 KiB
Markdown
317 lines
14 KiB
Markdown
# Task Kanban Board Categorization Logic
|
|
|
|
This document describes how tasks are categorized into kanban columns for display in the Casera mobile app.
|
|
|
|
## Overview
|
|
|
|
Tasks are organized into 6 kanban columns based on their state and due date. The categorization logic is implemented in `internal/repositories/task_repo.go` in the `GetKanbanData` and `GetKanbanDataForMultipleResidences` functions.
|
|
|
|
## Columns Summary
|
|
|
|
| Column | Name | Color | Button Types | Description |
|
|
|--------|------|-------|--------------|-------------|
|
|
| 1 | **Overdue** | `#FF3B30` (Red) | edit, complete, cancel, mark_in_progress | Tasks past their due date |
|
|
| 2 | **Due Soon** | `#FF9500` (Orange) | edit, complete, cancel, mark_in_progress | Tasks due within the threshold (default 30 days) |
|
|
| 3 | **Upcoming** | `#007AFF` (Blue) | edit, complete, cancel, mark_in_progress | Tasks due beyond the threshold or with no due date |
|
|
| 4 | **In Progress** | `#5856D6` (Purple) | edit, complete, cancel | Tasks with status "In Progress" |
|
|
| 5 | **Completed** | `#34C759` (Green) | view | Tasks with at least one completion record |
|
|
| 6 | **Cancelled** | `#8E8E93` (Gray) | uncancel, delete | Tasks marked as cancelled |
|
|
|
|
## Categorization Flow
|
|
|
|
The categorization follows this priority order (first match wins):
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ START: Process Task │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────┐
|
|
│ Is task cancelled? │
|
|
│ (is_cancelled = true) │
|
|
└─────────────────────────┘
|
|
│ │
|
|
YES NO
|
|
│ │
|
|
▼ ▼
|
|
┌──────────┐ ┌─────────────────────────┐
|
|
│CANCELLED │ │ Has task completions? │
|
|
│ column │ │ (len(completions) > 0) │
|
|
└──────────┘ └─────────────────────────┘
|
|
│ │
|
|
YES NO
|
|
│ │
|
|
▼ ▼
|
|
┌──────────┐ ┌─────────────────────────┐
|
|
│COMPLETED │ │ Is status "In Progress"?│
|
|
│ column │ │ (status.name = "In...) │
|
|
└──────────┘ └─────────────────────────┘
|
|
│ │
|
|
YES NO
|
|
│ │
|
|
▼ ▼
|
|
┌───────────┐ ┌─────────────────┐
|
|
│IN PROGRESS│ │ Has due date? │
|
|
│ column │ └─────────────────┘
|
|
└───────────┘ │ │
|
|
YES NO
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────┐ ┌────────┐
|
|
│Check due date│ │UPCOMING│
|
|
└──────────────┘ │ column │
|
|
│ └────────┘
|
|
┌────────────┼────────────┐
|
|
▼ ▼ ▼
|
|
┌─────────────┐ ┌──────────┐ ┌────────────┐
|
|
│ due < now │ │due < now │ │due >= now +│
|
|
│ │ │+ threshold│ │ threshold │
|
|
└─────────────┘ └──────────┘ └────────────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌─────────┐ ┌──────────┐ ┌────────┐
|
|
│ OVERDUE │ │ DUE SOON │ │UPCOMING│
|
|
│ column │ │ column │ │ column │
|
|
└─────────┘ └──────────┘ └────────┘
|
|
```
|
|
|
|
## Detailed Rules
|
|
|
|
### 1. Cancelled (highest priority)
|
|
```go
|
|
if task.IsCancelled {
|
|
cancelled = append(cancelled, task)
|
|
continue
|
|
}
|
|
```
|
|
- **Condition**: `is_cancelled = true`
|
|
- **Button Types**: `uncancel`, `delete`
|
|
- **Rationale**: Cancelled tasks can be restored (uncancel) or permanently removed (delete)
|
|
|
|
### 2. Completed
|
|
```go
|
|
if len(task.Completions) > 0 {
|
|
completed = append(completed, task)
|
|
continue
|
|
}
|
|
```
|
|
- **Condition**: Task has at least one `TaskCompletion` record
|
|
- **Note**: A task is considered completed based on having completion records, NOT based on status
|
|
- **Button Types**: `view`
|
|
- **Rationale**: Completed tasks are read-only for historical purposes; users can view completion details and photos
|
|
|
|
### 3. In Progress
|
|
```go
|
|
if task.InProgress {
|
|
inProgress = append(inProgress, task)
|
|
continue
|
|
}
|
|
```
|
|
- **Condition**: Task's `in_progress` boolean is `true`
|
|
- **Button Types**: `edit`, `complete`, `cancel`
|
|
- **Rationale**: In-progress tasks can be edited, marked complete, or cancelled if work is abandoned
|
|
|
|
### 4. Due Date Based Categories (only for tasks not cancelled, completed, or in progress)
|
|
|
|
#### Overdue
|
|
```go
|
|
if task.DueDate.Before(now) {
|
|
overdue = append(overdue, task)
|
|
}
|
|
```
|
|
- **Condition**: `due_date < current_time`
|
|
- **Button Types**: `edit`, `complete`, `cancel`, `mark_in_progress`
|
|
- **Rationale**: Overdue tasks need urgent attention - can complete immediately, start working on them, edit the due date, or cancel if no longer needed
|
|
|
|
#### Due Soon
|
|
```go
|
|
if task.DueDate.Before(threshold) {
|
|
dueSoon = append(dueSoon, task)
|
|
}
|
|
```
|
|
- **Condition**: `current_time <= due_date < (current_time + days_threshold)`
|
|
- **Default threshold**: 30 days
|
|
- **Button Types**: `edit`, `complete`, `cancel`, `mark_in_progress`
|
|
- **Rationale**: Due soon tasks are approaching their deadline - users can complete early, start working, reschedule, or cancel
|
|
|
|
#### Upcoming
|
|
```go
|
|
upcoming = append(upcoming, task)
|
|
```
|
|
- **Condition**: `due_date >= (current_time + days_threshold)` OR `due_date IS NULL`
|
|
- **Button Types**: `edit`, `complete`, `cancel`, `mark_in_progress`
|
|
- **Rationale**: Future tasks may need to be completed early (ahead of schedule), started, rescheduled, or cancelled
|
|
|
|
## Button Types Reference
|
|
|
|
| Button Type | Action | Description |
|
|
|-------------|--------|-------------|
|
|
| `edit` | Update task | Modify task details (title, description, due date, category, etc.) |
|
|
| `complete` | Create completion | Mark task as done with optional notes and photos |
|
|
| `cancel` | Cancel task | Mark task as cancelled (soft delete, can be uncancelled) |
|
|
| `mark_in_progress` | Start work | Change status to "In Progress" to indicate work has begun |
|
|
| `view` | View details | View task and completion details (read-only) |
|
|
| `uncancel` | Restore task | Restore a cancelled task to its previous state |
|
|
| `delete` | Hard delete | Permanently remove task (only for cancelled tasks) |
|
|
|
|
## Column Metadata
|
|
|
|
Each column includes metadata for the mobile clients:
|
|
|
|
```go
|
|
{
|
|
Name: "overdue_tasks", // Internal identifier
|
|
DisplayName: "Overdue", // User-facing label
|
|
ButtonTypes: []string{"edit", "complete", "cancel", "mark_in_progress"},
|
|
Icons: map[string]string{ // Platform-specific icons
|
|
"ios": "exclamationmark.triangle",
|
|
"android": "Warning"
|
|
},
|
|
Color: "#FF3B30", // Display color
|
|
Tasks: []Task{...}, // Tasks in this column
|
|
Count: int, // Number of tasks
|
|
}
|
|
```
|
|
|
|
### Icons by Column
|
|
|
|
| Column | iOS Icon | Android Icon |
|
|
|--------|----------|--------------|
|
|
| Overdue | `exclamationmark.triangle` | `Warning` |
|
|
| Due Soon | `clock` | `Schedule` |
|
|
| Upcoming | `calendar` | `Event` |
|
|
| In Progress | `hammer` | `Build` |
|
|
| Completed | `checkmark.circle` | `CheckCircle` |
|
|
| Cancelled | `xmark.circle` | `Cancel` |
|
|
|
|
## Days Threshold Parameter
|
|
|
|
The `daysThreshold` parameter (default: 30) determines the boundary between "Due Soon" and "Upcoming":
|
|
|
|
- Tasks due within `daysThreshold` days from now → **Due Soon**
|
|
- Tasks due beyond `daysThreshold` days from now → **Upcoming**
|
|
|
|
This can be customized per request via query parameter.
|
|
|
|
## Sorting
|
|
|
|
Tasks within each column are sorted by:
|
|
1. `due_date ASC NULLS LAST` (earliest due date first, tasks without due dates at end)
|
|
2. `priority_id DESC` (higher priority first)
|
|
3. `created_at DESC` (newest first)
|
|
|
|
## Excluded Tasks
|
|
|
|
The following tasks are excluded from the kanban board entirely:
|
|
- **Archived tasks**: `is_archived = true`
|
|
|
|
## API Response Example
|
|
|
|
```json
|
|
{
|
|
"columns": [
|
|
{
|
|
"name": "overdue_tasks",
|
|
"display_name": "Overdue",
|
|
"button_types": ["edit", "complete", "cancel", "mark_in_progress"],
|
|
"icons": {
|
|
"ios": "exclamationmark.triangle",
|
|
"android": "Warning"
|
|
},
|
|
"color": "#FF3B30",
|
|
"tasks": [...],
|
|
"count": 2
|
|
},
|
|
{
|
|
"name": "due_soon_tasks",
|
|
"display_name": "Due Soon",
|
|
"button_types": ["edit", "complete", "cancel", "mark_in_progress"],
|
|
"icons": {
|
|
"ios": "clock",
|
|
"android": "Schedule"
|
|
},
|
|
"color": "#FF9500",
|
|
"tasks": [...],
|
|
"count": 5
|
|
},
|
|
{
|
|
"name": "upcoming_tasks",
|
|
"display_name": "Upcoming",
|
|
"button_types": ["edit", "complete", "cancel", "mark_in_progress"],
|
|
"icons": {
|
|
"ios": "calendar",
|
|
"android": "Event"
|
|
},
|
|
"color": "#007AFF",
|
|
"tasks": [...],
|
|
"count": 10
|
|
},
|
|
{
|
|
"name": "in_progress_tasks",
|
|
"display_name": "In Progress",
|
|
"button_types": ["edit", "complete", "cancel"],
|
|
"icons": {
|
|
"ios": "hammer",
|
|
"android": "Build"
|
|
},
|
|
"color": "#5856D6",
|
|
"tasks": [...],
|
|
"count": 3
|
|
},
|
|
{
|
|
"name": "completed_tasks",
|
|
"display_name": "Completed",
|
|
"button_types": ["view"],
|
|
"icons": {
|
|
"ios": "checkmark.circle",
|
|
"android": "CheckCircle"
|
|
},
|
|
"color": "#34C759",
|
|
"tasks": [...],
|
|
"count": 15
|
|
},
|
|
{
|
|
"name": "cancelled_tasks",
|
|
"display_name": "Cancelled",
|
|
"button_types": ["uncancel", "delete"],
|
|
"icons": {
|
|
"ios": "xmark.circle",
|
|
"android": "Cancel"
|
|
},
|
|
"color": "#8E8E93",
|
|
"tasks": [...],
|
|
"count": 1
|
|
}
|
|
],
|
|
"days_threshold": 30,
|
|
"residence_id": "123"
|
|
}
|
|
```
|
|
|
|
## Design Decisions
|
|
|
|
### Why "In Progress" doesn't have "mark_in_progress"
|
|
Tasks already in the "In Progress" column are already marked as in progress, so this action doesn't make sense.
|
|
|
|
### Why "Completed" only has "view"
|
|
Completed tasks represent historical records. Users can view them but shouldn't be able to uncomplete or modify them. If a task was completed incorrectly, the user should delete the completion record through the completion detail view.
|
|
|
|
### Why "Cancelled" has "delete" but others don't
|
|
Hard deletion is only available for cancelled tasks as a final cleanup action. For active tasks, users should cancel first (soft delete), then delete if truly needed. This prevents accidental data loss.
|
|
|
|
### Why all active columns have "cancel"
|
|
Users should always be able to abandon a task they no longer need, regardless of its due date or progress state.
|
|
|
|
## Code Location
|
|
|
|
- **Repository**: `internal/repositories/task_repo.go`
|
|
- `GetKanbanData()` - Single residence
|
|
- `GetKanbanDataForMultipleResidences()` - Multiple residences (all user's properties)
|
|
- **Service**: `internal/services/task_service.go`
|
|
- `ListTasks()` - All tasks for user
|
|
- `GetTasksByResidence()` - Tasks for specific residence
|
|
- **Response DTOs**: `internal/dto/responses/task.go`
|
|
- `KanbanBoardResponse`
|
|
- `KanbanColumnResponse`
|