f181525926
First-class reminders tied to the fundraising grid — foundation of the agreed reminders -> NL-search -> bot-mutations plan (keep LP data off third-party LLMs). - reminders table (migration 0006; logical FK to fundraising_investors.id + denormalized name), CRUD at /api/reminders (soft-delete; open/done/snoozed/ cancelled; assignee; source; source_row_id resolution) - read-only derived reminder_status grid column (overdue/due_soon/open), filterable; orphan reconciler cancels reminders when an investor leaves the grid - Reminders page, Dashboard "Reminders Due" card, daily-digest reminders section - per-investor last_activity_at recency rollup (shared block for the W2 NL query) - tests: test_reminders.py + digest reminders test (31/31 green, render-smoke green)
46 lines
3.1 KiB
SQL
46 lines
3.1 KiB
SQL
-- Reminders & follow-ups — a real tickler/task model tied to the fundraising grid.
|
|
--
|
|
-- ADDITIVE + REVERSIBLE (CLAUDE.md guardrail #3): one new table + indexes; nothing
|
|
-- existing is touched. Until now the only follow-up surfaces were the grid's binary
|
|
-- `follow_up` checkbox (no date, owner, or status) and communications.next_action_date
|
|
-- (tied to a single logged comm). This gives investors first-class reminders with a due
|
|
-- date, status lifecycle, assignee, and provenance — the foundation for "who needs a
|
|
-- follow-up?" queries, the daily digest's due/overdue section, and (later) bot-proposed
|
|
-- reminders behind the Matrix approval gate.
|
|
--
|
|
-- investor_id is a LOGICAL foreign key to fundraising_investors(id) — deliberately NOT a
|
|
-- declared SQLite FOREIGN KEY, matching opportunities.fundraising_investor_id (migration
|
|
-- 0005). fundraising_investors rows are upserted by source_row_id on every grid save with
|
|
-- a STABLE id (so the link survives saves), but a row dropped from the grid is DELETEd —
|
|
-- there is nothing to cascade, and reconcile_grid_reminders() cancels the orphans on the
|
|
-- next save (the pipeline reconciler's twin). investor_name is denormalized so a reminder
|
|
-- stays readable in history even after its grid row is gone. investor_id is nullable: a
|
|
-- reminder can be a standalone team task not tied to any investor.
|
|
--
|
|
-- contact_id is an optional logical FK to contacts(id) (the specific person). assignee_id
|
|
-- is a logical ref to users(id) (NULL = team-wide). created_by holds a users.id OR a
|
|
-- non-user sentinel ('bot'/'automation'), so it is plain TEXT with no FK.
|
|
CREATE TABLE IF NOT EXISTS reminders (
|
|
id TEXT PRIMARY KEY,
|
|
investor_id TEXT, -- logical FK -> fundraising_investors.id (NULL = standalone task)
|
|
investor_name TEXT, -- denormalized; survives grid-row deletion
|
|
contact_id TEXT, -- optional logical FK -> contacts.id
|
|
title TEXT NOT NULL,
|
|
details TEXT,
|
|
due_date TEXT, -- ISO date 'YYYY-MM-DD' (or datetime)
|
|
status TEXT NOT NULL DEFAULT 'open', -- open | done | snoozed | cancelled
|
|
snoozed_until TEXT,
|
|
assignee_id TEXT, -- logical ref -> users.id; NULL = team-wide
|
|
created_by TEXT, -- users.id, or 'bot' / 'automation'
|
|
source TEXT NOT NULL DEFAULT 'human', -- human | bot | automation
|
|
completed_at TEXT,
|
|
created_at TEXT DEFAULT (datetime('now')),
|
|
updated_at TEXT DEFAULT (datetime('now')),
|
|
deleted_at TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_investor ON reminders(investor_id) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_status ON reminders(status) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_due ON reminders(due_date) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_assignee ON reminders(assignee_id) WHERE deleted_at IS NULL;
|