- Add IsFree boolean field to UserSubscription model - When IsFree is true, user sees limitations_enabled=false regardless of global setting - CheckLimit() bypasses all limit checks for IsFree users - Add admin endpoint GET /api/admin/subscriptions/user/:user_id - Add IsFree toggle to admin user detail page under Subscription card - Add database migration 004_subscription_is_free - Add integration tests for IsFree functionality - Add task kanban categorization documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
9.5 KiB
9.5 KiB
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
| Column | Name | Color | Description |
|---|---|---|---|
| 1 | Overdue | #FF3B30 (Red) |
Tasks past their due date |
| 2 | Due Soon | #FF9500 (Orange) |
Tasks due within the threshold (default 30 days) |
| 3 | Upcoming | #007AFF (Blue) |
Tasks due beyond the threshold or with no due date |
| 4 | In Progress | #5856D6 (Purple) |
Tasks with status "In Progress" |
| 5 | Completed | #34C759 (Green) |
Tasks with at least one completion record |
| 6 | Cancelled | #8E8E93 (Gray) |
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)
if task.IsCancelled {
cancelled = append(cancelled, task)
continue
}
- Condition:
is_cancelled = true - Actions Available:
uncancel,delete
2. Completed
if len(task.Completions) > 0 {
completed = append(completed, task)
continue
}
- Condition: Task has at least one
TaskCompletionrecord - Note: A task is considered completed based on having completion records, NOT based on status
- Actions Available:
view
3. In Progress
if task.Status != nil && task.Status.Name == "In Progress" {
inProgress = append(inProgress, task)
continue
}
- Condition: Task's status name is exactly
"In Progress" - Actions Available:
edit,complete
4. Due Date Based Categories (only for tasks not cancelled, completed, or in progress)
Overdue
if task.DueDate.Before(now) {
overdue = append(overdue, task)
}
- Condition:
due_date < current_time - Actions Available:
edit,cancel,mark_in_progress
Due Soon
if task.DueDate.Before(threshold) {
dueSoon = append(dueSoon, task)
}
- Condition:
current_time <= due_date < (current_time + days_threshold) - Default threshold: 30 days
- Actions Available:
edit,complete,mark_in_progress
Upcoming
upcoming = append(upcoming, task)
- Condition:
due_date >= (current_time + days_threshold)ORdue_date IS NULL - Actions Available:
edit,cancel
Column Metadata
Each column includes metadata for the mobile clients:
{
Name: "overdue_tasks", // Internal identifier
DisplayName: "Overdue", // User-facing label
ButtonTypes: []string{"edit", "cancel", "mark_in_progress"}, // Available actions
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
}
Days Threshold Parameter
The daysThreshold parameter (default: 30) determines the boundary between "Due Soon" and "Upcoming":
- Tasks due within
daysThresholddays from now → Due Soon - Tasks due beyond
daysThresholddays from now → Upcoming
This can be customized per request via query parameter.
Sorting
Tasks within each column are sorted by:
due_date ASC NULLS LAST(earliest due date first, tasks without due dates at end)priority_id DESC(higher priority first)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
{
"columns": [
{
"name": "overdue_tasks",
"display_name": "Overdue",
"button_types": ["edit", "cancel", "mark_in_progress"],
"icons": {
"ios": "exclamationmark.triangle",
"android": "Warning"
},
"color": "#FF3B30",
"tasks": [...],
"count": 2
},
// ... other columns
],
"days_threshold": 30,
"residence_id": "123"
}
Code Location
- Repository:
internal/repositories/task_repo.goGetKanbanData()- Single residenceGetKanbanDataForMultipleResidences()- Multiple residences (all user's properties)
- Service:
internal/services/task_service.goListTasks()- All tasks for userGetTasksByResidence()- Tasks for specific residence
- Response DTOs:
internal/dto/responses/task.goKanbanBoardResponseKanbanColumnResponse