add dukcapil
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
-- Rollback: Drop users table
|
||||
DROP TABLE IF EXISTS users;
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Example migration: Create users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(255) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
CREATE INDEX idx_users_username ON users(username);
|
||||
@@ -1,14 +0,0 @@
|
||||
DROP TABLE IF EXISTS position_roles;
|
||||
DROP TABLE IF EXISTS user_position;
|
||||
DROP TABLE IF EXISTS user_department;
|
||||
DROP TABLE IF EXISTS positions;
|
||||
DROP TABLE IF EXISTS departments;
|
||||
|
||||
DROP TABLE IF EXISTS role_permissions;
|
||||
DROP TABLE IF EXISTS user_role;
|
||||
DROP TABLE IF EXISTS permissions;
|
||||
DROP TABLE IF EXISTS roles;
|
||||
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP FUNCTION IF EXISTS set_updated_at() CASCADE;
|
||||
@@ -1,146 +0,0 @@
|
||||
-- ESLOGAD Core Init (Users, Roles, Permissions, Departments, Positions)
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
CREATE EXTENSION IF NOT EXISTS ltree;
|
||||
|
||||
-- Helper to auto-update updated_at
|
||||
CREATE OR REPLACE FUNCTION set_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- =======================
|
||||
-- USERS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
email VARCHAR(255) UNIQUE,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','inactive')),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
last_login_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
|
||||
CREATE TRIGGER trg_users_updated_at
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- ROLES & PERMISSIONS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL, -- e.g., SUPERADMIN
|
||||
code TEXT UNIQUE NOT NULL, -- e.g., superadmin
|
||||
description TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER trg_roles_updated_at
|
||||
BEFORE UPDATE ON roles
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT UNIQUE NOT NULL, -- e.g., letter.view
|
||||
description TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_permissions_updated_at
|
||||
BEFORE UPDATE ON permissions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
assigned_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
removed_at TIMESTAMP WITHOUT TIME ZONE
|
||||
)
|
||||
;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_user_role_active
|
||||
ON user_role(user_id, role_id) WHERE removed_at IS NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (role_id, permission_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS departments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
code TEXT UNIQUE,
|
||||
path LTREE UNIQUE NOT NULL, -- e.g., eslogad.aslog.waaslog_faskon_bmn
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_departments_path_gist
|
||||
ON departments USING GIST (path);
|
||||
|
||||
CREATE TRIGGER trg_departments_updated_at
|
||||
BEFORE UPDATE ON departments
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS positions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL, -- e.g., PABAN III/FASKON
|
||||
code TEXT UNIQUE, -- e.g., paban-III-faskon
|
||||
path LTREE UNIQUE NOT NULL, -- hierarchy within org chart
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_positions_path_gist
|
||||
ON positions USING GIST (path);
|
||||
|
||||
CREATE TRIGGER trg_positions_updated_at
|
||||
BEFORE UPDATE ON positions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_department (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE,
|
||||
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
assigned_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
removed_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_user_department_active
|
||||
ON user_department(user_id, department_id) WHERE removed_at IS NULL;
|
||||
|
||||
CREATE TRIGGER trg_user_department_updated_at
|
||||
BEFORE UPDATE ON user_department
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_position (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
position_id UUID NOT NULL REFERENCES positions(id) ON DELETE CASCADE,
|
||||
assigned_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
removed_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_user_position_active
|
||||
ON user_position(user_id, position_id) WHERE removed_at IS NULL;
|
||||
|
||||
CREATE TRIGGER trg_user_position_updated_at
|
||||
BEFORE UPDATE ON user_position
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS position_roles (
|
||||
position_id UUID NOT NULL REFERENCES positions(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (position_id, role_id)
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Rollback: Drop user profiles table
|
||||
DROP TABLE IF EXISTS user_profiles;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Example migration: Create user profiles table
|
||||
CREATE TABLE IF NOT EXISTS user_profiles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
avatar_url VARCHAR(500),
|
||||
bio TEXT,
|
||||
phone VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
|
||||
@@ -1,119 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =========================
|
||||
-- Positions (hierarchy)
|
||||
-- =========================
|
||||
-- Conventions:
|
||||
-- - superadmin is a separate root
|
||||
-- - eslogad.aslog is head; waaslog_* under aslog
|
||||
-- - paban_* under each waaslog_*; pabandya_* under its paban_*
|
||||
INSERT INTO departments (name, code, path) VALUES
|
||||
-- ROOTS
|
||||
('SUPERADMIN', 'superadmin', 'superadmin'),
|
||||
('ASLOG', 'aslog', 'eslogad.aslog'),
|
||||
|
||||
-- WAASLOG under ASLOG
|
||||
('WAASLOG RENBINMINLOG', 'waaslogrenbinminlog', 'eslogad.aslog.waaslog_renbinminlog'),
|
||||
('WAASLOG FASKON BMN', 'waaslogfaskonbmn', 'eslogad.aslog.waaslog_faskon_bmn'),
|
||||
('WAASLOG BEKPALKES', 'waaslogbekpalkes', 'eslogad.aslog.waaslog_bekpalkes'),
|
||||
|
||||
-- Other posts directly under ASLOG
|
||||
('KADISADAAD', 'kadisadaad', 'eslogad.aslog.kadisadaad'),
|
||||
('KATUUD', 'katuud', 'eslogad.aslog.katuud'),
|
||||
('SPRI', 'spri', 'eslogad.aslog.spri'),
|
||||
|
||||
-- PABAN under WAASLOG RENBINMINLOG
|
||||
('PABAN I/REN', 'paban-I-ren', 'eslogad.aslog.waaslog_renbinminlog.paban_I_ren'),
|
||||
('PABAN II/BINMINLOG', 'paban-II-binminlog', 'eslogad.aslog.waaslog_renbinminlog.paban_II_binminlog'),
|
||||
|
||||
-- PABAN under WAASLOG FASKON BMN
|
||||
('PABAN III/FASKON', 'paban-III-faskon', 'eslogad.aslog.waaslog_faskon_bmn.paban_III_faskon'),
|
||||
('PABAN IV/BMN', 'paban-iv-bmn', 'eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn'),
|
||||
|
||||
-- PABAN under WAASLOG BEKPALKES
|
||||
('PABAN V/BEK', 'paban-v-bek', 'eslogad.aslog.waaslog_bekpalkes.paban_V_bek'),
|
||||
('PABAN VI/ALPAL', 'paban-vi-alpal', 'eslogad.aslog.waaslog_bekpalkes.paban_VI_alpal'),
|
||||
('PABAN VII/KES', 'paban-vii-kes', 'eslogad.aslog.waaslog_bekpalkes.paban_VII_kes'),
|
||||
|
||||
-- PABANDYA under PABAN I/REN
|
||||
('PABANDYA 1 / RENPROGGAR', 'pabandya-1-renproggar', 'eslogad.aslog.waaslog_renbinminlog.paban_I_ren.pabandya_1_renproggar'),
|
||||
('PABANDYA 2 / DALWASGAR', 'pabandya-2-dalwasgar', 'eslogad.aslog.waaslog_renbinminlog.paban_I_ren.pabandya_2_dalwasgar'),
|
||||
('PABANDYA 3 / ANEVDATA', 'pabandya-3-anevdata', 'eslogad.aslog.waaslog_renbinminlog.paban_I_ren.pabandya_3_anevdata'),
|
||||
|
||||
-- PABANDYA under PABAN II/BINMINLOG
|
||||
('PABANDYA 1 / MINLOG', 'pabandya-1-minlog', 'eslogad.aslog.waaslog_renbinminlog.paban_II_binminlog.pabandya_1_minlog'),
|
||||
('PABANDYA 2 / HIBAHKOD', 'pabandya-2-hibahkod', 'eslogad.aslog.waaslog_renbinminlog.paban_II_binminlog.pabandya_2_hibahkod'),
|
||||
('PABANDYA 3 / PUSMAT', 'pabandya-3-pusmat', 'eslogad.aslog.waaslog_renbinminlog.paban_II_binminlog.pabandya_3_pusmat'),
|
||||
|
||||
-- PABANDYA under PABAN IV/BMN
|
||||
('PABANDYA 1 / TANAH', 'pabandya-1-tanah', 'eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn.pabandya_1_tanah'),
|
||||
('PABANDYA 2 / PANGKALAN KONSTRUKSI','pabandya-2-pangkalankonstruksi','eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn.pabandya_2_pangkalan_konstruksi'),
|
||||
('PABANDYA 3 / FASMATZI', 'pabandya-3-fasmatzi', 'eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn.pabandya_3_fasmatzi'),
|
||||
|
||||
-- PABANDYA under PABAN IV/BMN (AKUN group)
|
||||
('PABANDYA 1 / AKUN BB', 'pabandya-1-akunbb', 'eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn.pabandya_1_akun_bb'),
|
||||
('PABANDYA 2 / AKUN BTB', 'pabandya-2-akunbtb', 'eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn.pabandya_2_akun_btb'),
|
||||
('PABANDYA 3 / SISFO BMN DAN UAKPB-KP','pabandya-3-sisfo-bmn-uakpbkp','eslogad.aslog.waaslog_faskon_bmn.paban_IV_bmn.pabandya_3_sisfo_bmn_uakpb_kp'),
|
||||
|
||||
-- PABANDYA under PABAN III/FASKON
|
||||
('PABANDYA 1 / JATOPTIKMU', 'pabandya-1-jatoptikmu', 'eslogad.aslog.waaslog_faskon_bmn.paban_III_faskon.pabandya_1_jatoptikmu'),
|
||||
('PABANDYA 2 / RANTEKMEK', 'pabandya-2-rantekmek', 'eslogad.aslog.waaslog_faskon_bmn.paban_III_faskon.pabandya_2_rantekmek'),
|
||||
('PABANDYA 3 / ALHUBTOPPALSUS', 'pabandya-3-alhubtoppalsus', 'eslogad.aslog.waaslog_faskon_bmn.paban_III_faskon.pabandya_3_alhubtoppalsus'),
|
||||
('PABANDYA 4 / PESUD', 'pabandya-4-pesud', 'eslogad.aslog.waaslog_faskon_bmn.paban_III_faskon.pabandya_4_pesud'),
|
||||
|
||||
-- PABANDYA under PABAN VII/KES
|
||||
('PABANDYA 1 / BEKKES', 'pabandya-1-bekkes', 'eslogad.aslog.waaslog_bekpalkes.paban_VII_kes.pabandya_1_bekkes'),
|
||||
('PABANDYA 2 / ALKES', 'pabandya-2-alkes', 'eslogad.aslog.waaslog_bekpalkes.paban_VII_kes.pabandya_2_alkes')
|
||||
ON CONFLICT (code) DO UPDATE
|
||||
SET name = EXCLUDED.name,
|
||||
path = EXCLUDED.path,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
-- =========================
|
||||
-- SUPERADMIN role (minimal)
|
||||
-- =========================
|
||||
INSERT INTO roles (name, code, description) VALUES
|
||||
('SUPERADMIN', 'superadmin', 'Full system access and management'),
|
||||
('ADMIN', 'admin', 'Manage users, letters, and settings within their department'),
|
||||
('HEAD', 'head', 'Approve outgoing letters and manage dispositions in their department'),
|
||||
('STAFF', 'staff', 'Create letters, process assigned dispositions')
|
||||
ON CONFLICT (code) DO UPDATE
|
||||
SET name = EXCLUDED.name,
|
||||
description = EXCLUDED.description,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
-- =========================
|
||||
-- Users (seed 1 superadmin)
|
||||
-- =========================
|
||||
-- Replace the plaintext password as needed; pgcrypto hashes it with bcrypt.
|
||||
INSERT INTO users (username, password_hash, name, email, status, is_active)
|
||||
VALUES ('superadmin',
|
||||
crypt('ChangeMe!Super#123', gen_salt('bf')),
|
||||
'Super Admin',
|
||||
'superadmin@example.com',
|
||||
'active',
|
||||
TRUE)
|
||||
ON CONFLICT (username) DO UPDATE
|
||||
SET name = EXCLUDED.name,
|
||||
email = EXCLUDED.email,
|
||||
status = EXCLUDED.status,
|
||||
is_active = EXCLUDED.is_active,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
-- =========================
|
||||
-- Link: SUPERADMIN user ↔ role ↔ position
|
||||
-- =========================
|
||||
WITH u AS (SELECT id FROM users WHERE username = 'superadmin'),
|
||||
r AS (SELECT id FROM roles WHERE code = 'superadmin'),
|
||||
p AS (SELECT id FROM positions WHERE code = 'superadmin')
|
||||
INSERT INTO user_role (user_id, role_id)
|
||||
SELECT u.id, r.id FROM u, r
|
||||
ON CONFLICT (user_id, role_id) WHERE removed_at IS NULL DO NOTHING;
|
||||
|
||||
WITH u AS (SELECT id FROM users WHERE username = 'superadmin'),
|
||||
p AS (SELECT id FROM positions WHERE code = 'superadmin')
|
||||
INSERT INTO user_position (user_id, position_id)
|
||||
SELECT u.id, p.id FROM u, p
|
||||
ON CONFLICT (user_id, position_id) WHERE removed_at IS NULL DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,30 +0,0 @@
|
||||
INSERT INTO permissions (id, code, description, created_at, updated_at) VALUES
|
||||
-- Users
|
||||
(gen_random_uuid(), 'user.read', 'View user list and details', now(), now()),
|
||||
(gen_random_uuid(), 'user.create', 'Create new users', now(), now()),
|
||||
(gen_random_uuid(), 'user.update', 'Edit existing users', now(), now()),
|
||||
(gen_random_uuid(), 'user.delete', 'Delete users', now(), now()),
|
||||
|
||||
-- Roles
|
||||
(gen_random_uuid(), 'role.read', 'View roles', now(), now()),
|
||||
(gen_random_uuid(), 'role.create', 'Create new roles', now(), now()),
|
||||
(gen_random_uuid(), 'role.update', 'Edit existing roles', now(), now()),
|
||||
(gen_random_uuid(), 'role.delete', 'Delete roles', now(), now()),
|
||||
|
||||
-- Permissions
|
||||
(gen_random_uuid(), 'permission.read', 'View permissions', now(), now()),
|
||||
(gen_random_uuid(), 'permission.create', 'Create new permissions', now(), now()),
|
||||
(gen_random_uuid(), 'permission.update', 'Edit existing permissions', now(), now()),
|
||||
(gen_random_uuid(), 'permission.delete', 'Delete permissions', now(), now()),
|
||||
|
||||
-- Departments
|
||||
(gen_random_uuid(), 'department.read', 'View departments', now(), now()),
|
||||
(gen_random_uuid(), 'department.create', 'Create new departments', now(), now()),
|
||||
(gen_random_uuid(), 'department.update', 'Edit existing departments', now(), now()),
|
||||
(gen_random_uuid(), 'department.delete', 'Delete departments', now(), now()),
|
||||
|
||||
-- Positions
|
||||
(gen_random_uuid(), 'position.read', 'View positions', now(), now()),
|
||||
(gen_random_uuid(), 'position.create', 'Create new positions', now(), now()),
|
||||
(gen_random_uuid(), 'position.update', 'Edit existing positions', now(), now()),
|
||||
(gen_random_uuid(), 'position.delete', 'Delete positions', now(), now());
|
||||
@@ -1 +0,0 @@
|
||||
DROP TABLE IF EXISTS user_profiles;
|
||||
@@ -1,30 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_profiles (
|
||||
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
full_name VARCHAR(150) NOT NULL,
|
||||
display_name VARCHAR(100),
|
||||
phone VARCHAR(50),
|
||||
avatar_url TEXT,
|
||||
job_title VARCHAR(120),
|
||||
employee_no VARCHAR(60),
|
||||
bio TEXT,
|
||||
timezone VARCHAR(64) DEFAULT 'Asia/Jakarta',
|
||||
locale VARCHAR(16) DEFAULT 'id-ID',
|
||||
preferences JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
notification_prefs JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
last_seen_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_phone ON user_profiles(phone);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_employee_no ON user_profiles(employee_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_prefs_gin ON user_profiles USING GIN (preferences);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_notif_gin ON user_profiles USING GIN (notification_prefs);
|
||||
|
||||
CREATE TRIGGER trg_user_profiles_updated_at
|
||||
BEFORE UPDATE ON user_profiles
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
COMMIT;
|
||||
@@ -1,60 +0,0 @@
|
||||
-- =======================
|
||||
-- TITLES
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS titles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL, -- e.g., "Senior Software Engineer"
|
||||
code TEXT UNIQUE, -- e.g., "senior-software-engineer"
|
||||
description TEXT, -- optional: extra details
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE TRIGGER trg_titles_updated_at
|
||||
BEFORE UPDATE ON titles
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- Perwira Tinggi (High-ranking Officers)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Jenderal', 'jenderal', 'Pangkat tertinggi di TNI AD'),
|
||||
('Letnan Jenderal', 'letnan-jenderal', 'Pangkat tinggi di bawah Jenderal'),
|
||||
('Mayor Jenderal', 'mayor-jenderal', 'Pangkat tinggi di bawah Letnan Jenderal'),
|
||||
('Brigadir Jenderal', 'brigadir-jenderal', 'Pangkat tinggi di bawah Mayor Jenderal');
|
||||
|
||||
-- Perwira Menengah (Middle-ranking Officers)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Kolonel', 'kolonel', 'Pangkat perwira menengah tertinggi'),
|
||||
('Letnan Kolonel', 'letnan-kolonel', 'Pangkat perwira menengah di bawah Kolonel'),
|
||||
('Mayor', 'mayor', 'Pangkat perwira menengah di bawah Letnan Kolonel');
|
||||
|
||||
-- Perwira Pertama (Junior Officers)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Kapten', 'kapten', 'Pangkat perwira pertama tertinggi'),
|
||||
('Letnan Satu', 'letnan-satu', 'Pangkat perwira pertama di bawah Kapten'),
|
||||
('Letnan Dua', 'letnan-dua', 'Pangkat perwira pertama di bawah Letnan Satu');
|
||||
|
||||
-- Bintara Tinggi (Senior NCOs)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Pembantu Letnan Satu', 'pembantu-letnan-satu', 'Pangkat bintara tinggi tertinggi'),
|
||||
('Pembantu Letnan Dua', 'pembantu-letnan-dua', 'Pangkat bintara tinggi di bawah Pelda');
|
||||
|
||||
-- Bintara (NCOs)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Sersan Mayor', 'sersan-mayor', 'Pangkat bintara di bawah Pelda'),
|
||||
('Sersan Kepala', 'sersan-kepala', 'Pangkat bintara di bawah Serma'),
|
||||
('Sersan Satu', 'sersan-satu', 'Pangkat bintara di bawah Serka'),
|
||||
('Sersan Dua', 'sersan-dua', 'Pangkat bintara di bawah Sertu');
|
||||
|
||||
-- Tamtama Tinggi (Senior Enlisted)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Kopral Kepala', 'kopral-kepala', 'Pangkat tamtama tinggi tertinggi'),
|
||||
('Kopral Satu', 'kopral-satu', 'Pangkat tamtama tinggi di bawah Kopka'),
|
||||
('Kopral Dua', 'kopral-dua', 'Pangkat tamtama tinggi di bawah Koptu');
|
||||
|
||||
-- Tamtama (Enlisted)
|
||||
INSERT INTO titles (name, code, description) VALUES
|
||||
('Prajurit Kepala', 'prajurit-kepala', 'Pangkat tamtama di bawah Kopda'),
|
||||
('Prajurit Satu', 'prajurit-satu', 'Pangkat tamtama di bawah Prada'),
|
||||
('Prajurit Dua', 'prajurit-dua', 'Pangkat tamtama terendah');
|
||||
@@ -1,7 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS institutions;
|
||||
DROP TABLE IF EXISTS priorities;
|
||||
DROP TABLE IF EXISTS labels;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,52 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- LABELS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS labels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
color VARCHAR(16), -- HEX color code (e.g., #FF0000)
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_labels_updated_at
|
||||
BEFORE UPDATE ON labels
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- PRIORITIES
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS priorities (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
level INT NOT NULL,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_priorities_updated_at
|
||||
BEFORE UPDATE ON priorities
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- INSTITUTIONS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS institutions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type TEXT NOT NULL CHECK (type IN ('government','private','ngo','individual')),
|
||||
address TEXT,
|
||||
contact_person VARCHAR(255),
|
||||
phone VARCHAR(50),
|
||||
email VARCHAR(255),
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_institutions_updated_at
|
||||
BEFORE UPDATE ON institutions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
COMMIT;
|
||||
@@ -1,5 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS disposition_actions;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,23 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- DISPOSITION ACTIONS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS disposition_actions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
label TEXT NOT NULL,
|
||||
description TEXT,
|
||||
requires_note BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
group_name TEXT,
|
||||
sort_order INT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_disposition_actions_updated_at
|
||||
BEFORE UPDATE ON disposition_actions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
COMMIT;
|
||||
@@ -1,17 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS letter_incoming_activity_logs;
|
||||
DROP TABLE IF EXISTS letter_incoming_discussion_attachments;
|
||||
DROP TABLE IF EXISTS letter_incoming_discussions;
|
||||
DROP TABLE IF EXISTS letter_disposition_actions;
|
||||
DROP TABLE IF EXISTS disposition_notes;
|
||||
DROP TABLE IF EXISTS letter_incoming_dispositions_department;
|
||||
DROP TABLE IF EXISTS letter_incoming_dispositions;
|
||||
DROP TABLE IF EXISTS letter_incoming_attachments;
|
||||
DROP TABLE IF EXISTS letter_incoming_labels;
|
||||
DROP TABLE IF EXISTS letter_incoming_recipients;
|
||||
DROP TABLE IF EXISTS letters_incoming;
|
||||
|
||||
DROP SEQUENCE IF EXISTS letters_incoming_seq;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,189 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- SEQUENCE FOR LETTER NUMBER
|
||||
-- =======================
|
||||
CREATE SEQUENCE IF NOT EXISTS letters_incoming_seq;
|
||||
|
||||
-- =======================
|
||||
-- LETTERS INCOMING
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letters_incoming (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_number TEXT NOT NULL UNIQUE DEFAULT ('IN-' || lpad(nextval('letters_incoming_seq')::text, 8, '0')),
|
||||
reference_number TEXT,
|
||||
subject TEXT NOT NULL,
|
||||
description TEXT,
|
||||
priority_id UUID REFERENCES priorities(id) ON DELETE SET NULL,
|
||||
sender_institution_id UUID REFERENCES institutions(id) ON DELETE SET NULL,
|
||||
received_date DATE NOT NULL,
|
||||
due_date DATE,
|
||||
status TEXT NOT NULL DEFAULT 'new' CHECK (status IN ('new','in_progress','completed')),
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_status ON letters_incoming(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_received_date ON letters_incoming(received_date);
|
||||
|
||||
CREATE TRIGGER trg_letters_incoming_updated_at
|
||||
BEFORE UPDATE ON letters_incoming
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- LETTER INCOMING RECIPIENTS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_recipients (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||
recipient_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
recipient_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL DEFAULT 'new' CHECK (status IN ('new','read','completed')),
|
||||
read_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);I
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_letter ON letter_incoming_recipients(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER INCOMING LABELS (M:N)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_labels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||
label_id UUID NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (letter_id, label_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_labels_letter ON letter_incoming_labels(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER INCOMING ATTACHMENTS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||
file_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_attachments_letter ON letter_incoming_attachments(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER DISPOSITIONS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_dispositions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||
from_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
from_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||
to_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
to_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||
notes TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','read','rejected','completed')),
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
read_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_dispositions_letter ON letter_dispositions(letter_id);
|
||||
|
||||
CREATE TRIGGER trg_letter_dispositions_updated_at
|
||||
BEFORE UPDATE ON letter_dispositions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- DISPOSITION NOTES
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS disposition_notes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
disposition_id UUID NOT NULL REFERENCES letter_incoming_dispositions(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
note TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_disposition_notes_disposition ON disposition_notes(disposition_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER DISPOSITION ACTIONS (Selections)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_disposition_actions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
disposition_id UUID NOT NULL REFERENCES letter_incoming_dispositions(id) ON DELETE CASCADE,
|
||||
action_id UUID NOT NULL REFERENCES disposition_actions(id) ON DELETE RESTRICT,
|
||||
note TEXT,
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (disposition_id, action_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_disposition_actions_disposition ON letter_disposition_actions(disposition_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER INCOMING DISCUSSIONS (Threaded)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_discussions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||
parent_id UUID REFERENCES letter_incoming_discussions(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
message TEXT NOT NULL,
|
||||
mentions JSONB,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
edited_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_discussions_letter ON letter_incoming_discussions(letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_discussions_parent ON letter_incoming_discussions(parent_id);
|
||||
|
||||
CREATE TRIGGER trg_letter_incoming_discussions_updated_at
|
||||
BEFORE UPDATE ON letter_incoming_discussions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- LETTER INCOMING DISCUSSION ATTACHMENTS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_discussion_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
discussion_id UUID NOT NULL REFERENCES letter_incoming_discussions(id) ON DELETE CASCADE,
|
||||
file_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_discussion_attachments_discussion ON letter_incoming_discussion_attachments(discussion_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER INCOMING ACTIVITY LOGS (Immutable)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_activity_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||
action_type TEXT NOT NULL,
|
||||
actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
actor_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||
target_type TEXT,
|
||||
target_id UUID,
|
||||
from_status TEXT,
|
||||
to_status TEXT,
|
||||
context JSONB,
|
||||
occurred_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_activity_logs_letter ON letter_incoming_activity_logs(letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_activity_logs_action ON letter_incoming_activity_logs(action_type);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,5 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS disposition_routes;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,27 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- DISPOSITION ROUTES
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS disposition_routes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
from_department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE,
|
||||
to_department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
allowed_actions JSONB,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_disposition_routes_from_dept ON disposition_routes(from_department_id);
|
||||
|
||||
-- Prevent duplicate active routes from -> to
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_disposition_routes_active
|
||||
ON disposition_routes(from_department_id, to_department_id)
|
||||
WHERE is_active = TRUE;
|
||||
|
||||
CREATE TRIGGER trg_disposition_routes_updated_at
|
||||
BEFORE UPDATE ON disposition_routes
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
COMMIT;
|
||||
@@ -1,4 +0,0 @@
|
||||
BEGIN;
|
||||
DROP TRIGGER IF EXISTS trg_app_settings_updated_at ON app_settings;
|
||||
DROP TABLE IF EXISTS app_settings;
|
||||
COMMIT;
|
||||
@@ -1,21 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_app_settings_updated_at
|
||||
BEFORE UPDATE ON app_settings
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
INSERT INTO app_settings(key, value)
|
||||
VALUES
|
||||
('INCOMING_LETTER_PREFIX', '{"value": "ESLI"}'::jsonb),
|
||||
('INCOMING_LETTER_SEQUENCE', '{"value": 0}'::jsonb),
|
||||
('INCOMING_LETTER_RECIPIENTS', '{"department_codes": ["aslog"]}'::jsonb)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,41 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- DROP NEW ASSOCIATION TABLE
|
||||
-- =======================
|
||||
DROP TABLE IF EXISTS letter_incoming_dispositions_department;
|
||||
|
||||
-- =======================
|
||||
-- RESTORE LETTER DISPOSITIONS TABLE STRUCTURE
|
||||
-- =======================
|
||||
-- Add back the columns that were removed
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS from_user_id UUID REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS to_user_id UUID REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS to_department_id UUID REFERENCES departments(id) ON DELETE SET NULL;
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','read','rejected','completed'));
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS completed_at TIMESTAMP WITHOUT TIME ZONE;
|
||||
|
||||
-- Rename department_id back to from_department_id
|
||||
ALTER TABLE letter_incoming_dispositions RENAME COLUMN department_id TO from_department_id;
|
||||
|
||||
-- =======================
|
||||
-- RESTORE TRIGGERS AND INDEXES
|
||||
-- =======================
|
||||
-- Drop new trigger
|
||||
DROP TRIGGER IF EXISTS trg_letter_incoming_dispositions_updated_at ON letter_incoming_dispositions;
|
||||
|
||||
-- Restore old trigger
|
||||
CREATE TRIGGER trg_letter_dispositions_updated_at
|
||||
BEFORE UPDATE ON letter_incoming_dispositions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- Restore index names
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_dispositions_letter;
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_dispositions_letter ON letter_incoming_dispositions(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- RENAME TABLE BACK
|
||||
-- =======================
|
||||
ALTER TABLE letter_incoming_dispositions RENAME TO letter_dispositions;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,54 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- RENAME LETTER DISPOSITIONS TABLE
|
||||
-- =======================
|
||||
ALTER TABLE letter_dispositions RENAME TO letter_incoming_dispositions;
|
||||
|
||||
-- =======================
|
||||
-- MODIFY LETTER INCOMING DISPOSITIONS TABLE STRUCTURE
|
||||
-- =======================
|
||||
-- Drop existing columns that are not needed
|
||||
ALTER TABLE letter_incoming_dispositions DROP COLUMN IF EXISTS from_user_id;
|
||||
ALTER TABLE letter_incoming_dispositions DROP COLUMN IF EXISTS to_user_id;
|
||||
ALTER TABLE letter_incoming_dispositions DROP COLUMN IF EXISTS to_department_id;
|
||||
ALTER TABLE letter_incoming_dispositions DROP COLUMN IF EXISTS status;
|
||||
ALTER TABLE letter_incoming_dispositions DROP COLUMN IF EXISTS completed_at;
|
||||
|
||||
-- Rename from_department_id to department_id
|
||||
ALTER TABLE letter_incoming_dispositions RENAME COLUMN from_department_id TO department_id;
|
||||
|
||||
-- Add missing columns if they don't exist
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS read_at TIMESTAMP WITHOUT TIME ZONE;
|
||||
ALTER TABLE letter_incoming_dispositions ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- =======================
|
||||
-- CREATE LETTER INCOMING DISPOSITIONS DEPARTMENT ASSOCIATION TABLE
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_incoming_dispositions_department (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_incoming_disposition_id UUID NOT NULL REFERENCES letter_incoming_dispositions(id) ON DELETE CASCADE,
|
||||
department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (letter_incoming_disposition_id, department_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_dispositions_department_disposition ON letter_incoming_dispositions_department(letter_incoming_disposition_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_dispositions_department_dept ON letter_incoming_dispositions_department(department_id);
|
||||
|
||||
-- =======================
|
||||
-- UPDATE TRIGGERS AND INDEXES
|
||||
-- =======================
|
||||
-- Drop old trigger
|
||||
DROP TRIGGER IF EXISTS trg_letter_dispositions_updated_at ON letter_incoming_dispositions;
|
||||
|
||||
-- Create new trigger
|
||||
CREATE TRIGGER trg_letter_incoming_dispositions_updated_at
|
||||
BEFORE UPDATE ON letter_incoming_dispositions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- Update index names
|
||||
DROP INDEX IF EXISTS idx_letter_dispositions_letter;
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_dispositions_letter ON letter_incoming_dispositions(letter_id);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,24 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop triggers first
|
||||
DROP TRIGGER IF EXISTS trg_letter_outgoing_discussions_updated_at ON letter_outgoing_discussions;
|
||||
DROP TRIGGER IF EXISTS trg_approval_flow_steps_updated_at ON approval_flow_steps;
|
||||
DROP TRIGGER IF EXISTS trg_approval_flows_updated_at ON approval_flows;
|
||||
DROP TRIGGER IF EXISTS trg_letters_outgoing_updated_at ON letters_outgoing;
|
||||
|
||||
-- Drop tables in reverse order (due to foreign key constraints)
|
||||
DROP TABLE IF EXISTS letter_outgoing_activity_logs;
|
||||
DROP TABLE IF EXISTS letter_outgoing_discussion_attachments;
|
||||
DROP TABLE IF EXISTS letter_outgoing_discussions;
|
||||
DROP TABLE IF EXISTS letter_outgoing_approvals;
|
||||
DROP TABLE IF EXISTS letter_outgoing_attachments;
|
||||
DROP TABLE IF EXISTS letter_outgoing_labels;
|
||||
DROP TABLE IF EXISTS letter_outgoing_recipients;
|
||||
DROP TABLE IF EXISTS letters_outgoing;
|
||||
DROP TABLE IF EXISTS approval_flow_steps;
|
||||
DROP TABLE IF EXISTS approval_flows;
|
||||
|
||||
-- Drop sequence
|
||||
DROP SEQUENCE IF EXISTS letters_outgoing_seq;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,199 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- =======================
|
||||
-- SEQUENCE FOR LETTER NUMBER
|
||||
-- =======================
|
||||
CREATE SEQUENCE IF NOT EXISTS letters_outgoing_seq;
|
||||
|
||||
-- =======================
|
||||
-- APPROVAL FLOWS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS approval_flows (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
department_id UUID NOT NULL REFERENCES departments(id) ON DELETE RESTRICT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_flows_department ON approval_flows(department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_flows_active ON approval_flows(is_active);
|
||||
|
||||
CREATE TRIGGER trg_approval_flows_updated_at
|
||||
BEFORE UPDATE ON approval_flows
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- APPROVAL FLOW STEPS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS approval_flow_steps (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
flow_id UUID NOT NULL REFERENCES approval_flows(id) ON DELETE CASCADE,
|
||||
step_order INT NOT NULL,
|
||||
parallel_group INT DEFAULT 1,
|
||||
approver_role_id UUID REFERENCES roles(id) ON DELETE SET NULL,
|
||||
approver_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
required BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(flow_id, step_order, parallel_group, approver_role_id, approver_user_id),
|
||||
CHECK ((approver_role_id IS NOT NULL) OR (approver_user_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_flow_steps_flow ON approval_flow_steps(flow_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_flow_steps_order ON approval_flow_steps(flow_id, step_order);
|
||||
|
||||
CREATE TRIGGER trg_approval_flow_steps_updated_at
|
||||
BEFORE UPDATE ON approval_flow_steps
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- LETTERS OUTGOING
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letters_outgoing (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_number TEXT NOT NULL UNIQUE DEFAULT ('OUT-' || lpad(nextval('letters_outgoing_seq')::text, 8, '0')),
|
||||
reference_number TEXT,
|
||||
subject TEXT NOT NULL,
|
||||
description TEXT,
|
||||
priority_id UUID REFERENCES priorities(id) ON DELETE SET NULL,
|
||||
receiver_institution_id UUID REFERENCES institutions(id) ON DELETE SET NULL,
|
||||
issue_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','pending_approval','approved','sent','archived')),
|
||||
approval_flow_id UUID REFERENCES approval_flows(id) ON DELETE SET NULL,
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_status ON letters_outgoing(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_issue_date ON letters_outgoing(issue_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_approval_flow ON letters_outgoing(approval_flow_id);
|
||||
|
||||
CREATE TRIGGER trg_letters_outgoing_updated_at
|
||||
BEFORE UPDATE ON letters_outgoing
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING RECIPIENTS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_recipients (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
recipient_name TEXT NOT NULL,
|
||||
recipient_email TEXT,
|
||||
recipient_position TEXT,
|
||||
recipient_institution TEXT,
|
||||
is_primary BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_letter ON letter_outgoing_recipients(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING LABELS (M:N)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_labels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
label_id UUID NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (letter_id, label_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_labels_letter ON letter_outgoing_labels(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING ATTACHMENTS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
file_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_attachments_letter ON letter_outgoing_attachments(letter_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING APPROVALS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_approvals (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
step_id UUID NOT NULL REFERENCES approval_flow_steps(id) ON DELETE CASCADE,
|
||||
approver_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','approved','rejected')),
|
||||
remarks TEXT,
|
||||
acted_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_letter ON letter_outgoing_approvals(letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_step ON letter_outgoing_approvals(step_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_status ON letter_outgoing_approvals(status);
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING DISCUSSIONS (Threaded)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_discussions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
parent_id UUID REFERENCES letter_outgoing_discussions(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
message TEXT NOT NULL,
|
||||
mentions JSONB,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
edited_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_discussions_letter ON letter_outgoing_discussions(letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_discussions_parent ON letter_outgoing_discussions(parent_id);
|
||||
|
||||
CREATE TRIGGER trg_letter_outgoing_discussions_updated_at
|
||||
BEFORE UPDATE ON letter_outgoing_discussions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING DISCUSSION ATTACHMENTS
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_discussion_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
discussion_id UUID NOT NULL REFERENCES letter_outgoing_discussions(id) ON DELETE CASCADE,
|
||||
file_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_discussion_attachments_discussion ON letter_outgoing_discussion_attachments(discussion_id);
|
||||
|
||||
-- =======================
|
||||
-- LETTER OUTGOING ACTIVITY LOGS (Immutable)
|
||||
-- =======================
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_activity_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
action_type TEXT NOT NULL,
|
||||
actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
actor_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||
target_type TEXT,
|
||||
target_id UUID,
|
||||
from_status TEXT,
|
||||
to_status TEXT,
|
||||
context JSONB,
|
||||
occurred_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_activity_logs_letter ON letter_outgoing_activity_logs(letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_activity_logs_action ON letter_outgoing_activity_logs(action_type);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,15 +0,0 @@
|
||||
-- Rollback modules and permissions update
|
||||
|
||||
-- Remove the new structured permissions
|
||||
DELETE FROM permissions WHERE code LIKE '%_READ' OR code LIKE '%_WRITE' OR code LIKE '%_CREATE' OR code LIKE '%_DELETE';
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_permissions_module_id;
|
||||
|
||||
-- Remove columns from permissions table
|
||||
ALTER TABLE permissions
|
||||
DROP COLUMN IF EXISTS module_id,
|
||||
DROP COLUMN IF EXISTS action;
|
||||
|
||||
-- Drop modules table
|
||||
DROP TABLE IF EXISTS modules CASCADE;
|
||||
@@ -1,65 +0,0 @@
|
||||
-- Add modules table and update permissions structure
|
||||
|
||||
-- Create modules table
|
||||
CREATE TABLE IF NOT EXISTS modules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_modules_updated_at
|
||||
BEFORE UPDATE ON modules
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- Add module_id and action columns to permissions table
|
||||
ALTER TABLE permissions
|
||||
ADD COLUMN IF NOT EXISTS module_id UUID REFERENCES modules(id) ON DELETE CASCADE,
|
||||
ADD COLUMN IF NOT EXISTS action TEXT;
|
||||
|
||||
-- Create index on module_id for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_permissions_module_id ON permissions(module_id);
|
||||
|
||||
-- Seed initial modules
|
||||
INSERT INTO modules (name, code) VALUES
|
||||
('User Management', 'USER_MANAGEMENT'),
|
||||
('Content Management', 'CONTENT_MANAGEMENT'),
|
||||
('Letter Management', 'LETTER_MANAGEMENT'),
|
||||
('Disposition Management', 'DISPOSITION_MANAGEMENT'),
|
||||
('Reporting', 'REPORTING'),
|
||||
('Settings', 'SETTINGS')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- Update existing permissions to include module_id and action
|
||||
-- This is a sample mapping - adjust based on your existing permission codes
|
||||
UPDATE permissions SET
|
||||
module_id = (SELECT id FROM modules WHERE code = 'LETTER_MANAGEMENT'),
|
||||
action = 'READ'
|
||||
WHERE code LIKE 'letter.%' AND code LIKE '%.view';
|
||||
|
||||
UPDATE permissions SET
|
||||
module_id = (SELECT id FROM modules WHERE code = 'LETTER_MANAGEMENT'),
|
||||
action = 'WRITE'
|
||||
WHERE code LIKE 'letter.%' AND code LIKE '%.edit';
|
||||
|
||||
UPDATE permissions SET
|
||||
module_id = (SELECT id FROM modules WHERE code = 'LETTER_MANAGEMENT'),
|
||||
action = 'CREATE'
|
||||
WHERE code LIKE 'letter.%' AND code LIKE '%.create';
|
||||
|
||||
UPDATE permissions SET
|
||||
module_id = (SELECT id FROM modules WHERE code = 'LETTER_MANAGEMENT'),
|
||||
action = 'DELETE'
|
||||
WHERE code LIKE 'letter.%' AND code LIKE '%.delete';
|
||||
|
||||
-- Insert new structured permissions for each module
|
||||
INSERT INTO permissions (module_id, action, code, description)
|
||||
SELECT
|
||||
m.id,
|
||||
a.action,
|
||||
CONCAT(m.code, '_', a.action),
|
||||
CONCAT('Can ', LOWER(a.action), ' ', LOWER(m.name))
|
||||
FROM modules m
|
||||
CROSS JOIN (VALUES ('READ'), ('WRITE'), ('CREATE'), ('DELETE')) AS a(action)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
@@ -1,14 +0,0 @@
|
||||
-- Revert changes to letter_outgoing_recipients table
|
||||
ALTER TABLE letter_outgoing_recipients
|
||||
DROP COLUMN IF EXISTS user_id,
|
||||
DROP COLUMN IF EXISTS department_id,
|
||||
DROP COLUMN IF EXISTS status,
|
||||
DROP COLUMN IF EXISTS read_at,
|
||||
DROP COLUMN IF EXISTS flag,
|
||||
DROP COLUMN IF EXISTS is_archived;
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_user;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_department;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_status;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_archived;
|
||||
@@ -1,14 +0,0 @@
|
||||
-- Add missing fields to letter_outgoing_recipients table to match incoming letter structure
|
||||
ALTER TABLE letter_outgoing_recipients
|
||||
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES users(id),
|
||||
ADD COLUMN IF NOT EXISTS department_id UUID REFERENCES departments(id),
|
||||
ADD COLUMN IF NOT EXISTS status TEXT DEFAULT 'pending',
|
||||
ADD COLUMN IF NOT EXISTS read_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS flag TEXT,
|
||||
ADD COLUMN IF NOT EXISTS is_archived BOOLEAN DEFAULT false;
|
||||
|
||||
-- Add indexes for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_user ON letter_outgoing_recipients(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_department ON letter_outgoing_recipients(department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_status ON letter_outgoing_recipients(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_archived ON letter_outgoing_recipients(is_archived);
|
||||
@@ -1,4 +0,0 @@
|
||||
ALTER TABLE letter_outgoing_recipients
|
||||
ADD COLUMN IF NOT EXISTS recipient_name VARCHAR(255) NOT NULL,
|
||||
ADD COLUMN IF NOT EXISTS recipient_email VARCHAR(255) NOT NULL,
|
||||
ADD COLUMN IF NOT EXISTS recipient_position VARCHAR(255) NOT NULL;
|
||||
@@ -1,4 +0,0 @@
|
||||
ALTER TABLE letter_outgoing_recipients
|
||||
DROP COLUMN IF EXISTS recipient_name,
|
||||
DROP COLUMN IF EXISTS recipient_email,
|
||||
DROP COLUMN IF EXISTS recipient_position;
|
||||
@@ -1,12 +0,0 @@
|
||||
-- Drop triggers
|
||||
DROP TRIGGER IF EXISTS update_document_sessions_updated_at ON document_sessions;
|
||||
DROP TRIGGER IF EXISTS update_document_metadata_updated_at ON document_metadata;
|
||||
|
||||
-- Drop function
|
||||
DROP FUNCTION IF EXISTS update_updated_at_column();
|
||||
|
||||
-- Drop tables
|
||||
DROP TABLE IF EXISTS document_errors;
|
||||
DROP TABLE IF EXISTS document_metadata;
|
||||
DROP TABLE IF EXISTS document_versions;
|
||||
DROP TABLE IF EXISTS document_sessions;
|
||||
@@ -1,91 +0,0 @@
|
||||
-- Create document sessions table for OnlyOffice integration
|
||||
CREATE TABLE IF NOT EXISTS document_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
document_id UUID NOT NULL,
|
||||
document_key VARCHAR(255) UNIQUE NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES users(id),
|
||||
status INTEGER NOT NULL DEFAULT 0,
|
||||
is_locked BOOLEAN DEFAULT false,
|
||||
locked_by UUID REFERENCES users(id),
|
||||
locked_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
last_saved_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes for document sessions
|
||||
CREATE INDEX idx_document_sessions_document ON document_sessions(document_id);
|
||||
CREATE INDEX idx_document_sessions_user ON document_sessions(user_id);
|
||||
CREATE INDEX idx_document_sessions_status ON document_sessions(status);
|
||||
CREATE INDEX idx_document_sessions_locked ON document_sessions(is_locked);
|
||||
|
||||
-- Create document versions table
|
||||
CREATE TABLE IF NOT EXISTS document_versions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
document_id UUID NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
file_url TEXT NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
changes_url TEXT,
|
||||
saved_by UUID NOT NULL REFERENCES users(id),
|
||||
saved_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
is_active BOOLEAN DEFAULT false,
|
||||
comments TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes for document versions
|
||||
CREATE INDEX idx_document_versions_document ON document_versions(document_id);
|
||||
CREATE INDEX idx_document_versions_active ON document_versions(is_active);
|
||||
CREATE UNIQUE INDEX idx_document_versions_active_unique ON document_versions(document_id, is_active) WHERE is_active = true;
|
||||
|
||||
-- Create document metadata table
|
||||
CREATE TABLE IF NOT EXISTS document_metadata (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
document_id UUID UNIQUE NOT NULL,
|
||||
document_type VARCHAR(50) NOT NULL,
|
||||
reference_id UUID NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type VARCHAR(50) NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
mime_type VARCHAR(255),
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes for document metadata
|
||||
CREATE INDEX idx_document_metadata_document ON document_metadata(document_id);
|
||||
CREATE INDEX idx_document_metadata_type ON document_metadata(document_type);
|
||||
CREATE INDEX idx_document_metadata_reference ON document_metadata(reference_id);
|
||||
|
||||
-- Create document errors table for logging
|
||||
CREATE TABLE IF NOT EXISTS document_errors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
document_id UUID NOT NULL,
|
||||
session_id UUID REFERENCES document_sessions(id),
|
||||
error_type VARCHAR(100) NOT NULL,
|
||||
error_msg TEXT NOT NULL,
|
||||
details JSONB,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes for document errors
|
||||
CREATE INDEX idx_document_errors_document ON document_errors(document_id);
|
||||
CREATE INDEX idx_document_errors_session ON document_errors(session_id);
|
||||
CREATE INDEX idx_document_errors_type ON document_errors(error_type);
|
||||
|
||||
-- Add trigger to update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_document_sessions_updated_at BEFORE UPDATE ON document_sessions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_document_metadata_updated_at BEFORE UPDATE ON document_metadata
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -1,12 +0,0 @@
|
||||
-- Drop performance indexes
|
||||
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_id_deleted;
|
||||
DROP INDEX IF EXISTS idx_users_id;
|
||||
DROP INDEX IF EXISTS idx_document_sessions_document_key;
|
||||
DROP INDEX IF EXISTS idx_document_metadata_document_id;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_attachments_id;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_attachments_letter_id;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_attachments_id;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_attachments_letter_id;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_discussion_attachments_id;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_discussion_attachments_discussion_id;
|
||||
@@ -1,22 +0,0 @@
|
||||
-- Add indexes to improve query performance
|
||||
|
||||
-- Index for letters_outgoing
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_id_deleted ON letters_outgoing(id, deleted_at);
|
||||
|
||||
-- Index for users table
|
||||
CREATE INDEX IF NOT EXISTS idx_users_id ON users(id);
|
||||
|
||||
-- Indexes for document sessions lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_document_sessions_document_key ON document_sessions(document_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_document_metadata_document_id ON document_metadata(document_id);
|
||||
|
||||
-- Additional indexes for letter attachments
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_attachments_id ON letter_outgoing_attachments(id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_attachments_letter_id ON letter_outgoing_attachments(letter_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_attachments_id ON letter_incoming_attachments(id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_attachments_letter_id ON letter_incoming_attachments(letter_id);
|
||||
|
||||
-- Index for discussion attachments
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_discussion_attachments_id ON letter_outgoing_discussion_attachments(id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_discussion_attachments_discussion_id ON letter_outgoing_discussion_attachments(discussion_id);
|
||||
@@ -1,9 +0,0 @@
|
||||
-- Revert document metadata VARCHAR constraints
|
||||
-- Note: This may fail if existing data exceeds 50 characters
|
||||
|
||||
ALTER TABLE document_metadata
|
||||
ALTER COLUMN document_type TYPE VARCHAR(50),
|
||||
ALTER COLUMN file_type TYPE VARCHAR(50);
|
||||
|
||||
ALTER TABLE document_metadata
|
||||
ALTER COLUMN mime_type TYPE VARCHAR(255);
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Fix document metadata VARCHAR constraints
|
||||
-- Increase the size limit for document_type and file_type columns
|
||||
|
||||
ALTER TABLE document_metadata
|
||||
ALTER COLUMN document_type TYPE VARCHAR(255),
|
||||
ALTER COLUMN file_type TYPE VARCHAR(255);
|
||||
|
||||
-- Also ensure mime_type has sufficient length
|
||||
ALTER TABLE document_metadata
|
||||
ALTER COLUMN mime_type TYPE VARCHAR(255);
|
||||
@@ -1,13 +0,0 @@
|
||||
-- Revert to original status check constraint without 'not_started'
|
||||
-- First update any 'not_started' statuses to 'pending'
|
||||
UPDATE letter_outgoing_approvals
|
||||
SET status = 'pending'
|
||||
WHERE status = 'not_started';
|
||||
|
||||
-- Drop and recreate the constraint
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
DROP CONSTRAINT IF EXISTS letter_outgoing_approvals_status_check;
|
||||
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
ADD CONSTRAINT letter_outgoing_approvals_status_check
|
||||
CHECK (status IN ('pending', 'approved', 'rejected'));
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Add 'not_started' status to letter_outgoing_approvals status check constraint
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
DROP CONSTRAINT IF EXISTS letter_outgoing_approvals_status_check;
|
||||
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
ADD CONSTRAINT letter_outgoing_approvals_status_check
|
||||
CHECK (status IN ('not_started', 'pending', 'approved', 'rejected'));
|
||||
|
||||
-- Update default value for new approvals (optional, keeping 'pending' as default)
|
||||
-- ALTER TABLE letter_outgoing_approvals ALTER COLUMN status SET DEFAULT 'not_started';
|
||||
@@ -1,9 +0,0 @@
|
||||
-- Remove indexes
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_approvals_step_order;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_approvals_parallel_group;
|
||||
|
||||
-- Remove columns
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
DROP COLUMN IF EXISTS step_order,
|
||||
DROP COLUMN IF EXISTS parallel_group,
|
||||
DROP COLUMN IF EXISTS is_required;
|
||||
@@ -1,17 +0,0 @@
|
||||
-- Add step details to letter_outgoing_approvals table
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
ADD COLUMN IF NOT EXISTS step_order INT NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS parallel_group INT DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS is_required BOOLEAN DEFAULT true;
|
||||
|
||||
-- Add indexes for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_step_order
|
||||
ON letter_outgoing_approvals(letter_id, step_order);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_parallel_group
|
||||
ON letter_outgoing_approvals(letter_id, parallel_group);
|
||||
|
||||
-- Add comments for documentation
|
||||
COMMENT ON COLUMN letter_outgoing_approvals.step_order IS 'The order in which this approval step should be processed';
|
||||
COMMENT ON COLUMN letter_outgoing_approvals.parallel_group IS 'Steps with the same parallel_group value can be processed simultaneously';
|
||||
COMMENT ON COLUMN letter_outgoing_approvals.is_required IS 'Whether this approval step is required for the letter to proceed';
|
||||
@@ -1,23 +0,0 @@
|
||||
-- Drop triggers
|
||||
DROP TRIGGER IF EXISTS update_letter_summary_on_incoming ON letters_incoming;
|
||||
DROP TRIGGER IF EXISTS update_letter_summary_on_outgoing ON letters_outgoing;
|
||||
|
||||
-- Drop functions
|
||||
DROP FUNCTION IF EXISTS update_letter_summary();
|
||||
DROP FUNCTION IF EXISTS populate_letter_summary_history();
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_letter_summary_date;
|
||||
DROP INDEX IF EXISTS idx_letter_summary_type_date;
|
||||
DROP INDEX IF EXISTS idx_dept_letter_summary_dept_date;
|
||||
DROP INDEX IF EXISTS idx_dept_letter_summary_date;
|
||||
DROP INDEX IF EXISTS idx_inst_letter_summary_inst_date;
|
||||
DROP INDEX IF EXISTS idx_inst_letter_summary_date;
|
||||
DROP INDEX IF EXISTS idx_approval_sla_summary_date;
|
||||
DROP INDEX IF EXISTS idx_approval_sla_summary_dept_date;
|
||||
|
||||
-- Drop tables
|
||||
DROP TABLE IF EXISTS approval_sla_summary;
|
||||
DROP TABLE IF EXISTS institution_letter_summary;
|
||||
DROP TABLE IF EXISTS department_letter_summary;
|
||||
DROP TABLE IF EXISTS letter_summary;
|
||||
@@ -1,389 +0,0 @@
|
||||
-- Create letter_summary table for daily aggregated statistics
|
||||
CREATE TABLE IF NOT EXISTS letter_summary (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
summary_date DATE NOT NULL,
|
||||
letter_type VARCHAR(20) NOT NULL, -- 'incoming' or 'outgoing'
|
||||
total_count INTEGER DEFAULT 0,
|
||||
pending_count INTEGER DEFAULT 0,
|
||||
approved_count INTEGER DEFAULT 0,
|
||||
rejected_count INTEGER DEFAULT 0,
|
||||
archived_count INTEGER DEFAULT 0,
|
||||
sent_count INTEGER DEFAULT 0,
|
||||
avg_processing_hours DECIMAL(10,2),
|
||||
min_processing_hours DECIMAL(10,2),
|
||||
max_processing_hours DECIMAL(10,2),
|
||||
median_processing_hours DECIMAL(10,2),
|
||||
p95_processing_hours DECIMAL(10,2),
|
||||
p99_processing_hours DECIMAL(10,2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(summary_date, letter_type)
|
||||
);
|
||||
|
||||
-- Create department_letter_summary table
|
||||
CREATE TABLE IF NOT EXISTS department_letter_summary (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
department_id UUID NOT NULL REFERENCES departments(id),
|
||||
summary_date DATE NOT NULL,
|
||||
incoming_count INTEGER DEFAULT 0,
|
||||
outgoing_count INTEGER DEFAULT 0,
|
||||
pending_incoming INTEGER DEFAULT 0,
|
||||
pending_outgoing INTEGER DEFAULT 0,
|
||||
approved_outgoing INTEGER DEFAULT 0,
|
||||
rejected_outgoing INTEGER DEFAULT 0,
|
||||
avg_response_hours DECIMAL(10,2),
|
||||
completion_rate DECIMAL(5,2),
|
||||
total_recipients INTEGER DEFAULT 0,
|
||||
unique_senders INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(department_id, summary_date)
|
||||
);
|
||||
|
||||
-- Create institution_letter_summary table
|
||||
CREATE TABLE IF NOT EXISTS institution_letter_summary (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
institution_id UUID NOT NULL REFERENCES institutions(id),
|
||||
summary_date DATE NOT NULL,
|
||||
incoming_sent INTEGER DEFAULT 0,
|
||||
outgoing_received INTEGER DEFAULT 0,
|
||||
total_correspondence INTEGER DEFAULT 0,
|
||||
avg_turnaround_hours DECIMAL(10,2),
|
||||
last_activity_at TIMESTAMP,
|
||||
priority_high_count INTEGER DEFAULT 0,
|
||||
priority_medium_count INTEGER DEFAULT 0,
|
||||
priority_low_count INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(institution_id, summary_date)
|
||||
);
|
||||
|
||||
-- Create approval_sla_summary table
|
||||
CREATE TABLE IF NOT EXISTS approval_sla_summary (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
summary_date DATE NOT NULL,
|
||||
department_id UUID REFERENCES departments(id),
|
||||
total_approvals INTEGER DEFAULT 0,
|
||||
approved_count INTEGER DEFAULT 0,
|
||||
rejected_count INTEGER DEFAULT 0,
|
||||
pending_count INTEGER DEFAULT 0,
|
||||
avg_approval_hours DECIMAL(10,2),
|
||||
min_approval_hours DECIMAL(10,2),
|
||||
max_approval_hours DECIMAL(10,2),
|
||||
median_approval_hours DECIMAL(10,2),
|
||||
within_sla_count INTEGER DEFAULT 0,
|
||||
exceeded_sla_count INTEGER DEFAULT 0,
|
||||
sla_compliance_rate DECIMAL(5,2),
|
||||
avg_approval_steps DECIMAL(5,2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(summary_date, department_id)
|
||||
);
|
||||
|
||||
-- Create indexes for better query performance
|
||||
CREATE INDEX idx_letter_summary_date ON letter_summary(summary_date DESC);
|
||||
CREATE INDEX idx_letter_summary_type_date ON letter_summary(letter_type, summary_date DESC);
|
||||
|
||||
CREATE INDEX idx_dept_letter_summary_dept_date ON department_letter_summary(department_id, summary_date DESC);
|
||||
CREATE INDEX idx_dept_letter_summary_date ON department_letter_summary(summary_date DESC);
|
||||
|
||||
CREATE INDEX idx_inst_letter_summary_inst_date ON institution_letter_summary(institution_id, summary_date DESC);
|
||||
CREATE INDEX idx_inst_letter_summary_date ON institution_letter_summary(summary_date DESC);
|
||||
|
||||
CREATE INDEX idx_approval_sla_summary_date ON approval_sla_summary(summary_date DESC);
|
||||
CREATE INDEX idx_approval_sla_summary_dept_date ON approval_sla_summary(department_id, summary_date DESC);
|
||||
|
||||
-- Create function to update letter_summary
|
||||
CREATE OR REPLACE FUNCTION update_letter_summary()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_date DATE;
|
||||
v_type VARCHAR(20);
|
||||
BEGIN
|
||||
-- Determine date and type
|
||||
IF TG_TABLE_NAME = 'letters_incoming' THEN
|
||||
v_type := 'incoming';
|
||||
v_date := DATE(COALESCE(NEW.created_at, OLD.created_at));
|
||||
ELSE
|
||||
v_type := 'outgoing';
|
||||
v_date := DATE(COALESCE(NEW.created_at, OLD.created_at));
|
||||
END IF;
|
||||
|
||||
-- Update or insert summary
|
||||
INSERT INTO letter_summary (
|
||||
summary_date,
|
||||
letter_type,
|
||||
total_count,
|
||||
pending_count,
|
||||
approved_count,
|
||||
rejected_count,
|
||||
archived_count,
|
||||
sent_count
|
||||
)
|
||||
SELECT
|
||||
v_date,
|
||||
v_type,
|
||||
COUNT(*) as total_count,
|
||||
COUNT(*) FILTER (WHERE status = 'pending' OR status = 'pending_approval') as pending_count,
|
||||
COUNT(*) FILTER (WHERE status = 'approved') as approved_count,
|
||||
COUNT(*) FILTER (WHERE status = 'rejected') as rejected_count,
|
||||
COUNT(*) FILTER (WHERE status = 'archived') as archived_count,
|
||||
COUNT(*) FILTER (WHERE status = 'sent') as sent_count
|
||||
FROM (
|
||||
SELECT status, created_at FROM letters_incoming WHERE DATE(created_at) = v_date AND v_type = 'incoming'
|
||||
UNION ALL
|
||||
SELECT status, created_at FROM letters_outgoing WHERE DATE(created_at) = v_date AND v_type = 'outgoing'
|
||||
) t
|
||||
ON CONFLICT (summary_date, letter_type)
|
||||
DO UPDATE SET
|
||||
total_count = EXCLUDED.total_count,
|
||||
pending_count = EXCLUDED.pending_count,
|
||||
approved_count = EXCLUDED.approved_count,
|
||||
rejected_count = EXCLUDED.rejected_count,
|
||||
archived_count = EXCLUDED.archived_count,
|
||||
sent_count = EXCLUDED.sent_count,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create triggers for automatic updates
|
||||
CREATE TRIGGER update_letter_summary_on_incoming
|
||||
AFTER INSERT OR UPDATE OR DELETE ON letters_incoming
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_letter_summary();
|
||||
|
||||
CREATE TRIGGER update_letter_summary_on_outgoing
|
||||
AFTER INSERT OR UPDATE OR DELETE ON letters_outgoing
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_letter_summary();
|
||||
|
||||
-- Function to populate historical data
|
||||
CREATE OR REPLACE FUNCTION populate_letter_summary_history()
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
-- Populate letter_summary with historical data
|
||||
INSERT INTO letter_summary (
|
||||
summary_date,
|
||||
letter_type,
|
||||
total_count,
|
||||
pending_count,
|
||||
approved_count,
|
||||
rejected_count,
|
||||
archived_count,
|
||||
sent_count,
|
||||
avg_processing_hours
|
||||
)
|
||||
SELECT
|
||||
DATE(created_at) as summary_date,
|
||||
'incoming' as letter_type,
|
||||
COUNT(*) as total_count,
|
||||
COUNT(*) FILTER (WHERE status = 'pending') as pending_count,
|
||||
COUNT(*) FILTER (WHERE status = 'approved') as approved_count,
|
||||
COUNT(*) FILTER (WHERE status = 'rejected') as rejected_count,
|
||||
COUNT(*) FILTER (WHERE status = 'archived') as archived_count,
|
||||
0 as sent_count,
|
||||
AVG(EXTRACT(EPOCH FROM (updated_at - created_at))/3600) as avg_processing_hours
|
||||
FROM letters_incoming
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY DATE(created_at)
|
||||
UNION ALL
|
||||
SELECT
|
||||
DATE(created_at) as summary_date,
|
||||
'outgoing' as letter_type,
|
||||
COUNT(*) as total_count,
|
||||
COUNT(*) FILTER (WHERE status = 'pending_approval') as pending_count,
|
||||
COUNT(*) FILTER (WHERE status = 'approved') as approved_count,
|
||||
COUNT(*) FILTER (WHERE status = 'rejected') as rejected_count,
|
||||
COUNT(*) FILTER (WHERE status = 'archived') as archived_count,
|
||||
COUNT(*) FILTER (WHERE status = 'sent') as sent_count,
|
||||
AVG(EXTRACT(EPOCH FROM (updated_at - created_at))/3600) as avg_processing_hours
|
||||
FROM letters_outgoing
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY DATE(created_at)
|
||||
ON CONFLICT (summary_date, letter_type) DO NOTHING;
|
||||
|
||||
-- Populate department_letter_summary
|
||||
INSERT INTO department_letter_summary (
|
||||
department_id,
|
||||
summary_date,
|
||||
incoming_count,
|
||||
outgoing_count,
|
||||
pending_incoming,
|
||||
pending_outgoing,
|
||||
approved_outgoing,
|
||||
rejected_outgoing,
|
||||
avg_response_hours,
|
||||
completion_rate
|
||||
)
|
||||
SELECT
|
||||
d.id as department_id,
|
||||
dates.summary_date,
|
||||
COALESCE(inc.count, 0) as incoming_count,
|
||||
COALESCE(outg.count, 0) as outgoing_count,
|
||||
COALESCE(inc.pending, 0) as pending_incoming,
|
||||
COALESCE(outg.pending, 0) as pending_outgoing,
|
||||
COALESCE(outg.approved, 0) as approved_outgoing,
|
||||
COALESCE(outg.rejected, 0) as rejected_outgoing,
|
||||
COALESCE(outg.avg_hours, 0) as avg_response_hours,
|
||||
CASE
|
||||
WHEN COALESCE(outg.count, 0) > 0
|
||||
THEN (COALESCE(outg.completed, 0)::DECIMAL / outg.count::DECIMAL) * 100
|
||||
ELSE 0
|
||||
END as completion_rate
|
||||
FROM departments d
|
||||
CROSS JOIN (
|
||||
SELECT DISTINCT DATE(created_at) as summary_date
|
||||
FROM letters_incoming
|
||||
UNION
|
||||
SELECT DISTINCT DATE(created_at)
|
||||
FROM letters_outgoing
|
||||
) dates
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
lir.recipient_department_id as dept_id,
|
||||
DATE(li.created_at) as date,
|
||||
COUNT(DISTINCT li.id) as count,
|
||||
COUNT(DISTINCT li.id) FILTER (WHERE li.status = 'pending') as pending
|
||||
FROM letters_incoming li
|
||||
JOIN letter_incoming_recipients lir ON lir.letter_id = li.id
|
||||
WHERE li.deleted_at IS NULL
|
||||
GROUP BY lir.recipient_department_id, DATE(li.created_at)
|
||||
) inc ON inc.dept_id = d.id AND inc.date = dates.summary_date
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
lor.department_id as dept_id,
|
||||
DATE(lo.created_at) as date,
|
||||
COUNT(DISTINCT lo.id) as count,
|
||||
COUNT(DISTINCT lo.id) FILTER (WHERE lo.status = 'pending_approval') as pending,
|
||||
COUNT(DISTINCT lo.id) FILTER (WHERE lo.status = 'approved') as approved,
|
||||
COUNT(DISTINCT lo.id) FILTER (WHERE lo.status = 'rejected') as rejected,
|
||||
COUNT(DISTINCT lo.id) FILTER (WHERE lo.status IN ('sent', 'archived')) as completed,
|
||||
AVG(EXTRACT(EPOCH FROM (lo.updated_at - lo.created_at))/3600) as avg_hours
|
||||
FROM letters_outgoing lo
|
||||
JOIN letter_outgoing_recipients lor ON lor.letter_id = lo.id
|
||||
WHERE lo.deleted_at IS NULL
|
||||
GROUP BY lor.department_id, DATE(lo.created_at)
|
||||
) outg ON outg.dept_id = d.id AND outg.date = dates.summary_date
|
||||
WHERE inc.count > 0 OR outg.count > 0
|
||||
ON CONFLICT (department_id, summary_date) DO NOTHING;
|
||||
|
||||
-- Populate institution_letter_summary
|
||||
INSERT INTO institution_letter_summary (
|
||||
institution_id,
|
||||
summary_date,
|
||||
incoming_sent,
|
||||
outgoing_received,
|
||||
total_correspondence,
|
||||
avg_turnaround_hours,
|
||||
last_activity_at,
|
||||
priority_high_count,
|
||||
priority_medium_count,
|
||||
priority_low_count
|
||||
)
|
||||
SELECT
|
||||
i.id as institution_id,
|
||||
dates.summary_date,
|
||||
COALESCE(inc.count, 0) as incoming_sent,
|
||||
COALESCE(outg.count, 0) as outgoing_received,
|
||||
COALESCE(inc.count, 0) + COALESCE(outg.count, 0) as total_correspondence,
|
||||
COALESCE((inc.avg_hours + outg.avg_hours) / 2, 0) as avg_turnaround_hours,
|
||||
GREATEST(inc.last_activity, outg.last_activity) as last_activity_at,
|
||||
COALESCE(inc.high_priority, 0) + COALESCE(outg.high_priority, 0) as priority_high_count,
|
||||
COALESCE(inc.medium_priority, 0) + COALESCE(outg.medium_priority, 0) as priority_medium_count,
|
||||
COALESCE(inc.low_priority, 0) + COALESCE(outg.low_priority, 0) as priority_low_count
|
||||
FROM institutions i
|
||||
CROSS JOIN (
|
||||
SELECT DISTINCT DATE(created_at) as summary_date
|
||||
FROM letters_incoming
|
||||
UNION
|
||||
SELECT DISTINCT DATE(created_at)
|
||||
FROM letters_outgoing
|
||||
) dates
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
sender_institution_id as inst_id,
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count,
|
||||
AVG(EXTRACT(EPOCH FROM (updated_at - created_at))/3600) as avg_hours,
|
||||
MAX(created_at) as last_activity,
|
||||
COUNT(*) FILTER (WHERE priority_id IN (SELECT id FROM priorities WHERE level = 1)) as high_priority,
|
||||
COUNT(*) FILTER (WHERE priority_id IN (SELECT id FROM priorities WHERE level = 2)) as medium_priority,
|
||||
COUNT(*) FILTER (WHERE priority_id IN (SELECT id FROM priorities WHERE level = 3)) as low_priority
|
||||
FROM letters_incoming
|
||||
WHERE deleted_at IS NULL AND sender_institution_id IS NOT NULL
|
||||
GROUP BY sender_institution_id, DATE(created_at)
|
||||
) inc ON inc.inst_id = i.id AND inc.date = dates.summary_date
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
receiver_institution_id as inst_id,
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count,
|
||||
AVG(EXTRACT(EPOCH FROM (updated_at - created_at))/3600) as avg_hours,
|
||||
MAX(created_at) as last_activity,
|
||||
COUNT(*) FILTER (WHERE priority_id IN (SELECT id FROM priorities WHERE level = 1)) as high_priority,
|
||||
COUNT(*) FILTER (WHERE priority_id IN (SELECT id FROM priorities WHERE level = 2)) as medium_priority,
|
||||
COUNT(*) FILTER (WHERE priority_id IN (SELECT id FROM priorities WHERE level = 3)) as low_priority
|
||||
FROM letters_outgoing
|
||||
WHERE deleted_at IS NULL AND receiver_institution_id IS NOT NULL
|
||||
GROUP BY receiver_institution_id, DATE(created_at)
|
||||
) outg ON outg.inst_id = i.id AND outg.date = dates.summary_date
|
||||
WHERE inc.count > 0 OR outg.count > 0
|
||||
ON CONFLICT (institution_id, summary_date) DO NOTHING;
|
||||
|
||||
-- Populate approval_sla_summary
|
||||
INSERT INTO approval_sla_summary (
|
||||
summary_date,
|
||||
department_id,
|
||||
total_approvals,
|
||||
approved_count,
|
||||
rejected_count,
|
||||
pending_count,
|
||||
avg_approval_hours,
|
||||
min_approval_hours,
|
||||
max_approval_hours,
|
||||
median_approval_hours,
|
||||
within_sla_count,
|
||||
exceeded_sla_count,
|
||||
sla_compliance_rate,
|
||||
avg_approval_steps
|
||||
)
|
||||
SELECT
|
||||
DATE(loa.created_at) as summary_date,
|
||||
d.id as department_id,
|
||||
COUNT(loa.id) as total_approvals,
|
||||
COUNT(loa.id) FILTER (WHERE loa.status = 'approved') as approved_count,
|
||||
COUNT(loa.id) FILTER (WHERE loa.status = 'rejected') as rejected_count,
|
||||
COUNT(loa.id) FILTER (WHERE loa.status = 'pending') as pending_count,
|
||||
AVG(EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600) FILTER (WHERE loa.acted_at IS NOT NULL) as avg_approval_hours,
|
||||
MIN(EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600) FILTER (WHERE loa.acted_at IS NOT NULL) as min_approval_hours,
|
||||
MAX(EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600) FILTER (WHERE loa.acted_at IS NOT NULL) as max_approval_hours,
|
||||
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600) FILTER (WHERE loa.acted_at IS NOT NULL) as median_approval_hours,
|
||||
COUNT(loa.id) FILTER (WHERE EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600 <= 24 AND loa.acted_at IS NOT NULL) as within_sla_count,
|
||||
COUNT(loa.id) FILTER (WHERE EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600 > 24 AND loa.acted_at IS NOT NULL) as exceeded_sla_count,
|
||||
CASE
|
||||
WHEN COUNT(loa.id) FILTER (WHERE loa.acted_at IS NOT NULL) > 0
|
||||
THEN (COUNT(loa.id) FILTER (WHERE EXTRACT(EPOCH FROM (loa.acted_at - loa.created_at))/3600 <= 24 AND loa.acted_at IS NOT NULL)::DECIMAL /
|
||||
COUNT(loa.id) FILTER (WHERE loa.acted_at IS NOT NULL)::DECIMAL) * 100
|
||||
ELSE 0
|
||||
END as sla_compliance_rate,
|
||||
AVG(step_counts.step_count) as avg_approval_steps
|
||||
FROM letter_outgoing_approvals loa
|
||||
JOIN letters_outgoing lo ON lo.id = loa.letter_id
|
||||
JOIN letter_outgoing_recipients lor ON lor.letter_id = lo.id
|
||||
JOIN departments d ON d.id = lor.department_id
|
||||
LEFT JOIN (
|
||||
SELECT letter_id, COUNT(*) as step_count
|
||||
FROM letter_outgoing_approvals
|
||||
GROUP BY letter_id
|
||||
) step_counts ON step_counts.letter_id = loa.letter_id
|
||||
WHERE lo.deleted_at IS NULL
|
||||
GROUP BY DATE(loa.created_at), d.id
|
||||
ON CONFLICT (summary_date, department_id) DO NOTHING;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Execute the function to populate historical data
|
||||
SELECT populate_letter_summary_history();
|
||||
@@ -1,25 +0,0 @@
|
||||
-- Rollback: Remove status and letter_incoming_id columns from letter_incoming_dispositions_department table
|
||||
|
||||
-- Drop the constraint first
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
DROP CONSTRAINT IF EXISTS check_disposition_department_status;
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_dispositions_department_status;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_dispositions_department_letter_id;
|
||||
|
||||
-- Drop the columns
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
DROP COLUMN IF EXISTS status;
|
||||
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
DROP COLUMN IF EXISTS letter_incoming_id;
|
||||
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
DROP COLUMN IF EXISTS read_at;
|
||||
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
DROP COLUMN IF EXISTS completed_at;
|
||||
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
DROP COLUMN IF EXISTS updated_at;
|
||||
@@ -1,38 +0,0 @@
|
||||
-- Add status and letter_incoming_id columns to letter_incoming_dispositions_department table
|
||||
|
||||
-- Add letter_incoming_id column
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD COLUMN IF NOT EXISTS letter_incoming_id UUID NOT NULL REFERENCES letters_incoming(id);
|
||||
|
||||
-- Add status column with default value
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'pending';
|
||||
|
||||
-- Add read_at column
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD COLUMN IF NOT EXISTS read_at TIMESTAMP;
|
||||
|
||||
-- Add completed_at column
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD COLUMN IF NOT EXISTS completed_at TIMESTAMP;
|
||||
|
||||
-- Add updated_at column
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- Create index for faster queries
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_dispositions_department_status
|
||||
ON letter_incoming_dispositions_department(department_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_dispositions_department_letter_id
|
||||
ON letter_incoming_dispositions_department(letter_incoming_id);
|
||||
|
||||
-- Update existing records to have status 'dispositioned' (assuming existing records are already dispositioned)
|
||||
UPDATE letter_incoming_dispositions_department
|
||||
SET status = 'dispositioned'
|
||||
WHERE status = 'pending';
|
||||
|
||||
-- Add constraint to ensure valid status values
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD CONSTRAINT check_disposition_department_status
|
||||
CHECK (status IN ('pending', 'dispositioned', 'read', 'completed'));
|
||||
@@ -1,12 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Delete new setting
|
||||
DELETE FROM app_settings WHERE key = 'INCOMING_LETTER_DEPARTMENT_RECIPIENTS';
|
||||
|
||||
-- Restore old setting
|
||||
INSERT INTO app_settings(key, value)
|
||||
VALUES
|
||||
('INCOMING_LETTER_RECIPIENTS', '{"department_codes": ["aslog"]}'::jsonb)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,18 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Delete old setting if exists
|
||||
DELETE FROM app_settings WHERE key = 'INCOMING_LETTER_RECIPIENTS';
|
||||
|
||||
-- Insert new setting with department IDs as a direct array
|
||||
-- Using empty array as placeholder that should be replaced with actual department IDs
|
||||
INSERT INTO app_settings(key, value)
|
||||
VALUES
|
||||
('INCOMING_LETTER_DEPARTMENT_RECIPIENTS', '[]'::jsonb)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- Example to set actual department IDs (uncomment and modify as needed):
|
||||
-- UPDATE app_settings
|
||||
-- SET value = '["ae66af10-bcb7-4939-8110-940d2a387e19"]'::jsonb
|
||||
-- WHERE key = 'INCOMING_LETTER_DEPARTMENT_RECIPIENTS';
|
||||
|
||||
COMMIT;
|
||||
@@ -1,16 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_users_email;
|
||||
DROP INDEX IF EXISTS idx_user_department_user_id;
|
||||
DROP INDEX IF EXISTS idx_user_department_department_id;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_user_id;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_letter_id;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_status;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_deleted_at;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_status;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_deleted_at;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_dept_id;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_user_id;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,26 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Add index for users.email for faster email lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
|
||||
-- Add indexes for user_department relationship (singular table name)
|
||||
CREATE INDEX IF NOT EXISTS idx_user_department_user_id ON user_department(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_department_department_id ON user_department(department_id);
|
||||
|
||||
-- Add indexes for letter_outgoing_recipients for faster joins
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_user_id ON letter_outgoing_recipients(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_letter_id ON letter_outgoing_recipients(letter_id);
|
||||
|
||||
-- Add index for letters_outgoing status queries
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_status ON letters_outgoing(status) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_deleted_at ON letters_outgoing(deleted_at);
|
||||
|
||||
-- Add index for letters_incoming status queries
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_status ON letters_incoming(status) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_deleted_at ON letters_incoming(deleted_at);
|
||||
|
||||
-- Add index for letter_incoming_recipients for department lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_dept_id ON letter_incoming_recipients(recipient_department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_user_id ON letter_incoming_recipients(recipient_user_id);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,9 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_user_profiles_user_id;
|
||||
DROP INDEX IF EXISTS idx_departments_id;
|
||||
DROP INDEX IF EXISTS idx_user_department_composite;
|
||||
DROP INDEX IF EXISTS idx_users_id_not_deleted;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,11 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_user_id ON user_profiles(user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_departments_id ON departments(id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_department_composite ON user_department(user_id, department_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_id_not_deleted ON users(id);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,20 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Remove system user from department (cascade should handle this, but being explicit)
|
||||
DELETE FROM user_department
|
||||
WHERE user_id = '11111111-2222-3333-4444-555555555555'::uuid
|
||||
AND department_id = '11111111-2222-3333-4444-555555555555'::uuid;
|
||||
|
||||
-- Remove system user profile
|
||||
DELETE FROM user_profiles
|
||||
WHERE user_id = '11111111-2222-3333-4444-555555555555'::uuid;
|
||||
|
||||
-- Remove system user
|
||||
DELETE FROM users
|
||||
WHERE id = '11111111-2222-3333-4444-555555555555'::uuid;
|
||||
|
||||
-- Remove system department
|
||||
DELETE FROM departments
|
||||
WHERE id = '11111111-2222-3333-4444-555555555555'::uuid;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,88 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
INSERT INTO departments (id, name, code, path, created_at, updated_at)
|
||||
VALUES (
|
||||
'11111111-2222-3333-4444-555555555555'::uuid,
|
||||
'System',
|
||||
'SYSTEM',
|
||||
'system'::ltree,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
INSERT INTO users (
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
name,
|
||||
password_hash,
|
||||
status,
|
||||
is_active,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
'11111111-2222-3333-4444-555555555555'::uuid,
|
||||
'system',
|
||||
'system@eslogad.internal',
|
||||
'System User',
|
||||
'$2a$10$SYSTEM.USER.SHOULD.NEVER.LOGIN.WITH.PASSWORD.HASH',
|
||||
'active',
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Create user profile for system user
|
||||
INSERT INTO user_profiles (
|
||||
user_id,
|
||||
full_name,
|
||||
display_name,
|
||||
job_title,
|
||||
preferences,
|
||||
notification_prefs,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
'11111111-2222-3333-4444-555555555555'::uuid,
|
||||
'System User',
|
||||
'System',
|
||||
'Automated Process',
|
||||
'{}'::jsonb,
|
||||
'{}'::jsonb,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
) ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
-- Link system user to system department
|
||||
INSERT INTO user_department (
|
||||
id,
|
||||
user_id,
|
||||
department_id,
|
||||
is_primary,
|
||||
assigned_at,
|
||||
removed_at
|
||||
)
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
'11111111-2222-3333-4444-555555555555'::uuid,
|
||||
'11111111-2222-3333-4444-555555555555'::uuid,
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL
|
||||
) ON CONFLICT (user_id, department_id) WHERE removed_at IS NULL DO NOTHING;
|
||||
|
||||
-- Optionally, create a system role if your application uses roles
|
||||
-- Uncomment and modify if needed:
|
||||
-- INSERT INTO roles (id, code, name, description, created_at, updated_at)
|
||||
-- VALUES (
|
||||
-- gen_random_uuid(),
|
||||
-- 'SYSTEM',
|
||||
-- 'System Role',
|
||||
-- 'Role for system automated processes',
|
||||
-- CURRENT_TIMESTAMP,
|
||||
-- CURRENT_TIMESTAMP
|
||||
-- ) ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,10 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop the index first
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_disposition_dept_status_notes;
|
||||
|
||||
-- Remove notes column from letter_incoming_disposition_department table
|
||||
ALTER TABLE letter_incoming_disposition_department
|
||||
DROP COLUMN IF EXISTS notes;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,12 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Add notes column to letter_incoming_dispositions_department table
|
||||
ALTER TABLE letter_incoming_dispositions_department
|
||||
ADD COLUMN IF NOT EXISTS notes TEXT;
|
||||
|
||||
-- Add index for better performance when querying by status with notes
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_disposition_dept_status_notes
|
||||
ON letter_incoming_dispositions_department(status)
|
||||
WHERE notes IS NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,17 +0,0 @@
|
||||
-- Drop indexes for letter_incoming_recipients
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_archived;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_user_archived;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_letter_archived;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_dept_archived;
|
||||
|
||||
-- Drop indexes for letter_outgoing_recipients
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_archived;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_user_archived;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_letter_archived;
|
||||
|
||||
-- Drop the is_archived columns
|
||||
ALTER TABLE letter_incoming_recipients
|
||||
DROP COLUMN IF EXISTS is_archived;
|
||||
|
||||
ALTER TABLE letter_outgoing_recipients
|
||||
DROP COLUMN IF EXISTS is_archived;
|
||||
@@ -1,36 +0,0 @@
|
||||
-- Add is_archived column to letter_incoming_recipients if it doesn't exist
|
||||
ALTER TABLE letter_incoming_recipients
|
||||
ADD COLUMN IF NOT EXISTS is_archived BOOLEAN DEFAULT FALSE;
|
||||
|
||||
-- Add is_archived column to letter_outgoing_recipients if it doesn't exist
|
||||
ALTER TABLE letter_outgoing_recipients
|
||||
ADD COLUMN IF NOT EXISTS is_archived BOOLEAN DEFAULT FALSE;
|
||||
|
||||
-- Add indexes for efficient archiving queries on letter_incoming_recipients
|
||||
-- Note: letter_incoming_recipients uses recipient_user_id and recipient_department_id
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_archived
|
||||
ON letter_incoming_recipients(is_archived, recipient_user_id, letter_id)
|
||||
WHERE is_archived = false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_user_archived
|
||||
ON letter_incoming_recipients(recipient_user_id, is_archived, letter_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_letter_archived
|
||||
ON letter_incoming_recipients(letter_id, is_archived);
|
||||
|
||||
-- Add indexes for efficient archiving queries on letter_outgoing_recipients
|
||||
-- Note: letter_outgoing_recipients uses user_id and department_id
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_archived
|
||||
ON letter_outgoing_recipients(is_archived, user_id, letter_id)
|
||||
WHERE is_archived = false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_user_archived
|
||||
ON letter_outgoing_recipients(user_id, is_archived, letter_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_letter_archived
|
||||
ON letter_outgoing_recipients(letter_id, is_archived);
|
||||
|
||||
-- Add composite index for filtering non-archived letters by department
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_dept_archived
|
||||
ON letter_incoming_recipients(recipient_department_id, is_archived, letter_id)
|
||||
WHERE is_archived = false;
|
||||
@@ -1,15 +0,0 @@
|
||||
-- Drop the view
|
||||
DROP VIEW IF EXISTS department_hierarchy;
|
||||
|
||||
-- Drop the functions
|
||||
DROP FUNCTION IF EXISTS get_department_hierarchy_path(UUID);
|
||||
DROP FUNCTION IF EXISTS check_department_hierarchy();
|
||||
|
||||
-- Drop the trigger
|
||||
DROP TRIGGER IF EXISTS check_department_hierarchy_trigger ON departments;
|
||||
|
||||
-- Drop the index
|
||||
DROP INDEX IF EXISTS idx_departments_parent_id;
|
||||
|
||||
-- Remove the parent_department_id column
|
||||
ALTER TABLE departments DROP COLUMN IF EXISTS parent_department_id;
|
||||
@@ -1,128 +0,0 @@
|
||||
-- 1) Schema changes
|
||||
ALTER TABLE departments
|
||||
ADD COLUMN parent_department_id UUID REFERENCES departments(id) ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX idx_departments_parent_id ON departments(parent_department_id);
|
||||
|
||||
-- (Optional but recommended for ltree lookups)
|
||||
-- CREATE EXTENSION IF NOT EXISTS ltree;
|
||||
-- CREATE INDEX idx_departments_path_gist ON departments USING GIST (path);
|
||||
-- CREATE INDEX idx_departments_path_nlevel ON departments ((nlevel(path)));
|
||||
|
||||
-- 2) Migrate parent ids from existing ltree path
|
||||
-- Use ltree helpers: nlevel() and subpath()
|
||||
UPDATE departments d1
|
||||
SET parent_department_id = d2.id
|
||||
FROM departments d2
|
||||
WHERE nlevel(d1.path) > 1
|
||||
AND d2.path = subpath(d1.path, 0, nlevel(d1.path) - 1);
|
||||
|
||||
-- 3) Guard against cycles/self-parent via trigger
|
||||
CREATE OR REPLACE FUNCTION check_department_hierarchy()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
current_id UUID;
|
||||
max_depth INT := 100;
|
||||
depth INT := 0;
|
||||
BEGIN
|
||||
IF NEW.parent_department_id IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
-- self-reference
|
||||
IF NEW.parent_department_id = NEW.id THEN
|
||||
RAISE EXCEPTION 'Department cannot be its own parent';
|
||||
END IF;
|
||||
|
||||
-- walk up the tree
|
||||
current_id := NEW.parent_department_id;
|
||||
WHILE current_id IS NOT NULL AND depth < max_depth LOOP
|
||||
IF current_id = NEW.id THEN
|
||||
RAISE EXCEPTION 'Circular reference detected in department hierarchy';
|
||||
END IF;
|
||||
|
||||
SELECT parent_department_id INTO current_id
|
||||
FROM departments
|
||||
WHERE id = current_id;
|
||||
|
||||
depth := depth + 1;
|
||||
END LOOP;
|
||||
|
||||
IF depth >= max_depth THEN
|
||||
RAISE EXCEPTION 'Department hierarchy too deep (max: %)', max_depth;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS check_department_hierarchy_trigger ON departments;
|
||||
|
||||
CREATE TRIGGER check_department_hierarchy_trigger
|
||||
BEFORE INSERT OR UPDATE OF parent_department_id ON departments
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION check_department_hierarchy();
|
||||
|
||||
-- 4) Helper to rebuild dotted text path from codes (top.down.leaf)
|
||||
CREATE OR REPLACE FUNCTION get_department_hierarchy_path(dept_id UUID)
|
||||
RETURNS TEXT AS $$
|
||||
DECLARE
|
||||
path_text TEXT := '';
|
||||
current_dept RECORD;
|
||||
current_id UUID := dept_id;
|
||||
BEGIN
|
||||
WHILE current_id IS NOT NULL LOOP
|
||||
SELECT id, name, parent_department_id, code
|
||||
INTO current_dept
|
||||
FROM departments
|
||||
WHERE id = current_id;
|
||||
|
||||
IF current_dept IS NULL THEN
|
||||
EXIT;
|
||||
END IF;
|
||||
|
||||
IF path_text = '' THEN
|
||||
path_text := current_dept.code;
|
||||
ELSE
|
||||
path_text := current_dept.code || '.' || path_text;
|
||||
END IF;
|
||||
|
||||
current_id := current_dept.parent_department_id;
|
||||
END LOOP;
|
||||
|
||||
RETURN path_text;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 5) View for easy hierarchy queries
|
||||
CREATE OR REPLACE VIEW department_hierarchy AS
|
||||
WITH RECURSIVE dept_tree AS (
|
||||
-- roots
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
code,
|
||||
parent_department_id,
|
||||
path,
|
||||
0 AS level,
|
||||
ARRAY[id] AS hierarchy_ids,
|
||||
ARRAY[name] AS hierarchy_names
|
||||
FROM departments
|
||||
WHERE parent_department_id IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- children
|
||||
SELECT
|
||||
d.id,
|
||||
d.name,
|
||||
d.code,
|
||||
d.parent_department_id,
|
||||
d.path,
|
||||
dt.level + 1,
|
||||
dt.hierarchy_ids || d.id,
|
||||
dt.hierarchy_names || d.name
|
||||
FROM departments d
|
||||
JOIN dept_tree dt ON d.parent_department_id = dt.id
|
||||
)
|
||||
SELECT * FROM dept_tree;
|
||||
@@ -1,31 +0,0 @@
|
||||
-- Drop functions
|
||||
DROP FUNCTION IF EXISTS search_outgoing_letters(text);
|
||||
DROP FUNCTION IF EXISTS search_incoming_letters(text);
|
||||
|
||||
-- Drop indexes for outgoing letters
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_letter_number_text;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_subject_text;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_description_text;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_reference_number_text;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_status_created;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_priority_created;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_institution_created;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_created_by;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_issue_date;
|
||||
|
||||
-- Drop indexes for incoming letters
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_letter_number_text;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_subject_text;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_description_text;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_reference_number_text;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_status_created;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_priority_created;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_institution_created;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_created_by;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_received_date;
|
||||
|
||||
-- Drop recipient indexes
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_user;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_dept;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_user;
|
||||
DROP INDEX IF EXISTS idx_letter_incoming_recipients_dept;
|
||||
@@ -1,63 +0,0 @@
|
||||
-- Add indexes for optimized search on outgoing letters
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_letter_number_text ON letters_outgoing USING gin(to_tsvector('simple', letter_number));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_subject_text ON letters_outgoing USING gin(to_tsvector('simple', subject));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_description_text ON letters_outgoing USING gin(to_tsvector('simple', COALESCE(description, '')));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_reference_number_text ON letters_outgoing USING gin(to_tsvector('simple', COALESCE(reference_number, '')));
|
||||
|
||||
-- Composite indexes for common query patterns on outgoing letters
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_status_created ON letters_outgoing(status, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_priority_created ON letters_outgoing(priority_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_institution_created ON letters_outgoing(receiver_institution_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_created_by ON letters_outgoing(created_by, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_issue_date ON letters_outgoing(issue_date DESC) WHERE deleted_at IS NULL;
|
||||
|
||||
-- Add indexes for optimized search on incoming letters
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_letter_number_text ON letters_incoming USING gin(to_tsvector('simple', letter_number));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_subject_text ON letters_incoming USING gin(to_tsvector('simple', subject));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_description_text ON letters_incoming USING gin(to_tsvector('simple', COALESCE(description, '')));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_reference_number_text ON letters_incoming USING gin(to_tsvector('simple', COALESCE(reference_number, '')));
|
||||
|
||||
-- Composite indexes for common query patterns on incoming letters
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_status_created ON letters_incoming(status, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_priority_created ON letters_incoming(priority_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_institution_created ON letters_incoming(sender_institution_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_created_by ON letters_incoming(created_by, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_received_date ON letters_incoming(received_date DESC) WHERE deleted_at IS NULL;
|
||||
|
||||
-- Indexes for recipient lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_user ON letter_outgoing_recipients(user_id, letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_dept ON letter_outgoing_recipients(department_id, letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_user ON letter_incoming_recipients(recipient_user_id, letter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_dept ON letter_incoming_recipients(recipient_department_id, letter_id);
|
||||
|
||||
-- Create a function for full-text search on outgoing letters
|
||||
CREATE OR REPLACE FUNCTION search_outgoing_letters(search_query text)
|
||||
RETURNS SETOF letters_outgoing AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT * FROM letters_outgoing
|
||||
WHERE deleted_at IS NULL
|
||||
AND (
|
||||
to_tsvector('simple', letter_number) @@ plainto_tsquery('simple', search_query)
|
||||
OR to_tsvector('simple', subject) @@ plainto_tsquery('simple', search_query)
|
||||
OR to_tsvector('simple', COALESCE(description, '')) @@ plainto_tsquery('simple', search_query)
|
||||
OR to_tsvector('simple', COALESCE(reference_number, '')) @@ plainto_tsquery('simple', search_query)
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create a function for full-text search on incoming letters
|
||||
CREATE OR REPLACE FUNCTION search_incoming_letters(search_query text)
|
||||
RETURNS SETOF letters_incoming AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT * FROM letters_incoming
|
||||
WHERE deleted_at IS NULL
|
||||
AND (
|
||||
to_tsvector('simple', letter_number) @@ plainto_tsquery('simple', search_query)
|
||||
OR to_tsvector('simple', subject) @@ plainto_tsquery('simple', search_query)
|
||||
OR to_tsvector('simple', COALESCE(description, '')) @@ plainto_tsquery('simple', search_query)
|
||||
OR to_tsvector('simple', COALESCE(reference_number, '')) @@ plainto_tsquery('simple', search_query)
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -1,12 +0,0 @@
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_sender_name_text;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_receiver_name_text;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_sender_name;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_receiver_name;
|
||||
|
||||
-- Remove columns
|
||||
ALTER TABLE letters_incoming
|
||||
DROP COLUMN IF EXISTS sender_name;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
DROP COLUMN IF EXISTS receiver_name;
|
||||
@@ -1,15 +0,0 @@
|
||||
-- Add sender_name to incoming letters
|
||||
ALTER TABLE letters_incoming
|
||||
ADD COLUMN IF NOT EXISTS sender_name VARCHAR(255);
|
||||
|
||||
-- Add receiver_name to outgoing letters
|
||||
ALTER TABLE letters_outgoing
|
||||
ADD COLUMN IF NOT EXISTS receiver_name VARCHAR(255);
|
||||
|
||||
-- Add indexes for the new fields to support searching
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_sender_name ON letters_incoming(sender_name) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_receiver_name ON letters_outgoing(receiver_name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- Add GIN indexes for full-text search
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_sender_name_text ON letters_incoming USING gin(to_tsvector('simple', COALESCE(sender_name, '')));
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_receiver_name_text ON letters_outgoing USING gin(to_tsvector('simple', COALESCE(receiver_name, '')));
|
||||
@@ -1,9 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_type;
|
||||
|
||||
-- Remove type column from letters_incoming table
|
||||
ALTER TABLE letters_incoming DROP COLUMN IF EXISTS type;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,11 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Add type column to letters_incoming table
|
||||
ALTER TABLE letters_incoming
|
||||
ADD COLUMN IF NOT EXISTS type TEXT DEFAULT 'UTAMA'
|
||||
CHECK (type IN ('UTAMA', 'TEMBUSAN'));
|
||||
|
||||
-- Add index for type column for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_type ON letters_incoming(type);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,9 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_addressee;
|
||||
|
||||
-- Remove addressee column from letters_incoming table
|
||||
ALTER TABLE letters_incoming DROP COLUMN IF EXISTS addressee;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,10 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Add addressee column to letters_incoming table
|
||||
ALTER TABLE letters_incoming
|
||||
ADD COLUMN IF NOT EXISTS addressee TEXT;
|
||||
|
||||
-- Add index for addressee column for better search performance
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_addressee ON letters_incoming(addressee);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,11 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_is_archived;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_archived_at;
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_archived_status;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
DROP COLUMN IF EXISTS is_archived,
|
||||
DROP COLUMN IF EXISTS archived_at;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,12 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
ADD COLUMN IF NOT EXISTS is_archived BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS archived_at TIMESTAMP WITHOUT TIME ZONE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_is_archived ON letters_outgoing(is_archived);
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_archived_at ON letters_outgoing(archived_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_archived_status ON letters_outgoing(is_archived, status);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,11 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_is_archived;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_archived_at;
|
||||
DROP INDEX IF EXISTS idx_letters_incoming_archived_status;
|
||||
|
||||
ALTER TABLE letters_incoming
|
||||
DROP COLUMN IF EXISTS is_archived,
|
||||
DROP COLUMN IF EXISTS archived_at;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,12 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE letters_incoming
|
||||
ADD COLUMN IF NOT EXISTS is_archived BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS archived_at TIMESTAMP WITHOUT TIME ZONE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_is_archived ON letters_incoming(is_archived);
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_archived_at ON letters_incoming(archived_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_incoming_archived_status ON letters_incoming(is_archived, status);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,8 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_revision_number;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
DROP COLUMN IF EXISTS revision_number;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,8 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
ADD COLUMN IF NOT EXISTS revision_number INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_revision_number ON letters_outgoing(revision_number);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,14 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_attachments_revision;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_approvals_revision;
|
||||
|
||||
-- Remove revision_number from tables
|
||||
ALTER TABLE letter_outgoing_attachments
|
||||
DROP COLUMN IF EXISTS revision_number;
|
||||
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
DROP COLUMN IF EXISTS revision_number;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,15 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Add revision_number to letter_outgoing_attachments
|
||||
ALTER TABLE letter_outgoing_attachments
|
||||
ADD COLUMN IF NOT EXISTS revision_number INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Add revision_number to letter_outgoing_approvals
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
ADD COLUMN IF NOT EXISTS revision_number INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Create indexes for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_attachments_revision ON letter_outgoing_attachments(letter_id, revision_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_revision ON letter_outgoing_approvals(letter_id, revision_number);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,13 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS repository_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
file_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,2 +0,0 @@
|
||||
ALTER TABLE letter_outgoing_attachments
|
||||
ADD COLUMN IF NOT EXISTS is_final BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -1,12 +0,0 @@
|
||||
ALTER TABLE letter_outgoing_attachments
|
||||
DROP COLUMN IF EXISTS is_final;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS letter_outgoing_final_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
letter_id UUID NOT NULL REFERENCES letters_outgoing(id) ON DELETE CASCADE,
|
||||
file_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
Reference in New Issue
Block a user