feat: MedSchedule модуль — семейный медицинский помощник с Telegram/MAX ботом, n8n OCR и /med UI
This commit is contained in:
150
migrations/0059_medschedule_tables.sql
Normal file
150
migrations/0059_medschedule_tables.sql
Normal file
@@ -0,0 +1,150 @@
|
||||
-- MedSchedule — семейное медицинское расписание
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_family_groups (
|
||||
id SERIAL PRIMARY KEY,
|
||||
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
invite_code TEXT NOT NULL UNIQUE,
|
||||
created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_family_groups_org_idx ON med_family_groups(organization_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS med_family_groups_invite_idx ON med_family_groups(invite_code);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_family_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
family_id INTEGER NOT NULL REFERENCES med_family_groups(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
profile_name TEXT NOT NULL,
|
||||
profile_type TEXT NOT NULL DEFAULT 'person',
|
||||
date_of_birth DATE,
|
||||
gender TEXT,
|
||||
notes TEXT,
|
||||
telegram_chat_id TEXT,
|
||||
max_chat_id TEXT,
|
||||
role TEXT NOT NULL DEFAULT 'member',
|
||||
created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_family_members_family_idx ON med_family_members(family_id);
|
||||
CREATE INDEX IF NOT EXISTS med_family_members_user_idx ON med_family_members(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_medications (
|
||||
id SERIAL PRIMARY KEY,
|
||||
family_id INTEGER NOT NULL REFERENCES med_family_groups(id) ON DELETE CASCADE,
|
||||
member_id INTEGER NOT NULL REFERENCES med_family_members(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
dosage TEXT,
|
||||
form TEXT,
|
||||
schedule JSONB NOT NULL,
|
||||
instructions TEXT,
|
||||
color TEXT DEFAULT '#4CAF50',
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_medications_family_idx ON med_medications(family_id);
|
||||
CREATE INDEX IF NOT EXISTS med_medications_member_idx ON med_medications(member_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_intake_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
medication_id INTEGER NOT NULL REFERENCES med_medications(id) ON DELETE CASCADE,
|
||||
member_id INTEGER NOT NULL REFERENCES med_family_members(id) ON DELETE CASCADE,
|
||||
scheduled_at TIMESTAMP NOT NULL,
|
||||
taken_at TIMESTAMP,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
note TEXT,
|
||||
reminded_via TEXT[] DEFAULT '{}',
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_intake_logs_medication_idx ON med_intake_logs(medication_id);
|
||||
CREATE INDEX IF NOT EXISTS med_intake_logs_member_idx ON med_intake_logs(member_id);
|
||||
CREATE INDEX IF NOT EXISTS med_intake_logs_scheduled_idx ON med_intake_logs(scheduled_at);
|
||||
CREATE INDEX IF NOT EXISTS med_intake_logs_status_idx ON med_intake_logs(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_cabinet_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
family_id INTEGER NOT NULL REFERENCES med_family_groups(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
form TEXT,
|
||||
dosage TEXT,
|
||||
quantity REAL DEFAULT 0,
|
||||
unit TEXT DEFAULT 'шт',
|
||||
expiry_date DATE,
|
||||
indication TEXT,
|
||||
location TEXT,
|
||||
photo_url TEXT,
|
||||
added_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_cabinet_items_family_idx ON med_cabinet_items(family_id);
|
||||
CREATE INDEX IF NOT EXISTS med_cabinet_items_expiry_idx ON med_cabinet_items(expiry_date);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_cabinet_stock (
|
||||
id SERIAL PRIMARY KEY,
|
||||
cabinet_item_id INTEGER NOT NULL REFERENCES med_cabinet_items(id) ON DELETE CASCADE,
|
||||
change REAL NOT NULL,
|
||||
reason TEXT,
|
||||
note TEXT,
|
||||
changed_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_cabinet_stock_item_idx ON med_cabinet_stock(cabinet_item_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_medical_records (
|
||||
id SERIAL PRIMARY KEY,
|
||||
family_id INTEGER NOT NULL REFERENCES med_family_groups(id) ON DELETE CASCADE,
|
||||
member_id INTEGER NOT NULL REFERENCES med_family_members(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
record_type TEXT NOT NULL,
|
||||
record_date DATE,
|
||||
doctor_name TEXT,
|
||||
clinic_name TEXT,
|
||||
raw_text TEXT,
|
||||
ai_summary TEXT,
|
||||
ai_findings JSONB,
|
||||
file_urls TEXT[] DEFAULT '{}',
|
||||
added_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_medical_records_family_idx ON med_medical_records(family_id);
|
||||
CREATE INDEX IF NOT EXISTS med_medical_records_member_idx ON med_medical_records(member_id);
|
||||
CREATE INDEX IF NOT EXISTS med_medical_records_date_idx ON med_medical_records(record_date);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_daily_checkins (
|
||||
id SERIAL PRIMARY KEY,
|
||||
family_id INTEGER NOT NULL REFERENCES med_family_groups(id) ON DELETE CASCADE,
|
||||
member_id INTEGER REFERENCES med_family_members(id) ON DELETE CASCADE,
|
||||
check_in_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
question TEXT NOT NULL,
|
||||
answer_text TEXT,
|
||||
ai_summary TEXT,
|
||||
ai_alert TEXT,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS med_daily_checkins_family_idx ON med_daily_checkins(family_id);
|
||||
CREATE INDEX IF NOT EXISTS med_daily_checkins_member_idx ON med_daily_checkins(member_id);
|
||||
CREATE INDEX IF NOT EXISTS med_daily_checkins_date_idx ON med_daily_checkins(check_in_date);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS med_bot_bindings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
telegram_chat_id TEXT,
|
||||
max_chat_id TEXT,
|
||||
telegram_username TEXT,
|
||||
max_username TEXT,
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS med_bot_bindings_user_org_idx ON med_bot_bindings(user_id, organization_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS med_bot_bindings_telegram_idx ON med_bot_bindings(organization_id, telegram_chat_id) WHERE telegram_chat_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS med_bot_bindings_max_idx ON med_bot_bindings(organization_id, max_chat_id) WHERE max_chat_id IS NOT NULL;
|
||||
Reference in New Issue
Block a user