Tasks Models - Task Management & Delivery Scheduling
File: api/nextango/apps/tasks/models.py
Purpose: Store-level task management and delivery scheduling
Database Tables: tasks_task, tasks_deliveryschedule
Overview
The Tasks domain provides lightweight task management for store operations and delivery coordination. Tasks are assigned to employees, tracked by status, and can be prioritized. DeliverySchedule tracks vendor deliveries with scheduled times and actual arrivals.
Key Features:
- Store-specific task assignment and tracking
- Priority-based task organization (low, medium, high)
- Task lifecycle management (pending → in_progress → completed)
- Delivery scheduling with vendor tracking
- Sanity CMS integration (sanity_id field)
Models
Task
Purpose: Manage store-level tasks with assignment tracking and completion history
Source Code:
# From models.py:4-29
class Task(models.Model):
PRIORITY_CHOICES = [('low', 'Low'), ('medium', 'Medium'), ('high', 'High')]
TASK_STATUS_CHOICES = [('pending', 'Pending'), ('in_progress', 'In Progress'), ('completed', 'Completed')]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
task_id = models.CharField(max_length=100, unique=True)
sanity_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
assigned_to = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, blank=True)
store = models.ForeignKey('stores.Store', on_delete=models.PROTECT)
priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES)
status = models.CharField(max_length=20, choices=TASK_STATUS_CHOICES, default='pending')
due_date = models.DateField(null=True, blank=True)
completed_by = models.ForeignKey(
'users.User',
related_name='completed_tasks',
on_delete=models.SET_NULL,
null=True,
blank=True
)
completed_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Fields
Primary Key:
id(UUIDField, primary_key=True) - UUID primary key, auto-generated
Identifiers:
task_id(CharField, max_length=100, unique=True) - Business identifier for tasks- Format: Typically application-generated unique string
- Used in API URLs:
/api/tasks/{task_id}/
sanity_id(CharField, max_length=255, unique=True, nullable) - Sanity CMS reference- Format:
task_{uuid}or Sanity document ID - Optional: Can be null for tasks created in Django
- Enables bi-directional sync with Sanity CMS
- Format:
Core Fields:
title(CharField, max_length=255) - Short task title- Examples: "Restock dairy section", "Clean POS terminals", "Count cash drawer"
description(TextField, blank=True) - Detailed task description- Optional: Additional context or instructions
priority(CharField, max_length=20, choices) - Task priority- Choices:
low,medium,high - Used for sorting and filtering urgent tasks
- Choices:
status(CharField, max_length=20, choices, default='pending') - Task status- Choices:
pending,in_progress,completed - Lifecycle: pending → in_progress → completed
- Default:
pendingfor new tasks
- Choices:
Assignment & Tracking:
assigned_to(ForeignKey → User, SET_NULL, nullable) - Employee assigned to task- Nullable: Tasks can exist without assignment
- SET_NULL: Task persists if employee deleted
- Related name:
task_set(default)
completed_by(ForeignKey → User, SET_NULL, nullable, related_name='completed_tasks') - Employee who completed task- Nullable: Only set when task completed
- SET_NULL: Completion record persists even if employee deleted
- Tracks actual completer (may differ from assigned_to)
due_date(DateField, nullable) - Task deadline- Optional: Not all tasks have deadlines
- Used to calculate overdue status
completed_at(DateTimeField, nullable) - Completion timestamp- Auto-set when status changes to 'completed'
- Null for pending/in_progress tasks
Store Association:
store(ForeignKey → Store, PROTECT) - Store this task belongs to- Required: Every task must be associated with a store
- PROTECT: Cannot delete store with active tasks
- Ensures task isolation per store
Metadata:
created_at(DateTimeField, auto_now_add=True) - Task creation timestamp
Task Lifecycle
Status Transitions:
pending (new task)
│
├─ Employee accepts task
│
▼
in_progress (being worked on)
│
├─ Employee completes task
│
▼
completed (finished)
│
└─ completed_at timestamp set
└─ completed_by employee recorded
Business Rules:
- New tasks start with
status='pending' - In-progress tasks have
status='in_progress'but nocompleted_at - Completed tasks have
status='completed',completed_attimestamp, andcompleted_byuser - Overdue tasks have
due_date< today andstatus != 'completed' - Assignment is optional - tasks can be unassigned or reassigned
DeliverySchedule
Purpose: Track scheduled vendor deliveries to stores
Source Code:
# From models.py:31-43
class DeliverySchedule(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
delivery_id = models.CharField(max_length=100, unique=True)
vendor_name = models.CharField(max_length=255, help_text='Vendor or supplier name for this delivery')
vendor_reference = models.CharField(max_length=100, blank=True, help_text='External vendor reference or ID')
store = models.ForeignKey('stores.Store', on_delete=models.PROTECT)
scheduled_date = models.DateField()
scheduled_time = models.TimeField()
status = models.CharField(max_length=50, default='scheduled')
actual_arrival = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f'Delivery {self.delivery_id} from {self.vendor_name}'
Fields
Primary Key:
id(UUIDField, primary_key=True) - UUID primary key
Identifiers:
delivery_id(CharField, max_length=100, unique=True) - Business identifier- Used in API URLs and external references
- Unique across all deliveries
Vendor Information:
vendor_name(CharField, max_length=255) - Vendor/supplier name- Examples: "Sysco Foods", "Coca-Cola Bottling", "Local Produce Co."
- Required: Must know who's delivering
vendor_reference(CharField, max_length=100, blank=True) - External vendor ID- Optional: Vendor's own reference number for this delivery
- Examples: "PO-12345", "INV-2024-001"
Scheduling:
scheduled_date(DateField) - Planned delivery date- Required: When delivery is expected
scheduled_time(TimeField) - Planned delivery time- Required: Specific time slot
- Examples: "08:00:00" for 8 AM, "14:30:00" for 2:30 PM
status(CharField, max_length=50, default='scheduled') - Delivery status- Default:
scheduledfor new deliveries - Common values:
scheduled,in_transit,arrived,cancelled - Not enforced by choices (flexible for business evolution)
- Default:
actual_arrival(DateTimeField, nullable) - Actual arrival timestamp- Null until delivery arrives
- Set when delivery actually arrives at store
- Compare with scheduled_date/time to track punctuality
Store Association:
store(ForeignKey → Store, PROTECT) - Delivery destination- Required: Must know which store
- PROTECT: Cannot delete store with scheduled deliveries
Delivery Lifecycle
Typical Flow:
scheduled (new delivery scheduled)
│
├─ Vendor dispatches
│
▼
in_transit (on the way)
│
├─ Arrives at store
│
▼
arrived (delivery received)
│
└─ actual_arrival timestamp set
Business Rules:
- Scheduled deliveries have
status='scheduled'andactual_arrival=None - Arrived deliveries have
actual_arrivaltimestamp set - Late deliveries have
actual_arrival>scheduled_date + scheduled_time - Store association is immutable (PROTECT constraint)
Database Schema
tasks_task
CREATE TABLE tasks_task (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
task_id VARCHAR(100) UNIQUE NOT NULL,
sanity_id VARCHAR(255) UNIQUE NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
assigned_to_id UUID NULL REFERENCES users_user(id) ON DELETE SET NULL,
store_id UUID NOT NULL REFERENCES stores_store(id) ON DELETE PROTECT,
priority VARCHAR(20) NOT NULL CHECK (priority IN ('low', 'medium', 'high')),
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed')),
due_date DATE NULL,
completed_by_id UUID NULL REFERENCES users_user(id) ON DELETE SET NULL,
completed_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_tasks_task_status ON tasks_task(status);
CREATE INDEX idx_tasks_task_assigned_to ON tasks_task(assigned_to_id);
CREATE INDEX idx_tasks_task_store ON tasks_task(store_id);
CREATE INDEX idx_tasks_task_due_date ON tasks_task(due_date);
tasks_deliveryschedule
CREATE TABLE tasks_deliveryschedule (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
delivery_id VARCHAR(100) UNIQUE NOT NULL,
vendor_name VARCHAR(255) NOT NULL,
vendor_reference VARCHAR(100),
store_id UUID NOT NULL REFERENCES stores_store(id) ON DELETE PROTECT,
scheduled_date DATE NOT NULL,
scheduled_time TIME NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'scheduled',
actual_arrival TIMESTAMP NULL
);
-- Indexes
CREATE INDEX idx_delivery_schedule_store ON tasks_deliveryschedule(store_id);
CREATE INDEX idx_delivery_schedule_date ON tasks_deliveryschedule(scheduled_date);
CREATE INDEX idx_delivery_schedule_status ON tasks_deliveryschedule(status);
Key Patterns
Sanity Integration
Pattern: Tasks can originate from Sanity CMS or Django
# Webhook from Sanity creates task with sanity_id
task = Task.objects.create(
task_id='task_12345',
sanity_id='task_a1b2c3d4', # From Sanity document
title='Restock dairy section',
store=store,
priority='high',
status='pending'
)
# Django-created task (no sanity_id initially)
task = Task.objects.create(
task_id='task_67890',
title='Emergency cleanup',
store=store,
priority='high',
status='pending'
# sanity_id will be set during outbound sync
)
Task Assignment Pattern
Pattern: Assign → Work → Complete
# 1. Assign task to employee
task.assigned_to = employee
task.status = 'in_progress'
task.save(update_fields=['assigned_to', 'status'])
# 2. Employee works on task (status remains 'in_progress')
# 3. Complete task
from django.utils import timezone
task.status = 'completed'
task.completed_by = employee # May differ from assigned_to
task.completed_at = timezone.now()
task.save(update_fields=['status', 'completed_by', 'completed_at'])
Overdue Detection
Pattern: Calculate overdue status dynamically
from datetime import date
# In service or view logic
task = Task.objects.get(task_id='task_12345')
is_overdue = (
task.due_date and
task.due_date < date.today() and
task.status != 'completed'
)
# Used in TaskService.get_task_with_details()
task_details = {
'is_overdue': is_overdue,
# ... other fields
}
Store Isolation
Pattern: Tasks are always scoped to a single store
# Query tasks for specific store
store_tasks = Task.objects.filter(store=store)
# Cannot reassign task to different store (PROTECT constraint)
task.store = other_store # Requires creating new task instead
Performance Considerations
Query Optimization
Pattern: Use select_related() for foreign key lookups
# Good: Single query with JOINs
task = Task.objects.select_related(
'assigned_to', 'completed_by', 'store'
).get(task_id=task_id)
# Bad: N+1 queries
task = Task.objects.get(task_id=task_id)
assigned_name = task.assigned_to.name # Extra query
store_name = task.store.name # Extra query
Used In: TaskService.get_task_with_details() (services.py:22-24)
Index Usage
Critical Indexes:
task_id- Unique lookup for API retrievalstore_id- Filter tasks by store (common query)status- Filter by pending/completed tasksdue_date- Find overdue tasksassigned_to_id- Employee task lists
Integration Points
Sanity CMS Sync
Inbound (Sanity → Django):
Sanity Webhook → InboundWebhookView → TaskAdapter
│
├─ Creates/updates Task with sanity_id
├─ Resolves store reference
├─ Sets priority and status
│
▼
Task persisted in Django
Outbound (Django → Sanity):
Task saved → post_save signal → sync_task_to_sanity()
│
├─ Generates sanity_id if missing
├─ Formats payload for Sanity
├─ Sends to Sanity API
│
▼
Sanity document created/updated
Services Layer
Pattern: Business logic in TaskService
# View delegates to service
from .services import TaskService
task_details = TaskService.get_task_with_details(task_id)
# Returns enhanced data with related objects, computed fields
See: services.md for detailed service documentation
API Layer
Pattern: TaskViewSet with custom retrieve()
# GET /api/tasks/{task_id}/
# Uses TaskService for enhanced response
response = TaskViewSet.retrieve(request, task_id='task_12345')
# Returns full task details with is_overdue, related names, etc.
See: views.md for API endpoint documentation
Status Note
DeliverySchedule:
- Model exists and is complete
- Serializer implementation commented out (serializers.py:16-24)
- ViewSet implementation commented out (views.py:29-32)
- Likely Pending: API endpoint activation or feature flag
The delivery scheduling infrastructure is in place but may not be actively exposed via API endpoints yet.
Related Documentation
- services.md - TaskService business logic
- serializers.md - Task data validation
- views.md - Task API endpoints
- Integrations - Sanity webhook integration
- Users - Employee/User model
- Stores - Store model