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

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
  • status (CharField, max_length=20, choices, default='pending') - Task status
    • Choices: pending, in_progress, completed
    • Lifecycle: pending → in_progress → completed
    • Default: pending for new tasks

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:

  1. New tasks start with status='pending'
  2. In-progress tasks have status='in_progress' but no completed_at
  3. Completed tasks have status='completed', completed_at timestamp, and completed_by user
  4. Overdue tasks have due_date < today and status != 'completed'
  5. 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: scheduled for new deliveries
    • Common values: scheduled, in_transit, arrived, cancelled
    • Not enforced by choices (flexible for business evolution)
  • 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:

  1. Scheduled deliveries have status='scheduled' and actual_arrival=None
  2. Arrived deliveries have actual_arrival timestamp set
  3. Late deliveries have actual_arrival > scheduled_date + scheduled_time
  4. 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 retrieval
  • store_id - Filter tasks by store (common query)
  • status - Filter by pending/completed tasks
  • due_date - Find overdue tasks
  • assigned_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.


Was this page helpful?