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_idon the item. This allows the workspace UI to show a mic badge and play the original audio in context. - Reminder timezone —
remind_atis 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_assignedindex makes this fast. Without it, a full table scan on workspace_items is painful.