@platform-modules/civil-aviation-authority 2.3.310 → 2.3.312

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/data-source.js +2 -0
  2. package/dist/i18n/workflow-chat-i18n.json +8 -0
  3. package/dist/i18n/workflow-chat-message.builder.d.ts +1 -1
  4. package/dist/i18n/workflow-chat-message.builder.js +21 -3
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/models/ChatbotQnsAnsMappingModel.d.ts +8 -0
  8. package/dist/models/ChatbotQnsAnsMappingModel.js +40 -0
  9. package/dist/models/DocumentMetadataModel.d.ts +45 -0
  10. package/dist/models/DocumentMetadataModel.js +171 -0
  11. package/dist/models/DocumentationDepartmentsModel.d.ts +13 -0
  12. package/dist/models/DocumentationDepartmentsModel.js +53 -0
  13. package/dist/models/FolderModel.d.ts +16 -0
  14. package/dist/models/FolderModel.js +85 -0
  15. package/dist/models/ImportExportMaterialModels.d.ts +92 -0
  16. package/dist/models/ImportExportMaterialModels.js +307 -0
  17. package/dist/models/PermissionModel.d.ts +18 -0
  18. package/dist/models/PermissionModel.js +68 -0
  19. package/dist/models/SecurityAccessApprovalModel.d.ts +23 -0
  20. package/dist/models/SecurityAccessApprovalModel.js +82 -0
  21. package/dist/models/SecurityAccessAttachmentModel.d.ts +12 -0
  22. package/dist/models/SecurityAccessAttachmentModel.js +56 -0
  23. package/dist/models/SecurityAccessChatModel.d.ts +12 -0
  24. package/dist/models/SecurityAccessChatModel.js +56 -0
  25. package/dist/models/SecurityAccessRequestModel.d.ts +25 -0
  26. package/dist/models/SecurityAccessRequestModel.js +80 -0
  27. package/dist/models/SecurityAccessWorkflowModel.d.ts +24 -0
  28. package/dist/models/SecurityAccessWorkflowModel.js +84 -0
  29. package/dist/models/ServiceExtensionAfterAge60Models.d.ts +93 -0
  30. package/dist/models/ServiceExtensionAfterAge60Models.js +312 -0
  31. package/dist/models/SlaApprovalsViewModel.js +54 -47
  32. package/dist/models/SlaMyRequestsViewModel.d.ts +3 -0
  33. package/dist/models/SlaMyRequestsViewModel.js +22 -2
  34. package/dist/models/UUIDBaseModel.d.ts +14 -0
  35. package/dist/models/UUIDBaseModel.js +66 -0
  36. package/dist/models/WorkingHoursExtensionModels.d.ts +88 -0
  37. package/dist/models/WorkingHoursExtensionModels.js +295 -0
  38. package/package.json +1 -1
  39. package/scripts/sync-sla-reports-sql.js +92 -92
  40. package/sql/README.md +21 -21
  41. package/sql/create_chatbot_qns_ans_mapping.sql +25 -0
  42. package/sql/sla_reports_admin_procedures.sql +323 -304
  43. package/sql/sla_reports_approvals_workbook.sql +430 -383
  44. package/sql/sla_reports_procedures.sql +886 -886
  45. package/sql/user_role_department_section_validation.sql +214 -0
  46. package/sql/vw_sla_approvals.sql +115 -108
  47. package/sql/vw_sla_my_requests.sql +58 -53
  48. package/src/data-source.ts +2 -0
  49. package/src/i18n/workflow-chat-i18n.json +8 -0
  50. package/src/i18n/workflow-chat-message.builder.ts +27 -3
  51. package/src/index.ts +1 -0
  52. package/src/models/AccessCardRequestModel.ts +135 -135
  53. package/src/models/AirportEntryPermitModel.ts +276 -276
  54. package/src/models/AnnualTrainingPlanRequestModel.ts +153 -153
  55. package/src/models/ChatbotQnsAnsMappingModel.ts +21 -0
  56. package/src/models/DocumentDriveModel.ts +28 -28
  57. package/src/models/DocumentFolderModel.ts +45 -45
  58. package/src/models/HotelApprovalModel.ts +83 -83
  59. package/src/models/HousingContractCancelApprovalModel.ts +64 -64
  60. package/src/models/HousingContractRenewalApprovalModel.ts +64 -64
  61. package/src/models/ITRequestAttachmentModel.ts +73 -73
  62. package/src/models/ItApprovalsModel.ts +84 -84
  63. package/src/models/LegalConsultationApprovalModel.ts +65 -65
  64. package/src/models/MissionTravelPassportExpiryNotificationConfigModel.ts +36 -36
  65. package/src/models/NotificationModel.ts +89 -89
  66. package/src/models/ResidentialUnitRentalApprovalModel.ts +143 -143
  67. package/src/models/ServiceExtensionAfter60ApprovalModel.ts +87 -87
  68. package/src/models/ServiceSlaApprovalModel.ts +64 -64
  69. package/src/models/ServicesNotificationConfigModel.ts +55 -55
  70. package/src/models/SlaApprovalsViewModel.ts +142 -135
  71. package/src/models/SlaMyRequestsViewModel.ts +21 -1
  72. package/src/models/StudyLeaveRequestModel.ts +144 -144
  73. package/src/models/TrainingRequestModel.ts +164 -164
  74. package/src/models/TrainingRoomBookingRequestModel.ts +142 -142
  75. package/src/models/TrainingRoomNotificationConfigModel.ts +30 -30
  76. package/src/models/role.ts +34 -34
  77. package/src/sla/sla-table-sync.service.ts +90 -90
  78. package/dist/helpers/cancellation-bilingual-persist.helper.d.ts +0 -37
  79. package/dist/helpers/cancellation-bilingual-persist.helper.js +0 -77
@@ -1,383 +1,430 @@
1
- -- Approvals Excel download: per sub-service "{name} Requests" + "{name} Approvals"
2
- -- Approver queue filter uses EXISTS on sla_approval (same pattern as HR annual-training-plan).
3
- -- Applied via: npm run sync:sla-sql
4
-
5
- CREATE OR REPLACE FUNCTION sla_approval_matches_user_view(p_va_alias TEXT, p_user_id INT)
6
- RETURNS TEXT
7
- LANGUAGE sql
8
- IMMUTABLE
9
- AS $$
10
- SELECT format('(
11
- %1$s.approver_user_id = %2$s
12
- OR %1$s.delegate_user_id = %2$s
13
- OR (
14
- %1$s.approver_user_id IS NULL
15
- AND %1$s.delegate_user_id IS NULL
16
- AND %1$s.approval_role_id IS NOT NULL
17
- AND %1$s.approval_department_id IS NOT NULL
18
- AND EXISTS (
19
- SELECT 1 FROM user_role ur
20
- WHERE ur.user_id = %2$s
21
- AND ur.role_id = %1$s.approval_role_id
22
- AND ur.department_id = %1$s.approval_department_id
23
- AND (
24
- %1$s.approval_section_id IS NULL
25
- OR (%1$s.approval_section_id IS NOT NULL AND (ur.section_id = %1$s.approval_section_id OR ur.section_id IS NULL))
26
- )
27
- AND ur.is_deleted = false AND ur.is_active = true
28
- )
29
- )
30
- OR (
31
- %1$s.approver_user_id IS NULL
32
- AND %1$s.delegate_user_id IS NULL
33
- AND %1$s.approval_role_id IS NULL
34
- AND %1$s.approval_department_id IS NOT NULL
35
- AND %1$s.approval_section_id IS NOT NULL
36
- AND EXISTS (
37
- SELECT 1 FROM users u
38
- WHERE u.id = %2$s AND COALESCE(u.is_deleted, false) = false
39
- AND u.department = %1$s.approval_department_id AND u.section = %1$s.approval_section_id
40
- )
41
- )
42
- OR (
43
- %1$s.approver_user_id IS NULL
44
- AND %1$s.delegate_user_id IS NULL
45
- AND %1$s.approval_role_id IS NULL
46
- AND %1$s.approval_department_id IS NOT NULL
47
- AND %1$s.approval_section_id IS NULL
48
- AND EXISTS (
49
- SELECT 1 FROM users u
50
- WHERE u.id = %2$s AND COALESCE(u.is_deleted, false) = false
51
- AND u.department = %1$s.approval_department_id
52
- )
53
- )
54
- )', p_va_alias, p_user_id);
55
- $$;
56
-
57
- -- EXISTS (sla_approval appr ...) — mirrors annual_training_plan_approvals queue filter
58
- CREATE OR REPLACE FUNCTION sla_approval_queue_exists_sql(p_user_id INT)
59
- RETURNS TEXT
60
- LANGUAGE sql
61
- IMMUTABLE
62
- AS $$
63
- SELECT format(
64
- 'EXISTS (
65
- SELECT 1
66
- FROM sla_approval appr
67
- WHERE appr.request_id = sr.request_id
68
- AND COALESCE(appr.is_deleted, false) = false
69
- AND (appr.service_id = sr.service_id OR appr.service_id IS NULL)
70
- AND (appr.sub_service_id = sr.sub_service_id OR appr.sub_service_id IS NULL)
71
- AND (%s)
72
- AND (appr.approval_status = ANY($1) OR appr.approval_status IS NULL)
73
- )',
74
- sla_approval_matches_user('appr', p_user_id)
75
- );
76
- $$;
77
-
78
- -- Admin: all approvers (NULL user) — any request with a matching approval row
79
- CREATE OR REPLACE FUNCTION sla_approval_queue_exists_sql_optional(p_user_id INT)
80
- RETURNS TEXT
81
- LANGUAGE sql
82
- STABLE
83
- AS $$
84
- SELECT CASE
85
- WHEN p_user_id IS NULL THEN
86
- 'EXISTS (
87
- SELECT 1
88
- FROM sla_approval appr
89
- WHERE appr.request_id = sr.request_id
90
- AND COALESCE(appr.is_deleted, false) = false
91
- AND (appr.service_id = sr.service_id OR appr.service_id IS NULL)
92
- AND (appr.sub_service_id = sr.sub_service_id OR appr.sub_service_id IS NULL)
93
- AND (appr.approval_status = ANY($1) OR appr.approval_status IS NULL)
94
- )'
95
- ELSE sla_approval_queue_exists_sql(p_user_id)
96
- END;
97
- $$;
98
-
99
- DROP FUNCTION IF EXISTS sp_sla_approvals_workbook_excel_export(
100
- TEXT, TEXT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
101
- );
102
-
103
- CREATE OR REPLACE FUNCTION sp_sla_approvals_workbook_excel_export(
104
- p_queue_exists_sql TEXT,
105
- p_own_requests_filter_sql TEXT,
106
- p_service_ids INT[] DEFAULT NULL,
107
- p_sub_service_ids INT[] DEFAULT NULL,
108
- p_request_statuses TEXT[] DEFAULT ARRAY['Pending','In Progress','Approved','Rejected'],
109
- p_approval_statuses TEXT[] DEFAULT ARRAY['In Progress','Approved','Rejected'],
110
- p_from_date DATE DEFAULT NULL,
111
- p_to_date DATE DEFAULT NULL,
112
- p_search_text TEXT DEFAULT NULL,
113
- p_sort_by TEXT DEFAULT 'created_at',
114
- p_sort_order TEXT DEFAULT 'DESC'
115
- )
116
- RETURNS TABLE (
117
- sheet_order INT,
118
- sub_service_id INT,
119
- sub_service_name TEXT,
120
- sheet_name TEXT,
121
- headers JSONB,
122
- rows JSONB
123
- )
124
- LANGUAGE plpgsql
125
- STABLE
126
- AS $$
127
- DECLARE
128
- v_sort_col_req TEXT;
129
- v_sort_dir TEXT;
130
- v_sub RECORD;
131
- v_row RECORD;
132
- v_approval RECORD;
133
- v_dyn_keys TEXT[];
134
- v_dyn_labels TEXT[];
135
- v_common_req_labels TEXT[] := ARRAY[
136
- 'SLA Request ID', 'Request ID', 'User', 'Status', 'Created At',
137
- 'Created By', 'Department', 'Section'
138
- ];
139
- v_approval_headers TEXT[] := ARRAY[
140
- 'Request ID', 'Service ID', 'Service Name', 'Sub Service ID', 'Sub Service Name',
141
- 'SLA Approval ID', 'Source Approval ID', 'Level', 'Approval Status', 'Request Status',
142
- 'Approval Role ID', 'Approval Role Name', 'Approval Department ID', 'Approval Department Name',
143
- 'Approval Section ID', 'Approval Section Name',
144
- 'Approver User ID', 'Approver User Name', 'Delegate User ID', 'Delegate User Name',
145
- 'Approved By ID', 'Approved By Name', 'Comment', 'Created At', 'Updated At'
146
- ];
147
- v_headers_labels TEXT[];
148
- v_headers_json JSONB;
149
- v_cells TEXT[];
150
- v_all_rows JSONB;
151
- v_k TEXT;
152
- v_sheet_order INT := 0;
153
- v_sheet_label TEXT;
154
- BEGIN
155
- v_sort_col_req := CASE lower(trim(p_sort_by))
156
- WHEN 'updated_at' THEN 'v.created_at'
157
- WHEN 'id' THEN 'v.sla_request_id'
158
- WHEN 'status' THEN 'v.status'
159
- WHEN 'request_id' THEN 'v.request_id'
160
- ELSE 'v.created_at'
161
- END;
162
- v_sort_dir := CASE WHEN upper(trim(p_sort_order)) = 'ASC' THEN 'ASC' ELSE 'DESC' END;
163
-
164
- -- ----- Per sub-service: Requests (queue requests with dynamic request_fields) -----
165
- FOR v_sub IN
166
- EXECUTE format($q$
167
- SELECT DISTINCT
168
- COALESCE(sr.sub_service_id, 0) AS sub_service_id_num,
169
- COALESCE(NULLIF(trim(subsvc.sub_service_name), ''), NULLIF(trim(v.sub_service_name), ''), 'Uncategorized') AS sub_service_name
170
- FROM vw_sla_my_requests v
171
- INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
172
- LEFT JOIN caa_sub_services subsvc ON subsvc.id = sr.sub_service_id
173
- WHERE (%s)
174
- AND (%s)
175
- AND sr.status::TEXT = ANY($1)
176
- AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
177
- AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
178
- AND ($4 IS NULL OR v.created_at::date >= $4)
179
- AND ($5 IS NULL OR v.created_at::date <= $5)
180
- AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
181
- ORDER BY sub_service_name ASC
182
- $q$, p_own_requests_filter_sql, p_queue_exists_sql)
183
- USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
184
- LOOP
185
- v_sheet_order := v_sheet_order + 1;
186
-
187
- EXECUTE format($q$
188
- SELECT COALESCE(array_agg(DISTINCT keys.k ORDER BY keys.k), ARRAY[]::TEXT[])
189
- FROM vw_sla_my_requests v
190
- INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
191
- CROSS JOIN LATERAL jsonb_object_keys(COALESCE(v.request_fields, '{}'::jsonb)) AS keys(k)
192
- WHERE (%s)
193
- AND (%s)
194
- AND sr.status::TEXT = ANY($1)
195
- AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
196
- AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
197
- AND ($4 IS NULL OR v.created_at::date >= $4)
198
- AND ($5 IS NULL OR v.created_at::date <= $5)
199
- AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
200
- AND COALESCE(sr.sub_service_id, 0) = %s
201
- $q$, p_own_requests_filter_sql, p_queue_exists_sql, v_sub.sub_service_id_num)
202
- INTO v_dyn_keys
203
- USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text;
204
-
205
- v_dyn_labels := ARRAY[]::TEXT[];
206
- IF v_dyn_keys IS NOT NULL THEN
207
- FOREACH v_k IN ARRAY v_dyn_keys LOOP
208
- v_dyn_labels := array_append(v_dyn_labels, sla_excel_header_label(v_k));
209
- END LOOP;
210
- END IF;
211
-
212
- v_headers_labels := ARRAY(
213
- SELECT sla_excel_header_label(lbl)
214
- FROM unnest(v_common_req_labels || COALESCE(v_dyn_labels, ARRAY[]::TEXT[])) AS u(lbl)
215
- );
216
- v_headers_json := to_jsonb(v_headers_labels);
217
- v_all_rows := '[]'::jsonb;
218
-
219
- FOR v_row IN
220
- EXECUTE format($q$
221
- SELECT
222
- v.sla_request_id,
223
- v.request_id,
224
- v.status,
225
- v.created_at,
226
- v.created_by,
227
- v.req_user_department_id,
228
- v.req_user_section_id,
229
- v.request_fields,
230
- TRIM(COALESCE(u.employee_name, '')) AS user_name
231
- FROM vw_sla_my_requests v
232
- INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
233
- LEFT JOIN users u ON u.id = v.user_id AND COALESCE(u.is_deleted, false) = false
234
- WHERE (%s)
235
- AND (%s)
236
- AND sr.status::TEXT = ANY($1)
237
- AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
238
- AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
239
- AND ($4 IS NULL OR v.created_at::date >= $4)
240
- AND ($5 IS NULL OR v.created_at::date <= $5)
241
- AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
242
- AND COALESCE(sr.sub_service_id, 0) = %s
243
- ORDER BY %s %s, v.sla_request_id ASC
244
- $q$, p_own_requests_filter_sql, p_queue_exists_sql, v_sub.sub_service_id_num, v_sort_col_req, v_sort_dir)
245
- USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
246
- LOOP
247
- v_cells := ARRAY[
248
- COALESCE(v_row.sla_request_id::TEXT, ''),
249
- COALESCE(v_row.request_id::TEXT, ''),
250
- COALESCE(v_row.user_name, ''),
251
- COALESCE(v_row.status, ''),
252
- COALESCE(to_char(v_row.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS'), ''),
253
- COALESCE(v_row.created_by, ''),
254
- COALESCE(v_row.req_user_department_id, ''),
255
- COALESCE(v_row.req_user_section_id, '')
256
- ];
257
- IF v_dyn_keys IS NOT NULL THEN
258
- FOREACH v_k IN ARRAY v_dyn_keys LOOP
259
- v_cells := array_append(v_cells, COALESCE(sla_request_field_cell(v_row.request_fields, v_k), ''));
260
- END LOOP;
261
- END IF;
262
- v_all_rows := v_all_rows || jsonb_build_array(to_jsonb(v_cells));
263
- END LOOP;
264
-
265
- sheet_order := v_sheet_order;
266
- sub_service_id := NULLIF(v_sub.sub_service_id_num, 0);
267
- sub_service_name := v_sub.sub_service_name;
268
- sheet_name := sla_excel_sheet_name(v_sub.sub_service_name);
269
- headers := v_headers_json;
270
- rows := v_all_rows;
271
- RETURN NEXT;
272
- END LOOP;
273
-
274
- -- ----- Per sub-service: Approvals (all steps for queue requests) -----
275
- FOR v_sub IN
276
- EXECUTE format($q$
277
- SELECT DISTINCT
278
- COALESCE(sr.sub_service_id, 0) AS sub_service_id_num,
279
- COALESCE(NULLIF(trim(subsvc.sub_service_name), ''), NULLIF(trim(v.sub_service_name), ''), 'Uncategorized') AS sub_service_name
280
- FROM vw_sla_my_requests v
281
- INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
282
- LEFT JOIN caa_sub_services subsvc ON subsvc.id = sr.sub_service_id
283
- WHERE (%s)
284
- AND (%s)
285
- AND sr.status::TEXT = ANY($1)
286
- AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
287
- AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
288
- AND ($4 IS NULL OR v.created_at::date >= $4)
289
- AND ($5 IS NULL OR v.created_at::date <= $5)
290
- AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
291
- ORDER BY sub_service_name ASC
292
- $q$, p_own_requests_filter_sql, p_queue_exists_sql)
293
- USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
294
- LOOP
295
- v_sheet_order := v_sheet_order + 1;
296
- v_sheet_label := sla_excel_approvals_sheet_name(v_sub.sub_service_name);
297
- v_all_rows := '[]'::jsonb;
298
-
299
- FOR v_approval IN
300
- EXECUTE format($q$
301
- SELECT
302
- va.request_id,
303
- sr.service_id,
304
- va.service_name,
305
- sr.sub_service_id,
306
- va.sub_service_name,
307
- va.sla_approval_id,
308
- va.source_approval_id,
309
- va.level,
310
- va.approval_status,
311
- va.request_status,
312
- va.approval_role_id,
313
- va.approval_role_name,
314
- va.approval_department_id,
315
- va.approval_department_name,
316
- va.approval_section_id,
317
- va.approval_section_name,
318
- va.approver_user_id,
319
- va.approver_user_name,
320
- va.delegate_user_id,
321
- va.delegate_user_name,
322
- va.approved_by,
323
- va.approved_by_name,
324
- va.comment,
325
- va.created_at,
326
- va.updated_at
327
- FROM vw_sla_approvals va
328
- INNER JOIN sla_requests sr
329
- ON sr.request_id = va.request_id AND COALESCE(sr.is_deleted, false) = false
330
- INNER JOIN vw_sla_my_requests v ON v.sla_request_id = sr.id
331
- WHERE (%s)
332
- AND (%s)
333
- AND sr.status::TEXT = ANY($1)
334
- AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
335
- AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
336
- AND ($4 IS NULL OR v.created_at::date >= $4)
337
- AND ($5 IS NULL OR v.created_at::date <= $5)
338
- AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
339
- AND COALESCE(sr.sub_service_id, 0) = %s
340
- ORDER BY va.request_id ASC, va.level ASC NULLS LAST, va.sla_approval_id ASC
341
- $q$, p_own_requests_filter_sql, p_queue_exists_sql, v_sub.sub_service_id_num)
342
- USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
343
- LOOP
344
- v_cells := ARRAY[
345
- COALESCE(v_approval.request_id::TEXT, ''),
346
- COALESCE(v_approval.service_id::TEXT, ''),
347
- COALESCE(v_approval.service_name, ''),
348
- COALESCE(v_approval.sub_service_id::TEXT, ''),
349
- COALESCE(v_approval.sub_service_name, ''),
350
- COALESCE(v_approval.sla_approval_id::TEXT, ''),
351
- COALESCE(v_approval.source_approval_id::TEXT, ''),
352
- COALESCE(v_approval.level::TEXT, ''),
353
- COALESCE(v_approval.approval_status, ''),
354
- COALESCE(v_approval.request_status, ''),
355
- COALESCE(v_approval.approval_role_id::TEXT, ''),
356
- COALESCE(v_approval.approval_role_name, ''),
357
- COALESCE(v_approval.approval_department_id::TEXT, ''),
358
- COALESCE(v_approval.approval_department_name, ''),
359
- COALESCE(v_approval.approval_section_id::TEXT, ''),
360
- COALESCE(v_approval.approval_section_name, ''),
361
- COALESCE(v_approval.approver_user_id::TEXT, ''),
362
- COALESCE(v_approval.approver_user_name, ''),
363
- COALESCE(v_approval.delegate_user_id::TEXT, ''),
364
- COALESCE(v_approval.delegate_user_name, ''),
365
- COALESCE(v_approval.approved_by::TEXT, ''),
366
- COALESCE(v_approval.approved_by_name, ''),
367
- COALESCE(v_approval.comment, ''),
368
- COALESCE(to_char(v_approval.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS'), ''),
369
- COALESCE(to_char(v_approval.updated_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS'), '')
370
- ];
371
- v_all_rows := v_all_rows || jsonb_build_array(to_jsonb(v_cells));
372
- END LOOP;
373
-
374
- sheet_order := v_sheet_order;
375
- sub_service_id := NULLIF(v_sub.sub_service_id_num, 0);
376
- sub_service_name := v_sub.sub_service_name;
377
- sheet_name := v_sheet_label;
378
- headers := to_jsonb(v_approval_headers);
379
- rows := v_all_rows;
380
- RETURN NEXT;
381
- END LOOP;
382
- END;
383
- $$;
1
+ -- Approvals Excel download: per sub-service "{name} Requests" + "{name} Approvals"
2
+ -- Approver queue filter uses EXISTS on sla_approval (same pattern as HR annual-training-plan).
3
+ -- Applied via: npm run sync:sla-sql
4
+
5
+ CREATE OR REPLACE FUNCTION sla_approval_matches_user_view(p_va_alias TEXT, p_user_id INT)
6
+ RETURNS TEXT
7
+ LANGUAGE sql
8
+ IMMUTABLE
9
+ AS $$
10
+ SELECT format('(
11
+ %1$s.approver_user_id = %2$s
12
+ OR %1$s.delegate_user_id = %2$s
13
+ OR (
14
+ %1$s.approver_user_id IS NULL
15
+ AND %1$s.delegate_user_id IS NULL
16
+ AND %1$s.approval_role_id IS NOT NULL
17
+ AND %1$s.approval_department_id IS NOT NULL
18
+ AND EXISTS (
19
+ SELECT 1 FROM user_role ur
20
+ WHERE ur.user_id = %2$s
21
+ AND ur.role_id = %1$s.approval_role_id
22
+ AND ur.department_id = %1$s.approval_department_id
23
+ AND (
24
+ %1$s.approval_section_id IS NULL
25
+ OR (%1$s.approval_section_id IS NOT NULL AND (ur.section_id = %1$s.approval_section_id OR ur.section_id IS NULL))
26
+ )
27
+ AND ur.is_deleted = false AND ur.is_active = true
28
+ )
29
+ )
30
+ OR (
31
+ %1$s.approver_user_id IS NULL
32
+ AND %1$s.delegate_user_id IS NULL
33
+ AND %1$s.approval_role_id IS NULL
34
+ AND %1$s.approval_department_id IS NOT NULL
35
+ AND %1$s.approval_section_id IS NOT NULL
36
+ AND EXISTS (
37
+ SELECT 1 FROM users u
38
+ WHERE u.id = %2$s AND COALESCE(u.is_deleted, false) = false
39
+ AND u.department = %1$s.approval_department_id AND u.section = %1$s.approval_section_id
40
+ )
41
+ )
42
+ OR (
43
+ %1$s.approver_user_id IS NULL
44
+ AND %1$s.delegate_user_id IS NULL
45
+ AND %1$s.approval_role_id IS NULL
46
+ AND %1$s.approval_department_id IS NOT NULL
47
+ AND %1$s.approval_section_id IS NULL
48
+ AND EXISTS (
49
+ SELECT 1 FROM users u
50
+ WHERE u.id = %2$s AND COALESCE(u.is_deleted, false) = false
51
+ AND u.department = %1$s.approval_department_id
52
+ )
53
+ )
54
+ )', p_va_alias, p_user_id);
55
+ $$;
56
+
57
+ -- EXISTS (sla_approval appr ...) — mirrors annual_training_plan_approvals queue filter
58
+ CREATE OR REPLACE FUNCTION sla_approval_queue_exists_sql(p_user_id INT)
59
+ RETURNS TEXT
60
+ LANGUAGE sql
61
+ IMMUTABLE
62
+ AS $$
63
+ SELECT format(
64
+ 'EXISTS (
65
+ SELECT 1
66
+ FROM sla_approval appr
67
+ WHERE appr.request_id = sr.request_id
68
+ AND COALESCE(appr.is_deleted, false) = false
69
+ AND (appr.service_id = sr.service_id OR appr.service_id IS NULL)
70
+ AND (appr.sub_service_id = sr.sub_service_id OR appr.sub_service_id IS NULL)
71
+ AND (%s)
72
+ AND (appr.approval_status = ANY($1) OR appr.approval_status IS NULL)
73
+ )',
74
+ sla_approval_matches_user('appr', p_user_id)
75
+ );
76
+ $$;
77
+
78
+ -- Admin: all approvers (NULL user) — any request with a matching approval row
79
+ CREATE OR REPLACE FUNCTION sla_approval_queue_exists_sql_optional(p_user_id INT)
80
+ RETURNS TEXT
81
+ LANGUAGE sql
82
+ STABLE
83
+ AS $$
84
+ SELECT CASE
85
+ WHEN p_user_id IS NULL THEN
86
+ 'EXISTS (
87
+ SELECT 1
88
+ FROM sla_approval appr
89
+ WHERE appr.request_id = sr.request_id
90
+ AND COALESCE(appr.is_deleted, false) = false
91
+ AND (appr.service_id = sr.service_id OR appr.service_id IS NULL)
92
+ AND (appr.sub_service_id = sr.sub_service_id OR appr.sub_service_id IS NULL)
93
+ AND (appr.approval_status = ANY($1) OR appr.approval_status IS NULL)
94
+ )'
95
+ ELSE sla_approval_queue_exists_sql(p_user_id)
96
+ END;
97
+ $$;
98
+
99
+ -- Admin: any of multiple approver user ids (NULL/empty = all queues)
100
+ CREATE OR REPLACE FUNCTION sla_approval_queue_exists_sql_any(p_user_ids INT[])
101
+ RETURNS TEXT
102
+ LANGUAGE plpgsql
103
+ STABLE
104
+ AS $$
105
+ DECLARE
106
+ v_id INT;
107
+ v_parts TEXT[] := ARRAY[]::TEXT[];
108
+ v_match TEXT;
109
+ BEGIN
110
+ IF p_user_ids IS NULL OR cardinality(p_user_ids) = 0 THEN
111
+ RETURN sla_approval_queue_exists_sql_optional(NULL);
112
+ END IF;
113
+
114
+ IF cardinality(p_user_ids) = 1 THEN
115
+ RETURN sla_approval_queue_exists_sql(p_user_ids[1]);
116
+ END IF;
117
+
118
+ FOREACH v_id IN ARRAY p_user_ids LOOP
119
+ IF v_id IS NOT NULL AND v_id > 0 THEN
120
+ v_parts := array_append(v_parts, '(' || sla_approval_matches_user('appr', v_id) || ')');
121
+ END IF;
122
+ END LOOP;
123
+
124
+ IF cardinality(v_parts) = 0 THEN
125
+ RETURN sla_approval_queue_exists_sql_optional(NULL);
126
+ END IF;
127
+
128
+ v_match := array_to_string(v_parts, ' OR ');
129
+
130
+ RETURN format(
131
+ 'EXISTS (
132
+ SELECT 1
133
+ FROM sla_approval appr
134
+ WHERE appr.request_id = sr.request_id
135
+ AND COALESCE(appr.is_deleted, false) = false
136
+ AND (appr.service_id = sr.service_id OR appr.service_id IS NULL)
137
+ AND (appr.sub_service_id = sr.sub_service_id OR appr.sub_service_id IS NULL)
138
+ AND (%s)
139
+ AND (appr.approval_status = ANY($1) OR appr.approval_status IS NULL)
140
+ )',
141
+ v_match
142
+ );
143
+ END;
144
+ $$;
145
+
146
+ DROP FUNCTION IF EXISTS sp_sla_approvals_workbook_excel_export(
147
+ TEXT, TEXT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
148
+ );
149
+
150
+ CREATE OR REPLACE FUNCTION sp_sla_approvals_workbook_excel_export(
151
+ p_queue_exists_sql TEXT,
152
+ p_own_requests_filter_sql TEXT,
153
+ p_service_ids INT[] DEFAULT NULL,
154
+ p_sub_service_ids INT[] DEFAULT NULL,
155
+ p_request_statuses TEXT[] DEFAULT ARRAY['Pending','In Progress','Approved','Rejected'],
156
+ p_approval_statuses TEXT[] DEFAULT ARRAY['In Progress','Approved','Rejected'],
157
+ p_from_date DATE DEFAULT NULL,
158
+ p_to_date DATE DEFAULT NULL,
159
+ p_search_text TEXT DEFAULT NULL,
160
+ p_sort_by TEXT DEFAULT 'created_at',
161
+ p_sort_order TEXT DEFAULT 'DESC'
162
+ )
163
+ RETURNS TABLE (
164
+ sheet_order INT,
165
+ sub_service_id INT,
166
+ sub_service_name TEXT,
167
+ sheet_name TEXT,
168
+ headers JSONB,
169
+ rows JSONB
170
+ )
171
+ LANGUAGE plpgsql
172
+ STABLE
173
+ AS $$
174
+ DECLARE
175
+ v_sort_col_req TEXT;
176
+ v_sort_dir TEXT;
177
+ v_sub RECORD;
178
+ v_row RECORD;
179
+ v_approval RECORD;
180
+ v_dyn_keys TEXT[];
181
+ v_dyn_labels TEXT[];
182
+ v_common_req_labels TEXT[] := ARRAY[
183
+ 'SLA Request ID', 'Request ID', 'User', 'Status', 'Created At',
184
+ 'Created By', 'Department', 'Section'
185
+ ];
186
+ v_approval_headers TEXT[] := ARRAY[
187
+ 'Request ID', 'Service ID', 'Service Name', 'Sub Service ID', 'Sub Service Name',
188
+ 'SLA Approval ID', 'Source Approval ID', 'Level', 'Approval Status', 'Request Status',
189
+ 'Approval Role ID', 'Approval Role Name', 'Approval Department ID', 'Approval Department Name',
190
+ 'Approval Section ID', 'Approval Section Name',
191
+ 'Approver User ID', 'Approver User Name', 'Delegate User ID', 'Delegate User Name',
192
+ 'Approved By ID', 'Approved By Name', 'Comment', 'Created At', 'Updated At'
193
+ ];
194
+ v_headers_labels TEXT[];
195
+ v_headers_json JSONB;
196
+ v_cells TEXT[];
197
+ v_all_rows JSONB;
198
+ v_k TEXT;
199
+ v_sheet_order INT := 0;
200
+ v_sheet_label TEXT;
201
+ BEGIN
202
+ v_sort_col_req := CASE lower(trim(p_sort_by))
203
+ WHEN 'updated_at' THEN 'v.created_at'
204
+ WHEN 'id' THEN 'v.sla_request_id'
205
+ WHEN 'status' THEN 'v.status'
206
+ WHEN 'request_id' THEN 'v.request_id'
207
+ ELSE 'v.created_at'
208
+ END;
209
+ v_sort_dir := CASE WHEN upper(trim(p_sort_order)) = 'ASC' THEN 'ASC' ELSE 'DESC' END;
210
+
211
+ -- ----- Per sub-service: Requests (queue requests with dynamic request_fields) -----
212
+ FOR v_sub IN
213
+ EXECUTE format($q$
214
+ SELECT DISTINCT
215
+ COALESCE(sr.sub_service_id, 0) AS sub_service_id_num,
216
+ COALESCE(NULLIF(trim(subsvc.sub_service_name), ''), NULLIF(trim(v.sub_service_name), ''), 'Uncategorized') AS sub_service_name
217
+ FROM vw_sla_my_requests v
218
+ INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
219
+ LEFT JOIN caa_sub_services subsvc ON subsvc.id = sr.sub_service_id
220
+ WHERE (%s)
221
+ AND (%s)
222
+ AND sr.status::TEXT = ANY($1)
223
+ AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
224
+ AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
225
+ AND ($4 IS NULL OR v.created_at::date >= $4)
226
+ AND ($5 IS NULL OR v.created_at::date <= $5)
227
+ AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
228
+ ORDER BY sub_service_name ASC
229
+ $q$, p_own_requests_filter_sql, p_queue_exists_sql)
230
+ USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
231
+ LOOP
232
+ v_sheet_order := v_sheet_order + 1;
233
+
234
+ EXECUTE format($q$
235
+ SELECT COALESCE(array_agg(DISTINCT keys.k ORDER BY keys.k), ARRAY[]::TEXT[])
236
+ FROM vw_sla_my_requests v
237
+ INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
238
+ CROSS JOIN LATERAL jsonb_object_keys(COALESCE(v.request_fields, '{}'::jsonb)) AS keys(k)
239
+ WHERE (%s)
240
+ AND (%s)
241
+ AND sr.status::TEXT = ANY($1)
242
+ AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
243
+ AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
244
+ AND ($4 IS NULL OR v.created_at::date >= $4)
245
+ AND ($5 IS NULL OR v.created_at::date <= $5)
246
+ AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
247
+ AND COALESCE(sr.sub_service_id, 0) = %s
248
+ $q$, p_own_requests_filter_sql, p_queue_exists_sql, v_sub.sub_service_id_num)
249
+ INTO v_dyn_keys
250
+ USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text;
251
+
252
+ v_dyn_labels := ARRAY[]::TEXT[];
253
+ IF v_dyn_keys IS NOT NULL THEN
254
+ FOREACH v_k IN ARRAY v_dyn_keys LOOP
255
+ v_dyn_labels := array_append(v_dyn_labels, sla_excel_header_label(v_k));
256
+ END LOOP;
257
+ END IF;
258
+
259
+ v_headers_labels := ARRAY(
260
+ SELECT sla_excel_header_label(lbl)
261
+ FROM unnest(v_common_req_labels || COALESCE(v_dyn_labels, ARRAY[]::TEXT[])) AS u(lbl)
262
+ );
263
+ v_headers_json := to_jsonb(v_headers_labels);
264
+ v_all_rows := '[]'::jsonb;
265
+
266
+ FOR v_row IN
267
+ EXECUTE format($q$
268
+ SELECT
269
+ v.sla_request_id,
270
+ v.request_id,
271
+ v.status,
272
+ v.created_at,
273
+ v.created_by,
274
+ v.req_user_department_id,
275
+ v.req_user_section_id,
276
+ v.request_fields,
277
+ TRIM(COALESCE(u.employee_name, '')) AS user_name
278
+ FROM vw_sla_my_requests v
279
+ INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
280
+ LEFT JOIN users u ON u.id = v.user_id AND COALESCE(u.is_deleted, false) = false
281
+ WHERE (%s)
282
+ AND (%s)
283
+ AND sr.status::TEXT = ANY($1)
284
+ AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
285
+ AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
286
+ AND ($4 IS NULL OR v.created_at::date >= $4)
287
+ AND ($5 IS NULL OR v.created_at::date <= $5)
288
+ AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
289
+ AND COALESCE(sr.sub_service_id, 0) = %s
290
+ ORDER BY %s %s, v.sla_request_id ASC
291
+ $q$, p_own_requests_filter_sql, p_queue_exists_sql, v_sub.sub_service_id_num, v_sort_col_req, v_sort_dir)
292
+ USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
293
+ LOOP
294
+ v_cells := ARRAY[
295
+ COALESCE(v_row.sla_request_id::TEXT, ''),
296
+ COALESCE(v_row.request_id::TEXT, ''),
297
+ COALESCE(v_row.user_name, ''),
298
+ COALESCE(v_row.status, ''),
299
+ COALESCE(to_char(v_row.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS'), ''),
300
+ COALESCE(v_row.created_by, ''),
301
+ COALESCE(v_row.req_user_department_id, ''),
302
+ COALESCE(v_row.req_user_section_id, '')
303
+ ];
304
+ IF v_dyn_keys IS NOT NULL THEN
305
+ FOREACH v_k IN ARRAY v_dyn_keys LOOP
306
+ v_cells := array_append(v_cells, COALESCE(sla_request_field_cell(v_row.request_fields, v_k), ''));
307
+ END LOOP;
308
+ END IF;
309
+ v_all_rows := v_all_rows || jsonb_build_array(to_jsonb(v_cells));
310
+ END LOOP;
311
+
312
+ sheet_order := v_sheet_order;
313
+ sub_service_id := NULLIF(v_sub.sub_service_id_num, 0);
314
+ sub_service_name := v_sub.sub_service_name;
315
+ sheet_name := sla_excel_sheet_name(v_sub.sub_service_name);
316
+ headers := v_headers_json;
317
+ rows := v_all_rows;
318
+ RETURN NEXT;
319
+ END LOOP;
320
+
321
+ -- ----- Per sub-service: Approvals (all steps for queue requests) -----
322
+ FOR v_sub IN
323
+ EXECUTE format($q$
324
+ SELECT DISTINCT
325
+ COALESCE(sr.sub_service_id, 0) AS sub_service_id_num,
326
+ COALESCE(NULLIF(trim(subsvc.sub_service_name), ''), NULLIF(trim(v.sub_service_name), ''), 'Uncategorized') AS sub_service_name
327
+ FROM vw_sla_my_requests v
328
+ INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
329
+ LEFT JOIN caa_sub_services subsvc ON subsvc.id = sr.sub_service_id
330
+ WHERE (%s)
331
+ AND (%s)
332
+ AND sr.status::TEXT = ANY($1)
333
+ AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
334
+ AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
335
+ AND ($4 IS NULL OR v.created_at::date >= $4)
336
+ AND ($5 IS NULL OR v.created_at::date <= $5)
337
+ AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
338
+ ORDER BY sub_service_name ASC
339
+ $q$, p_own_requests_filter_sql, p_queue_exists_sql)
340
+ USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
341
+ LOOP
342
+ v_sheet_order := v_sheet_order + 1;
343
+ v_sheet_label := sla_excel_approvals_sheet_name(v_sub.sub_service_name);
344
+ v_all_rows := '[]'::jsonb;
345
+
346
+ FOR v_approval IN
347
+ EXECUTE format($q$
348
+ SELECT
349
+ va.request_id,
350
+ sr.service_id,
351
+ va.service_name,
352
+ sr.sub_service_id,
353
+ va.sub_service_name,
354
+ va.sla_approval_id,
355
+ va.source_approval_id,
356
+ va.level,
357
+ va.approval_status,
358
+ va.request_status,
359
+ va.approval_role_id,
360
+ va.approval_role_name,
361
+ va.approval_department_id,
362
+ va.approval_department_name,
363
+ va.approval_section_id,
364
+ va.approval_section_name,
365
+ va.approver_user_id,
366
+ va.approver_user_name,
367
+ va.delegate_user_id,
368
+ va.delegate_user_name,
369
+ va.approved_by,
370
+ va.approved_by_name,
371
+ va.comment,
372
+ va.created_at,
373
+ va.updated_at
374
+ FROM vw_sla_approvals va
375
+ INNER JOIN sla_requests sr
376
+ ON sr.request_id = va.request_id AND COALESCE(sr.is_deleted, false) = false
377
+ INNER JOIN vw_sla_my_requests v ON v.sla_request_id = sr.id
378
+ WHERE (%s)
379
+ AND (%s)
380
+ AND sr.status::TEXT = ANY($1)
381
+ AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
382
+ AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
383
+ AND ($4 IS NULL OR v.created_at::date >= $4)
384
+ AND ($5 IS NULL OR v.created_at::date <= $5)
385
+ AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
386
+ AND COALESCE(sr.sub_service_id, 0) = %s
387
+ ORDER BY va.request_id ASC, va.level ASC NULLS LAST, va.sla_approval_id ASC
388
+ $q$, p_own_requests_filter_sql, p_queue_exists_sql, v_sub.sub_service_id_num)
389
+ USING p_request_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
390
+ LOOP
391
+ v_cells := ARRAY[
392
+ COALESCE(v_approval.request_id::TEXT, ''),
393
+ COALESCE(v_approval.service_id::TEXT, ''),
394
+ COALESCE(v_approval.service_name, ''),
395
+ COALESCE(v_approval.sub_service_id::TEXT, ''),
396
+ COALESCE(v_approval.sub_service_name, ''),
397
+ COALESCE(v_approval.sla_approval_id::TEXT, ''),
398
+ COALESCE(v_approval.source_approval_id::TEXT, ''),
399
+ COALESCE(v_approval.level::TEXT, ''),
400
+ COALESCE(v_approval.approval_status, ''),
401
+ COALESCE(v_approval.request_status, ''),
402
+ COALESCE(v_approval.approval_role_id::TEXT, ''),
403
+ COALESCE(v_approval.approval_role_name, ''),
404
+ COALESCE(v_approval.approval_department_id::TEXT, ''),
405
+ COALESCE(v_approval.approval_department_name, ''),
406
+ COALESCE(v_approval.approval_section_id::TEXT, ''),
407
+ COALESCE(v_approval.approval_section_name, ''),
408
+ COALESCE(v_approval.approver_user_id::TEXT, ''),
409
+ COALESCE(v_approval.approver_user_name, ''),
410
+ COALESCE(v_approval.delegate_user_id::TEXT, ''),
411
+ COALESCE(v_approval.delegate_user_name, ''),
412
+ COALESCE(v_approval.approved_by::TEXT, ''),
413
+ COALESCE(v_approval.approved_by_name, ''),
414
+ COALESCE(v_approval.comment, ''),
415
+ COALESCE(to_char(v_approval.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS'), ''),
416
+ COALESCE(to_char(v_approval.updated_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS'), '')
417
+ ];
418
+ v_all_rows := v_all_rows || jsonb_build_array(to_jsonb(v_cells));
419
+ END LOOP;
420
+
421
+ sheet_order := v_sheet_order;
422
+ sub_service_id := NULLIF(v_sub.sub_service_id_num, 0);
423
+ sub_service_name := v_sub.sub_service_name;
424
+ sheet_name := v_sheet_label;
425
+ headers := to_jsonb(v_approval_headers);
426
+ rows := v_all_rows;
427
+ RETURN NEXT;
428
+ END LOOP;
429
+ END;
430
+ $$;