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;