-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadmin_analytics.sql
More file actions
445 lines (418 loc) · 16.5 KB
/
admin_analytics.sql
File metadata and controls
445 lines (418 loc) · 16.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
-- ============================================
-- SEVASETU ADMIN ANALYTICS FUNCTIONS
-- ============================================
-- SECTION 1: Get analytics for system admin (all states)
CREATE OR REPLACE FUNCTION get_system_analytics()
RETURNS TABLE (
total_issues BIGINT,
pending_issues BIGINT,
in_progress_issues BIGINT,
resolved_issues BIGINT,
high_priority_issues BIGINT,
avg_priority_score NUMERIC,
issues_today BIGINT,
issues_this_week BIGINT,
total_offices BIGINT,
total_admins BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('assigned', 'in_progress'))::BIGINT as in_progress_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE)::BIGINT as high_priority_issues,
COALESCE(AVG(ci.priority_score), 0)::NUMERIC as avg_priority_score,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE)::BIGINT as issues_today,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE - INTERVAL '7 days')::BIGINT as issues_this_week,
(SELECT COUNT(*) FROM offices)::BIGINT as total_offices,
(SELECT COUNT(*) FROM admin_roles WHERE is_active = TRUE)::BIGINT as total_admins
FROM civic_issues ci;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 2: Get state-wise breakdown for system admin
CREATE OR REPLACE FUNCTION get_state_breakdown()
RETURNS TABLE (
state TEXT,
total_issues BIGINT,
pending_issues BIGINT,
resolved_issues BIGINT,
high_priority_issues BIGINT,
avg_resolution_days NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
ci.state,
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE)::BIGINT as high_priority_issues,
COALESCE(AVG(
CASE WHEN ci.status IN ('resolved', 'completed')
THEN EXTRACT(EPOCH FROM (ci.updated_at - ci.created_at)) / 86400
END
), 0)::NUMERIC as avg_resolution_days
FROM civic_issues ci
WHERE ci.state IS NOT NULL
GROUP BY ci.state
ORDER BY total_issues DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 3: Get analytics for state admin
CREATE OR REPLACE FUNCTION get_state_analytics(p_state TEXT)
RETURNS TABLE (
total_issues BIGINT,
pending_issues BIGINT,
in_progress_issues BIGINT,
resolved_issues BIGINT,
high_priority_issues BIGINT,
avg_priority_score NUMERIC,
issues_today BIGINT,
issues_this_week BIGINT,
total_offices BIGINT,
total_workers BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('assigned', 'in_progress'))::BIGINT as in_progress_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE)::BIGINT as high_priority_issues,
COALESCE(AVG(ci.priority_score), 0)::NUMERIC as avg_priority_score,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE)::BIGINT as issues_today,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE - INTERVAL '7 days')::BIGINT as issues_this_week,
(SELECT COUNT(*) FROM offices WHERE offices.state = p_state)::BIGINT as total_offices,
(SELECT COUNT(*) FROM admin_roles WHERE admin_roles.state = p_state AND role = 'worker' AND is_active = TRUE)::BIGINT as total_workers
FROM civic_issues ci
WHERE ci.state = p_state;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 4: Get department breakdown for state admin
CREATE OR REPLACE FUNCTION get_department_breakdown(p_state TEXT)
RETURNS TABLE (
department TEXT,
total_issues BIGINT,
pending_issues BIGINT,
resolved_issues BIGINT,
high_priority_issues BIGINT,
avg_resolution_days NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
ci.assigned_department as department,
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE)::BIGINT as high_priority_issues,
COALESCE(AVG(
CASE WHEN ci.status IN ('resolved', 'completed')
THEN EXTRACT(EPOCH FROM (ci.updated_at - ci.created_at)) / 86400
END
), 0)::NUMERIC as avg_resolution_days
FROM civic_issues ci
WHERE ci.state = p_state AND ci.assigned_department IS NOT NULL
GROUP BY ci.assigned_department
ORDER BY total_issues DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 5: Get analytics for department admin
CREATE OR REPLACE FUNCTION get_department_analytics(p_state TEXT, p_department TEXT)
RETURNS TABLE (
total_issues BIGINT,
pending_issues BIGINT,
in_progress_issues BIGINT,
resolved_issues BIGINT,
high_priority_issues BIGINT,
avg_priority_score NUMERIC,
issues_today BIGINT,
issues_this_week BIGINT,
total_offices BIGINT,
total_workers BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('assigned', 'in_progress'))::BIGINT as in_progress_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE)::BIGINT as high_priority_issues,
COALESCE(AVG(ci.priority_score), 0)::NUMERIC as avg_priority_score,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE)::BIGINT as issues_today,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE - INTERVAL '7 days')::BIGINT as issues_this_week,
(SELECT COUNT(*) FROM offices WHERE offices.state = p_state AND offices.department = p_department)::BIGINT as total_offices,
(SELECT COUNT(*) FROM admin_roles WHERE admin_roles.state = p_state AND admin_roles.department = p_department AND role = 'worker' AND is_active = TRUE)::BIGINT as total_workers
FROM civic_issues ci
WHERE ci.state = p_state AND ci.assigned_department = p_department;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 6: Get office breakdown for department admin
CREATE OR REPLACE FUNCTION get_office_breakdown(p_state TEXT, p_department TEXT)
RETURNS TABLE (
office_id UUID,
office_name TEXT,
district TEXT,
total_issues BIGINT,
pending_issues BIGINT,
resolved_issues BIGINT,
worker_count BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
o.id as office_id,
o.name as office_name,
o.district,
COUNT(ci.id)::BIGINT as total_issues,
COUNT(ci.id) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(ci.id) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
(SELECT COUNT(*) FROM admin_roles ar WHERE ar.office_id = o.id AND ar.role = 'worker' AND ar.is_active = TRUE)::BIGINT as worker_count
FROM offices o
LEFT JOIN civic_issues ci ON ci.assigned_office_id = o.id
WHERE o.state = p_state AND o.department = p_department
GROUP BY o.id, o.name, o.district
ORDER BY total_issues DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 7: Get analytics for office admin
CREATE OR REPLACE FUNCTION get_office_analytics(p_office_id UUID)
RETURNS TABLE (
total_issues BIGINT,
pending_issues BIGINT,
in_progress_issues BIGINT,
resolved_issues BIGINT,
high_priority_issues BIGINT,
avg_priority_score NUMERIC,
issues_today BIGINT,
issues_this_week BIGINT,
total_workers BIGINT,
avg_resolution_hours NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('assigned', 'in_progress'))::BIGINT as in_progress_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE)::BIGINT as high_priority_issues,
COALESCE(AVG(ci.priority_score), 0)::NUMERIC as avg_priority_score,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE)::BIGINT as issues_today,
COUNT(*) FILTER (WHERE ci.created_at >= CURRENT_DATE - INTERVAL '7 days')::BIGINT as issues_this_week,
(SELECT COUNT(*) FROM admin_roles WHERE office_id = p_office_id AND role = 'worker' AND is_active = TRUE)::BIGINT as total_workers,
COALESCE(AVG(
CASE WHEN ci.status IN ('resolved', 'completed')
THEN EXTRACT(EPOCH FROM (ci.updated_at - ci.created_at)) / 3600
END
), 0)::NUMERIC as avg_resolution_hours
FROM civic_issues ci
WHERE ci.assigned_office_id = p_office_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 8: Get worker stats for office admin
CREATE OR REPLACE FUNCTION get_worker_stats(p_office_id UUID)
RETURNS TABLE (
worker_id UUID,
worker_email TEXT,
assigned_issues BIGINT,
resolved_issues BIGINT,
in_progress_issues BIGINT,
avg_resolution_hours NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
ar.user_id as worker_id,
u.email as worker_email,
COUNT(ci.id)::BIGINT as assigned_issues,
COUNT(ci.id) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(ci.id) FILTER (WHERE ci.status = 'in_progress')::BIGINT as in_progress_issues,
COALESCE(AVG(
CASE WHEN ci.status IN ('resolved', 'completed')
THEN EXTRACT(EPOCH FROM (ci.updated_at - ci.created_at)) / 3600
END
), 0)::NUMERIC as avg_resolution_hours
FROM admin_roles ar
JOIN auth.users u ON ar.user_id = u.id
LEFT JOIN civic_issues ci ON ci.assigned_worker_id = ar.user_id
WHERE ar.office_id = p_office_id AND ar.role = 'worker' AND ar.is_active = TRUE
GROUP BY ar.user_id, u.email
ORDER BY resolved_issues DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 9: Get worker's own analytics
CREATE OR REPLACE FUNCTION get_worker_analytics(p_worker_id UUID)
RETURNS TABLE (
total_assigned BIGINT,
pending_issues BIGINT,
in_progress_issues BIGINT,
resolved_issues BIGINT,
resolved_today BIGINT,
resolved_this_week BIGINT,
avg_resolution_hours NUMERIC,
high_priority_pending BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*)::BIGINT as total_assigned,
COUNT(*) FILTER (WHERE ci.status = 'assigned')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status = 'in_progress')::BIGINT as in_progress_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed') AND ci.updated_at >= CURRENT_DATE)::BIGINT as resolved_today,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed') AND ci.updated_at >= CURRENT_DATE - INTERVAL '7 days')::BIGINT as resolved_this_week,
COALESCE(AVG(
CASE WHEN ci.status IN ('resolved', 'completed')
THEN EXTRACT(EPOCH FROM (ci.updated_at - ci.created_at)) / 3600
END
), 0)::NUMERIC as avg_resolution_hours,
COUNT(*) FILTER (WHERE ci.is_high_priority = TRUE AND ci.status NOT IN ('resolved', 'completed'))::BIGINT as high_priority_pending
FROM civic_issues ci
WHERE ci.assigned_worker_id = p_worker_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 10: Get category breakdown
CREATE OR REPLACE FUNCTION get_category_breakdown(
p_state TEXT DEFAULT NULL,
p_department TEXT DEFAULT NULL,
p_office_id UUID DEFAULT NULL
)
RETURNS TABLE (
category TEXT,
total_issues BIGINT,
pending_issues BIGINT,
resolved_issues BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
ci.category,
COUNT(*)::BIGINT as total_issues,
COUNT(*) FILTER (WHERE ci.status = 'reported')::BIGINT as pending_issues,
COUNT(*) FILTER (WHERE ci.status IN ('resolved', 'completed'))::BIGINT as resolved_issues
FROM civic_issues ci
WHERE
(p_state IS NULL OR ci.state = p_state)
AND (p_department IS NULL OR ci.assigned_department = p_department)
AND (p_office_id IS NULL OR ci.assigned_office_id = p_office_id)
GROUP BY ci.category
ORDER BY total_issues DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 11: Add assigned_worker_id column to civic_issues if not exists
ALTER TABLE civic_issues ADD COLUMN IF NOT EXISTS assigned_worker_id UUID REFERENCES auth.users(id);
CREATE INDEX IF NOT EXISTS idx_civic_issues_worker ON civic_issues(assigned_worker_id);
-- SECTION 12: Function to assign issue to worker
CREATE OR REPLACE FUNCTION assign_issue_to_worker(
p_issue_id UUID,
p_worker_id UUID
)
RETURNS BOOLEAN AS $$
DECLARE
caller_id UUID;
caller_role TEXT;
caller_office UUID;
worker_office UUID;
BEGIN
caller_id := auth.uid();
-- Get caller's role and office
SELECT role, office_id INTO caller_role, caller_office
FROM admin_roles WHERE user_id = caller_id AND is_active = TRUE
ORDER BY CASE role WHEN 'system_admin' THEN 1 WHEN 'state_admin' THEN 2 WHEN 'department_admin' THEN 3 WHEN 'office_admin' THEN 4 END
LIMIT 1;
-- Get worker's office
SELECT office_id INTO worker_office
FROM admin_roles WHERE user_id = p_worker_id AND role = 'worker' AND is_active = TRUE;
-- Check permissions
IF caller_role NOT IN ('system_admin', 'state_admin', 'department_admin', 'office_admin') THEN
RAISE EXCEPTION 'You do not have permission to assign issues';
END IF;
IF caller_role = 'office_admin' AND caller_office != worker_office THEN
RAISE EXCEPTION 'You can only assign to workers in your office';
END IF;
-- Update the issue
UPDATE civic_issues
SET
assigned_worker_id = p_worker_id,
status = 'assigned',
updated_at = NOW()
WHERE id = p_issue_id;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 13: Function for worker to update issue status
CREATE OR REPLACE FUNCTION update_issue_status(
p_issue_id UUID,
p_status TEXT,
p_notes TEXT DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
caller_id UUID;
issue_worker UUID;
caller_role TEXT;
BEGIN
caller_id := auth.uid();
-- Get issue's assigned worker
SELECT assigned_worker_id INTO issue_worker FROM civic_issues WHERE id = p_issue_id;
-- Get caller's role
SELECT role INTO caller_role FROM admin_roles WHERE user_id = caller_id AND is_active = TRUE LIMIT 1;
-- Workers can only update their own issues
IF caller_role = 'worker' AND issue_worker != caller_id THEN
RAISE EXCEPTION 'You can only update issues assigned to you';
END IF;
-- Validate status
IF p_status NOT IN ('assigned', 'in_progress', 'resolved', 'completed', 'escalated') THEN
RAISE EXCEPTION 'Invalid status';
END IF;
-- Update the issue
UPDATE civic_issues
SET
status = p_status,
updated_at = NOW()
WHERE id = p_issue_id;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- SECTION 14: Get issues for worker
CREATE OR REPLACE FUNCTION get_worker_issues(
p_worker_id UUID,
p_status TEXT DEFAULT NULL
)
RETURNS TABLE (
id UUID,
category TEXT,
description TEXT,
address TEXT,
status TEXT,
priority_score INTEGER,
is_high_priority BOOLEAN,
created_at TIMESTAMP,
media_files TEXT[]
) AS $$
BEGIN
RETURN QUERY
SELECT
ci.id,
ci.category,
ci.description,
ci.address,
ci.status,
ci.priority_score,
ci.is_high_priority,
ci.created_at,
ci.media_files
FROM civic_issues ci
WHERE ci.assigned_worker_id = p_worker_id
AND (p_status IS NULL OR ci.status = p_status)
ORDER BY ci.is_high_priority DESC, ci.priority_score DESC, ci.created_at ASC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- ============================================
-- ANALYTICS MIGRATION COMPLETE!
-- ============================================