Add IsFree subscription toggle to bypass all tier limitations
- 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>
This commit is contained in:
214
docs/TASK_KANBAN_LOGIC.md
Normal file
214
docs/TASK_KANBAN_LOGIC.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 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)
|
||||
```go
|
||||
if task.IsCancelled {
|
||||
cancelled = append(cancelled, task)
|
||||
continue
|
||||
}
|
||||
```
|
||||
- **Condition**: `is_cancelled = true`
|
||||
- **Actions Available**: `uncancel`, `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
|
||||
- **Actions Available**: `view`
|
||||
|
||||
### 3. In Progress
|
||||
```go
|
||||
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
|
||||
```go
|
||||
if task.DueDate.Before(now) {
|
||||
overdue = append(overdue, task)
|
||||
}
|
||||
```
|
||||
- **Condition**: `due_date < current_time`
|
||||
- **Actions Available**: `edit`, `cancel`, `mark_in_progress`
|
||||
|
||||
#### 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
|
||||
- **Actions Available**: `edit`, `complete`, `mark_in_progress`
|
||||
|
||||
#### Upcoming
|
||||
```go
|
||||
upcoming = append(upcoming, task)
|
||||
```
|
||||
- **Condition**: `due_date >= (current_time + days_threshold)` OR `due_date IS NULL`
|
||||
- **Actions Available**: `edit`, `cancel`
|
||||
|
||||
## Column Metadata
|
||||
|
||||
Each column includes metadata for the mobile clients:
|
||||
|
||||
```go
|
||||
{
|
||||
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 `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", "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.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`
|
||||
Reference in New Issue
Block a user