@platform-modules/civil-aviation-authority 2.3.278 → 2.3.280

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.
@@ -15,14 +15,18 @@ export declare function workflowTemplate(key: string, params?: Record<string, st
15
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
- /** Prefer Arabic name for Arabic content; never fall back to English. */
19
- export declare function pickArabicName(arabicName?: string | null, fallback?: string): string;
20
- /** Prefer Arabic for Arabic locale; English for English locale. */
18
+ /** Arabic column value only never falls back to English (for content_ar / message_ar). */
19
+ export declare function pickArabicName(arabicName?: string | null): string;
21
20
  export declare function pickLocalizedName(englishName?: string | null, arabicName?: string | null, fallback?: string): string;
22
- /** Build dept/section label from Arabic columns only (for content_ar). */
21
+ /** EN from English columns; AR from Arabic columns only (no English in ar). */
22
+ export declare function joinDeptSectionBilingualLabels(deptEn?: string | null, deptAr?: string | null, sectEn?: string | null, sectAr?: string | null): {
23
+ en: string;
24
+ ar: string;
25
+ };
26
+ /** @deprecated Use joinDeptSectionBilingualLabels */
23
27
  export declare function joinDeptSectionArabicLabels(deptArabic?: string | null, sectionArabic?: string | null): string;
24
28
  export declare function buildApprovalWorkflowContent(params: {
25
- status: 'Approved' | 'Rejected' | 'Pending' | string;
29
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
26
30
  roleName?: string | null;
27
31
  roleArabicName?: string | null;
28
32
  comment?: string | null;
@@ -31,18 +35,20 @@ export declare function buildHumanApprovalWorkflowContent(params: {
31
35
  isFirst: boolean;
32
36
  roleName?: string | null;
33
37
  roleArabicName?: string | null;
34
- deptSectionEn?: string | null;
35
- deptSectionAr?: string | null;
38
+ deptName?: string | null;
39
+ deptArabicName?: string | null;
40
+ sectionName?: string | null;
41
+ sectionArabicName?: string | null;
36
42
  }): BilingualText;
37
43
  export declare function buildApprovalChatMessage(params: {
38
- status: 'Approved' | 'Rejected' | string;
44
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | string;
39
45
  roleName?: string | null;
40
46
  roleArabicName?: string | null;
41
47
  comment?: string | null;
42
48
  }): BilingualText;
43
- /** Resolve Arabic workflow content from a stored English content string. */
49
+ /** Resolve Arabic workflow content from a stored English content string (legacy rows). */
44
50
  export declare function resolveWorkflowContentAr(content: string | null | undefined): string | null;
45
- /** Resolve Arabic chat message from a stored English message string. */
51
+ /** Resolve Arabic chat message from a stored English message string (legacy rows). */
46
52
  export declare function resolveChatMessageAr(message: string | null | undefined): string | null;
47
53
  export declare function enrichCancellationWorkflowLog(log: Record<string, unknown>): Record<string, unknown>;
48
54
  export declare function enrichCancellationChatMessage(chat: Record<string, unknown>): Record<string, unknown>;
@@ -11,6 +11,7 @@ exports.resolveWorkflowStatusAr = resolveWorkflowStatusAr;
11
11
  exports.resolveChatStatusAr = resolveChatStatusAr;
12
12
  exports.pickArabicName = pickArabicName;
13
13
  exports.pickLocalizedName = pickLocalizedName;
14
+ exports.joinDeptSectionBilingualLabels = joinDeptSectionBilingualLabels;
14
15
  exports.joinDeptSectionArabicLabels = joinDeptSectionArabicLabels;
15
16
  exports.buildApprovalWorkflowContent = buildApprovalWorkflowContent;
16
17
  exports.buildHumanApprovalWorkflowContent = buildHumanApprovalWorkflowContent;
@@ -58,11 +59,10 @@ function resolveChatStatusAr(status) {
58
59
  return null;
59
60
  return CHAT_STATUS_LABELS[status] ?? status;
60
61
  }
61
- /** Prefer Arabic name for Arabic content; never fall back to English. */
62
- function pickArabicName(arabicName, fallback = '') {
63
- return arabicName?.trim() || fallback;
62
+ /** Arabic column value only never falls back to English (for content_ar / message_ar). */
63
+ function pickArabicName(arabicName) {
64
+ return arabicName?.trim() || '';
64
65
  }
65
- /** Prefer Arabic for Arabic locale; English for English locale. */
66
66
  function pickLocalizedName(englishName, arabicName, fallback = '') {
67
67
  const ar = arabicName?.trim();
68
68
  if (ar)
@@ -75,62 +75,93 @@ function pickLocalizedName(englishName, arabicName, fallback = '') {
75
75
  function joinDeptSectionLabels(dept, section) {
76
76
  return [dept, section].map((s) => s?.trim()).filter(Boolean).join(' - ');
77
77
  }
78
- /** Build dept/section label from Arabic columns only (for content_ar). */
78
+ /** EN from English columns; AR from Arabic columns only (no English in ar). */
79
+ function joinDeptSectionBilingualLabels(deptEn, deptAr, sectEn, sectAr) {
80
+ return {
81
+ en: joinDeptSectionLabels(deptEn, sectEn),
82
+ ar: joinDeptSectionLabels(deptAr, sectAr),
83
+ };
84
+ }
85
+ /** @deprecated Use joinDeptSectionBilingualLabels */
79
86
  function joinDeptSectionArabicLabels(deptArabic, sectionArabic) {
80
87
  return joinDeptSectionLabels(deptArabic, sectionArabic);
81
88
  }
89
+ function formatWorkflowCommentSuffix(comment) {
90
+ const text = comment?.trim();
91
+ return text ? `: ${text}` : '';
92
+ }
93
+ function formatChatCommentSuffix(comment) {
94
+ const text = comment?.trim();
95
+ return text ? ` - ${text}` : '';
96
+ }
97
+ function buildWorkflowApprovalByName(templateKey, fallbackKey, nameEn, nameAr, comment) {
98
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
99
+ return {
100
+ en: workflowTemplate(templateKey, { name: nameEn, comment: commentSuffix }).en,
101
+ ar: nameAr
102
+ ? workflowTemplate(templateKey, { name: nameAr, comment: '' }).ar
103
+ : workflowTemplate(fallbackKey, { comment: '' }).ar,
104
+ };
105
+ }
106
+ function buildWorkflowApprovalWithoutName(templateKey, comment) {
107
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
108
+ return {
109
+ en: workflowTemplate(templateKey, { comment: commentSuffix }).en,
110
+ ar: workflowTemplate(templateKey, { comment: '' }).ar,
111
+ };
112
+ }
113
+ function buildWorkflowFromNamePair(templateWithNameKey, templateWithoutNameKey, nameEn, nameAr) {
114
+ return {
115
+ en: workflowTemplate(templateWithNameKey, { name: nameEn }).en,
116
+ ar: nameAr
117
+ ? workflowTemplate(templateWithNameKey, { name: nameAr }).ar
118
+ : workflowTemplate(templateWithoutNameKey).ar,
119
+ };
120
+ }
82
121
  function buildApprovalWorkflowContent(params) {
83
122
  const nameEn = params.roleName?.trim() || '';
84
123
  const nameAr = pickArabicName(params.roleArabicName);
85
- let base;
86
124
  if (params.status === 'Approved') {
87
- base = nameEn
88
- ? workflowTemplate('request_approved_by', { name: nameEn })
89
- : workflowTemplate('request_approved');
90
- base.ar = nameAr
91
- ? workflowTemplate('request_approved_by', { name: nameAr }).ar
92
- : workflowTemplate('request_approved').ar;
125
+ return nameEn
126
+ ? buildWorkflowApprovalByName('request_approved_by', 'request_approved', nameEn, nameAr, params.comment)
127
+ : buildWorkflowApprovalWithoutName('request_approved', params.comment);
93
128
  }
94
- else if (params.status === 'Rejected') {
95
- base = nameEn
96
- ? workflowTemplate('request_rejected_by', { name: nameEn })
97
- : workflowTemplate('request_rejected');
98
- base.ar = nameAr
99
- ? workflowTemplate('request_rejected_by', { name: nameAr }).ar
100
- : workflowTemplate('request_rejected').ar;
129
+ if (params.status === 'Rejected') {
130
+ return nameEn
131
+ ? buildWorkflowApprovalByName('request_rejected_by', 'request_rejected', nameEn, nameAr, params.comment)
132
+ : buildWorkflowApprovalWithoutName('request_rejected', params.comment);
101
133
  }
102
- else {
103
- base = nameEn
104
- ? workflowTemplate('request_approval_pending_from', { name: nameEn })
105
- : workflowTemplate('request_approval_pending');
106
- base.ar = nameAr
107
- ? workflowTemplate('request_approval_pending_from', { name: nameAr }).ar
108
- : workflowTemplate('request_approval_pending').ar;
109
- }
110
- const comment = params.comment?.trim();
111
- if (!comment)
112
- return base;
113
- return {
114
- en: `${base.en}: ${comment}`,
115
- ar: base.ar,
116
- };
117
- }
118
- function buildHumanApprovalWorkflowContent(params) {
119
- const nameEn = params.roleName?.trim() || params.deptSectionEn?.trim() || '';
120
- const nameAr = params.roleName
121
- ? pickArabicName(params.roleArabicName)
122
- : (params.deptSectionAr?.trim() || '');
134
+ if (params.status === 'Returned for Modification') {
135
+ return nameEn
136
+ ? buildWorkflowApprovalByName('request_returned_for_modification_by', 'request_returned_for_modification', nameEn, nameAr, params.comment)
137
+ : buildWorkflowApprovalWithoutName('request_returned_for_modification', params.comment);
138
+ }
139
+ const commentSuffix = formatWorkflowCommentSuffix(params.comment);
123
140
  if (nameEn) {
124
- const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
125
141
  return {
126
- en: workflowTemplate(key, { name: nameEn }).en,
142
+ en: workflowTemplate('request_approval_pending_from', { name: nameEn, comment: commentSuffix }).en,
127
143
  ar: nameAr
128
- ? workflowTemplate(key, { name: nameAr }).ar
129
- : params.isFirst
130
- ? workflowTemplate('request_approval_in_progress').ar
131
- : workflowTemplate('request_approval_pending').ar,
144
+ ? workflowTemplate('request_approval_pending_from', { name: nameAr, comment: '' }).ar
145
+ : workflowTemplate('request_approval_pending', { comment: '' }).ar,
132
146
  };
133
147
  }
148
+ return {
149
+ en: workflowTemplate('request_approval_pending', { comment: commentSuffix }).en,
150
+ ar: workflowTemplate('request_approval_pending', { comment: '' }).ar,
151
+ };
152
+ }
153
+ function buildHumanApprovalWorkflowContent(params) {
154
+ const withNameKey = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
155
+ const withoutNameKey = params.isFirst ? 'request_approval_in_progress' : 'request_approval_pending';
156
+ if (params.roleName?.trim()) {
157
+ const nameEn = params.roleName.trim();
158
+ const nameAr = pickArabicName(params.roleArabicName);
159
+ return buildWorkflowFromNamePair(withNameKey, withoutNameKey, nameEn, nameAr);
160
+ }
161
+ const { en, ar } = joinDeptSectionBilingualLabels(params.deptName, params.deptArabicName, params.sectionName, params.sectionArabicName);
162
+ if (en) {
163
+ return buildWorkflowFromNamePair(withNameKey, withoutNameKey, en, ar);
164
+ }
134
165
  return params.isFirst
135
166
  ? workflowTemplate('request_approval_in_progress')
136
167
  : workflowTemplate('request_approval_pending');
@@ -138,14 +169,33 @@ function buildHumanApprovalWorkflowContent(params) {
138
169
  function buildApprovalChatMessage(params) {
139
170
  const roleEn = params.roleName?.trim() || 'Unknown Role';
140
171
  const roleAr = pickArabicName(params.roleArabicName);
141
- const commentSuffix = params.comment?.trim() ? ` - ${params.comment.trim()}` : '';
142
- const key = params.status === 'Approved' ? 'request_approved_by_role' : 'request_rejected_by_role';
172
+ const commentSuffix = formatChatCommentSuffix(params.comment);
173
+ let byRoleKey;
174
+ let withoutRoleKey;
175
+ if (params.status === 'Approved') {
176
+ byRoleKey = 'request_approved_by_role';
177
+ withoutRoleKey = 'request_approved';
178
+ }
179
+ else if (params.status === 'Rejected') {
180
+ byRoleKey = 'request_rejected_by_role';
181
+ withoutRoleKey = 'request_rejected';
182
+ }
183
+ else if (params.status === 'Returned for Modification') {
184
+ byRoleKey = 'request_returned_for_modification_by_role';
185
+ withoutRoleKey = 'request_returned_for_modification';
186
+ }
187
+ else {
188
+ byRoleKey = 'request_approved_by_role';
189
+ withoutRoleKey = 'request_approved';
190
+ }
143
191
  return {
144
- en: chatTemplate(key, { roleName: roleEn, comment: commentSuffix }).en,
145
- ar: chatTemplate(key, { roleName: roleAr, comment: '' }).ar,
192
+ en: chatTemplate(byRoleKey, { roleName: roleEn, comment: commentSuffix }).en,
193
+ ar: roleAr
194
+ ? chatTemplate(byRoleKey, { roleName: roleAr, comment: '' }).ar
195
+ : chatTemplate(withoutRoleKey, { comment: '' }).ar,
146
196
  };
147
197
  }
148
- /** Resolve Arabic workflow content from a stored English content string. */
198
+ /** Resolve Arabic workflow content from a stored English content string (legacy rows). */
149
199
  function resolveWorkflowContentAr(content) {
150
200
  if (content == null || content.trim() === '')
151
201
  return null;
@@ -154,9 +204,42 @@ function resolveWorkflowContentAr(content) {
154
204
  if (tpl.en === normalized)
155
205
  return tpl.ar;
156
206
  }
207
+ const commentIdx = normalized.indexOf(': ');
208
+ if (commentIdx > 0) {
209
+ const baseEn = normalized.slice(0, commentIdx).trim();
210
+ for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
211
+ if (!tpl.en.includes('{{comment}}'))
212
+ continue;
213
+ const withoutComment = tpl.en.replace('{{comment}}', '');
214
+ if (withoutComment.includes('{{name}}')) {
215
+ const staticPrefix = withoutComment.replace('{{name}}', '').trim();
216
+ if (baseEn.startsWith(staticPrefix)) {
217
+ const fallbackKey = Object.entries(WORKFLOW_TEMPLATES).find(([, t]) => t === tpl)?.[0] ?? '';
218
+ if (fallbackKey.includes('_by')) {
219
+ const genericKey = fallbackKey.replace('_by', '');
220
+ if (WORKFLOW_TEMPLATES[genericKey]) {
221
+ return WORKFLOW_TEMPLATES[genericKey].ar;
222
+ }
223
+ }
224
+ return tpl.ar.split('{{name}}')[0].trim();
225
+ }
226
+ }
227
+ else if (withoutComment.trim() === baseEn) {
228
+ return interpolate(tpl.ar, { comment: '' });
229
+ }
230
+ }
231
+ }
232
+ const inProgressPrefix = WORKFLOW_TEMPLATES.request_approval_in_progress_from?.en.split('{{name}}')[0].trim();
233
+ if (inProgressPrefix && normalized.startsWith(inProgressPrefix)) {
234
+ return WORKFLOW_TEMPLATES.request_approval_in_progress.ar;
235
+ }
236
+ const pendingPrefix = WORKFLOW_TEMPLATES.request_approval_pending_from?.en.split('{{name}}')[0].trim();
237
+ if (pendingPrefix && normalized.startsWith(pendingPrefix)) {
238
+ return WORKFLOW_TEMPLATES.request_approval_pending.ar;
239
+ }
157
240
  return null;
158
241
  }
159
- /** Resolve Arabic chat message from a stored English message string. */
242
+ /** Resolve Arabic chat message from a stored English message string (legacy rows). */
160
243
  function resolveChatMessageAr(message) {
161
244
  if (message == null || message.trim() === '')
162
245
  return null;
@@ -165,26 +248,31 @@ function resolveChatMessageAr(message) {
165
248
  if (tpl.en === normalized)
166
249
  return tpl.ar;
167
250
  }
168
- if (normalized === 'Request Received') {
169
- return CHAT_TEMPLATES.request_received?.ar ?? null;
170
- }
171
251
  return null;
172
252
  }
173
253
  function enrichCancellationWorkflowLog(log) {
174
254
  const status = log.status != null ? String(log.status) : null;
175
255
  const content = log.content != null ? String(log.content) : null;
256
+ const storedAr = log.content_ar != null ? String(log.content_ar).trim() : '';
257
+ const hasEnglishInAr = storedAr && /[A-Za-z]/.test(storedAr);
176
258
  return {
177
259
  ...log,
178
- content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
260
+ content_ar: hasEnglishInAr
261
+ ? (resolveWorkflowContentAr(content) ?? storedAr)
262
+ : (log.content_ar ?? resolveWorkflowContentAr(content)),
179
263
  status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
180
264
  };
181
265
  }
182
266
  function enrichCancellationChatMessage(chat) {
183
267
  const status = chat.status != null ? String(chat.status) : null;
184
268
  const message = chat.message != null ? String(chat.message) : null;
269
+ const storedAr = chat.message_ar != null ? String(chat.message_ar).trim() : '';
270
+ const hasEnglishInAr = storedAr && /[A-Za-z]/.test(storedAr);
185
271
  return {
186
272
  ...chat,
187
- message_ar: chat.message_ar ?? resolveChatMessageAr(message),
273
+ message_ar: hasEnglishInAr
274
+ ? (resolveChatMessageAr(message) ?? storedAr)
275
+ : (chat.message_ar ?? resolveChatMessageAr(message)),
188
276
  status_ar: chat.status_ar ?? resolveChatStatusAr(status),
189
277
  };
190
278
  }
@@ -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": {
@@ -50,6 +58,10 @@
50
58
  "en": "Cancellation request submitted by {{userName}} ({{employeeId}})",
51
59
  "ar": "تم تقديم طلب الإلغاء بواسطة {{userName}} ({{employeeId}})"
52
60
  },
61
+ "cancellation_submitted": {
62
+ "en": "Cancellation request submitted",
63
+ "ar": "تم تقديم طلب الإلغاء"
64
+ },
53
65
  "request_received": {
54
66
  "en": "Request Received",
55
67
  "ar": "تم استلام الطلب"
@@ -61,6 +73,22 @@
61
73
  "request_rejected_by_role": {
62
74
  "en": "Request Rejected by: {{roleName}}{{comment}}",
63
75
  "ar": "تم رفض الطلب من قبل: {{roleName}}{{comment}}"
76
+ },
77
+ "request_returned_for_modification_by_role": {
78
+ "en": "Request Returned for Modification by: {{roleName}}{{comment}}",
79
+ "ar": "تم إرجاع الطلب للتعديل من قبل: {{roleName}}{{comment}}"
80
+ },
81
+ "request_approved": {
82
+ "en": "Request Approved{{comment}}",
83
+ "ar": "تمت الموافقة على الطلب{{comment}}"
84
+ },
85
+ "request_rejected": {
86
+ "en": "Request Rejected{{comment}}",
87
+ "ar": "تم رفض الطلب{{comment}}"
88
+ },
89
+ "request_returned_for_modification": {
90
+ "en": "Request Returned for Modification{{comment}}",
91
+ "ar": "تم إرجاع الطلب للتعديل{{comment}}"
64
92
  }
65
93
  },
66
94
  "workflowStatusLabels": {
@@ -73,6 +101,7 @@
73
101
  "Received": "تم الاستلام",
74
102
  "Approved": "موافق عليه",
75
103
  "Rejected": "مرفوض",
76
- "Pending": "قيد الانتظار"
104
+ "Pending": "قيد الانتظار",
105
+ "Returned for Modification": "أُعيد للتعديل"
77
106
  }
78
107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platform-modules/civil-aviation-authority",
3
- "version": "2.3.278",
3
+ "version": "2.3.280",
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": {
@@ -50,6 +58,10 @@
50
58
  "en": "Cancellation request submitted by {{userName}} ({{employeeId}})",
51
59
  "ar": "تم تقديم طلب الإلغاء بواسطة {{userName}} ({{employeeId}})"
52
60
  },
61
+ "cancellation_submitted": {
62
+ "en": "Cancellation request submitted",
63
+ "ar": "تم تقديم طلب الإلغاء"
64
+ },
53
65
  "request_received": {
54
66
  "en": "Request Received",
55
67
  "ar": "تم استلام الطلب"
@@ -61,6 +73,22 @@
61
73
  "request_rejected_by_role": {
62
74
  "en": "Request Rejected by: {{roleName}}{{comment}}",
63
75
  "ar": "تم رفض الطلب من قبل: {{roleName}}{{comment}}"
76
+ },
77
+ "request_returned_for_modification_by_role": {
78
+ "en": "Request Returned for Modification by: {{roleName}}{{comment}}",
79
+ "ar": "تم إرجاع الطلب للتعديل من قبل: {{roleName}}{{comment}}"
80
+ },
81
+ "request_approved": {
82
+ "en": "Request Approved{{comment}}",
83
+ "ar": "تمت الموافقة على الطلب{{comment}}"
84
+ },
85
+ "request_rejected": {
86
+ "en": "Request Rejected{{comment}}",
87
+ "ar": "تم رفض الطلب{{comment}}"
88
+ },
89
+ "request_returned_for_modification": {
90
+ "en": "Request Returned for Modification{{comment}}",
91
+ "ar": "تم إرجاع الطلب للتعديل{{comment}}"
64
92
  }
65
93
  },
66
94
  "workflowStatusLabels": {
@@ -73,6 +101,7 @@
73
101
  "Received": "تم الاستلام",
74
102
  "Approved": "موافق عليه",
75
103
  "Rejected": "مرفوض",
76
- "Pending": "قيد الانتظار"
104
+ "Pending": "قيد الانتظار",
105
+ "Returned for Modification": "أُعيد للتعديل"
77
106
  }
78
107
  }
@@ -66,12 +66,11 @@ export function resolveChatStatusAr(status: string | null | undefined): string |
66
66
  return CHAT_STATUS_LABELS[status] ?? status;
67
67
  }
68
68
 
69
- /** Prefer Arabic name for Arabic content; never fall back to English. */
70
- export function pickArabicName(arabicName?: string | null, fallback = ''): string {
71
- return arabicName?.trim() || fallback;
69
+ /** Arabic column value only never falls back to English (for content_ar / message_ar). */
70
+ export function pickArabicName(arabicName?: string | null): string {
71
+ return arabicName?.trim() || '';
72
72
  }
73
73
 
74
- /** Prefer Arabic for Arabic locale; English for English locale. */
75
74
  export function pickLocalizedName(
76
75
  englishName?: string | null,
77
76
  arabicName?: string | null,
@@ -91,7 +90,20 @@ function joinDeptSectionLabels(
91
90
  return [dept, section].map((s) => s?.trim()).filter(Boolean).join(' - ');
92
91
  }
93
92
 
94
- /** Build dept/section label from Arabic columns only (for content_ar). */
93
+ /** EN from English columns; AR from Arabic columns only (no English in ar). */
94
+ export function joinDeptSectionBilingualLabels(
95
+ deptEn?: string | null,
96
+ deptAr?: string | null,
97
+ sectEn?: string | null,
98
+ sectAr?: string | null,
99
+ ): { en: string; ar: string } {
100
+ return {
101
+ en: joinDeptSectionLabels(deptEn, sectEn),
102
+ ar: joinDeptSectionLabels(deptAr, sectAr),
103
+ };
104
+ }
105
+
106
+ /** @deprecated Use joinDeptSectionBilingualLabels */
95
107
  export function joinDeptSectionArabicLabels(
96
108
  deptArabic?: string | null,
97
109
  sectionArabic?: string | null,
@@ -99,8 +111,69 @@ export function joinDeptSectionArabicLabels(
99
111
  return joinDeptSectionLabels(deptArabic, sectionArabic);
100
112
  }
101
113
 
114
+ function formatWorkflowCommentSuffix(comment?: string | null): string {
115
+ const text = comment?.trim();
116
+ return text ? `: ${text}` : '';
117
+ }
118
+
119
+ function formatChatCommentSuffix(comment?: string | null): string {
120
+ const text = comment?.trim();
121
+ return text ? ` - ${text}` : '';
122
+ }
123
+
124
+ type WorkflowByNameKey =
125
+ | 'request_approved_by'
126
+ | 'request_rejected_by'
127
+ | 'request_returned_for_modification_by';
128
+
129
+ type WorkflowWithoutNameKey =
130
+ | 'request_approved'
131
+ | 'request_rejected'
132
+ | 'request_returned_for_modification';
133
+
134
+ function buildWorkflowApprovalByName(
135
+ templateKey: WorkflowByNameKey,
136
+ fallbackKey: WorkflowWithoutNameKey,
137
+ nameEn: string,
138
+ nameAr: string,
139
+ comment?: string | null,
140
+ ): BilingualText {
141
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
142
+ return {
143
+ en: workflowTemplate(templateKey, { name: nameEn, comment: commentSuffix }).en,
144
+ ar: nameAr
145
+ ? workflowTemplate(templateKey, { name: nameAr, comment: '' }).ar
146
+ : workflowTemplate(fallbackKey, { comment: '' }).ar,
147
+ };
148
+ }
149
+
150
+ function buildWorkflowApprovalWithoutName(
151
+ templateKey: WorkflowWithoutNameKey,
152
+ comment?: string | null,
153
+ ): BilingualText {
154
+ const commentSuffix = formatWorkflowCommentSuffix(comment);
155
+ return {
156
+ en: workflowTemplate(templateKey, { comment: commentSuffix }).en,
157
+ ar: workflowTemplate(templateKey, { comment: '' }).ar,
158
+ };
159
+ }
160
+
161
+ function buildWorkflowFromNamePair(
162
+ templateWithNameKey: 'request_approval_in_progress_from' | 'request_approval_pending_from',
163
+ templateWithoutNameKey: 'request_approval_in_progress' | 'request_approval_pending',
164
+ nameEn: string,
165
+ nameAr: string,
166
+ ): BilingualText {
167
+ return {
168
+ en: workflowTemplate(templateWithNameKey, { name: nameEn }).en,
169
+ ar: nameAr
170
+ ? workflowTemplate(templateWithNameKey, { name: nameAr }).ar
171
+ : workflowTemplate(templateWithoutNameKey).ar,
172
+ };
173
+ }
174
+
102
175
  export function buildApprovalWorkflowContent(params: {
103
- status: 'Approved' | 'Rejected' | 'Pending' | string;
176
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
104
177
  roleName?: string | null;
105
178
  roleArabicName?: string | null;
106
179
  comment?: string | null;
@@ -108,36 +181,43 @@ export function buildApprovalWorkflowContent(params: {
108
181
  const nameEn = params.roleName?.trim() || '';
109
182
  const nameAr = pickArabicName(params.roleArabicName);
110
183
 
111
- let base: BilingualText;
112
184
  if (params.status === 'Approved') {
113
- base = nameEn
114
- ? workflowTemplate('request_approved_by', { name: nameEn })
115
- : workflowTemplate('request_approved');
116
- base.ar = nameAr
117
- ? workflowTemplate('request_approved_by', { name: nameAr }).ar
118
- : workflowTemplate('request_approved').ar;
119
- } else if (params.status === 'Rejected') {
120
- base = nameEn
121
- ? workflowTemplate('request_rejected_by', { name: nameEn })
122
- : workflowTemplate('request_rejected');
123
- base.ar = nameAr
124
- ? workflowTemplate('request_rejected_by', { name: nameAr }).ar
125
- : workflowTemplate('request_rejected').ar;
126
- } else {
127
- base = nameEn
128
- ? workflowTemplate('request_approval_pending_from', { name: nameEn })
129
- : workflowTemplate('request_approval_pending');
130
- base.ar = nameAr
131
- ? workflowTemplate('request_approval_pending_from', { name: nameAr }).ar
132
- : workflowTemplate('request_approval_pending').ar;
185
+ return nameEn
186
+ ? buildWorkflowApprovalByName('request_approved_by', 'request_approved', nameEn, nameAr, params.comment)
187
+ : buildWorkflowApprovalWithoutName('request_approved', params.comment);
188
+ }
189
+
190
+ if (params.status === 'Rejected') {
191
+ return nameEn
192
+ ? buildWorkflowApprovalByName('request_rejected_by', 'request_rejected', nameEn, nameAr, params.comment)
193
+ : buildWorkflowApprovalWithoutName('request_rejected', params.comment);
133
194
  }
134
195
 
135
- const comment = params.comment?.trim();
136
- if (!comment) return base;
196
+ if (params.status === 'Returned for Modification') {
197
+ return nameEn
198
+ ? buildWorkflowApprovalByName(
199
+ 'request_returned_for_modification_by',
200
+ 'request_returned_for_modification',
201
+ nameEn,
202
+ nameAr,
203
+ params.comment,
204
+ )
205
+ : buildWorkflowApprovalWithoutName('request_returned_for_modification', params.comment);
206
+ }
207
+
208
+ const commentSuffix = formatWorkflowCommentSuffix(params.comment);
209
+ if (nameEn) {
210
+ return {
211
+ en: workflowTemplate('request_approval_pending_from', { name: nameEn, comment: commentSuffix }).en,
212
+ ar: nameAr
213
+ ? workflowTemplate('request_approval_pending_from', { name: nameAr, comment: '' }).ar
214
+ : workflowTemplate('request_approval_pending', { comment: '' }).ar,
215
+ };
216
+ }
137
217
 
138
218
  return {
139
- en: `${base.en}: ${comment}`,
140
- ar: base.ar,
219
+ en: workflowTemplate('request_approval_pending', { comment: commentSuffix }).en,
220
+ ar: workflowTemplate('request_approval_pending', { comment: '' }).ar,
141
221
  };
142
222
  }
143
223
 
@@ -145,24 +225,28 @@ export function buildHumanApprovalWorkflowContent(params: {
145
225
  isFirst: boolean;
146
226
  roleName?: string | null;
147
227
  roleArabicName?: string | null;
148
- deptSectionEn?: string | null;
149
- deptSectionAr?: string | null;
228
+ deptName?: string | null;
229
+ deptArabicName?: string | null;
230
+ sectionName?: string | null;
231
+ sectionArabicName?: string | null;
150
232
  }): BilingualText {
151
- const nameEn = params.roleName?.trim() || params.deptSectionEn?.trim() || '';
152
- const nameAr = params.roleName
153
- ? pickArabicName(params.roleArabicName)
154
- : (params.deptSectionAr?.trim() || '');
233
+ const withNameKey = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
234
+ const withoutNameKey = params.isFirst ? 'request_approval_in_progress' : 'request_approval_pending';
155
235
 
156
- if (nameEn) {
157
- const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
158
- return {
159
- en: workflowTemplate(key, { name: nameEn }).en,
160
- ar: nameAr
161
- ? workflowTemplate(key, { name: nameAr }).ar
162
- : params.isFirst
163
- ? workflowTemplate('request_approval_in_progress').ar
164
- : workflowTemplate('request_approval_pending').ar,
165
- };
236
+ if (params.roleName?.trim()) {
237
+ const nameEn = params.roleName.trim();
238
+ const nameAr = pickArabicName(params.roleArabicName);
239
+ return buildWorkflowFromNamePair(withNameKey, withoutNameKey, nameEn, nameAr);
240
+ }
241
+
242
+ const { en, ar } = joinDeptSectionBilingualLabels(
243
+ params.deptName,
244
+ params.deptArabicName,
245
+ params.sectionName,
246
+ params.sectionArabicName,
247
+ );
248
+ if (en) {
249
+ return buildWorkflowFromNamePair(withNameKey, withoutNameKey, en, ar);
166
250
  }
167
251
 
168
252
  return params.isFirst
@@ -171,51 +255,107 @@ export function buildHumanApprovalWorkflowContent(params: {
171
255
  }
172
256
 
173
257
  export function buildApprovalChatMessage(params: {
174
- status: 'Approved' | 'Rejected' | string;
258
+ status: 'Approved' | 'Rejected' | 'Returned for Modification' | string;
175
259
  roleName?: string | null;
176
260
  roleArabicName?: string | null;
177
261
  comment?: string | null;
178
262
  }): BilingualText {
179
263
  const roleEn = params.roleName?.trim() || 'Unknown Role';
180
264
  const roleAr = pickArabicName(params.roleArabicName);
181
- const commentSuffix = params.comment?.trim() ? ` - ${params.comment.trim()}` : '';
182
- const key = params.status === 'Approved' ? 'request_approved_by_role' : 'request_rejected_by_role';
265
+ const commentSuffix = formatChatCommentSuffix(params.comment);
266
+
267
+ let byRoleKey: string;
268
+ let withoutRoleKey: string;
269
+ if (params.status === 'Approved') {
270
+ byRoleKey = 'request_approved_by_role';
271
+ withoutRoleKey = 'request_approved';
272
+ } else if (params.status === 'Rejected') {
273
+ byRoleKey = 'request_rejected_by_role';
274
+ withoutRoleKey = 'request_rejected';
275
+ } else if (params.status === 'Returned for Modification') {
276
+ byRoleKey = 'request_returned_for_modification_by_role';
277
+ withoutRoleKey = 'request_returned_for_modification';
278
+ } else {
279
+ byRoleKey = 'request_approved_by_role';
280
+ withoutRoleKey = 'request_approved';
281
+ }
183
282
 
184
283
  return {
185
- en: chatTemplate(key, { roleName: roleEn, comment: commentSuffix }).en,
186
- ar: chatTemplate(key, { roleName: roleAr, comment: '' }).ar,
284
+ en: chatTemplate(byRoleKey, { roleName: roleEn, comment: commentSuffix }).en,
285
+ ar: roleAr
286
+ ? chatTemplate(byRoleKey, { roleName: roleAr, comment: '' }).ar
287
+ : chatTemplate(withoutRoleKey, { comment: '' }).ar,
187
288
  };
188
289
  }
189
290
 
190
- /** Resolve Arabic workflow content from a stored English content string. */
291
+ /** Resolve Arabic workflow content from a stored English content string (legacy rows). */
191
292
  export function resolveWorkflowContentAr(content: string | null | undefined): string | null {
192
293
  if (content == null || content.trim() === '') return null;
193
294
  const normalized = content.trim();
295
+
194
296
  for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
195
297
  if (tpl.en === normalized) return tpl.ar;
196
298
  }
299
+
300
+ const commentIdx = normalized.indexOf(': ');
301
+ if (commentIdx > 0) {
302
+ const baseEn = normalized.slice(0, commentIdx).trim();
303
+
304
+ for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
305
+ if (!tpl.en.includes('{{comment}}')) continue;
306
+ const withoutComment = tpl.en.replace('{{comment}}', '');
307
+ if (withoutComment.includes('{{name}}')) {
308
+ const staticPrefix = withoutComment.replace('{{name}}', '').trim();
309
+ if (baseEn.startsWith(staticPrefix)) {
310
+ const fallbackKey = Object.entries(WORKFLOW_TEMPLATES).find(([, t]) => t === tpl)?.[0] ?? '';
311
+ if (fallbackKey.includes('_by')) {
312
+ const genericKey = fallbackKey.replace('_by', '') as WorkflowWithoutNameKey;
313
+ if (WORKFLOW_TEMPLATES[genericKey]) {
314
+ return WORKFLOW_TEMPLATES[genericKey].ar;
315
+ }
316
+ }
317
+ return tpl.ar.split('{{name}}')[0].trim();
318
+ }
319
+ } else if (withoutComment.trim() === baseEn) {
320
+ return interpolate(tpl.ar, { comment: '' });
321
+ }
322
+ }
323
+ }
324
+
325
+ const inProgressPrefix = WORKFLOW_TEMPLATES.request_approval_in_progress_from?.en.split('{{name}}')[0].trim();
326
+ if (inProgressPrefix && normalized.startsWith(inProgressPrefix)) {
327
+ return WORKFLOW_TEMPLATES.request_approval_in_progress.ar;
328
+ }
329
+ const pendingPrefix = WORKFLOW_TEMPLATES.request_approval_pending_from?.en.split('{{name}}')[0].trim();
330
+ if (pendingPrefix && normalized.startsWith(pendingPrefix)) {
331
+ return WORKFLOW_TEMPLATES.request_approval_pending.ar;
332
+ }
333
+
197
334
  return null;
198
335
  }
199
336
 
200
- /** Resolve Arabic chat message from a stored English message string. */
337
+ /** Resolve Arabic chat message from a stored English message string (legacy rows). */
201
338
  export function resolveChatMessageAr(message: string | null | undefined): string | null {
202
339
  if (message == null || message.trim() === '') return null;
203
340
  const normalized = message.trim();
341
+
204
342
  for (const tpl of Object.values(CHAT_TEMPLATES)) {
205
343
  if (tpl.en === normalized) return tpl.ar;
206
344
  }
207
- if (normalized === 'Request Received') {
208
- return CHAT_TEMPLATES.request_received?.ar ?? null;
209
- }
345
+
210
346
  return null;
211
347
  }
212
348
 
213
349
  export function enrichCancellationWorkflowLog(log: Record<string, unknown>): Record<string, unknown> {
214
350
  const status = log.status != null ? String(log.status) : null;
215
351
  const content = log.content != null ? String(log.content) : null;
352
+ const storedAr = log.content_ar != null ? String(log.content_ar).trim() : '';
353
+ const hasEnglishInAr = storedAr && /[A-Za-z]/.test(storedAr);
216
354
  return {
217
355
  ...log,
218
- content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
356
+ content_ar: hasEnglishInAr
357
+ ? (resolveWorkflowContentAr(content) ?? storedAr)
358
+ : (log.content_ar ?? resolveWorkflowContentAr(content)),
219
359
  status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
220
360
  };
221
361
  }
@@ -223,9 +363,13 @@ export function enrichCancellationWorkflowLog(log: Record<string, unknown>): Rec
223
363
  export function enrichCancellationChatMessage(chat: Record<string, unknown>): Record<string, unknown> {
224
364
  const status = chat.status != null ? String(chat.status) : null;
225
365
  const message = chat.message != null ? String(chat.message) : null;
366
+ const storedAr = chat.message_ar != null ? String(chat.message_ar).trim() : '';
367
+ const hasEnglishInAr = storedAr && /[A-Za-z]/.test(storedAr);
226
368
  return {
227
369
  ...chat,
228
- message_ar: chat.message_ar ?? resolveChatMessageAr(message),
370
+ message_ar: hasEnglishInAr
371
+ ? (resolveChatMessageAr(message) ?? storedAr)
372
+ : (chat.message_ar ?? resolveChatMessageAr(message)),
229
373
  status_ar: chat.status_ar ?? resolveChatStatusAr(status),
230
374
  };
231
375
  }