kamau1 commited on
Commit
8169ef9
·
1 Parent(s): 8b6529e

update the metadata field in all tables to additional_metadata

Browse files
docs/dev/METADATA_MIGRATION.md ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Metadata Column Migration Guide
2
+
3
+ ## Problem
4
+
5
+ The column name `metadata` conflicts with SQLAlchemy's internal `metadata` attribute, causing this error:
6
+
7
+ ```
8
+ sqlalchemy.exc.InvalidRequestError: Attribute name 'metadata' is reserved when using the Declarative API.
9
+ ```
10
+
11
+ ## Solution
12
+
13
+ Rename `metadata` → `additional_metadata` across all tables.
14
+
15
+ ## Migration Steps
16
+
17
+ ### 1. Run SQL Migration
18
+
19
+ **Location:** `migrations/001_rename_metadata_to_additional_metadata.sql`
20
+
21
+ **How to run:**
22
+ 1. Open Supabase Dashboard
23
+ 2. Go to SQL Editor
24
+ 3. Copy the migration file content
25
+ 4. Execute the SQL
26
+ 5. Verify success
27
+
28
+ **Verification query:**
29
+ ```sql
30
+ SELECT
31
+ table_name,
32
+ column_name,
33
+ data_type
34
+ FROM information_schema.columns
35
+ WHERE table_schema = 'public'
36
+ AND column_name IN ('metadata', 'additional_metadata')
37
+ ORDER BY
38
+ CASE WHEN column_name = 'metadata' THEN 0 ELSE 1 END,
39
+ table_name;
40
+ ```
41
+
42
+ **Expected result:**
43
+ - 0 rows with `metadata`
44
+ - 32 rows with `additional_metadata`
45
+
46
+ ### 2. Update Application Code
47
+
48
+ **Before:**
49
+ ```python
50
+ # Workaround (don't use this)
51
+ user_metadata = Column('metadata', JSONB, default={})
52
+ ```
53
+
54
+ **After:**
55
+ ```python
56
+ # Clean approach
57
+ additional_metadata = Column(JSONB, default={})
58
+ ```
59
+
60
+ ### 3. Update All Models
61
+
62
+ When creating new models, use `additional_metadata`:
63
+
64
+ ```python
65
+ from sqlalchemy import Column
66
+ from sqlalchemy.dialects.postgresql import JSONB
67
+ from app.models.base import BaseModel
68
+
69
+ class MyModel(BaseModel):
70
+ __tablename__ = "my_table"
71
+
72
+ # ... other columns ...
73
+
74
+ # Use additional_metadata (not metadata!)
75
+ additional_metadata = Column(JSONB, default={})
76
+ ```
77
+
78
+ ### 4. Usage in Code
79
+
80
+ ```python
81
+ # Create user with metadata
82
+ user = User(
83
+ name="John Doe",
84
+ email="john@example.com",
85
+ additional_metadata={
86
+ "source": "mobile_app",
87
+ "referral_code": "ABC123"
88
+ }
89
+ )
90
+
91
+ # Access metadata
92
+ print(user.additional_metadata)
93
+ # Output: {"source": "mobile_app", "referral_code": "ABC123"}
94
+
95
+ # Update metadata
96
+ user.additional_metadata["last_login"] = "2025-11-15"
97
+ db.commit()
98
+ ```
99
+
100
+ ## Tables Affected
101
+
102
+ 32 tables have the `additional_metadata` column:
103
+
104
+ **Organizations & Users:**
105
+ - clients
106
+ - contractors
107
+ - users
108
+ - user_financial_accounts
109
+ - user_asset_assignments
110
+ - user_document_links
111
+
112
+ **Projects & Teams:**
113
+ - projects
114
+ - project_regions
115
+ - project_subcontractors
116
+ - timesheets
117
+
118
+ **Customers & Sales:**
119
+ - customers
120
+ - sales_orders
121
+ - subscriptions
122
+ - incidents
123
+
124
+ **Tickets & Work:**
125
+ - tickets
126
+ - ticket_status_history
127
+ - ticket_expenses
128
+ - ticket_comments
129
+ - customer_communications
130
+
131
+ **Tasks:**
132
+ - tasks
133
+
134
+ **Inventory:**
135
+ - inventory_assignments
136
+
137
+ **Financial:**
138
+ - project_finance
139
+ - payment_logs
140
+ - user_payroll
141
+
142
+ **Documents & Notifications:**
143
+ - documents
144
+ - notifications
145
+
146
+ **System & Audit:**
147
+ - audit_logs
148
+
149
+ **Platform Billing:**
150
+ - organization_subscriptions
151
+ - usage_metrics
152
+
153
+ ## Rollback
154
+
155
+ If you need to rollback (not recommended):
156
+
157
+ ```sql
158
+ BEGIN;
159
+
160
+ -- Reverse all renames
161
+ ALTER TABLE clients RENAME COLUMN additional_metadata TO metadata;
162
+ -- ... (repeat for all tables)
163
+
164
+ COMMIT;
165
+ ```
166
+
167
+ ## Benefits
168
+
169
+ 1. ✅ **No SQLAlchemy Conflicts** - Clean model definitions
170
+ 2. ✅ **Better Naming** - More descriptive column name
171
+ 3. ✅ **Consistent** - All tables follow same pattern
172
+ 4. ✅ **Future-Proof** - No workarounds needed
173
+
174
+ ## Checklist
175
+
176
+ - [ ] Run SQL migration in Supabase
177
+ - [ ] Verify all columns renamed (0 `metadata`, 32 `additional_metadata`)
178
+ - [ ] Update User model (already done)
179
+ - [ ] Test application startup
180
+ - [ ] Update schema.sql documentation
181
+ - [ ] Create future models with `additional_metadata`
182
+
183
+ ## Notes
184
+
185
+ - This is a **one-time migration**
186
+ - Run it **before** you have production data
187
+ - The migration is **transactional** (all or nothing)
188
+ - **No data loss** - just column rename
docs/schema/schema.sql CHANGED
@@ -138,7 +138,7 @@ CREATE TABLE clients (
138
  website TEXT,
139
  is_active BOOLEAN DEFAULT TRUE,
140
  default_sla_days INTEGER, -- Default SLA window (days) for jobs under this client
141
- metadata JSONB DEFAULT '{}', -- Flexible storage for additional fields
142
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
143
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
144
  deleted_at TIMESTAMP WITH TIME ZONE NULL
@@ -156,7 +156,7 @@ CREATE TABLE contractors (
156
  main_phone TEXT,
157
  is_active BOOLEAN DEFAULT TRUE,
158
  competencies JSONB DEFAULT '{}', -- Array of specializations (e.g., ["FTTH", "Fixed Wireless", "Fiber Splicing"])
159
- metadata JSONB DEFAULT '{}', -- Flexible storage for additional fields
160
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
161
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
162
  deleted_at TIMESTAMP WITH TIME ZONE NULL,
@@ -216,7 +216,7 @@ CREATE TABLE users (
216
  current_location_updated_at TIMESTAMP WITH TIME ZONE,
217
 
218
  -- Additional metadata for future flexibility
219
- metadata JSONB DEFAULT '{}',
220
 
221
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
222
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -480,7 +480,7 @@ CREATE TABLE projects (
480
  activation_requirements JSONB DEFAULT '[]',
481
 
482
  -- Additional metadata for future flexibility
483
- metadata JSONB DEFAULT '{}',
484
 
485
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
486
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -531,7 +531,7 @@ CREATE TABLE project_subcontractors (
531
 
532
  -- Metadata
533
  notes TEXT,
534
- metadata JSONB DEFAULT '{}',
535
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
536
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
537
  deleted_at TIMESTAMP WITH TIME ZONE NULL
@@ -580,7 +580,7 @@ CREATE TABLE project_regions (
580
 
581
  -- Metadata
582
  notes TEXT,
583
- metadata JSONB DEFAULT '{}',
584
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
585
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
586
  deleted_at TIMESTAMP WITH TIME ZONE NULL
@@ -696,7 +696,7 @@ CREATE TABLE timesheets (
696
 
697
  -- Metadata
698
  notes TEXT,
699
- metadata JSONB DEFAULT '{}', -- Additional data (e.g., device info, ticket IDs worked on)
700
 
701
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
702
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -767,7 +767,7 @@ CREATE TABLE customers (
767
  is_active BOOLEAN DEFAULT TRUE,
768
 
769
  -- Additional metadata for future flexibility
770
- metadata JSONB DEFAULT '{}',
771
 
772
  -- Timestamps
773
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -845,7 +845,7 @@ CREATE TABLE sales_orders (
845
 
846
  -- Metadata
847
  notes TEXT,
848
- metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
849
  submitted_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
850
  submitted_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
851
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -932,7 +932,7 @@ CREATE TABLE subscriptions (
932
 
933
  -- Metadata
934
  notes TEXT,
935
- metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
936
  activated_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
937
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
938
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -987,7 +987,7 @@ CREATE TABLE incidents (
987
  cancellation_reason TEXT, -- Why incident was cancelled (if status='cancelled')
988
 
989
  -- Metadata
990
- metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
991
 
992
  -- Timestamps
993
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -1129,7 +1129,7 @@ CREATE TABLE tickets (
1129
 
1130
  -- Metadata
1131
  notes TEXT,
1132
- metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
1133
 
1134
  -- Concurrency Control (Optimistic Locking)
1135
  version INTEGER DEFAULT 1 NOT NULL,
@@ -1350,7 +1350,7 @@ CREATE TABLE ticket_status_history (
1350
 
1351
  -- Metadata
1352
  notes TEXT,
1353
- metadata JSONB DEFAULT '{}', -- Device info, IP address, etc.
1354
 
1355
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1356
  deleted_at TIMESTAMP WITH TIME ZONE NULL,
@@ -1401,7 +1401,7 @@ CREATE TABLE ticket_comments (
1401
  edited_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
1402
 
1403
  -- Metadata
1404
- metadata JSONB DEFAULT '{}',
1405
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1406
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1407
  deleted_at TIMESTAMP WITH TIME ZONE NULL,
@@ -1455,7 +1455,7 @@ CREATE TABLE customer_communications (
1455
  followup_notes TEXT,
1456
 
1457
  -- Metadata
1458
- metadata JSONB DEFAULT '{}', -- Additional data (call duration, recording URL, etc.)
1459
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1460
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1461
 
@@ -1542,7 +1542,7 @@ CREATE TABLE ticket_expenses (
1542
 
1543
  -- Metadata
1544
  notes TEXT,
1545
- metadata JSONB DEFAULT '{}', -- Can store split details: {"split_with": ["user-id-1", "user-id-2"], "split_amount": 500}
1546
 
1547
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1548
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -1612,7 +1612,7 @@ CREATE TABLE tasks (
1612
 
1613
  -- Metadata
1614
  notes TEXT,
1615
- metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
1616
  created_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
1617
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1618
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -1838,7 +1838,7 @@ CREATE TABLE inventory_assignments (
1838
 
1839
  -- Notes (optional)
1840
  notes TEXT,
1841
- metadata JSONB DEFAULT '{}',
1842
 
1843
  -- Concurrency Control (Optimistic Locking)
1844
  version INTEGER DEFAULT 1 NOT NULL,
@@ -2001,7 +2001,7 @@ CREATE TABLE project_finance (
2001
 
2002
  -- Metadata
2003
  notes TEXT,
2004
- metadata JSONB DEFAULT '{}', -- Additional data (e.g., split payments, installment info)
2005
 
2006
  -- Concurrency Control (Optimistic Locking)
2007
  version INTEGER DEFAULT 1 NOT NULL,
@@ -2127,7 +2127,7 @@ CREATE TABLE payment_logs (
2127
 
2128
  -- Additional context
2129
  notes TEXT, -- Admin notes
2130
- metadata JSONB DEFAULT '{}', -- Additional data (webhook headers, etc.)
2131
 
2132
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2133
 
@@ -2189,7 +2189,7 @@ CREATE TABLE user_payroll (
2189
  paid_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
2190
 
2191
  -- Metadata
2192
- metadata JSONB DEFAULT '{}', -- Additional data (e.g., ticket IDs, bonus reasons)
2193
 
2194
  -- Concurrency Control (Optimistic Locking)
2195
  version INTEGER DEFAULT 1 NOT NULL,
@@ -2272,7 +2272,7 @@ CREATE TABLE documents (
2272
  -- Metadata
2273
  description TEXT,
2274
  tags JSONB DEFAULT '[]', -- Array of tags for search
2275
- metadata JSONB DEFAULT '{}', -- Additional metadata (e.g., GPS coordinates for photos, OCR text)
2276
 
2277
  -- Access Control
2278
  uploaded_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
@@ -2322,7 +2322,7 @@ CREATE TABLE user_document_links (
2322
  issued_at DATE,
2323
  expires_at DATE,
2324
  notes TEXT,
2325
- metadata JSONB DEFAULT '{}', -- Additional metadata (e.g., GPS coordinates for photos, OCR text)
2326
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2327
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2328
 
@@ -2364,7 +2364,7 @@ CREATE TABLE notifications (
2364
  failure_reason TEXT,
2365
 
2366
  -- Metadata
2367
- metadata JSONB DEFAULT '{}', -- Additional data (e.g., action buttons, deep links)
2368
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now())
2369
  );
2370
 
@@ -2459,7 +2459,7 @@ CREATE TABLE audit_logs (
2459
  longitude DOUBLE PRECISION,
2460
 
2461
  -- Metadata
2462
- metadata JSONB DEFAULT '{}', -- Additional context
2463
 
2464
  -- Timestamp
2465
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -2550,7 +2550,7 @@ CREATE TABLE contractor_invoices (
2550
  -- Metadata
2551
  notes TEXT,
2552
  terms_and_conditions TEXT,
2553
- metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
2554
  created_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
2555
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2556
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
@@ -2957,14 +2957,14 @@ ON CONFLICT (config_key) DO NOTHING;
2957
  -- These are critical for performance when filtering or searching within JSONB data
2958
 
2959
  -- clients & contractors
2960
- CREATE INDEX idx_clients_metadata_gin ON clients USING GIN(metadata);
2961
  CREATE INDEX idx_contractors_competencies_gin ON contractors USING GIN(competencies);
2962
- CREATE INDEX idx_contractors_metadata_gin ON contractors USING GIN(metadata);
2963
 
2964
  -- users
2965
  CREATE INDEX idx_users_health_info_gin ON users USING GIN(health_info);
2966
  CREATE INDEX idx_users_ppe_sizes_gin ON users USING GIN(ppe_sizes);
2967
- CREATE INDEX idx_users_metadata_gin ON users USING GIN(metadata);
2968
 
2969
  -- User Preferences
2970
  CREATE INDEX idx_user_preferences_additional_settings_gin ON user_preferences USING GIN(additional_settings);
@@ -2974,39 +2974,39 @@ CREATE INDEX idx_projects_budget_gin ON projects USING GIN(budget);
2974
  CREATE INDEX idx_projects_inventory_requirements_gin ON projects USING GIN(inventory_requirements);
2975
  CREATE INDEX idx_projects_photo_requirements_gin ON projects USING GIN(photo_requirements);
2976
  CREATE INDEX idx_projects_activation_requirements_gin ON projects USING GIN(activation_requirements);
2977
- CREATE INDEX idx_projects_metadata_gin ON projects USING GIN(metadata);
2978
 
2979
  -- Project Regions
2980
- CREATE INDEX idx_project_regions_metadata_gin ON project_regions USING GIN(metadata);
2981
 
2982
  -- timesheets
2983
- CREATE INDEX idx_timesheets_metadata_gin ON timesheets USING GIN(metadata);
2984
 
2985
  -- customers
2986
- CREATE INDEX idx_customers_metadata_gin ON customers USING GIN(metadata);
2987
 
2988
  -- Sales Orders
2989
- CREATE INDEX idx_sales_orders_metadata_gin ON sales_orders USING GIN(metadata);
2990
 
2991
  -- subscriptions
2992
  CREATE INDEX idx_subscriptions_equipment_details_gin ON subscriptions USING GIN(equipment_details);
2993
  CREATE INDEX idx_subscriptions_activation_details_gin ON subscriptions USING GIN(activation_details);
2994
- CREATE INDEX idx_subscriptions_metadata_gin ON subscriptions USING GIN(metadata);
2995
 
2996
  -- incidents
2997
- CREATE INDEX idx_incidents_metadata_gin ON incidents USING GIN(metadata);
2998
 
2999
  -- tickets
3000
- CREATE INDEX idx_tickets_metadata_gin ON tickets USING GIN(metadata);
3001
 
3002
  -- Ticket Status History
3003
- CREATE INDEX idx_ticket_status_history_metadata_gin ON ticket_status_history USING GIN(metadata);
3004
 
3005
  -- Ticket Expenses
3006
- CREATE INDEX idx_ticket_expenses_metadata_gin ON ticket_expenses USING GIN(metadata);
3007
 
3008
  -- tasks
3009
- CREATE INDEX idx_tasks_metadata_gin ON tasks USING GIN(metadata);
3010
 
3011
  -- Project Inventory
3012
  CREATE INDEX idx_project_inventory_serial_numbers_gin ON project_inventory USING GIN(serial_numbers);
@@ -3016,29 +3016,29 @@ CREATE INDEX idx_inventory_distribution_serial_numbers_gin ON project_inventory_
3016
 
3017
  -- Inventory Assignments
3018
  CREATE INDEX idx_inventory_assignments_unit_contents_gin ON inventory_assignments USING GIN(unit_contents);
3019
- CREATE INDEX idx_inventory_assignments_metadata_gin ON inventory_assignments USING GIN(metadata);
3020
 
3021
  -- Project Finance
3022
- CREATE INDEX idx_project_finance_metadata_gin ON project_finance USING GIN(metadata);
3023
 
3024
  -- Payment Logs
3025
  CREATE INDEX idx_payment_logs_request_payload_gin ON payment_logs USING GIN(request_payload);
3026
  CREATE INDEX idx_payment_logs_response_payload_gin ON payment_logs USING GIN(response_payload);
3027
  CREATE INDEX idx_payment_logs_device_info_gin ON payment_logs USING GIN(device_info);
3028
- CREATE INDEX idx_payment_logs_metadata_gin ON payment_logs USING GIN(metadata);
3029
 
3030
  -- documents
3031
- CREATE INDEX idx_documents_metadata_gin ON documents USING GIN(metadata);
3032
 
3033
  -- notifications
3034
- CREATE INDEX idx_notifications_metadata_gin ON notifications USING GIN(metadata);
3035
 
3036
  -- System Configuration
3037
  CREATE INDEX idx_system_configuration_validation_rules_gin ON system_configuration USING GIN(validation_rules);
3038
 
3039
  -- Audit Logs
3040
  CREATE INDEX idx_audit_logs_changes_gin ON audit_logs USING GIN(changes);
3041
- CREATE INDEX idx_audit_logs_metadata_gin ON audit_logs USING GIN(metadata);
3042
 
3043
  -- External Integrations: REMOVED (over-engineering for MVP)
3044
 
@@ -3046,13 +3046,13 @@ CREATE INDEX idx_audit_logs_metadata_gin ON audit_logs USING GIN(metadata);
3046
  CREATE INDEX idx_billing_plans_features_gin ON billing_plans USING GIN(features);
3047
 
3048
  -- Organization subscriptions
3049
- CREATE INDEX idx_org_subscriptions_metadata_gin ON organization_subscriptions USING GIN(metadata);
3050
 
3051
  -- invoices
3052
  CREATE INDEX idx_invoices_line_items_gin ON invoices USING GIN(line_items);
3053
 
3054
  -- Usage Metrics
3055
- CREATE INDEX idx_usage_metrics_metadata_gin ON usage_metrics USING GIN(metadata);
3056
 
3057
  COMMENT ON INDEX idx_clients_metadata_gin IS 'GIN index for fast JSONB queries on clients.metadata';
3058
  COMMENT ON INDEX idx_projects_activation_requirements_gin IS 'GIN index for querying dynamic activation requirements per project';
 
138
  website TEXT,
139
  is_active BOOLEAN DEFAULT TRUE,
140
  default_sla_days INTEGER, -- Default SLA window (days) for jobs under this client
141
+ additional_metadata JSONB DEFAULT '{}', -- Flexible storage for additional fields
142
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
143
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
144
  deleted_at TIMESTAMP WITH TIME ZONE NULL
 
156
  main_phone TEXT,
157
  is_active BOOLEAN DEFAULT TRUE,
158
  competencies JSONB DEFAULT '{}', -- Array of specializations (e.g., ["FTTH", "Fixed Wireless", "Fiber Splicing"])
159
+ additional_metadata JSONB DEFAULT '{}', -- Flexible storage for additional fields
160
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
161
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
162
  deleted_at TIMESTAMP WITH TIME ZONE NULL,
 
216
  current_location_updated_at TIMESTAMP WITH TIME ZONE,
217
 
218
  -- Additional metadata for future flexibility
219
+ additional_metadata JSONB DEFAULT '{}',
220
 
221
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
222
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
480
  activation_requirements JSONB DEFAULT '[]',
481
 
482
  -- Additional metadata for future flexibility
483
+ additional_metadata JSONB DEFAULT '{}',
484
 
485
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
486
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
531
 
532
  -- Metadata
533
  notes TEXT,
534
+ additional_metadata JSONB DEFAULT '{}',
535
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
536
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
537
  deleted_at TIMESTAMP WITH TIME ZONE NULL
 
580
 
581
  -- Metadata
582
  notes TEXT,
583
+ additional_metadata JSONB DEFAULT '{}',
584
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
585
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
586
  deleted_at TIMESTAMP WITH TIME ZONE NULL
 
696
 
697
  -- Metadata
698
  notes TEXT,
699
+ additional_metadata JSONB DEFAULT '{}', -- Additional data (e.g., device info, ticket IDs worked on)
700
 
701
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
702
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
767
  is_active BOOLEAN DEFAULT TRUE,
768
 
769
  -- Additional metadata for future flexibility
770
+ additional_metadata JSONB DEFAULT '{}',
771
 
772
  -- Timestamps
773
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
845
 
846
  -- Metadata
847
  notes TEXT,
848
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
849
  submitted_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
850
  submitted_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
851
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
932
 
933
  -- Metadata
934
  notes TEXT,
935
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
936
  activated_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
937
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
938
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
987
  cancellation_reason TEXT, -- Why incident was cancelled (if status='cancelled')
988
 
989
  -- Metadata
990
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
991
 
992
  -- Timestamps
993
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
1129
 
1130
  -- Metadata
1131
  notes TEXT,
1132
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
1133
 
1134
  -- Concurrency Control (Optimistic Locking)
1135
  version INTEGER DEFAULT 1 NOT NULL,
 
1350
 
1351
  -- Metadata
1352
  notes TEXT,
1353
+ additional_metadata JSONB DEFAULT '{}', -- Device info, IP address, etc.
1354
 
1355
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1356
  deleted_at TIMESTAMP WITH TIME ZONE NULL,
 
1401
  edited_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
1402
 
1403
  -- Metadata
1404
+ additional_metadata JSONB DEFAULT '{}',
1405
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1406
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1407
  deleted_at TIMESTAMP WITH TIME ZONE NULL,
 
1455
  followup_notes TEXT,
1456
 
1457
  -- Metadata
1458
+ additional_metadata JSONB DEFAULT '{}', -- Additional data (call duration, recording URL, etc.)
1459
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1460
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1461
 
 
1542
 
1543
  -- Metadata
1544
  notes TEXT,
1545
+ additional_metadata JSONB DEFAULT '{}', -- Can store split details: {"split_with": ["user-id-1", "user-id-2"], "split_amount": 500}
1546
 
1547
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1548
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
1612
 
1613
  -- Metadata
1614
  notes TEXT,
1615
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
1616
  created_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
1617
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
1618
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
1838
 
1839
  -- Notes (optional)
1840
  notes TEXT,
1841
+ additional_metadata JSONB DEFAULT '{}',
1842
 
1843
  -- Concurrency Control (Optimistic Locking)
1844
  version INTEGER DEFAULT 1 NOT NULL,
 
2001
 
2002
  -- Metadata
2003
  notes TEXT,
2004
+ additional_metadata JSONB DEFAULT '{}', -- Additional data (e.g., split payments, installment info)
2005
 
2006
  -- Concurrency Control (Optimistic Locking)
2007
  version INTEGER DEFAULT 1 NOT NULL,
 
2127
 
2128
  -- Additional context
2129
  notes TEXT, -- Admin notes
2130
+ additional_metadata JSONB DEFAULT '{}', -- Additional data (webhook headers, etc.)
2131
 
2132
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2133
 
 
2189
  paid_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
2190
 
2191
  -- Metadata
2192
+ additional_metadata JSONB DEFAULT '{}', -- Additional data (e.g., ticket IDs, bonus reasons)
2193
 
2194
  -- Concurrency Control (Optimistic Locking)
2195
  version INTEGER DEFAULT 1 NOT NULL,
 
2272
  -- Metadata
2273
  description TEXT,
2274
  tags JSONB DEFAULT '[]', -- Array of tags for search
2275
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata (e.g., GPS coordinates for photos, OCR text)
2276
 
2277
  -- Access Control
2278
  uploaded_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
 
2322
  issued_at DATE,
2323
  expires_at DATE,
2324
  notes TEXT,
2325
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata (e.g., GPS coordinates for photos, OCR text)
2326
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2327
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2328
 
 
2364
  failure_reason TEXT,
2365
 
2366
  -- Metadata
2367
+ additional_metadata JSONB DEFAULT '{}', -- Additional data (e.g., action buttons, deep links)
2368
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now())
2369
  );
2370
 
 
2459
  longitude DOUBLE PRECISION,
2460
 
2461
  -- Metadata
2462
+ additional_metadata JSONB DEFAULT '{}', -- Additional context
2463
 
2464
  -- Timestamp
2465
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
2550
  -- Metadata
2551
  notes TEXT,
2552
  terms_and_conditions TEXT,
2553
+ additional_metadata JSONB DEFAULT '{}', -- Additional metadata for future flexibility
2554
  created_by_user_id UUID REFERENCES users (id) ON DELETE SET NULL,
2555
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
2556
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
 
2957
  -- These are critical for performance when filtering or searching within JSONB data
2958
 
2959
  -- clients & contractors
2960
+ CREATE INDEX idx_clients_metadata_gin ON clients USING GIN(additional_metadata);
2961
  CREATE INDEX idx_contractors_competencies_gin ON contractors USING GIN(competencies);
2962
+ CREATE INDEX idx_contractors_metadata_gin ON contractors USING GIN(additional_metadata);
2963
 
2964
  -- users
2965
  CREATE INDEX idx_users_health_info_gin ON users USING GIN(health_info);
2966
  CREATE INDEX idx_users_ppe_sizes_gin ON users USING GIN(ppe_sizes);
2967
+ CREATE INDEX idx_users_metadata_gin ON users USING GIN(additional_metadata);
2968
 
2969
  -- User Preferences
2970
  CREATE INDEX idx_user_preferences_additional_settings_gin ON user_preferences USING GIN(additional_settings);
 
2974
  CREATE INDEX idx_projects_inventory_requirements_gin ON projects USING GIN(inventory_requirements);
2975
  CREATE INDEX idx_projects_photo_requirements_gin ON projects USING GIN(photo_requirements);
2976
  CREATE INDEX idx_projects_activation_requirements_gin ON projects USING GIN(activation_requirements);
2977
+ CREATE INDEX idx_projects_metadata_gin ON projects USING GIN(additional_metadata);
2978
 
2979
  -- Project Regions
2980
+ CREATE INDEX idx_project_regions_metadata_gin ON project_regions USING GIN(additional_metadata);
2981
 
2982
  -- timesheets
2983
+ CREATE INDEX idx_timesheets_metadata_gin ON timesheets USING GIN(additional_metadata);
2984
 
2985
  -- customers
2986
+ CREATE INDEX idx_customers_metadata_gin ON customers USING GIN(additional_metadata);
2987
 
2988
  -- Sales Orders
2989
+ CREATE INDEX idx_sales_orders_metadata_gin ON sales_orders USING GIN(additional_metadata);
2990
 
2991
  -- subscriptions
2992
  CREATE INDEX idx_subscriptions_equipment_details_gin ON subscriptions USING GIN(equipment_details);
2993
  CREATE INDEX idx_subscriptions_activation_details_gin ON subscriptions USING GIN(activation_details);
2994
+ CREATE INDEX idx_subscriptions_metadata_gin ON subscriptions USING GIN(additional_metadata);
2995
 
2996
  -- incidents
2997
+ CREATE INDEX idx_incidents_metadata_gin ON incidents USING GIN(additional_metadata);
2998
 
2999
  -- tickets
3000
+ CREATE INDEX idx_tickets_metadata_gin ON tickets USING GIN(additional_metadata);
3001
 
3002
  -- Ticket Status History
3003
+ CREATE INDEX idx_ticket_status_history_metadata_gin ON ticket_status_history USING GIN(additional_metadata);
3004
 
3005
  -- Ticket Expenses
3006
+ CREATE INDEX idx_ticket_expenses_metadata_gin ON ticket_expenses USING GIN(additional_metadata);
3007
 
3008
  -- tasks
3009
+ CREATE INDEX idx_tasks_metadata_gin ON tasks USING GIN(additional_metadata);
3010
 
3011
  -- Project Inventory
3012
  CREATE INDEX idx_project_inventory_serial_numbers_gin ON project_inventory USING GIN(serial_numbers);
 
3016
 
3017
  -- Inventory Assignments
3018
  CREATE INDEX idx_inventory_assignments_unit_contents_gin ON inventory_assignments USING GIN(unit_contents);
3019
+ CREATE INDEX idx_inventory_assignments_metadata_gin ON inventory_assignments USING GIN(additional_metadata);
3020
 
3021
  -- Project Finance
3022
+ CREATE INDEX idx_project_finance_metadata_gin ON project_finance USING GIN(additional_metadata);
3023
 
3024
  -- Payment Logs
3025
  CREATE INDEX idx_payment_logs_request_payload_gin ON payment_logs USING GIN(request_payload);
3026
  CREATE INDEX idx_payment_logs_response_payload_gin ON payment_logs USING GIN(response_payload);
3027
  CREATE INDEX idx_payment_logs_device_info_gin ON payment_logs USING GIN(device_info);
3028
+ CREATE INDEX idx_payment_logs_metadata_gin ON payment_logs USING GIN(additional_metadata);
3029
 
3030
  -- documents
3031
+ CREATE INDEX idx_documents_metadata_gin ON documents USING GIN(additional_metadata);
3032
 
3033
  -- notifications
3034
+ CREATE INDEX idx_notifications_metadata_gin ON notifications USING GIN(additional_metadata);
3035
 
3036
  -- System Configuration
3037
  CREATE INDEX idx_system_configuration_validation_rules_gin ON system_configuration USING GIN(validation_rules);
3038
 
3039
  -- Audit Logs
3040
  CREATE INDEX idx_audit_logs_changes_gin ON audit_logs USING GIN(changes);
3041
+ CREATE INDEX idx_audit_logs_metadata_gin ON audit_logs USING GIN(additional_metadata);
3042
 
3043
  -- External Integrations: REMOVED (over-engineering for MVP)
3044
 
 
3046
  CREATE INDEX idx_billing_plans_features_gin ON billing_plans USING GIN(features);
3047
 
3048
  -- Organization subscriptions
3049
+ CREATE INDEX idx_org_subscriptions_metadata_gin ON organization_subscriptions USING GIN(additional_metadata);
3050
 
3051
  -- invoices
3052
  CREATE INDEX idx_invoices_line_items_gin ON invoices USING GIN(line_items);
3053
 
3054
  -- Usage Metrics
3055
+ CREATE INDEX idx_usage_metrics_metadata_gin ON usage_metrics USING GIN(additional_metadata);
3056
 
3057
  COMMENT ON INDEX idx_clients_metadata_gin IS 'GIN index for fast JSONB queries on clients.metadata';
3058
  COMMENT ON INDEX idx_projects_activation_requirements_gin IS 'GIN index for querying dynamic activation requirements per project';
migrations/001_rename_metadata_to_additional_metadata.sql ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- ============================================
2
+ -- Migration: Rename 'metadata' to 'additional_metadata'
3
+ -- Date: 2025-11-15
4
+ -- Reason: 'metadata' is a reserved word in SQLAlchemy ORM
5
+ -- ============================================
6
+ -- This migration renames the 'metadata' JSONB column to 'additional_metadata'
7
+ -- across all tables to avoid conflicts with SQLAlchemy's internal metadata attribute.
8
+ --
9
+ -- INSTRUCTIONS:
10
+ -- 1. Review this migration carefully
11
+ -- 2. Run in Supabase SQL Editor
12
+ -- 3. Verify all tables updated successfully
13
+ -- 4. Update application code to use 'additional_metadata'
14
+ --
15
+ -- ROLLBACK:
16
+ -- To rollback, replace 'additional_metadata' with 'metadata' in all ALTER statements below
17
+ -- ============================================
18
+
19
+ -- Start transaction
20
+ BEGIN;
21
+
22
+ -- 1. ORGANIZATIONS & USERS
23
+ ALTER TABLE clients RENAME COLUMN metadata TO additional_metadata;
24
+ ALTER TABLE contractors RENAME COLUMN metadata TO additional_metadata;
25
+ ALTER TABLE users RENAME COLUMN metadata TO additional_metadata;
26
+ ALTER TABLE user_financial_accounts RENAME COLUMN metadata TO additional_metadata;
27
+ ALTER TABLE user_asset_assignments RENAME COLUMN metadata TO additional_metadata;
28
+ ALTER TABLE user_document_links RENAME COLUMN metadata TO additional_metadata;
29
+
30
+ -- 2. PROJECTS & TEAMS
31
+ ALTER TABLE projects RENAME COLUMN metadata TO additional_metadata;
32
+ ALTER TABLE project_regions RENAME COLUMN metadata TO additional_metadata;
33
+ ALTER TABLE project_subcontractors RENAME COLUMN metadata TO additional_metadata;
34
+ ALTER TABLE timesheets RENAME COLUMN metadata TO additional_metadata;
35
+
36
+ -- 3. CUSTOMERS & SALES
37
+ ALTER TABLE customers RENAME COLUMN metadata TO additional_metadata;
38
+ ALTER TABLE sales_orders RENAME COLUMN metadata TO additional_metadata;
39
+ ALTER TABLE subscriptions RENAME COLUMN metadata TO additional_metadata;
40
+ ALTER TABLE incidents RENAME COLUMN metadata TO additional_metadata;
41
+
42
+ -- 4. TICKETS & WORK ORDERS
43
+ ALTER TABLE tickets RENAME COLUMN metadata TO additional_metadata;
44
+ ALTER TABLE ticket_status_history RENAME COLUMN metadata TO additional_metadata;
45
+ ALTER TABLE ticket_expenses RENAME COLUMN metadata TO additional_metadata;
46
+ ALTER TABLE ticket_comments RENAME COLUMN metadata TO additional_metadata;
47
+ ALTER TABLE customer_communications RENAME COLUMN metadata TO additional_metadata;
48
+
49
+ -- 5. TASKS
50
+ ALTER TABLE tasks RENAME COLUMN metadata TO additional_metadata;
51
+
52
+ -- 6. INVENTORY
53
+ ALTER TABLE inventory_assignments RENAME COLUMN metadata TO additional_metadata;
54
+
55
+ -- 7. FINANCIAL MANAGEMENT
56
+ ALTER TABLE project_finance RENAME COLUMN metadata TO additional_metadata;
57
+ ALTER TABLE payment_logs RENAME COLUMN metadata TO additional_metadata;
58
+ ALTER TABLE user_payroll RENAME COLUMN metadata TO additional_metadata;
59
+
60
+ -- 8. DOCUMENTS & NOTIFICATIONS
61
+ ALTER TABLE documents RENAME COLUMN metadata TO additional_metadata;
62
+ ALTER TABLE notifications RENAME COLUMN metadata TO additional_metadata;
63
+
64
+ -- 9. SYSTEM & AUDIT
65
+ ALTER TABLE audit_logs RENAME COLUMN metadata TO additional_metadata;
66
+
67
+ -- 10. PLATFORM BILLING
68
+ ALTER TABLE organization_subscriptions RENAME COLUMN metadata TO additional_metadata;
69
+ ALTER TABLE usage_metrics RENAME COLUMN metadata TO additional_metadata;
70
+
71
+ -- Commit transaction
72
+ COMMIT;
73
+
74
+ -- ============================================
75
+ -- Verification Query
76
+ -- ============================================
77
+ -- Run this to verify all columns were renamed:
78
+ /*
79
+ SELECT
80
+ table_name,
81
+ column_name,
82
+ data_type
83
+ FROM information_schema.columns
84
+ WHERE table_schema = 'public'
85
+ AND column_name IN ('metadata', 'additional_metadata')
86
+ ORDER BY
87
+ CASE WHEN column_name = 'metadata' THEN 0 ELSE 1 END,
88
+ table_name;
89
+
90
+ -- Expected: 0 rows with 'metadata', 32 rows with 'additional_metadata'
91
+ */
92
+
93
+ -- ============================================
94
+ -- Migration Complete
95
+ -- ============================================
96
+ -- Next steps:
97
+ -- 1. Update schema.sql documentation
98
+ -- 2. Update SQLAlchemy models to use 'additional_metadata'
99
+ -- 3. Test application startup
100
+ -- ============================================
migrations/001_rename_metadata_to_additional_metadata_safe.sql ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- ============================================
2
+ -- Migration: Rename 'metadata' to 'additional_metadata' (SAFE VERSION)
3
+ -- Date: 2025-11-15
4
+ -- Reason: 'metadata' is a reserved word in SQLAlchemy ORM
5
+ -- ============================================
6
+ -- This migration safely renames the 'metadata' JSONB column to 'additional_metadata'
7
+ -- It only attempts to rename columns that actually exist in the database.
8
+ --
9
+ -- INSTRUCTIONS:
10
+ -- 1. Run this in Supabase SQL Editor
11
+ -- 2. Check the output to see which tables were updated
12
+ -- 3. Verify with the verification query at the end
13
+ -- ============================================
14
+
15
+ -- Start transaction
16
+ BEGIN;
17
+
18
+ -- Use DO block to safely rename columns only if they exist
19
+ DO $$
20
+ DECLARE
21
+ table_record RECORD;
22
+ tables_to_check TEXT[] := ARRAY[
23
+ 'clients',
24
+ 'contractors',
25
+ 'users',
26
+ 'user_financial_accounts',
27
+ 'user_asset_assignments',
28
+ 'user_document_links',
29
+ 'projects',
30
+ 'project_regions',
31
+ 'project_subcontractors',
32
+ 'timesheets',
33
+ 'customers',
34
+ 'sales_orders',
35
+ 'subscriptions',
36
+ 'incidents',
37
+ 'tickets',
38
+ 'ticket_status_history',
39
+ 'ticket_expenses',
40
+ 'ticket_comments',
41
+ 'customer_communications',
42
+ 'tasks',
43
+ 'inventory_assignments',
44
+ 'project_finance',
45
+ 'payment_logs',
46
+ 'user_payroll',
47
+ 'documents',
48
+ 'notifications',
49
+ 'audit_logs',
50
+ 'organization_subscriptions',
51
+ 'usage_metrics'
52
+ ];
53
+ current_table TEXT;
54
+ column_exists BOOLEAN;
55
+ BEGIN
56
+ FOREACH current_table IN ARRAY tables_to_check
57
+ LOOP
58
+ -- Check if table exists and has metadata column
59
+ SELECT EXISTS (
60
+ SELECT 1
61
+ FROM information_schema.columns
62
+ WHERE table_schema = 'public'
63
+ AND table_name = current_table
64
+ AND column_name = 'metadata'
65
+ ) INTO column_exists;
66
+
67
+ IF column_exists THEN
68
+ -- Rename the column
69
+ EXECUTE format('ALTER TABLE %I RENAME COLUMN metadata TO additional_metadata', current_table);
70
+ RAISE NOTICE 'Renamed metadata column in table: %', current_table;
71
+ ELSE
72
+ RAISE NOTICE 'Skipped table (no metadata column): %', current_table;
73
+ END IF;
74
+ END LOOP;
75
+ END $$;
76
+
77
+ -- Commit transaction
78
+ COMMIT;
79
+
80
+ -- ============================================
81
+ -- Verification Query
82
+ -- ============================================
83
+ -- Run this to see which tables have metadata vs additional_metadata:
84
+ SELECT
85
+ table_name,
86
+ column_name,
87
+ data_type
88
+ FROM information_schema.columns
89
+ WHERE table_schema = 'public'
90
+ AND column_name IN ('metadata', 'additional_metadata')
91
+ ORDER BY
92
+ CASE WHEN column_name = 'metadata' THEN 0 ELSE 1 END,
93
+ table_name;
94
+
95
+ -- Expected: 0 rows with 'metadata', N rows with 'additional_metadata' (where N = number of tables that had metadata)
migrations/002_fix_contractor_invoices_metadata.sql ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- ============================================
2
+ -- Migration: Fix contractor_invoices metadata column
3
+ -- Date: 2025-11-15
4
+ -- Reason: This table was missed in the first migration
5
+ -- ============================================
6
+
7
+ BEGIN;
8
+
9
+ -- Rename metadata to additional_metadata in contractor_invoices
10
+ ALTER TABLE contractor_invoices RENAME COLUMN metadata TO additional_metadata;
11
+
12
+ COMMIT;
13
+
14
+ -- Verification: Should return 0 rows with 'metadata'
15
+ SELECT
16
+ table_name,
17
+ column_name,
18
+ data_type
19
+ FROM information_schema.columns
20
+ WHERE table_schema = 'public'
21
+ AND column_name = 'metadata';
src/app/models/user.py CHANGED
@@ -69,8 +69,8 @@ class User(BaseModel):
69
  current_longitude = Column(String(50), nullable=True) # DOUBLE PRECISION in DB
70
  current_location_updated_at = Column(DateTime(timezone=True), nullable=True)
71
 
72
- # Metadata (JSONB in schema) - renamed to avoid SQLAlchemy reserved word
73
- user_metadata = Column('metadata', JSONB, default={})
74
 
75
  def __repr__(self):
76
  return f"<User(email='{self.email}', name='{self.name}')>"
 
69
  current_longitude = Column(String(50), nullable=True) # DOUBLE PRECISION in DB
70
  current_location_updated_at = Column(DateTime(timezone=True), nullable=True)
71
 
72
+ # Additional Metadata (JSONB in schema)
73
+ additional_metadata = Column(JSONB, default={})
74
 
75
  def __repr__(self):
76
  return f"<User(email='{self.email}', name='{self.name}')>"