- apps/client (11 models): Client, ClientContact, ClientRequirement, ClientSchoolPreference, ClientFollowLog (partitioned), ClientFollowLogAttachment, ClientViewing, ClientPropertyMatch, ClientStatusLog, ClientFavoriteFolder, ClientFolderItem - apps/client/0002 RunSQL: PARTITION BY RANGE(created_at) for client_follow_logs + monthly partitions + default; triggers update_client_last_follow + update_client_viewing_progress; partial unique index on client_no WHERE deleted_at IS NULL - apps/setting (4 models): LookupGroup, LookupItem, TenantSetting, FieldRequirementRule (tenant schema only per spec) manage.py check green; all 9 Phase 2 apps complete.
100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
from django.db import migrations
|
|
|
|
CREATE_CLIENT_FOLLOW_LOGS = """
|
|
CREATE TABLE client_follow_logs (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
log_type VARCHAR(30) NOT NULL
|
|
CHECK (log_type IN ('written','modified','sensitive_view',
|
|
'other','system')),
|
|
purpose VARCHAR(50),
|
|
content TEXT,
|
|
log_tag VARCHAR(50),
|
|
change_detail JSONB,
|
|
is_public BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_deletable BOOLEAN NOT NULL DEFAULT TRUE,
|
|
operator_id UUID REFERENCES staff(id) ON DELETE SET NULL,
|
|
operator_snapshot JSONB,
|
|
deleted_at TIMESTAMPTZ,
|
|
PRIMARY KEY (id, created_at)
|
|
) PARTITION BY RANGE (created_at);
|
|
|
|
CREATE TABLE client_follow_logs_2026_04 PARTITION OF client_follow_logs
|
|
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
|
|
CREATE TABLE client_follow_logs_2026_05 PARTITION OF client_follow_logs
|
|
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
|
|
CREATE TABLE client_follow_logs_default PARTITION OF client_follow_logs DEFAULT;
|
|
|
|
CREATE INDEX idx_cfl_client_time ON client_follow_logs(client_id, created_at DESC)
|
|
WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_cfl_type ON client_follow_logs(client_id, log_type, created_at DESC)
|
|
WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_cfl_operator ON client_follow_logs(operator_id, created_at DESC)
|
|
WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_cfl_sensitive ON client_follow_logs(client_id, created_at DESC)
|
|
WHERE log_type = 'sensitive_view';
|
|
"""
|
|
|
|
DROP_CLIENT_FOLLOW_LOGS = "DROP TABLE IF EXISTS client_follow_logs CASCADE;"
|
|
|
|
CREATE_TRIGGERS = """
|
|
CREATE OR REPLACE FUNCTION update_client_last_follow()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.log_type = 'written' THEN
|
|
UPDATE clients
|
|
SET last_follow_at = NEW.created_at,
|
|
last_active_at = NEW.created_at,
|
|
updated_at = NOW()
|
|
WHERE id = NEW.client_id;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_client_last_follow
|
|
AFTER INSERT ON client_follow_logs
|
|
FOR EACH ROW EXECUTE FUNCTION update_client_last_follow();
|
|
|
|
CREATE OR REPLACE FUNCTION update_client_viewing_progress()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE clients
|
|
SET updated_at = NOW()
|
|
WHERE id = NEW.client_id;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_client_viewing_progress
|
|
AFTER INSERT ON client_viewings
|
|
FOR EACH ROW EXECUTE FUNCTION update_client_viewing_progress();
|
|
"""
|
|
|
|
DROP_TRIGGERS = """
|
|
DROP TRIGGER IF EXISTS trg_client_viewing_progress ON client_viewings;
|
|
DROP FUNCTION IF EXISTS update_client_viewing_progress();
|
|
DROP TRIGGER IF EXISTS trg_client_last_follow ON client_follow_logs;
|
|
DROP FUNCTION IF EXISTS update_client_last_follow();
|
|
"""
|
|
|
|
CREATE_UNIQUE_CLIENT_NO = """
|
|
CREATE UNIQUE INDEX idx_clients_client_no_active ON clients(client_no)
|
|
WHERE deleted_at IS NULL;
|
|
"""
|
|
|
|
DROP_UNIQUE_CLIENT_NO = "DROP INDEX IF EXISTS idx_clients_client_no_active;"
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
dependencies = [
|
|
("fonrey_client", "0001_initial"),
|
|
]
|
|
|
|
operations = [
|
|
migrations.RunSQL(CREATE_CLIENT_FOLLOW_LOGS, reverse_sql=DROP_CLIENT_FOLLOW_LOGS),
|
|
migrations.RunSQL(CREATE_TRIGGERS, reverse_sql=DROP_TRIGGERS),
|
|
migrations.RunSQL(CREATE_UNIQUE_CLIENT_NO, reverse_sql=DROP_UNIQUE_CLIENT_NO),
|
|
]
|