- Add schema_audit.md documenting current schema issues - Add target_schema.md with complete target schema design - Add old_to_new_mapping.md with table mapping - Add migration_plan.md with phased migration strategy - Add Phase 1 migrations (core infrastructure): - user_sessions table - users missing columns - departments updates - designations updates - employees updates - Add Phase 2 migrations (profile domain - CRITICAL): - create user_role_profiles root table - backfill user_role_profiles from existing profiles - add user_role_profile_id to extension tables - remove forbidden external portfolio links - Add user_role_profile Rust model - Update photographer model to use user_role_profile_id
767 lines
24 KiB
Markdown
767 lines
24 KiB
Markdown
# Migration Plan — Nxtgauge Database Redesign
|
|
|
|
This document outlines the step-by-step migration strategy to transform the current schema to the target schema.
|
|
|
|
---
|
|
|
|
## Migration Principles
|
|
|
|
1. **Additive First** — Always create new tables before modifying existing ones
|
|
2. **No Data Loss** — Preserve all existing data during migration
|
|
3. **Reversible** — Each step can be rolled back if needed
|
|
4. **Service-by-Service** — Migrate one domain at a time
|
|
5. **Backward Compatible** — Keep old tables until services are updated
|
|
|
|
---
|
|
|
|
## Migration Phases
|
|
|
|
### Phase 1: Core Infrastructure (Week 1)
|
|
|
|
### Phase 2: Profile Domain (Week 2)
|
|
|
|
### Phase 3: Portfolio Domain (Week 2-3)
|
|
|
|
### Phase 4: Verification & Approval (Week 3)
|
|
|
|
### Phase 5: Marketplace (Week 3-4)
|
|
|
|
### Phase 6: Finance (Week 4)
|
|
|
|
### Phase 7: Audit & Cleanup (Week 5)
|
|
|
|
---
|
|
|
|
## Phase 1: Core Infrastructure
|
|
|
|
### Step 1.1: Create New Tables
|
|
|
|
**Files:** `20260415000001_create_user_sessions.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS user_sessions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
session_token TEXT UNIQUE NOT NULL,
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_token ON user_sessions(session_token);
|
|
```
|
|
|
|
### Step 1.2: Add Missing Columns to users
|
|
|
|
**Files:** `20260415000002_add_users_missing_columns.up.sql`
|
|
|
|
```sql
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS account_type TEXT DEFAULT 'INDIVIDUAL';
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
|
|
UPDATE users SET updated_at = COALESCE(updated_at, created_at, NOW());
|
|
```
|
|
|
|
### Step 1.3: Update departments
|
|
|
|
**Files:** `20260415000003_update_departments.up.sql`
|
|
|
|
```sql
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS code VARCHAR(64);
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS description TEXT;
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS department_head VARCHAR(255);
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS department_email VARCHAR(255);
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS visibility VARCHAR(20) DEFAULT 'INTERNAL';
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS transfers_enabled BOOLEAN DEFAULT false;
|
|
ALTER TABLE departments ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_departments_code ON departments(LOWER(code)) WHERE code IS NOT NULL;
|
|
```
|
|
|
|
### Step 1.4: Update designations
|
|
|
|
**Files:** `20260415000004_update_designations.up.sql`
|
|
|
|
```sql
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS code VARCHAR(64);
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS department_id UUID REFERENCES departments(id);
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS description TEXT;
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS level VARCHAR(100);
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS can_manage_team BOOLEAN DEFAULT false;
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS can_approve BOOLEAN DEFAULT false;
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;
|
|
ALTER TABLE designations ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_designations_code ON designations(LOWER(code)) WHERE code IS NOT NULL;
|
|
```
|
|
|
|
### Step 1.5: Update employees
|
|
|
|
**Files:** `20260415000005_update_employees.up.sql`
|
|
|
|
```sql
|
|
ALTER TABLE employees ADD COLUMN IF NOT EXISTS joining_date DATE;
|
|
ALTER TABLE employees ADD COLUMN IF NOT EXISTS employment_status VARCHAR(50) DEFAULT 'ACTIVE';
|
|
ALTER TABLE employees ADD COLUMN IF NOT EXISTS manager_employee_id UUID REFERENCES employees(id);
|
|
ALTER TABLE employees ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
```
|
|
|
|
### Step 1.6: Update roles
|
|
|
|
**Files:** `20260415000006_update_roles.up.sql`
|
|
|
|
```sql
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS description TEXT;
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS can_approve_requests BOOLEAN DEFAULT false;
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS can_manage_system_settings BOOLEAN DEFAULT false;
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: Profile Domain (CRITICAL)
|
|
|
|
### Step 2.1: Create user_role_profiles Root Table
|
|
|
|
**Files:** `20260415010001_create_user_role_profiles.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS user_role_profiles (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
role_key VARCHAR(50) NOT NULL,
|
|
display_name TEXT,
|
|
bio TEXT,
|
|
location TEXT,
|
|
avatar_url TEXT,
|
|
phone TEXT,
|
|
email TEXT,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
|
|
verification_status VARCHAR(50) DEFAULT 'PENDING',
|
|
approval_status VARCHAR(50) DEFAULT 'PENDING',
|
|
rejection_reason TEXT,
|
|
approved_at TIMESTAMPTZ,
|
|
verified_at TIMESTAMPTZ,
|
|
is_profile_public BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(user_id, role_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_role_profiles_user_id ON user_role_profiles(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_role_profiles_role_key ON user_role_profiles(role_key);
|
|
CREATE INDEX IF NOT EXISTS idx_user_role_profiles_status ON user_role_profiles(status);
|
|
```
|
|
|
|
### Step 2.2: Backfill user_role_profiles from Existing Data
|
|
|
|
**Files:** `20260415010002_backfill_user_role_profiles.up.sql`
|
|
|
|
```sql
|
|
-- Backfill from photographer_profiles
|
|
INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approved_at, created_at, updated_at)
|
|
SELECT
|
|
gen_random_uuid(),
|
|
p.user_id,
|
|
'photographer',
|
|
COALESCE(p.display_name, ''),
|
|
p.bio,
|
|
p.location,
|
|
COALESCE(p.status, 'ACTIVE'),
|
|
p.approved_at,
|
|
p.created_at,
|
|
COALESCE(p.updated_at, NOW())
|
|
FROM photographer_profiles p
|
|
ON CONFLICT (user_id, 'photographer') DO NOTHING;
|
|
|
|
-- Repeat for all other profession tables...
|
|
|
|
-- Backfill from company_profiles
|
|
INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, created_at, updated_at)
|
|
SELECT
|
|
gen_random_uuid(),
|
|
cp.user_id,
|
|
'company',
|
|
cp.company_name,
|
|
cp.bio,
|
|
NULL,
|
|
COALESCE(cp.status, 'ACTIVE'),
|
|
cp.created_at,
|
|
COALESCE(cp.updated_at, NOW())
|
|
FROM company_profiles cp
|
|
ON CONFLICT (user_id, 'company') DO NOTHING;
|
|
|
|
-- Backfill from customer_profiles
|
|
INSERT INTO user_role_profiles (id, user_id, role_key, display_name, location, status, created_at, updated_at)
|
|
SELECT
|
|
gen_random_uuid(),
|
|
cp.user_id,
|
|
'customer',
|
|
COALESCE(cp.full_name, ''),
|
|
cp.city,
|
|
COALESCE(cp.status, 'ACTIVE'),
|
|
cp.created_at,
|
|
COALESCE(cp.updated_at, NOW())
|
|
FROM customer_profiles cp
|
|
ON CONFLICT (user_id, 'customer') DO NOTHING;
|
|
```
|
|
|
|
### Step 2.3: Add user_role_profile_id to Extension Tables
|
|
|
|
**Files:** `20260415010003_add_user_role_profile_id.up.sql`
|
|
|
|
```sql
|
|
-- Add temporary column for mapping
|
|
ALTER TABLE photographer_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID;
|
|
|
|
-- Update with backfilled data
|
|
UPDATE photographer_profiles p
|
|
SET user_role_profile_id = urp.id
|
|
FROM user_role_profiles urp
|
|
WHERE p.user_id = urp.user_id AND urp.role_key = 'photographer';
|
|
|
|
-- Add FK constraint
|
|
ALTER TABLE photographer_profiles
|
|
ADD CONSTRAINT fk_photographer_profiles_user_role_profile
|
|
FOREIGN KEY (user_role_profile_id) REFERENCES user_role_profiles(id);
|
|
|
|
-- Repeat for all extension tables...
|
|
```
|
|
|
|
### Step 2.4: Update Extension Tables Schema
|
|
|
|
**Files:** `20260415010004_update_extension_tables.up.sql`
|
|
|
|
```sql
|
|
-- Remove forbidden external links
|
|
ALTER TABLE developer_profiles DROP COLUMN IF EXISTS github_url;
|
|
ALTER TABLE developer_profiles DROP COLUMN IF EXISTS portfolio_url;
|
|
ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS reel_url;
|
|
ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS portfolio_url;
|
|
ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS portfolio_url;
|
|
|
|
-- Remove custom_data (preserve if needed)
|
|
ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS custom_data;
|
|
ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS custom_data;
|
|
-- ... repeat for all tables
|
|
|
|
-- Rename columns for consistency
|
|
ALTER TABLE tutor_profiles RENAME COLUMN subjects_taught TO subjects;
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 3: Portfolio Domain
|
|
|
|
### Step 3.1: Update portfolio_items
|
|
|
|
**Files:** `20260415020001_update_portfolio_items.up.sql`
|
|
|
|
```sql
|
|
-- Add user_role_profile_id
|
|
ALTER TABLE portfolio_items ADD COLUMN IF NOT EXISTS user_role_profile_id UUID;
|
|
ALTER TABLE portfolio_items ADD COLUMN IF NOT EXISTS display_order INTEGER DEFAULT 0;
|
|
|
|
-- Backfill from professionals
|
|
UPDATE portfolio_items pi
|
|
SET user_role_profile_id = urp.id
|
|
FROM user_role_profiles urp
|
|
WHERE pi.professional_id = urp.user_id;
|
|
|
|
-- Remove old columns
|
|
ALTER TABLE portfolio_items DROP COLUMN IF EXISTS professional_id;
|
|
ALTER TABLE portfolio_items DROP COLUMN IF EXISTS user_id;
|
|
ALTER TABLE portfolio_items DROP COLUMN IF EXISTS profession_key;
|
|
```
|
|
|
|
### Step 3.2: Update services
|
|
|
|
**Files:** `20260415020002_update_services.up.sql`
|
|
|
|
```sql
|
|
ALTER TABLE services ADD COLUMN IF NOT EXISTS user_role_profile_id UUID;
|
|
|
|
UPDATE services s
|
|
SET user_role_profile_id = urp.id
|
|
FROM user_role_profiles urp
|
|
WHERE s.professional_id = urp.user_id;
|
|
|
|
ALTER TABLE services DROP COLUMN IF EXISTS professional_id;
|
|
ALTER TABLE services DROP COLUMN IF EXISTS user_id;
|
|
ALTER TABLE services DROP COLUMN IF EXISTS profession_key;
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 4: Verification & Approval
|
|
|
|
### Step 4.1: Create Verification Tables
|
|
|
|
**Files:** `20260415030001_create_verification_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS verification_requests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_role_profile_id UUID NOT NULL REFERENCES user_role_profiles(id),
|
|
verification_type VARCHAR(50) NOT NULL,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
|
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
reviewed_at TIMESTAMPTZ,
|
|
reviewed_by_user_id UUID REFERENCES users(id),
|
|
remarks TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS verification_documents (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
verification_request_id UUID NOT NULL REFERENCES verification_requests(id) ON DELETE CASCADE,
|
|
document_type VARCHAR(100) NOT NULL,
|
|
file_url TEXT NOT NULL,
|
|
file_name TEXT,
|
|
mime_type TEXT,
|
|
status VARCHAR(50) DEFAULT 'PENDING',
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
reviewed_at TIMESTAMPTZ,
|
|
reviewed_by_user_id UUID REFERENCES users(id),
|
|
remarks TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS verification_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
verification_request_id UUID NOT NULL REFERENCES verification_requests(id),
|
|
action VARCHAR(50) NOT NULL,
|
|
old_status VARCHAR(50),
|
|
new_status VARCHAR(50),
|
|
acted_by_user_id UUID REFERENCES users(id),
|
|
remarks TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### Step 4.2: Create Approval Tables
|
|
|
|
**Files:** `20260415030002_create_approval_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS approval_requests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
entity_type VARCHAR(50) NOT NULL,
|
|
entity_id UUID NOT NULL,
|
|
approval_type VARCHAR(50),
|
|
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
|
submitted_by_user_id UUID REFERENCES users(id),
|
|
reviewed_by_user_id UUID REFERENCES users(id),
|
|
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
reviewed_at TIMESTAMPTZ,
|
|
remarks TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS approval_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
approval_request_id UUID NOT NULL REFERENCES approval_requests(id),
|
|
action VARCHAR(50) NOT NULL,
|
|
old_status VARCHAR(50),
|
|
new_status VARCHAR(50),
|
|
acted_by_user_id UUID REFERENCES users(id),
|
|
remarks TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 5: Marketplace
|
|
|
|
### Step 5.1: Rename and Update jobs-related Tables
|
|
|
|
**Files:** `20260415040001_update_jobs_tables.up.sql`
|
|
|
|
```sql
|
|
-- Rename applications to job_applications
|
|
ALTER TABLE applications RENAME TO job_applications;
|
|
|
|
-- Add new columns
|
|
ALTER TABLE job_applications ADD COLUMN IF NOT EXISTS applicant_user_id UUID;
|
|
|
|
-- Backfill from job_seeker_profiles
|
|
UPDATE job_applications ja
|
|
SET applicant_user_id = (
|
|
SELECT user_id FROM job_seeker_profiles jsp WHERE jsp.id = ja.job_seeker_id
|
|
);
|
|
|
|
-- Add FK
|
|
ALTER TABLE job_applications ADD CONSTRAINT fk_job_applications_applicant
|
|
FOREIGN KEY (applicant_user_id) REFERENCES users(id);
|
|
|
|
-- Remove old columns
|
|
ALTER TABLE job_applications DROP COLUMN IF EXISTS job_seeker_id;
|
|
ALTER TABLE job_applications DROP COLUMN IF EXISTS cover_letter;
|
|
ALTER TABLE job_applications DROP COLUMN IF EXISTS resume_url;
|
|
ALTER TABLE job_applications DROP COLUMN IF EXISTS contact_viewed;
|
|
|
|
-- Rename cover_note if needed
|
|
ALTER TABLE job_applications RENAME COLUMN cover_letter TO cover_note;
|
|
```
|
|
|
|
### Step 5.2: Update jobs Table
|
|
|
|
**Files:** `20260415040002_update_jobs.up.sql`
|
|
|
|
```sql
|
|
-- Add new columns
|
|
ALTER TABLE jobs ADD COLUMN IF NOT EXISTS posted_by_user_id UUID;
|
|
ALTER TABLE jobs ADD COLUMN IF NOT EXISTS mode_of_work VARCHAR(50);
|
|
ALTER TABLE jobs ADD COLUMN IF NOT EXISTS budget_inr INTEGER;
|
|
ALTER TABLE jobs ADD COLUMN IF NOT EXISTS salary_range_json JSONB;
|
|
|
|
-- Add FK
|
|
ALTER TABLE jobs ADD CONSTRAINT fk_jobs_posted_by
|
|
FOREIGN KEY (posted_by_user_id) REFERENCES users(id);
|
|
|
|
-- Add updated_at
|
|
ALTER TABLE jobs ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
UPDATE jobs SET updated_at = COALESCE(updated_at, created_at, NOW());
|
|
```
|
|
|
|
### Step 5.3: Rename requirements to leads
|
|
|
|
**Files:** `20260415040003_rename_requirements_to_leads.up.sql`
|
|
|
|
```sql
|
|
-- Rename table
|
|
ALTER TABLE requirements RENAME TO leads;
|
|
|
|
-- Rename columns
|
|
ALTER TABLE leads RENAME COLUMN customer_id TO created_by_user_id;
|
|
ALTER TABLE leads RENAME COLUMN preferred_date TO required_date;
|
|
|
|
-- Add FK
|
|
ALTER TABLE leads ADD CONSTRAINT fk_leads_created_by
|
|
FOREIGN KEY (created_by_user_id) REFERENCES users(id);
|
|
|
|
-- Add updated_at
|
|
ALTER TABLE leads ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
```
|
|
|
|
### Step 5.4: Update lead_requests
|
|
|
|
**Files:** `20260415040004_update_lead_requests.up.sql`
|
|
|
|
```sql
|
|
-- Add user_role_profile_id
|
|
ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS user_role_profile_id UUID;
|
|
|
|
-- Backfill from professionals
|
|
UPDATE lead_requests lr
|
|
SET user_role_profile_id = (
|
|
SELECT id FROM user_role_profiles urp
|
|
WHERE urp.user_id = (
|
|
SELECT user_id FROM professionals p WHERE p.id = lr.professional_id
|
|
)
|
|
);
|
|
|
|
-- Add FK
|
|
ALTER TABLE lead_requests ADD CONSTRAINT fk_lead_requests_profile
|
|
FOREIGN KEY (user_role_profile_id) REFERENCES user_role_profiles(id);
|
|
|
|
-- Rename columns
|
|
ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS remarks TEXT;
|
|
ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 6: Finance
|
|
|
|
### Step 6.1: Create Order Tables
|
|
|
|
**Files:** `20260415050001_create_order_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS orders (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id),
|
|
order_type VARCHAR(50) NOT NULL,
|
|
subtotal_inr INTEGER NOT NULL DEFAULT 0,
|
|
discount_inr INTEGER NOT NULL DEFAULT 0,
|
|
tax_inr INTEGER NOT NULL DEFAULT 0,
|
|
total_inr INTEGER NOT NULL DEFAULT 0,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS order_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
|
item_type VARCHAR(50) NOT NULL,
|
|
item_id UUID,
|
|
item_name TEXT NOT NULL,
|
|
quantity INTEGER NOT NULL DEFAULT 1,
|
|
unit_price_inr INTEGER NOT NULL,
|
|
total_price_inr INTEGER NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id);
|
|
```
|
|
|
|
### Step 6.2: Update Existing Finance Tables
|
|
|
|
**Files:** `20260415050002_update_finance_tables.up.sql`
|
|
|
|
```sql
|
|
-- Update tracecoin_wallets
|
|
ALTER TABLE tracecoin_wallets RENAME COLUMN balance TO current_balance;
|
|
ALTER TABLE tracecoin_wallets ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
|
|
-- Update tracecoin_ledger
|
|
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS balance_after INTEGER;
|
|
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS remarks TEXT;
|
|
ALTER TABLE tracecoin_ledger RENAME COLUMN type TO transaction_type;
|
|
ALTER TABLE tracecoin_ledger RENAME COLUMN reason TO reference_type;
|
|
|
|
-- Update coupons
|
|
ALTER TABLE coupons ADD COLUMN IF NOT EXISTS max_discount_inr INTEGER;
|
|
ALTER TABLE coupons ADD COLUMN IF NOT EXISTS min_order_value_inr INTEGER DEFAULT 0;
|
|
ALTER TABLE coupons ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ DEFAULT NOW();
|
|
ALTER TABLE coupons ADD COLUMN IF NOT EXISTS valid_to TIMESTAMPTZ;
|
|
|
|
-- Rename coupon_uses to coupon_redemptions
|
|
ALTER TABLE coupon_uses RENAME TO coupon_redemptions;
|
|
ALTER TABLE coupon_redemptions ADD COLUMN IF NOT EXISTS order_id UUID;
|
|
ALTER TABLE coupon_redemptions ADD COLUMN IF NOT EXISTS discount_amount_inr INTEGER;
|
|
ALTER TABLE coupon_redemptions RENAME COLUMN used_at TO redeemed_at;
|
|
```
|
|
|
|
### Step 6.3: Create Payment Infrastructure
|
|
|
|
**Files:** `20260415050003_create_payment_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS payment_gateway_configs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
gateway_key VARCHAR(50) NOT NULL,
|
|
display_name VARCHAR(255),
|
|
config_json JSONB,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_transactions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
payment_id UUID NOT NULL REFERENCES payments(id),
|
|
transaction_type VARCHAR(50) NOT NULL,
|
|
provider_reference TEXT,
|
|
request_payload_json JSONB,
|
|
response_payload_json JSONB,
|
|
status VARCHAR(50) NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tax_rules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(255) NOT NULL,
|
|
tax_type VARCHAR(50) NOT NULL,
|
|
tax_rate DECIMAL(5,2) NOT NULL,
|
|
applies_to VARCHAR(50),
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 7: Audit & Cleanup
|
|
|
|
### Step 7.1: Create Audit Tables
|
|
|
|
**Files:** `20260415060001_create_audit_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
actor_user_id UUID REFERENCES users(id),
|
|
actor_employee_id UUID REFERENCES employees(id),
|
|
actor_type VARCHAR(50),
|
|
action VARCHAR(100) NOT NULL,
|
|
entity_type VARCHAR(100),
|
|
entity_id UUID,
|
|
entity_label TEXT,
|
|
module_key VARCHAR(100),
|
|
source_type VARCHAR(50),
|
|
source_id UUID,
|
|
request_id UUID,
|
|
correlation_id UUID,
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'SUCCESS',
|
|
summary TEXT,
|
|
metadata_json JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_log_changes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
audit_log_id UUID NOT NULL REFERENCES audit_logs(id) ON DELETE CASCADE,
|
|
field_name TEXT NOT NULL,
|
|
old_value_text TEXT,
|
|
new_value_text TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_actor ON audit_logs(actor_user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_module ON audit_logs(module_key);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at);
|
|
```
|
|
|
|
### Step 7.2: Create Missing KB Tables
|
|
|
|
**Files:** `20260415060002_create_kb_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS kb_sections (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
category_id UUID NOT NULL REFERENCES kb_categories(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
display_order INTEGER DEFAULT 0,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS kb_article_feedback (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES kb_articles(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES users(id),
|
|
is_helpful BOOLEAN,
|
|
feedback_text TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Update kb_articles
|
|
ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS section_id UUID REFERENCES kb_sections(id);
|
|
ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS article_type VARCHAR(50) DEFAULT 'HOW_TO';
|
|
ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS audience_type VARCHAR(50) DEFAULT 'ALL';
|
|
ALTER TABLE kb_articles RENAME COLUMN body TO content_markdown;
|
|
ALTER TABLE kb_articles RENAME COLUMN created_by TO author_user_id;
|
|
ALTER TABLE kb_articles RENAME COLUMN is_published TO status;
|
|
```
|
|
|
|
### Step 7.3: Create Notification Infrastructure
|
|
|
|
**Files:** `20260415060003_create_notification_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS notification_templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
template_key VARCHAR(100) NOT NULL UNIQUE,
|
|
channel VARCHAR(50) NOT NULL,
|
|
title_template TEXT,
|
|
body_template TEXT,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS smtp_configs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
provider_name VARCHAR(100),
|
|
host VARCHAR(255),
|
|
port INTEGER,
|
|
username TEXT,
|
|
encryption_mode VARCHAR(20),
|
|
from_name VARCHAR(255),
|
|
from_email VARCHAR(255),
|
|
is_default BOOLEAN DEFAULT false,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Update notifications
|
|
ALTER TABLE notifications ADD COLUMN IF NOT EXISTS channel VARCHAR(50) DEFAULT 'IN_APP';
|
|
ALTER TABLE notifications ADD COLUMN IF NOT EXISTS related_entity_type VARCHAR(50);
|
|
ALTER TABLE notifications RENAME COLUMN reference_id TO related_entity_id;
|
|
ALTER TABLE notifications RENAME COLUMN is_read TO status;
|
|
```
|
|
|
|
### Step 7.4: Create Dashboard Widgets
|
|
|
|
**Files:** `20260415060004_create_dashboard_tables.up.sql`
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS dashboard_widgets (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
dashboard_config_id UUID NOT NULL REFERENCES dashboard_configs(id) ON DELETE CASCADE,
|
|
widget_key VARCHAR(100) NOT NULL,
|
|
widget_title VARCHAR(255),
|
|
config_json JSONB,
|
|
display_order INTEGER DEFAULT 0,
|
|
width_units INTEGER DEFAULT 1,
|
|
height_units INTEGER DEFAULT 1,
|
|
is_visible BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_dashboard_widgets_config ON dashboard_widgets(dashboard_config_id);
|
|
```
|
|
|
|
---
|
|
|
|
## Rollback Procedures
|
|
|
|
### Rollback Phase 1
|
|
|
|
```sql
|
|
-- Drop new tables
|
|
DROP TABLE IF EXISTS user_sessions;
|
|
|
|
-- Revert column changes (use down migrations)
|
|
```
|
|
|
|
### Rollback Phase 2
|
|
|
|
```sql
|
|
-- DO NOT rollback user_role_profiles if data exists
|
|
-- Instead, mark as deprecated and keep parallel structure
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
1. **Unit Tests** — Test each migration script independently
|
|
2. **Integration Tests** — Test application functionality after each phase
|
|
3. **Data Validation** — Verify all data is correctly migrated
|
|
4. **Performance Tests** — Ensure indexes perform well
|
|
5. **Rollback Tests** — Test rollback procedures in staging
|
|
|
|
---
|
|
|
|
## Pre-Migration Checklist
|
|
|
|
- [ ] Backup production database
|
|
- [ ] Test migrations on staging environment
|
|
- [ ] Verify all foreign key relationships
|
|
- [ ] Check for circular dependencies
|
|
- [ ] Plan maintenance window for critical migrations
|
|
- [ ] Notify stakeholders of potential downtime
|
|
|
|
---
|
|
|
|
## Post-Migration Checklist
|
|
|
|
- [ ] Verify all data integrity
|
|
- [ ] Check application logs for errors
|
|
- [ ] Update documentation
|
|
- [ ] Remove deprecated tables (after full validation)
|
|
- [ ] Archive old migration files
|