Series 5 — Part 7 of 7

Lawyers operate across matters, hearings, and client communications simultaneously. A unified workspace — notes, tasks, and reminders in one view — reduces the cognitive overhead of context-switching. This article covers the workspace schema, assignment workflow, reminder scheduling, and voice note linkage.

Unified Workspace Schema

CREATE TABLE workspace_items (
  id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  team_id         INT UNSIGNED NOT NULL,
  item_type       ENUM('note','task','reminder') NOT NULL,
  title           VARCHAR(500) NOT NULL,
  body            TEXT DEFAULT NULL,
  status          ENUM('open','in_progress','completed','archived') NOT NULL DEFAULT 'open',
  priority        ENUM('low','medium','high','urgent') NOT NULL DEFAULT 'medium',
  -- Source linkage
  source_type     ENUM('matter','hearing','voice_note','message','manual') DEFAULT 'manual',
  source_id       BIGINT UNSIGNED DEFAULT NULL,
  -- Assignment
  created_by      INT UNSIGNED NOT NULL,
  assigned_to     INT UNSIGNED DEFAULT NULL,
  -- Reminder scheduling
  remind_at       DATETIME DEFAULT NULL,
  reminded        TINYINT(1) NOT NULL DEFAULT 0,
  -- Voice note linkage
  wa_message_id   VARCHAR(255) DEFAULT NULL,
  transcript      TEXT DEFAULT NULL,
  created_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  deleted_at      DATETIME DEFAULT NULL,
  INDEX idx_team_assigned (team_id, assigned_to, status),
  INDEX idx_team_type_status (team_id, item_type, status),
  INDEX idx_remind_at (remind_at, reminded)
);

Assignment Workflow

class WorkspaceService
{
    public function assignItem(int $itemId, int $assigneeId, int $teamId, PDO $pdo): void
    {
        // Verify assignee is in the same team
        $stmt = $pdo->prepare('SELECT id FROM users WHERE id = ? AND team_id = ? AND is_active = 1');
        $stmt->execute([$assigneeId, $teamId]);
        if (!$stmt->fetch()) {
            throw new \InvalidArgumentException('Assignee not found in team.');
        }

        $pdo->prepare(
            'UPDATE workspace_items SET assigned_to = ?, updated_at = NOW()
             WHERE id = ? AND team_id = ? AND deleted_at IS NULL'
        )->execute([$assigneeId, $itemId, $teamId]);

        // Log to audit trail
        $this->auditLogger->log('workspace_item.assigned', 'workspace_item', $itemId,
            null, ['assigned_to' => $assigneeId]);
    }
}

Reminder Scheduling with Cron

// cron: * * * * * php /var/www/the legal SaaS platform/artisan reminders:dispatch
class RemindersDispatch
{
    public function handle(PDO $pdo, NotificationService $notify): void
    {
        $stmt = $pdo->prepare(
            'SELECT wi.*, u.email, u.full_name
             FROM workspace_items wi
             JOIN users u ON wi.assigned_to = u.id
             WHERE wi.remind_at <= NOW()
               AND wi.reminded = 0
               AND wi.deleted_at IS NULL
               AND wi.status NOT IN (\'completed\', \'archived\')'
        );
        $stmt->execute();

        foreach ($stmt->fetchAll() as $item) {
            $notify->sendReminder($item);
            $pdo->prepare('UPDATE workspace_items SET reminded = 1 WHERE id = ?')
                ->execute([$item['id']]);
        }
    }
}

What to Watch For

  • Voice note linkage — When a WhatsApp voice note creates a workspace item, store the wa_message_id on the item. This allows the workspace UI to show a mic badge and play the original audio in context.
  • Reminder timezoneremind_at is stored in UTC. The user sets the reminder in their local timezone. Convert at input time, not at dispatch time.
  • Cross-matter search — Lawyers frequently search "all open tasks assigned to me across all matters." The idx_team_assigned index makes this fast. Without it, a full table scan on workspace_items is painful.