@platform-modules/civil-aviation-authority 2.3.277 → 2.3.279

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/.env CHANGED
@@ -2,7 +2,7 @@ DB_HOST=164.52.222.169
2
2
  DB_PORT=5432
3
3
  DB_USER=postgres_admin_user
4
4
  DB_PASS=pg_admin_user_pwd_caa_fa_$%^&OIukhjgcvbn
5
- DB_NAME=CAA
5
+ DB_NAME=CAA_DEV
6
6
 
7
7
 
8
8
  # DB_HOST=localhost
@@ -0,0 +1,37 @@
1
+ import type { EntityManager } from 'typeorm';
2
+ import { CancellationMessageType } from '../models/CancellationChatModel';
3
+ import { CancellationWorkFlowStatus } from '../models/CancellationWorkflowModel';
4
+ export type CancellationWorkflowRowInput = {
5
+ request_id: number;
6
+ service_id?: number | null;
7
+ sub_service_id?: number | null;
8
+ content: string;
9
+ content_ar?: string | null;
10
+ status: CancellationWorkFlowStatus;
11
+ status_ar?: string | null;
12
+ user_id?: number | null;
13
+ role_id?: number | null;
14
+ department_id?: number | null;
15
+ section_id?: number | null;
16
+ step_order?: number | null;
17
+ created_by?: number | null;
18
+ };
19
+ export type CancellationChatRowInput = {
20
+ request_id: number;
21
+ service_id?: number | null;
22
+ sub_service_id?: number | null;
23
+ user_id: number;
24
+ role_id?: number | null;
25
+ message: string;
26
+ message_ar?: string | null;
27
+ messageType?: CancellationMessageType;
28
+ status?: string | null;
29
+ status_ar?: string | null;
30
+ created_by?: number | string | null;
31
+ };
32
+ /** Force-persist Arabic workflow columns (guards against stale TypeORM entity metadata). */
33
+ export declare function patchCancellationWorkflowArabic(manager: EntityManager, workflowId: number, contentAr: string | null, statusAr: string | null): Promise<void>;
34
+ /** Force-persist Arabic chat columns (guards against stale TypeORM entity metadata). */
35
+ export declare function patchCancellationChatArabic(manager: EntityManager, chatId: number, messageAr: string | null, statusAr: string | null): Promise<void>;
36
+ export declare function insertCancellationWorkflowRow(manager: EntityManager, input: CancellationWorkflowRowInput): Promise<number>;
37
+ export declare function insertCancellationChatRow(manager: EntityManager, input: CancellationChatRowInput): Promise<number>;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.patchCancellationWorkflowArabic = patchCancellationWorkflowArabic;
4
+ exports.patchCancellationChatArabic = patchCancellationChatArabic;
5
+ exports.insertCancellationWorkflowRow = insertCancellationWorkflowRow;
6
+ exports.insertCancellationChatRow = insertCancellationChatRow;
7
+ const CancellationChatModel_1 = require("../models/CancellationChatModel");
8
+ const CancellationWorkflowModel_1 = require("../models/CancellationWorkflowModel");
9
+ const workflow_chat_i18n_1 = require("../i18n/workflow-chat-i18n");
10
+ function resolveWorkflowRowId(insertResult) {
11
+ const id = insertResult.raw?.[0]?.id ?? insertResult.identifiers?.[0]?.id;
12
+ if (!id) {
13
+ throw new Error('Failed to insert cancellation workflow row');
14
+ }
15
+ return id;
16
+ }
17
+ function resolveChatRowId(insertResult) {
18
+ const id = insertResult.raw?.[0]?.id ?? insertResult.identifiers?.[0]?.id;
19
+ if (!id) {
20
+ throw new Error('Failed to insert cancellation chat row');
21
+ }
22
+ return id;
23
+ }
24
+ /** Force-persist Arabic workflow columns (guards against stale TypeORM entity metadata). */
25
+ async function patchCancellationWorkflowArabic(manager, workflowId, contentAr, statusAr) {
26
+ await manager.query(`UPDATE cancellation_workflows
27
+ SET content_ar = $1,
28
+ status_ar = $2,
29
+ updated_at = CURRENT_TIMESTAMP
30
+ WHERE id = $3`, [contentAr, statusAr, workflowId]);
31
+ }
32
+ /** Force-persist Arabic chat columns (guards against stale TypeORM entity metadata). */
33
+ async function patchCancellationChatArabic(manager, chatId, messageAr, statusAr) {
34
+ await manager.query(`UPDATE cancellation_chats
35
+ SET message_ar = $1,
36
+ status_ar = $2,
37
+ updated_at = CURRENT_TIMESTAMP
38
+ WHERE id = $3`, [messageAr, statusAr, chatId]);
39
+ }
40
+ async function insertCancellationWorkflowRow(manager, input) {
41
+ const contentAr = input.content_ar ?? null;
42
+ const statusAr = input.status_ar ?? (0, workflow_chat_i18n_1.resolveWorkflowStatusAr)(input.status);
43
+ const insertResult = await manager
44
+ .createQueryBuilder()
45
+ .insert()
46
+ .into(CancellationWorkflowModel_1.CancellationWorkFlow)
47
+ .values({
48
+ ...input,
49
+ content_ar: contentAr,
50
+ status_ar: statusAr,
51
+ })
52
+ .returning('id')
53
+ .execute();
54
+ const id = resolveWorkflowRowId(insertResult);
55
+ await patchCancellationWorkflowArabic(manager, id, contentAr, statusAr);
56
+ return id;
57
+ }
58
+ async function insertCancellationChatRow(manager, input) {
59
+ const messageAr = input.message_ar ?? null;
60
+ const statusAr = input.status_ar ?? (0, workflow_chat_i18n_1.resolveChatStatusAr)(input.status ?? null);
61
+ const messageType = input.messageType ?? CancellationChatModel_1.CancellationMessageType.TEXT;
62
+ const insertResult = await manager
63
+ .createQueryBuilder()
64
+ .insert()
65
+ .into(CancellationChatModel_1.CancellationRequestChat)
66
+ .values({
67
+ ...input,
68
+ message_ar: messageAr,
69
+ status_ar: statusAr,
70
+ messageType,
71
+ })
72
+ .returning('id')
73
+ .execute();
74
+ const id = resolveChatRowId(insertResult);
75
+ await patchCancellationChatArabic(manager, id, messageAr, statusAr);
76
+ return id;
77
+ }
@@ -10,14 +10,24 @@ type WorkflowChatI18nCatalog = {
10
10
  };
11
11
  /** Read-only view of the shared workflow/chat translation catalog. */
12
12
  export declare function getWorkflowChatI18nCatalog(): WorkflowChatI18nCatalog;
13
- export declare function buildBilingualFromTemplate(templateCatalog: Record<string, BilingualText>, key: string, params?: Record<string, string>): BilingualText;
14
- export declare function workflowTemplate(key: string, params?: Record<string, string>): BilingualText;
15
- export declare function chatTemplate(key: string, params?: Record<string, string>): BilingualText;
13
+ export declare function buildBilingualFromTemplate(templateCatalog: Record<string, BilingualText>, key: string, params?: Record<string, string>, arParams?: Record<string, string>): BilingualText;
14
+ export declare function workflowTemplate(key: string, params?: Record<string, string>, arParams?: Record<string, string>): BilingualText;
15
+ export declare function chatTemplate(key: string, params?: Record<string, string>, arParams?: Record<string, string>): BilingualText;
16
16
  export declare function resolveWorkflowStatusAr(status: string | null | undefined): string | null;
17
17
  export declare function resolveChatStatusAr(status: string | null | undefined): string | null;
18
+ /** Arabic DB value when present; otherwise English so AR text stays structurally complete. */
19
+ export declare function resolveBilingualName(englishName?: string | null, arabicName?: string | null): string;
20
+ export declare function pickArabicName(arabicName?: string | null, fallback?: string): string;
18
21
  export declare function pickLocalizedName(englishName?: string | null, arabicName?: string | null, fallback?: string): string;
22
+ /** Parallel EN/AR dept-section labels (Arabic columns preferred; English fallback per field). */
23
+ export declare function joinDeptSectionBilingualLabels(deptEn?: string | null, deptAr?: string | null, sectEn?: string | null, sectAr?: string | null): {
24
+ en: string;
25
+ ar: string;
26
+ };
27
+ /** @deprecated Use joinDeptSectionBilingualLabels */
28
+ export declare function joinDeptSectionArabicLabels(deptArabic?: string | null, sectionArabic?: string | null): string;
19
29
  export declare function buildApprovalWorkflowContent(params: {
20
- status: 'Approved' | 'Rejected' | 'Pending' | string;
30
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
21
31
  roleName?: string | null;
22
32
  roleArabicName?: string | null;
23
33
  comment?: string | null;
@@ -26,15 +36,21 @@ export declare function buildHumanApprovalWorkflowContent(params: {
26
36
  isFirst: boolean;
27
37
  roleName?: string | null;
28
38
  roleArabicName?: string | null;
29
- deptSectionEn?: string | null;
30
- deptSectionAr?: string | null;
39
+ deptName?: string | null;
40
+ deptArabicName?: string | null;
41
+ sectionName?: string | null;
42
+ sectionArabicName?: string | null;
31
43
  }): BilingualText;
32
44
  export declare function buildApprovalChatMessage(params: {
33
- status: 'Approved' | 'Rejected' | string;
45
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | string;
34
46
  roleName?: string | null;
35
47
  roleArabicName?: string | null;
36
48
  comment?: string | null;
37
49
  }): BilingualText;
50
+ /** Resolve Arabic workflow content from a stored English content string (legacy rows). */
51
+ export declare function resolveWorkflowContentAr(content: string | null | undefined): string | null;
52
+ /** Resolve Arabic chat message from a stored English message string (legacy rows). */
53
+ export declare function resolveChatMessageAr(message: string | null | undefined): string | null;
38
54
  export declare function enrichCancellationWorkflowLog(log: Record<string, unknown>): Record<string, unknown>;
39
55
  export declare function enrichCancellationChatMessage(chat: Record<string, unknown>): Record<string, unknown>;
40
56
  export {};
@@ -9,10 +9,16 @@ exports.workflowTemplate = workflowTemplate;
9
9
  exports.chatTemplate = chatTemplate;
10
10
  exports.resolveWorkflowStatusAr = resolveWorkflowStatusAr;
11
11
  exports.resolveChatStatusAr = resolveChatStatusAr;
12
+ exports.resolveBilingualName = resolveBilingualName;
13
+ exports.pickArabicName = pickArabicName;
12
14
  exports.pickLocalizedName = pickLocalizedName;
15
+ exports.joinDeptSectionBilingualLabels = joinDeptSectionBilingualLabels;
16
+ exports.joinDeptSectionArabicLabels = joinDeptSectionArabicLabels;
13
17
  exports.buildApprovalWorkflowContent = buildApprovalWorkflowContent;
14
18
  exports.buildHumanApprovalWorkflowContent = buildHumanApprovalWorkflowContent;
15
19
  exports.buildApprovalChatMessage = buildApprovalChatMessage;
20
+ exports.resolveWorkflowContentAr = resolveWorkflowContentAr;
21
+ exports.resolveChatMessageAr = resolveChatMessageAr;
16
22
  exports.enrichCancellationWorkflowLog = enrichCancellationWorkflowLog;
17
23
  exports.enrichCancellationChatMessage = enrichCancellationChatMessage;
18
24
  const workflow_chat_i18n_json_1 = __importDefault(require("./workflow-chat-i18n.json"));
@@ -27,21 +33,22 @@ function getWorkflowChatI18nCatalog() {
27
33
  function interpolate(template, params = {}) {
28
34
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => params[key] ?? '');
29
35
  }
30
- function buildBilingualFromTemplate(templateCatalog, key, params = {}) {
36
+ function buildBilingualFromTemplate(templateCatalog, key, params = {}, arParams) {
31
37
  const tpl = templateCatalog[key];
32
38
  if (!tpl) {
33
39
  return { en: key, ar: key };
34
40
  }
41
+ const arabicParams = arParams ?? params;
35
42
  return {
36
43
  en: interpolate(tpl.en, params),
37
- ar: interpolate(tpl.ar, params),
44
+ ar: interpolate(tpl.ar, arabicParams),
38
45
  };
39
46
  }
40
- function workflowTemplate(key, params = {}) {
41
- return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params);
47
+ function workflowTemplate(key, params = {}, arParams) {
48
+ return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params, arParams);
42
49
  }
43
- function chatTemplate(key, params = {}) {
44
- return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params);
50
+ function chatTemplate(key, params = {}, arParams) {
51
+ return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params, arParams);
45
52
  }
46
53
  function resolveWorkflowStatusAr(status) {
47
54
  if (status == null || status === '')
@@ -53,6 +60,16 @@ function resolveChatStatusAr(status) {
53
60
  return null;
54
61
  return CHAT_STATUS_LABELS[status] ?? status;
55
62
  }
63
+ /** Arabic DB value when present; otherwise English so AR text stays structurally complete. */
64
+ function resolveBilingualName(englishName, arabicName) {
65
+ const ar = arabicName?.trim();
66
+ if (ar)
67
+ return ar;
68
+ return englishName?.trim() || '';
69
+ }
70
+ function pickArabicName(arabicName, fallback = '') {
71
+ return arabicName?.trim() || fallback;
72
+ }
56
73
  function pickLocalizedName(englishName, arabicName, fallback = '') {
57
74
  const ar = arabicName?.trim();
58
75
  if (ar)
@@ -62,53 +79,69 @@ function pickLocalizedName(englishName, arabicName, fallback = '') {
62
79
  return en;
63
80
  return fallback;
64
81
  }
82
+ function joinDeptSectionLabels(dept, section) {
83
+ return [dept, section].map((s) => s?.trim()).filter(Boolean).join(' - ');
84
+ }
85
+ /** Parallel EN/AR dept-section labels (Arabic columns preferred; English fallback per field). */
86
+ function joinDeptSectionBilingualLabels(deptEn, deptAr, sectEn, sectAr) {
87
+ const en = joinDeptSectionLabels(deptEn, sectEn);
88
+ const ar = joinDeptSectionLabels(resolveBilingualName(deptEn, deptAr), resolveBilingualName(sectEn, sectAr));
89
+ return { en, ar };
90
+ }
91
+ /** @deprecated Use joinDeptSectionBilingualLabels */
92
+ function joinDeptSectionArabicLabels(deptArabic, sectionArabic) {
93
+ return joinDeptSectionLabels(deptArabic, sectionArabic);
94
+ }
95
+ function formatWorkflowCommentSuffix(comment) {
96
+ const text = comment?.trim();
97
+ return text ? `: ${text}` : '';
98
+ }
99
+ function formatChatCommentSuffix(comment) {
100
+ const text = comment?.trim();
101
+ return text ? ` - ${text}` : '';
102
+ }
103
+ function buildWorkflowApprovalByName(templateKey, nameEn, nameAr, comment) {
104
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
105
+ return workflowTemplate(templateKey, { name: nameEn, comment: commentSuffix }, { name: nameAr, comment: commentSuffix });
106
+ }
107
+ function buildWorkflowApprovalWithoutName(templateKey, comment) {
108
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
109
+ return workflowTemplate(templateKey, { comment: commentSuffix }, { comment: commentSuffix });
110
+ }
65
111
  function buildApprovalWorkflowContent(params) {
66
112
  const nameEn = params.roleName?.trim() || '';
67
- const nameAr = pickLocalizedName(params.roleName, params.roleArabicName, nameEn || 'Unknown');
68
- let base;
113
+ const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
69
114
  if (params.status === 'Approved') {
70
- base = nameEn
71
- ? workflowTemplate('request_approved_by', { name: nameEn })
72
- : workflowTemplate('request_approved');
73
- if (nameEn) {
74
- base.ar = workflowTemplate('request_approved_by', { name: nameAr }).ar;
75
- }
115
+ return nameEn
116
+ ? buildWorkflowApprovalByName('request_approved_by', nameEn, nameAr, params.comment)
117
+ : buildWorkflowApprovalWithoutName('request_approved', params.comment);
76
118
  }
77
- else if (params.status === 'Rejected') {
78
- base = nameEn
79
- ? workflowTemplate('request_rejected_by', { name: nameEn })
80
- : workflowTemplate('request_rejected');
81
- if (nameEn) {
82
- base.ar = workflowTemplate('request_rejected_by', { name: nameAr }).ar;
83
- }
119
+ if (params.status === 'Rejected') {
120
+ return nameEn
121
+ ? buildWorkflowApprovalByName('request_rejected_by', nameEn, nameAr, params.comment)
122
+ : buildWorkflowApprovalWithoutName('request_rejected', params.comment);
84
123
  }
85
- else {
86
- base = nameEn
87
- ? workflowTemplate('request_approval_pending_from', { name: nameEn })
88
- : workflowTemplate('request_approval_pending');
89
- if (nameEn) {
90
- base.ar = workflowTemplate('request_approval_pending_from', { name: nameAr }).ar;
91
- }
124
+ if (params.status === 'Returned for Modification') {
125
+ return nameEn
126
+ ? buildWorkflowApprovalByName('request_returned_for_modification_by', nameEn, nameAr, params.comment)
127
+ : buildWorkflowApprovalWithoutName('request_returned_for_modification', params.comment);
92
128
  }
93
- const comment = params.comment?.trim();
94
- if (!comment)
95
- return base;
96
- return {
97
- en: `${base.en}: ${comment}`,
98
- ar: `${base.ar}: ${comment}`,
99
- };
129
+ const commentSuffix = formatWorkflowCommentSuffix(params.comment);
130
+ if (nameEn) {
131
+ return workflowTemplate('request_approval_pending_from', { name: nameEn, comment: commentSuffix }, { name: nameAr, comment: commentSuffix });
132
+ }
133
+ return workflowTemplate('request_approval_pending', { comment: commentSuffix }, { comment: commentSuffix });
100
134
  }
101
135
  function buildHumanApprovalWorkflowContent(params) {
102
- const nameEn = params.roleName?.trim() || params.deptSectionEn?.trim() || '';
103
- const nameAr = params.roleName
104
- ? pickLocalizedName(params.roleName, params.roleArabicName, nameEn)
105
- : params.deptSectionAr?.trim() || params.deptSectionEn?.trim() || nameEn;
106
- if (nameEn) {
107
- const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
108
- return {
109
- en: workflowTemplate(key, { name: nameEn }).en,
110
- ar: workflowTemplate(key, { name: nameAr || nameEn }).ar,
111
- };
136
+ const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
137
+ if (params.roleName?.trim()) {
138
+ const nameEn = params.roleName.trim();
139
+ const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
140
+ return workflowTemplate(key, { name: nameEn }, { name: nameAr });
141
+ }
142
+ const { en, ar } = joinDeptSectionBilingualLabels(params.deptName, params.deptArabicName, params.sectionName, params.sectionArabicName);
143
+ if (en) {
144
+ return workflowTemplate(key, { name: en }, { name: ar });
112
145
  }
113
146
  return params.isFirst
114
147
  ? workflowTemplate('request_approval_in_progress')
@@ -116,25 +149,81 @@ function buildHumanApprovalWorkflowContent(params) {
116
149
  }
117
150
  function buildApprovalChatMessage(params) {
118
151
  const roleEn = params.roleName?.trim() || 'Unknown Role';
119
- const roleAr = pickLocalizedName(params.roleName, params.roleArabicName, roleEn);
120
- const commentSuffix = params.comment?.trim() ? ` - ${params.comment.trim()}` : '';
121
- const key = params.status === 'Approved' ? 'request_approved_by_role' : 'request_rejected_by_role';
122
- return {
123
- en: chatTemplate(key, { roleName: roleEn, comment: commentSuffix }).en,
124
- ar: chatTemplate(key, { roleName: roleAr, comment: commentSuffix }).ar,
125
- };
152
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
153
+ const commentSuffix = formatChatCommentSuffix(params.comment);
154
+ let key;
155
+ if (params.status === 'Approved') {
156
+ key = 'request_approved_by_role';
157
+ }
158
+ else if (params.status === 'Rejected') {
159
+ key = 'request_rejected_by_role';
160
+ }
161
+ else if (params.status === 'Returned for Modification') {
162
+ key = 'request_returned_for_modification_by_role';
163
+ }
164
+ else {
165
+ key = 'request_approved_by_role';
166
+ }
167
+ return chatTemplate(key, { roleName: roleEn, comment: commentSuffix }, { roleName: roleAr, comment: commentSuffix });
168
+ }
169
+ /** Resolve Arabic workflow content from a stored English content string (legacy rows). */
170
+ function resolveWorkflowContentAr(content) {
171
+ if (content == null || content.trim() === '')
172
+ return null;
173
+ const normalized = content.trim();
174
+ for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
175
+ if (tpl.en === normalized)
176
+ return tpl.ar;
177
+ }
178
+ const commentIdx = normalized.indexOf(': ');
179
+ if (commentIdx > 0) {
180
+ const baseEn = normalized.slice(0, commentIdx).trim();
181
+ const commentText = normalized.slice(commentIdx + 2).trim();
182
+ const commentSuffix = formatWorkflowCommentSuffix(commentText);
183
+ for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
184
+ if (!tpl.en.includes('{{comment}}'))
185
+ continue;
186
+ const withoutComment = tpl.en.replace('{{comment}}', '');
187
+ if (withoutComment.includes('{{name}}')) {
188
+ const staticPrefix = withoutComment.replace('{{name}}', '').trim();
189
+ if (baseEn.startsWith(staticPrefix)) {
190
+ const namePart = baseEn.slice(staticPrefix.length).trim();
191
+ return interpolate(tpl.ar, { name: namePart, comment: commentSuffix });
192
+ }
193
+ }
194
+ else if (withoutComment.trim() === baseEn) {
195
+ return interpolate(tpl.ar, { comment: commentSuffix });
196
+ }
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ /** Resolve Arabic chat message from a stored English message string (legacy rows). */
202
+ function resolveChatMessageAr(message) {
203
+ if (message == null || message.trim() === '')
204
+ return null;
205
+ const normalized = message.trim();
206
+ for (const tpl of Object.values(CHAT_TEMPLATES)) {
207
+ if (tpl.en === normalized)
208
+ return tpl.ar;
209
+ }
210
+ return null;
126
211
  }
127
212
  function enrichCancellationWorkflowLog(log) {
128
213
  const status = log.status != null ? String(log.status) : null;
214
+ const content = log.content != null ? String(log.content) : null;
129
215
  return {
130
216
  ...log,
217
+ content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
131
218
  status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
132
219
  };
133
220
  }
134
221
  function enrichCancellationChatMessage(chat) {
135
222
  const status = chat.status != null ? String(chat.status) : null;
223
+ const message = chat.message != null ? String(chat.message) : null;
136
224
  return {
137
225
  ...chat,
226
+ message_ar: chat.message_ar ?? resolveChatMessageAr(message),
138
227
  status_ar: chat.status_ar ?? resolveChatStatusAr(status),
139
228
  };
140
229
  }
@@ -29,20 +29,28 @@
29
29
  "ar": "في انتظار موافقة {{name}}"
30
30
  },
31
31
  "request_approved_by": {
32
- "en": "Request Approved by {{name}}",
33
- "ar": "تمت الموافقة على الطلب من قبل {{name}}"
32
+ "en": "Request Approved by {{name}}{{comment}}",
33
+ "ar": "تمت الموافقة على الطلب من قبل {{name}}{{comment}}"
34
34
  },
35
35
  "request_rejected_by": {
36
- "en": "Request Rejected by {{name}}",
37
- "ar": "تم رفض الطلب من قبل {{name}}"
36
+ "en": "Request Rejected by {{name}}{{comment}}",
37
+ "ar": "تم رفض الطلب من قبل {{name}}{{comment}}"
38
38
  },
39
39
  "request_approved": {
40
- "en": "Request Approved",
41
- "ar": "تمت الموافقة على الطلب"
40
+ "en": "Request Approved{{comment}}",
41
+ "ar": "تمت الموافقة على الطلب{{comment}}"
42
42
  },
43
43
  "request_rejected": {
44
- "en": "Request Rejected",
45
- "ar": "تم رفض الطلب"
44
+ "en": "Request Rejected{{comment}}",
45
+ "ar": "تم رفض الطلب{{comment}}"
46
+ },
47
+ "request_returned_for_modification_by": {
48
+ "en": "Request Returned for Modification by {{name}}{{comment}}",
49
+ "ar": "تم إرجاع الطلب للتعديل من قبل {{name}}{{comment}}"
50
+ },
51
+ "request_returned_for_modification": {
52
+ "en": "Request Returned for Modification{{comment}}",
53
+ "ar": "تم إرجاع الطلب للتعديل{{comment}}"
46
54
  }
47
55
  },
48
56
  "chatTemplates": {
@@ -61,6 +69,10 @@
61
69
  "request_rejected_by_role": {
62
70
  "en": "Request Rejected by: {{roleName}}{{comment}}",
63
71
  "ar": "تم رفض الطلب من قبل: {{roleName}}{{comment}}"
72
+ },
73
+ "request_returned_for_modification_by_role": {
74
+ "en": "Request Returned for Modification by: {{roleName}}{{comment}}",
75
+ "ar": "تم إرجاع الطلب للتعديل من قبل: {{roleName}}{{comment}}"
64
76
  }
65
77
  },
66
78
  "workflowStatusLabels": {
@@ -73,6 +85,7 @@
73
85
  "Received": "تم الاستلام",
74
86
  "Approved": "موافق عليه",
75
87
  "Rejected": "مرفوض",
76
- "Pending": "قيد الانتظار"
88
+ "Pending": "قيد الانتظار",
89
+ "Returned for Modification": "أُعيد للتعديل"
77
90
  }
78
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platform-modules/civil-aviation-authority",
3
- "version": "2.3.277",
3
+ "version": "2.3.279",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -29,20 +29,28 @@
29
29
  "ar": "في انتظار موافقة {{name}}"
30
30
  },
31
31
  "request_approved_by": {
32
- "en": "Request Approved by {{name}}",
33
- "ar": "تمت الموافقة على الطلب من قبل {{name}}"
32
+ "en": "Request Approved by {{name}}{{comment}}",
33
+ "ar": "تمت الموافقة على الطلب من قبل {{name}}{{comment}}"
34
34
  },
35
35
  "request_rejected_by": {
36
- "en": "Request Rejected by {{name}}",
37
- "ar": "تم رفض الطلب من قبل {{name}}"
36
+ "en": "Request Rejected by {{name}}{{comment}}",
37
+ "ar": "تم رفض الطلب من قبل {{name}}{{comment}}"
38
38
  },
39
39
  "request_approved": {
40
- "en": "Request Approved",
41
- "ar": "تمت الموافقة على الطلب"
40
+ "en": "Request Approved{{comment}}",
41
+ "ar": "تمت الموافقة على الطلب{{comment}}"
42
42
  },
43
43
  "request_rejected": {
44
- "en": "Request Rejected",
45
- "ar": "تم رفض الطلب"
44
+ "en": "Request Rejected{{comment}}",
45
+ "ar": "تم رفض الطلب{{comment}}"
46
+ },
47
+ "request_returned_for_modification_by": {
48
+ "en": "Request Returned for Modification by {{name}}{{comment}}",
49
+ "ar": "تم إرجاع الطلب للتعديل من قبل {{name}}{{comment}}"
50
+ },
51
+ "request_returned_for_modification": {
52
+ "en": "Request Returned for Modification{{comment}}",
53
+ "ar": "تم إرجاع الطلب للتعديل{{comment}}"
46
54
  }
47
55
  },
48
56
  "chatTemplates": {
@@ -61,6 +69,10 @@
61
69
  "request_rejected_by_role": {
62
70
  "en": "Request Rejected by: {{roleName}}{{comment}}",
63
71
  "ar": "تم رفض الطلب من قبل: {{roleName}}{{comment}}"
72
+ },
73
+ "request_returned_for_modification_by_role": {
74
+ "en": "Request Returned for Modification by: {{roleName}}{{comment}}",
75
+ "ar": "تم إرجاع الطلب للتعديل من قبل: {{roleName}}{{comment}}"
64
76
  }
65
77
  },
66
78
  "workflowStatusLabels": {
@@ -73,6 +85,7 @@
73
85
  "Received": "تم الاستلام",
74
86
  "Approved": "موافق عليه",
75
87
  "Rejected": "مرفوض",
76
- "Pending": "قيد الانتظار"
88
+ "Pending": "قيد الانتظار",
89
+ "Returned for Modification": "أُعيد للتعديل"
77
90
  }
78
91
  }
@@ -26,24 +26,34 @@ function interpolate(template: string, params: Record<string, string> = {}): str
26
26
  export function buildBilingualFromTemplate(
27
27
  templateCatalog: Record<string, BilingualText>,
28
28
  key: string,
29
- params: Record<string, string> = {}
29
+ params: Record<string, string> = {},
30
+ arParams?: Record<string, string>,
30
31
  ): BilingualText {
31
32
  const tpl = templateCatalog[key];
32
33
  if (!tpl) {
33
34
  return { en: key, ar: key };
34
35
  }
36
+ const arabicParams = arParams ?? params;
35
37
  return {
36
38
  en: interpolate(tpl.en, params),
37
- ar: interpolate(tpl.ar, params),
39
+ ar: interpolate(tpl.ar, arabicParams),
38
40
  };
39
41
  }
40
42
 
41
- export function workflowTemplate(key: string, params: Record<string, string> = {}): BilingualText {
42
- return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params);
43
+ export function workflowTemplate(
44
+ key: string,
45
+ params: Record<string, string> = {},
46
+ arParams?: Record<string, string>,
47
+ ): BilingualText {
48
+ return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params, arParams);
43
49
  }
44
50
 
45
- export function chatTemplate(key: string, params: Record<string, string> = {}): BilingualText {
46
- return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params);
51
+ export function chatTemplate(
52
+ key: string,
53
+ params: Record<string, string> = {},
54
+ arParams?: Record<string, string>,
55
+ ): BilingualText {
56
+ return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params, arParams);
47
57
  }
48
58
 
49
59
  export function resolveWorkflowStatusAr(status: string | null | undefined): string | null {
@@ -56,6 +66,20 @@ export function resolveChatStatusAr(status: string | null | undefined): string |
56
66
  return CHAT_STATUS_LABELS[status] ?? status;
57
67
  }
58
68
 
69
+ /** Arabic DB value when present; otherwise English so AR text stays structurally complete. */
70
+ export function resolveBilingualName(
71
+ englishName?: string | null,
72
+ arabicName?: string | null,
73
+ ): string {
74
+ const ar = arabicName?.trim();
75
+ if (ar) return ar;
76
+ return englishName?.trim() || '';
77
+ }
78
+
79
+ export function pickArabicName(arabicName?: string | null, fallback = ''): string {
80
+ return arabicName?.trim() || fallback;
81
+ }
82
+
59
83
  export function pickLocalizedName(
60
84
  englishName?: string | null,
61
85
  arabicName?: string | null,
@@ -68,66 +92,140 @@ export function pickLocalizedName(
68
92
  return fallback;
69
93
  }
70
94
 
95
+ function joinDeptSectionLabels(
96
+ dept?: string | null,
97
+ section?: string | null,
98
+ ): string {
99
+ return [dept, section].map((s) => s?.trim()).filter(Boolean).join(' - ');
100
+ }
101
+
102
+ /** Parallel EN/AR dept-section labels (Arabic columns preferred; English fallback per field). */
103
+ export function joinDeptSectionBilingualLabels(
104
+ deptEn?: string | null,
105
+ deptAr?: string | null,
106
+ sectEn?: string | null,
107
+ sectAr?: string | null,
108
+ ): { en: string; ar: string } {
109
+ const en = joinDeptSectionLabels(deptEn, sectEn);
110
+ const ar = joinDeptSectionLabels(
111
+ resolveBilingualName(deptEn, deptAr),
112
+ resolveBilingualName(sectEn, sectAr),
113
+ );
114
+ return { en, ar };
115
+ }
116
+
117
+ /** @deprecated Use joinDeptSectionBilingualLabels */
118
+ export function joinDeptSectionArabicLabels(
119
+ deptArabic?: string | null,
120
+ sectionArabic?: string | null,
121
+ ): string {
122
+ return joinDeptSectionLabels(deptArabic, sectionArabic);
123
+ }
124
+
125
+ function formatWorkflowCommentSuffix(comment?: string | null): string {
126
+ const text = comment?.trim();
127
+ return text ? `: ${text}` : '';
128
+ }
129
+
130
+ function formatChatCommentSuffix(comment?: string | null): string {
131
+ const text = comment?.trim();
132
+ return text ? ` - ${text}` : '';
133
+ }
134
+
135
+ function buildWorkflowApprovalByName(
136
+ templateKey: 'request_approved_by' | 'request_rejected_by' | 'request_returned_for_modification_by',
137
+ nameEn: string,
138
+ nameAr: string,
139
+ comment?: string | null,
140
+ ): BilingualText {
141
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
142
+ return workflowTemplate(
143
+ templateKey,
144
+ { name: nameEn, comment: commentSuffix },
145
+ { name: nameAr, comment: commentSuffix },
146
+ );
147
+ }
148
+
149
+ function buildWorkflowApprovalWithoutName(
150
+ templateKey: 'request_approved' | 'request_rejected' | 'request_returned_for_modification',
151
+ comment?: string | null,
152
+ ): BilingualText {
153
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
154
+ return workflowTemplate(
155
+ templateKey,
156
+ { comment: commentSuffix },
157
+ { comment: commentSuffix },
158
+ );
159
+ }
160
+
71
161
  export function buildApprovalWorkflowContent(params: {
72
- status: 'Approved' | 'Rejected' | 'Pending' | string;
162
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
73
163
  roleName?: string | null;
74
164
  roleArabicName?: string | null;
75
165
  comment?: string | null;
76
166
  }): BilingualText {
77
167
  const nameEn = params.roleName?.trim() || '';
78
- const nameAr = pickLocalizedName(params.roleName, params.roleArabicName, nameEn || 'Unknown');
168
+ const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
79
169
 
80
- let base: BilingualText;
81
170
  if (params.status === 'Approved') {
82
- base = nameEn
83
- ? workflowTemplate('request_approved_by', { name: nameEn })
84
- : workflowTemplate('request_approved');
85
- if (nameEn) {
86
- base.ar = workflowTemplate('request_approved_by', { name: nameAr }).ar;
87
- }
88
- } else if (params.status === 'Rejected') {
89
- base = nameEn
90
- ? workflowTemplate('request_rejected_by', { name: nameEn })
91
- : workflowTemplate('request_rejected');
92
- if (nameEn) {
93
- base.ar = workflowTemplate('request_rejected_by', { name: nameAr }).ar;
94
- }
95
- } else {
96
- base = nameEn
97
- ? workflowTemplate('request_approval_pending_from', { name: nameEn })
98
- : workflowTemplate('request_approval_pending');
99
- if (nameEn) {
100
- base.ar = workflowTemplate('request_approval_pending_from', { name: nameAr }).ar;
101
- }
171
+ return nameEn
172
+ ? buildWorkflowApprovalByName('request_approved_by', nameEn, nameAr, params.comment)
173
+ : buildWorkflowApprovalWithoutName('request_approved', params.comment);
102
174
  }
103
175
 
104
- const comment = params.comment?.trim();
105
- if (!comment) return base;
176
+ if (params.status === 'Rejected') {
177
+ return nameEn
178
+ ? buildWorkflowApprovalByName('request_rejected_by', nameEn, nameAr, params.comment)
179
+ : buildWorkflowApprovalWithoutName('request_rejected', params.comment);
180
+ }
106
181
 
107
- return {
108
- en: `${base.en}: ${comment}`,
109
- ar: `${base.ar}: ${comment}`,
110
- };
182
+ if (params.status === 'Returned for Modification') {
183
+ return nameEn
184
+ ? buildWorkflowApprovalByName('request_returned_for_modification_by', nameEn, nameAr, params.comment)
185
+ : buildWorkflowApprovalWithoutName('request_returned_for_modification', params.comment);
186
+ }
187
+
188
+ const commentSuffix = formatWorkflowCommentSuffix(params.comment);
189
+ if (nameEn) {
190
+ return workflowTemplate(
191
+ 'request_approval_pending_from',
192
+ { name: nameEn, comment: commentSuffix },
193
+ { name: nameAr, comment: commentSuffix },
194
+ );
195
+ }
196
+
197
+ return workflowTemplate(
198
+ 'request_approval_pending',
199
+ { comment: commentSuffix },
200
+ { comment: commentSuffix },
201
+ );
111
202
  }
112
203
 
113
204
  export function buildHumanApprovalWorkflowContent(params: {
114
205
  isFirst: boolean;
115
206
  roleName?: string | null;
116
207
  roleArabicName?: string | null;
117
- deptSectionEn?: string | null;
118
- deptSectionAr?: string | null;
208
+ deptName?: string | null;
209
+ deptArabicName?: string | null;
210
+ sectionName?: string | null;
211
+ sectionArabicName?: string | null;
119
212
  }): BilingualText {
120
- const nameEn = params.roleName?.trim() || params.deptSectionEn?.trim() || '';
121
- const nameAr = params.roleName
122
- ? pickLocalizedName(params.roleName, params.roleArabicName, nameEn)
123
- : params.deptSectionAr?.trim() || params.deptSectionEn?.trim() || nameEn;
213
+ const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
124
214
 
125
- if (nameEn) {
126
- const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
127
- return {
128
- en: workflowTemplate(key, { name: nameEn }).en,
129
- ar: workflowTemplate(key, { name: nameAr || nameEn }).ar,
130
- };
215
+ if (params.roleName?.trim()) {
216
+ const nameEn = params.roleName.trim();
217
+ const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
218
+ return workflowTemplate(key, { name: nameEn }, { name: nameAr });
219
+ }
220
+
221
+ const { en, ar } = joinDeptSectionBilingualLabels(
222
+ params.deptName,
223
+ params.deptArabicName,
224
+ params.sectionName,
225
+ params.sectionArabicName,
226
+ );
227
+ if (en) {
228
+ return workflowTemplate(key, { name: en }, { name: ar });
131
229
  }
132
230
 
133
231
  return params.isFirst
@@ -136,34 +234,94 @@ export function buildHumanApprovalWorkflowContent(params: {
136
234
  }
137
235
 
138
236
  export function buildApprovalChatMessage(params: {
139
- status: 'Approved' | 'Rejected' | string;
237
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | string;
140
238
  roleName?: string | null;
141
239
  roleArabicName?: string | null;
142
240
  comment?: string | null;
143
241
  }): BilingualText {
144
242
  const roleEn = params.roleName?.trim() || 'Unknown Role';
145
- const roleAr = pickLocalizedName(params.roleName, params.roleArabicName, roleEn);
146
- const commentSuffix = params.comment?.trim() ? ` - ${params.comment.trim()}` : '';
147
- const key = params.status === 'Approved' ? 'request_approved_by_role' : 'request_rejected_by_role';
243
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
244
+ const commentSuffix = formatChatCommentSuffix(params.comment);
148
245
 
149
- return {
150
- en: chatTemplate(key, { roleName: roleEn, comment: commentSuffix }).en,
151
- ar: chatTemplate(key, { roleName: roleAr, comment: commentSuffix }).ar,
152
- };
246
+ let key: string;
247
+ if (params.status === 'Approved') {
248
+ key = 'request_approved_by_role';
249
+ } else if (params.status === 'Rejected') {
250
+ key = 'request_rejected_by_role';
251
+ } else if (params.status === 'Returned for Modification') {
252
+ key = 'request_returned_for_modification_by_role';
253
+ } else {
254
+ key = 'request_approved_by_role';
255
+ }
256
+
257
+ return chatTemplate(
258
+ key,
259
+ { roleName: roleEn, comment: commentSuffix },
260
+ { roleName: roleAr, comment: commentSuffix },
261
+ );
262
+ }
263
+
264
+ /** Resolve Arabic workflow content from a stored English content string (legacy rows). */
265
+ export function resolveWorkflowContentAr(content: string | null | undefined): string | null {
266
+ if (content == null || content.trim() === '') return null;
267
+ const normalized = content.trim();
268
+
269
+ for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
270
+ if (tpl.en === normalized) return tpl.ar;
271
+ }
272
+
273
+ const commentIdx = normalized.indexOf(': ');
274
+ if (commentIdx > 0) {
275
+ const baseEn = normalized.slice(0, commentIdx).trim();
276
+ const commentText = normalized.slice(commentIdx + 2).trim();
277
+ const commentSuffix = formatWorkflowCommentSuffix(commentText);
278
+
279
+ for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
280
+ if (!tpl.en.includes('{{comment}}')) continue;
281
+ const withoutComment = tpl.en.replace('{{comment}}', '');
282
+ if (withoutComment.includes('{{name}}')) {
283
+ const staticPrefix = withoutComment.replace('{{name}}', '').trim();
284
+ if (baseEn.startsWith(staticPrefix)) {
285
+ const namePart = baseEn.slice(staticPrefix.length).trim();
286
+ return interpolate(tpl.ar, { name: namePart, comment: commentSuffix });
287
+ }
288
+ } else if (withoutComment.trim() === baseEn) {
289
+ return interpolate(tpl.ar, { comment: commentSuffix });
290
+ }
291
+ }
292
+ }
293
+
294
+ return null;
295
+ }
296
+
297
+ /** Resolve Arabic chat message from a stored English message string (legacy rows). */
298
+ export function resolveChatMessageAr(message: string | null | undefined): string | null {
299
+ if (message == null || message.trim() === '') return null;
300
+ const normalized = message.trim();
301
+
302
+ for (const tpl of Object.values(CHAT_TEMPLATES)) {
303
+ if (tpl.en === normalized) return tpl.ar;
304
+ }
305
+
306
+ return null;
153
307
  }
154
308
 
155
309
  export function enrichCancellationWorkflowLog(log: Record<string, unknown>): Record<string, unknown> {
156
310
  const status = log.status != null ? String(log.status) : null;
311
+ const content = log.content != null ? String(log.content) : null;
157
312
  return {
158
313
  ...log,
314
+ content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
159
315
  status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
160
316
  };
161
317
  }
162
318
 
163
319
  export function enrichCancellationChatMessage(chat: Record<string, unknown>): Record<string, unknown> {
164
320
  const status = chat.status != null ? String(chat.status) : null;
321
+ const message = chat.message != null ? String(chat.message) : null;
165
322
  return {
166
323
  ...chat,
324
+ message_ar: chat.message_ar ?? resolveChatMessageAr(message),
167
325
  status_ar: chat.status_ar ?? resolveChatStatusAr(status),
168
326
  };
169
327
  }