@platform-modules/civil-aviation-authority 2.3.304 → 2.3.306
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.
- package/dist/i18n/workflow-chat-i18n.json +4 -0
- package/dist/i18n/workflow-chat-message.builder.d.ts +11 -0
- package/dist/i18n/workflow-chat-message.builder.js +23 -0
- package/dist/models/DepartmentsModel.d.ts +1 -0
- package/dist/models/DepartmentsModel.js +4 -0
- package/package.json +1 -1
- package/scripts/sync-sla-reports-sql.js +11 -17
- package/sql/README.md +1 -1
- package/sql/alter_sla_requests_status_to_varchar.sql +18 -4
- package/sql/caa_api_payload_changes_2026_07.sql +4 -0
- package/sql/sla_reports_admin_procedures.sql +27 -6
- package/sql/sla_reports_procedures.sql +30 -18
- package/src/i18n/workflow-chat-i18n.json +4 -0
- package/src/i18n/workflow-chat-message.builder.ts +43 -0
- package/src/models/DepartmentsModel.ts +3 -0
|
@@ -214,6 +214,10 @@
|
|
|
214
214
|
"en": "Request Received",
|
|
215
215
|
"ar": "تم استلام الطلب"
|
|
216
216
|
},
|
|
217
|
+
"request_received_by_role": {
|
|
218
|
+
"en": "Request Received by {{roleName}}",
|
|
219
|
+
"ar": "تم استلام الطلب من قبل {{roleName}}"
|
|
220
|
+
},
|
|
217
221
|
"request_assigned_by_role": {
|
|
218
222
|
"en": "Request assigned by {{roleName}}{{comment}}",
|
|
219
223
|
"ar": "تم تعيين الطلب من قبل {{roleName}}{{comment}}"
|
|
@@ -80,6 +80,17 @@ export declare function buildSynchronizedWorkflowMessage(ctx: WorkflowMessageCon
|
|
|
80
80
|
* Use for every chat insert/update across all CAA modules.
|
|
81
81
|
*/
|
|
82
82
|
export declare function buildSynchronizedChatMessage(ctx: ChatMessageContext): BilingualText;
|
|
83
|
+
/** Chat row: "Request Received by {{roleName}}" with Arabic role name when available. */
|
|
84
|
+
export declare function buildRequestReceivedByRoleChatMessage(params: {
|
|
85
|
+
roleName: string;
|
|
86
|
+
roleArabicName?: string | null;
|
|
87
|
+
}): BilingualText;
|
|
88
|
+
/** Chat row: "Request assigned by {{roleName}}" with optional ". Comment: ..." suffix. */
|
|
89
|
+
export declare function buildAssignmentByRoleChatMessage(params: {
|
|
90
|
+
roleName: string;
|
|
91
|
+
roleArabicName?: string | null;
|
|
92
|
+
comment?: string | null;
|
|
93
|
+
}): BilingualText;
|
|
83
94
|
export type RequestApprovedAssignedWorkflowParams = {
|
|
84
95
|
assignedUserName: string;
|
|
85
96
|
roleName: string;
|
|
@@ -24,6 +24,8 @@ exports.buildAssignTasksEmpPendingWorkflowFields = buildAssignTasksEmpPendingWor
|
|
|
24
24
|
exports.chatTemplate = chatTemplate;
|
|
25
25
|
exports.buildSynchronizedWorkflowMessage = buildSynchronizedWorkflowMessage;
|
|
26
26
|
exports.buildSynchronizedChatMessage = buildSynchronizedChatMessage;
|
|
27
|
+
exports.buildRequestReceivedByRoleChatMessage = buildRequestReceivedByRoleChatMessage;
|
|
28
|
+
exports.buildAssignmentByRoleChatMessage = buildAssignmentByRoleChatMessage;
|
|
27
29
|
exports.tryParseRequestAssignedComment = tryParseRequestAssignedComment;
|
|
28
30
|
exports.buildRequestApprovedAssignedWorkflowFields = buildRequestApprovedAssignedWorkflowFields;
|
|
29
31
|
exports.buildWorkflowLogUpdateFields = buildWorkflowLogUpdateFields;
|
|
@@ -315,6 +317,19 @@ function buildSynchronizedChatMessage(ctx) {
|
|
|
315
317
|
return chatTemplate('request_received');
|
|
316
318
|
}
|
|
317
319
|
}
|
|
320
|
+
/** Chat row: "Request Received by {{roleName}}" with Arabic role name when available. */
|
|
321
|
+
function buildRequestReceivedByRoleChatMessage(params) {
|
|
322
|
+
const roleEn = params.roleName?.trim() || 'Approver';
|
|
323
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
324
|
+
return renderSynchronizedChatMessage('request_received_by_role', { roleName: roleEn }, { roleName: roleAr });
|
|
325
|
+
}
|
|
326
|
+
/** Chat row: "Request assigned by {{roleName}}" with optional ". Comment: ..." suffix. */
|
|
327
|
+
function buildAssignmentByRoleChatMessage(params) {
|
|
328
|
+
const roleEn = params.roleName?.trim() || 'Unknown Role';
|
|
329
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
330
|
+
const commentSuffix = params.comment?.trim() ? `. Comment: ${params.comment.trim()}` : '';
|
|
331
|
+
return renderSynchronizedChatMessage('request_assigned_by_role', { roleName: roleEn, comment: commentSuffix }, { roleName: roleAr, comment: commentSuffix });
|
|
332
|
+
}
|
|
318
333
|
/** Parse legacy English-only assignment comments used as workflow log suffixes. */
|
|
319
334
|
function parseRequestAssignedCommentText(text) {
|
|
320
335
|
const normalized = text.trim();
|
|
@@ -1392,6 +1407,14 @@ function resolveChatMessageAr(message, actors) {
|
|
|
1392
1407
|
continue;
|
|
1393
1408
|
return interpolate(tpl.ar, buildArabicChatTemplateParams(params, actors));
|
|
1394
1409
|
}
|
|
1410
|
+
const receivedByPrefix = 'Request Received by ';
|
|
1411
|
+
if (normalized.startsWith(receivedByPrefix)) {
|
|
1412
|
+
const rolePartEn = normalized.slice(receivedByPrefix.length).trim();
|
|
1413
|
+
if (rolePartEn) {
|
|
1414
|
+
const roleAr = resolveBilingualName(rolePartEn, actors?.roleArabicName);
|
|
1415
|
+
return renderSynchronizedChatMessage('request_received_by_role', { roleName: rolePartEn }, { roleName: roleAr }).ar;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1395
1418
|
return null;
|
|
1396
1419
|
}
|
|
1397
1420
|
/** True when message matches a known system chat template (safe to resolve message_ar). */
|
|
@@ -4,5 +4,6 @@ export declare class Departments extends BaseModel {
|
|
|
4
4
|
department_arabic_name: string | null;
|
|
5
5
|
department_code: string;
|
|
6
6
|
department_description: string;
|
|
7
|
+
DG_department_id: number | null;
|
|
7
8
|
constructor(department_name: string, department_code: string, department_description: string);
|
|
8
9
|
}
|
|
@@ -37,6 +37,10 @@ __decorate([
|
|
|
37
37
|
(0, typeorm_1.Column)({ nullable: false }),
|
|
38
38
|
__metadata("design:type", String)
|
|
39
39
|
], Departments.prototype, "department_description", void 0);
|
|
40
|
+
__decorate([
|
|
41
|
+
(0, typeorm_1.Column)({ type: 'integer', nullable: true, name: 'DG_department_id' }),
|
|
42
|
+
__metadata("design:type", Object)
|
|
43
|
+
], Departments.prototype, "DG_department_id", void 0);
|
|
40
44
|
exports.Departments = Departments = __decorate([
|
|
41
45
|
(0, typeorm_1.Entity)({ name: 'departments' }),
|
|
42
46
|
__metadata("design:paramtypes", [String, String, String])
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Applies SLA report views + stored procedures to PostgreSQL (idempotent CREATE OR REPLACE).
|
|
3
3
|
*
|
|
4
|
-
* Env
|
|
4
|
+
* Env from shared_models/.env only:
|
|
5
5
|
* DB_HOST / TYPEORM_HOST
|
|
6
6
|
* DB_PORT / TYPEORM_PORT
|
|
7
7
|
* DB_USER / TYPEORM_USERNAME
|
|
@@ -20,31 +20,25 @@ const { Client } = require('pg');
|
|
|
20
20
|
function loadEnvFiles() {
|
|
21
21
|
const dotenv = require('dotenv');
|
|
22
22
|
const packageRoot = path.resolve(__dirname, '..');
|
|
23
|
-
const cwdEnv = path.join(process.cwd(), '.env');
|
|
24
23
|
const packageEnv = path.join(packageRoot, '.env');
|
|
25
|
-
const reportsEnv = path.resolve(packageRoot, '..', 'Reports_Service', '.env');
|
|
26
24
|
|
|
27
|
-
if (fs.existsSync(
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
if (fs.existsSync(packageEnv)) {
|
|
31
|
-
dotenv.config({ path: packageEnv, override: true });
|
|
32
|
-
}
|
|
33
|
-
if (fs.existsSync(reportsEnv)) {
|
|
34
|
-
dotenv.config({ path: reportsEnv, override: true });
|
|
25
|
+
if (!fs.existsSync(packageEnv)) {
|
|
26
|
+
throw new Error(`Missing env file: ${packageEnv}`);
|
|
35
27
|
}
|
|
28
|
+
|
|
29
|
+
dotenv.config({ path: packageEnv, override: true });
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
function getDbConfig() {
|
|
39
|
-
const host = (process.env.
|
|
40
|
-
const port = parseInt(process.env.
|
|
41
|
-
const user = (process.env.
|
|
42
|
-
const password = process.env.
|
|
43
|
-
const database = (process.env.
|
|
33
|
+
const host = (process.env.DB_HOST || process.env.TYPEORM_HOST || '').trim();
|
|
34
|
+
const port = parseInt(process.env.DB_PORT || process.env.TYPEORM_PORT || '5432', 10);
|
|
35
|
+
const user = (process.env.DB_USER || process.env.TYPEORM_USERNAME || '').trim();
|
|
36
|
+
const password = process.env.DB_PASS || process.env.TYPEORM_PASSWORD || '';
|
|
37
|
+
const database = (process.env.DB_NAME || process.env.TYPEORM_DATABASE || '').trim();
|
|
44
38
|
|
|
45
39
|
if (!host || !user || !database) {
|
|
46
40
|
throw new Error(
|
|
47
|
-
'Missing DB config. Set
|
|
41
|
+
'Missing DB config. Set DB_HOST, DB_USER, DB_NAME (and DB_PASS) in shared_models/.env'
|
|
48
42
|
);
|
|
49
43
|
}
|
|
50
44
|
|
package/sql/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Applied in order by `npm run sync:sla-sql` (see `sla-reports-sync.manifest.json`
|
|
|
10
10
|
| `sla_reports_approvals_workbook.sql` | Approvals Excel workbook (Requests + Approvals per sub-service) |
|
|
11
11
|
| `sla_reports_admin_procedures.sql` | Admin SLA report functions (requires procedures file) |
|
|
12
12
|
|
|
13
|
-
From **shared_models** (
|
|
13
|
+
From **shared_models** (reads DB config from `shared_models/.env` only — `DB_HOST`, `DB_USER`, `DB_PASS`, `DB_NAME`):
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
cd shared_models && npm run sync:sla-sql
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
-- Migrate sla_requests.status from PostgreSQL enum to varchar(64) in-place (preserves data).
|
|
2
|
-
-- Drops SLA views
|
|
2
|
+
-- Drops SLA views when a type change is required (recreated by later sync:sla-sql files).
|
|
3
3
|
-- Idempotent when status is already varchar(64).
|
|
4
4
|
|
|
5
5
|
DO $$
|
|
6
6
|
DECLARE
|
|
7
7
|
status_udt text;
|
|
8
|
+
status_len integer;
|
|
8
9
|
BEGIN
|
|
9
|
-
SELECT c.udt_name
|
|
10
|
-
INTO status_udt
|
|
10
|
+
SELECT c.udt_name, c.character_maximum_length
|
|
11
|
+
INTO status_udt, status_len
|
|
11
12
|
FROM information_schema.columns c
|
|
12
13
|
WHERE c.table_schema = 'public'
|
|
13
14
|
AND c.table_name = 'sla_requests'
|
|
14
15
|
AND c.column_name = 'status';
|
|
15
16
|
|
|
17
|
+
IF status_udt IS NULL THEN
|
|
18
|
+
RAISE NOTICE 'sla_requests.status not found — skip type migration';
|
|
19
|
+
RETURN;
|
|
20
|
+
END IF;
|
|
21
|
+
|
|
22
|
+
-- Enum -> varchar(64)
|
|
16
23
|
IF status_udt = 'sla_requests_status_enum' THEN
|
|
17
24
|
DROP VIEW IF EXISTS vw_sla_approvals CASCADE;
|
|
18
25
|
DROP VIEW IF EXISTS vw_sla_my_requests CASCADE;
|
|
@@ -21,9 +28,16 @@ BEGIN
|
|
|
21
28
|
ALTER COLUMN status TYPE varchar(64) USING status::text;
|
|
22
29
|
|
|
23
30
|
DROP TYPE IF EXISTS sla_requests_status_enum;
|
|
24
|
-
|
|
31
|
+
|
|
32
|
+
-- Already varchar but not length 64 (views block ALTER TYPE)
|
|
33
|
+
ELSIF status_udt = 'varchar' AND status_len IS DISTINCT FROM 64 THEN
|
|
34
|
+
DROP VIEW IF EXISTS vw_sla_approvals CASCADE;
|
|
35
|
+
DROP VIEW IF EXISTS vw_sla_my_requests CASCADE;
|
|
36
|
+
|
|
25
37
|
ALTER TABLE sla_requests
|
|
26
38
|
ALTER COLUMN status TYPE varchar(64);
|
|
39
|
+
|
|
40
|
+
-- Already varchar(64) — nothing to do
|
|
27
41
|
END IF;
|
|
28
42
|
END $$;
|
|
29
43
|
|
|
@@ -139,3 +139,7 @@ ALTER TABLE followup_report_items
|
|
|
139
139
|
ADD COLUMN IF NOT EXISTS file_name VARCHAR(255),
|
|
140
140
|
ADD COLUMN IF NOT EXISTS file_type VARCHAR(100),
|
|
141
141
|
ADD COLUMN IF NOT EXISTS file_size BIGINT;
|
|
142
|
+
|
|
143
|
+
-- 10. Departments: optional parent DG department reference (self FK to departments.id)
|
|
144
|
+
ALTER TABLE departments
|
|
145
|
+
ADD COLUMN IF NOT EXISTS "DG_department_id" INTEGER NULL;
|
|
@@ -22,6 +22,8 @@ $$;
|
|
|
22
22
|
-- ADMIN — my requests Excel (optional p_target_user_id)
|
|
23
23
|
-- Per sub-service Requests + Approvals sheets (same as user my-requests download)
|
|
24
24
|
-- ---------------------------------------------------------------------------
|
|
25
|
+
DROP FUNCTION IF EXISTS sp_sla_admin_my_requests_excel_export(INT, INT[], INT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT);
|
|
26
|
+
|
|
25
27
|
CREATE OR REPLACE FUNCTION sp_sla_admin_my_requests_excel_export(
|
|
26
28
|
p_target_user_id INT DEFAULT NULL,
|
|
27
29
|
p_service_ids INT[] DEFAULT NULL,
|
|
@@ -31,7 +33,8 @@ CREATE OR REPLACE FUNCTION sp_sla_admin_my_requests_excel_export(
|
|
|
31
33
|
p_to_date DATE DEFAULT NULL,
|
|
32
34
|
p_search_text TEXT DEFAULT NULL,
|
|
33
35
|
p_sort_by TEXT DEFAULT 'created_at',
|
|
34
|
-
p_sort_order TEXT DEFAULT 'DESC'
|
|
36
|
+
p_sort_order TEXT DEFAULT 'DESC',
|
|
37
|
+
p_extra_filter_sql TEXT DEFAULT 'TRUE'
|
|
35
38
|
)
|
|
36
39
|
RETURNS TABLE (
|
|
37
40
|
sheet_order INT,
|
|
@@ -63,7 +66,11 @@ DECLARE
|
|
|
63
66
|
v_sheet_order INT := 0;
|
|
64
67
|
v_user_filter TEXT;
|
|
65
68
|
BEGIN
|
|
66
|
-
v_user_filter :=
|
|
69
|
+
v_user_filter := format(
|
|
70
|
+
'(%s) AND (%s)',
|
|
71
|
+
CASE WHEN p_target_user_id IS NULL THEN 'TRUE' ELSE format('v.user_id = %s', p_target_user_id) END,
|
|
72
|
+
COALESCE(NULLIF(trim(p_extra_filter_sql), ''), 'TRUE')
|
|
73
|
+
);
|
|
67
74
|
|
|
68
75
|
v_sort_col := CASE lower(trim(p_sort_by))
|
|
69
76
|
WHEN 'updated_at' THEN 'v.created_at'
|
|
@@ -199,6 +206,10 @@ DROP FUNCTION IF EXISTS sp_sla_approvals_excel_export_internal(
|
|
|
199
206
|
TEXT, BOOLEAN, INT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
|
|
200
207
|
);
|
|
201
208
|
|
|
209
|
+
DROP FUNCTION IF EXISTS sp_sla_approvals_excel_export(
|
|
210
|
+
INT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
|
|
211
|
+
);
|
|
212
|
+
|
|
202
213
|
CREATE OR REPLACE FUNCTION sp_sla_approvals_excel_export(
|
|
203
214
|
p_approver_user_id INT,
|
|
204
215
|
p_service_ids INT[] DEFAULT NULL,
|
|
@@ -209,7 +220,8 @@ CREATE OR REPLACE FUNCTION sp_sla_approvals_excel_export(
|
|
|
209
220
|
p_to_date DATE DEFAULT NULL,
|
|
210
221
|
p_search_text TEXT DEFAULT NULL,
|
|
211
222
|
p_sort_by TEXT DEFAULT 'created_at',
|
|
212
|
-
p_sort_order TEXT DEFAULT 'DESC'
|
|
223
|
+
p_sort_order TEXT DEFAULT 'DESC',
|
|
224
|
+
p_extra_filter_sql TEXT DEFAULT 'TRUE'
|
|
213
225
|
)
|
|
214
226
|
RETURNS TABLE (
|
|
215
227
|
sheet_order INT, sub_service_id INT, sub_service_name TEXT, sheet_name TEXT, headers JSONB, rows JSONB
|
|
@@ -219,7 +231,11 @@ STABLE
|
|
|
219
231
|
AS $$
|
|
220
232
|
DECLARE
|
|
221
233
|
v_exists TEXT := sla_approval_queue_exists_sql(p_approver_user_id);
|
|
222
|
-
v_own TEXT := format(
|
|
234
|
+
v_own TEXT := format(
|
|
235
|
+
'(%s) AND (%s)',
|
|
236
|
+
format('sr.user_id != %s', p_approver_user_id),
|
|
237
|
+
COALESCE(NULLIF(trim(p_extra_filter_sql), ''), 'TRUE')
|
|
238
|
+
);
|
|
223
239
|
BEGIN
|
|
224
240
|
RETURN QUERY
|
|
225
241
|
SELECT * FROM sp_sla_approvals_workbook_excel_export(
|
|
@@ -243,6 +259,10 @@ $$;
|
|
|
243
259
|
-- Per sub-service Requests + Approvals; optional approver_user_id (NULL = all queues)
|
|
244
260
|
-- Does not exclude own requests (unlike user approvals download)
|
|
245
261
|
-- ---------------------------------------------------------------------------
|
|
262
|
+
DROP FUNCTION IF EXISTS sp_sla_admin_approvals_excel_export(
|
|
263
|
+
INT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
|
|
264
|
+
);
|
|
265
|
+
|
|
246
266
|
CREATE OR REPLACE FUNCTION sp_sla_admin_approvals_excel_export(
|
|
247
267
|
p_target_approver_user_id INT DEFAULT NULL,
|
|
248
268
|
p_service_ids INT[] DEFAULT NULL,
|
|
@@ -253,7 +273,8 @@ CREATE OR REPLACE FUNCTION sp_sla_admin_approvals_excel_export(
|
|
|
253
273
|
p_to_date DATE DEFAULT NULL,
|
|
254
274
|
p_search_text TEXT DEFAULT NULL,
|
|
255
275
|
p_sort_by TEXT DEFAULT 'created_at',
|
|
256
|
-
p_sort_order TEXT DEFAULT 'DESC'
|
|
276
|
+
p_sort_order TEXT DEFAULT 'DESC',
|
|
277
|
+
p_extra_filter_sql TEXT DEFAULT 'TRUE'
|
|
257
278
|
)
|
|
258
279
|
RETURNS TABLE (
|
|
259
280
|
sheet_order INT, sub_service_id INT, sub_service_name TEXT, sheet_name TEXT, headers JSONB, rows JSONB
|
|
@@ -263,7 +284,7 @@ STABLE
|
|
|
263
284
|
AS $$
|
|
264
285
|
DECLARE
|
|
265
286
|
v_exists TEXT := sla_approval_queue_exists_sql_optional(p_target_approver_user_id);
|
|
266
|
-
v_own TEXT := 'TRUE';
|
|
287
|
+
v_own TEXT := COALESCE(NULLIF(trim(p_extra_filter_sql), ''), 'TRUE');
|
|
267
288
|
BEGIN
|
|
268
289
|
RETURN QUERY
|
|
269
290
|
SELECT * FROM sp_sla_approvals_workbook_excel_export(
|
|
@@ -700,6 +700,8 @@ $$;
|
|
|
700
700
|
-- MY REQUESTS — Excel workbook payload (one result row per sub-service sheet)
|
|
701
701
|
-- Uses vw_sla_my_requests; flattening and dynamic columns are built in the database.
|
|
702
702
|
-- ---------------------------------------------------------------------------
|
|
703
|
+
DROP FUNCTION IF EXISTS sp_sla_my_requests_excel_export(INT, INT[], INT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT);
|
|
704
|
+
|
|
703
705
|
CREATE OR REPLACE FUNCTION sp_sla_my_requests_excel_export(
|
|
704
706
|
p_user_id INT,
|
|
705
707
|
p_service_ids INT[] DEFAULT NULL,
|
|
@@ -709,7 +711,8 @@ CREATE OR REPLACE FUNCTION sp_sla_my_requests_excel_export(
|
|
|
709
711
|
p_to_date DATE DEFAULT NULL,
|
|
710
712
|
p_search_text TEXT DEFAULT NULL,
|
|
711
713
|
p_sort_by TEXT DEFAULT 'created_at',
|
|
712
|
-
p_sort_order TEXT DEFAULT 'DESC'
|
|
714
|
+
p_sort_order TEXT DEFAULT 'DESC',
|
|
715
|
+
p_extra_filter_sql TEXT DEFAULT 'TRUE'
|
|
713
716
|
)
|
|
714
717
|
RETURNS TABLE (
|
|
715
718
|
sheet_order INT,
|
|
@@ -739,7 +742,10 @@ DECLARE
|
|
|
739
742
|
v_all_rows JSONB;
|
|
740
743
|
v_k TEXT;
|
|
741
744
|
v_sheet_order INT := 0;
|
|
745
|
+
v_extra TEXT;
|
|
742
746
|
BEGIN
|
|
747
|
+
v_extra := COALESCE(NULLIF(trim(p_extra_filter_sql), ''), 'TRUE');
|
|
748
|
+
|
|
743
749
|
v_sort_col := CASE lower(trim(p_sort_by))
|
|
744
750
|
WHEN 'updated_at' THEN 'v.created_at'
|
|
745
751
|
WHEN 'id' THEN 'v.sla_request_id'
|
|
@@ -758,6 +764,7 @@ BEGIN
|
|
|
758
764
|
INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
|
|
759
765
|
LEFT JOIN caa_sub_services subsvc ON subsvc.id = sr.sub_service_id
|
|
760
766
|
WHERE v.user_id = %1$s
|
|
767
|
+
AND (%2$s)
|
|
761
768
|
AND v.status = ANY($1)
|
|
762
769
|
AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
|
|
763
770
|
AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
|
|
@@ -765,24 +772,28 @@ BEGIN
|
|
|
765
772
|
AND ($5 IS NULL OR v.created_at::date <= $5)
|
|
766
773
|
AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
|
|
767
774
|
ORDER BY sub_service_name ASC
|
|
768
|
-
$q$, p_user_id)
|
|
775
|
+
$q$, p_user_id, v_extra)
|
|
769
776
|
USING p_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
|
|
770
777
|
LOOP
|
|
771
778
|
v_sheet_order := v_sheet_order + 1;
|
|
772
779
|
|
|
773
|
-
|
|
780
|
+
EXECUTE format($q$
|
|
781
|
+
SELECT COALESCE(array_agg(DISTINCT keys.k ORDER BY keys.k), ARRAY[]::TEXT[])
|
|
782
|
+
FROM vw_sla_my_requests v
|
|
783
|
+
INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
|
|
784
|
+
CROSS JOIN LATERAL jsonb_object_keys(COALESCE(v.request_fields, '{}'::jsonb)) AS keys(k)
|
|
785
|
+
WHERE v.user_id = %1$s
|
|
786
|
+
AND (%2$s)
|
|
787
|
+
AND v.status = ANY($1)
|
|
788
|
+
AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
|
|
789
|
+
AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
|
|
790
|
+
AND ($4 IS NULL OR v.created_at::date >= $4)
|
|
791
|
+
AND ($5 IS NULL OR v.created_at::date <= $5)
|
|
792
|
+
AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
|
|
793
|
+
AND COALESCE(sr.sub_service_id, 0) = %3$s
|
|
794
|
+
$q$, p_user_id, v_extra, v_sub.sub_service_id_num)
|
|
774
795
|
INTO v_dyn_keys
|
|
775
|
-
|
|
776
|
-
INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
|
|
777
|
-
CROSS JOIN LATERAL jsonb_object_keys(COALESCE(v.request_fields, '{}'::jsonb)) AS keys(k)
|
|
778
|
-
WHERE v.user_id = p_user_id
|
|
779
|
-
AND v.status = ANY(p_statuses)
|
|
780
|
-
AND (p_service_ids IS NULL OR cardinality(p_service_ids) = 0 OR sr.service_id = ANY(p_service_ids))
|
|
781
|
-
AND (p_sub_service_ids IS NULL OR cardinality(p_sub_service_ids) = 0 OR sr.sub_service_id = ANY(p_sub_service_ids))
|
|
782
|
-
AND (p_from_date IS NULL OR v.created_at::date >= p_from_date)
|
|
783
|
-
AND (p_to_date IS NULL OR v.created_at::date <= p_to_date)
|
|
784
|
-
AND (p_search_text IS NULL OR trim(p_search_text) = '' OR CAST(v.request_id AS TEXT) ILIKE '%' || trim(p_search_text) || '%')
|
|
785
|
-
AND COALESCE(sr.sub_service_id, 0) = v_sub.sub_service_id_num;
|
|
796
|
+
USING p_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text;
|
|
786
797
|
|
|
787
798
|
v_dyn_labels := ARRAY[]::TEXT[];
|
|
788
799
|
IF v_dyn_keys IS NOT NULL THEN
|
|
@@ -815,15 +826,16 @@ BEGIN
|
|
|
815
826
|
INNER JOIN sla_requests sr ON sr.id = v.sla_request_id AND COALESCE(sr.is_deleted, false) = false
|
|
816
827
|
LEFT JOIN users u ON u.id = v.user_id AND COALESCE(u.is_deleted, false) = false
|
|
817
828
|
WHERE v.user_id = %1$s
|
|
829
|
+
AND (%2$s)
|
|
818
830
|
AND v.status = ANY($1)
|
|
819
831
|
AND ($2 IS NULL OR cardinality($2) = 0 OR sr.service_id = ANY($2))
|
|
820
832
|
AND ($3 IS NULL OR cardinality($3) = 0 OR sr.sub_service_id = ANY($3))
|
|
821
833
|
AND ($4 IS NULL OR v.created_at::date >= $4)
|
|
822
834
|
AND ($5 IS NULL OR v.created_at::date <= $5)
|
|
823
835
|
AND ($6 IS NULL OR trim($6) = '' OR CAST(v.request_id AS TEXT) ILIKE '%%' || trim($6) || '%%')
|
|
824
|
-
AND COALESCE(sr.sub_service_id, 0) = %
|
|
825
|
-
ORDER BY %
|
|
826
|
-
$q$, p_user_id, v_sub.sub_service_id_num, v_sort_col, v_sort_dir)
|
|
836
|
+
AND COALESCE(sr.sub_service_id, 0) = %3$s
|
|
837
|
+
ORDER BY %4$s %5$s, v.sla_request_id ASC
|
|
838
|
+
$q$, p_user_id, v_extra, v_sub.sub_service_id_num, v_sort_col, v_sort_dir)
|
|
827
839
|
USING p_statuses, p_service_ids, p_sub_service_ids, p_from_date, p_to_date, p_search_text
|
|
828
840
|
LOOP
|
|
829
841
|
v_cells := ARRAY[
|
|
@@ -859,7 +871,7 @@ BEGIN
|
|
|
859
871
|
RETURN QUERY
|
|
860
872
|
SELECT *
|
|
861
873
|
FROM sla_my_requests_approvals_excel_sheet(
|
|
862
|
-
format('v.user_id = %s', p_user_id),
|
|
874
|
+
format('v.user_id = %s AND (%s)', p_user_id, v_extra),
|
|
863
875
|
p_statuses,
|
|
864
876
|
p_service_ids,
|
|
865
877
|
p_sub_service_ids,
|
|
@@ -214,6 +214,10 @@
|
|
|
214
214
|
"en": "Request Received",
|
|
215
215
|
"ar": "تم استلام الطلب"
|
|
216
216
|
},
|
|
217
|
+
"request_received_by_role": {
|
|
218
|
+
"en": "Request Received by {{roleName}}",
|
|
219
|
+
"ar": "تم استلام الطلب من قبل {{roleName}}"
|
|
220
|
+
},
|
|
217
221
|
"request_assigned_by_role": {
|
|
218
222
|
"en": "Request assigned by {{roleName}}{{comment}}",
|
|
219
223
|
"ar": "تم تعيين الطلب من قبل {{roleName}}{{comment}}"
|
|
@@ -456,6 +456,36 @@ export function buildSynchronizedChatMessage(ctx: ChatMessageContext): Bilingual
|
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
/** Chat row: "Request Received by {{roleName}}" with Arabic role name when available. */
|
|
460
|
+
export function buildRequestReceivedByRoleChatMessage(params: {
|
|
461
|
+
roleName: string;
|
|
462
|
+
roleArabicName?: string | null;
|
|
463
|
+
}): BilingualText {
|
|
464
|
+
const roleEn = params.roleName?.trim() || 'Approver';
|
|
465
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
466
|
+
return renderSynchronizedChatMessage(
|
|
467
|
+
'request_received_by_role',
|
|
468
|
+
{ roleName: roleEn },
|
|
469
|
+
{ roleName: roleAr },
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** Chat row: "Request assigned by {{roleName}}" with optional ". Comment: ..." suffix. */
|
|
474
|
+
export function buildAssignmentByRoleChatMessage(params: {
|
|
475
|
+
roleName: string;
|
|
476
|
+
roleArabicName?: string | null;
|
|
477
|
+
comment?: string | null;
|
|
478
|
+
}): BilingualText {
|
|
479
|
+
const roleEn = params.roleName?.trim() || 'Unknown Role';
|
|
480
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
481
|
+
const commentSuffix = params.comment?.trim() ? `. Comment: ${params.comment.trim()}` : '';
|
|
482
|
+
return renderSynchronizedChatMessage(
|
|
483
|
+
'request_assigned_by_role',
|
|
484
|
+
{ roleName: roleEn, comment: commentSuffix },
|
|
485
|
+
{ roleName: roleAr, comment: commentSuffix },
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
459
489
|
// ─── Backward-compatible aliases used by Cancellation pilot ───────────────────
|
|
460
490
|
|
|
461
491
|
export type RequestApprovedAssignedWorkflowParams = {
|
|
@@ -2044,6 +2074,19 @@ export function resolveChatMessageAr(
|
|
|
2044
2074
|
return interpolate(tpl.ar, buildArabicChatTemplateParams(params, actors));
|
|
2045
2075
|
}
|
|
2046
2076
|
|
|
2077
|
+
const receivedByPrefix = 'Request Received by ';
|
|
2078
|
+
if (normalized.startsWith(receivedByPrefix)) {
|
|
2079
|
+
const rolePartEn = normalized.slice(receivedByPrefix.length).trim();
|
|
2080
|
+
if (rolePartEn) {
|
|
2081
|
+
const roleAr = resolveBilingualName(rolePartEn, actors?.roleArabicName);
|
|
2082
|
+
return renderSynchronizedChatMessage(
|
|
2083
|
+
'request_received_by_role',
|
|
2084
|
+
{ roleName: rolePartEn },
|
|
2085
|
+
{ roleName: roleAr },
|
|
2086
|
+
).ar;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2047
2090
|
return null;
|
|
2048
2091
|
}
|
|
2049
2092
|
|
|
@@ -16,6 +16,9 @@ export class Departments extends BaseModel {
|
|
|
16
16
|
@Column({ nullable: false })
|
|
17
17
|
department_description: string;
|
|
18
18
|
|
|
19
|
+
@Column({ type: 'integer', nullable: true, name: 'DG_department_id' })
|
|
20
|
+
DG_department_id: number | null;
|
|
21
|
+
|
|
19
22
|
constructor(
|
|
20
23
|
department_name: string,
|
|
21
24
|
department_code: string,
|