@platform-modules/civil-aviation-authority 2.3.296 → 2.3.298

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.
@@ -131,6 +131,70 @@
131
131
  "it_helpdesk_wf_routing_resolved": {
132
132
  "en": "Workflow routing completed - request resolved at L3",
133
133
  "ar": "تم استكمال مسار سير العمل - تم حل الطلب في المستوى الثالث (L3)"
134
+ },
135
+ "it_helpdesk_wf_routing_l3_reassign": {
136
+ "en": "Workflow routed to L3 assignment path - request reassigned by {{userName}}{{comment}}",
137
+ "ar": "تم توجيه سير العمل إلى مسار تعيين المستوى الثالث (L3) - تمت إعادة تعيين الطلب بواسطة {{userName}}{{comment}}"
138
+ },
139
+ "it_helpdesk_wf_routing_direct_approval": {
140
+ "en": "Workflow routed to direct resolution path - request approved by {{roleName}}{{comment}}",
141
+ "ar": "تم توجيه سير العمل إلى مسار الحل المباشر - تمت الموافقة على الطلب من قبل {{roleName}}{{comment}}"
142
+ },
143
+ "it_helpdesk_wf_routing_decision": {
144
+ "en": "Workflow decision made - {{status}} by {{roleName}}{{comment}}",
145
+ "ar": "تم اتخاذ قرار سير العمل - {{statusAr}} الطلب من قبل {{roleName}}{{comment}}"
146
+ },
147
+ "it_helpdesk_wf_l3_assignment_pending": {
148
+ "en": "L3 Technical Support assignment pending - request reassigned by {{roleName}}{{comment}}",
149
+ "ar": "تعيين دعم المستوى الثالث (L3) قيد الانتظار - تمت إعادة تعيين الطلب من قبل {{roleName}}{{comment}}"
150
+ },
151
+ "it_helpdesk_wf_l3_role_approved": {
152
+ "en": "{{roleName}} approved the request{{comment}}",
153
+ "ar": "تمت الموافقة على الطلب من قبل {{roleName}}{{commentAr}}"
154
+ },
155
+ "it_helpdesk_wf_l3_role_rejected": {
156
+ "en": "{{roleName}} rejected the request{{comment}}",
157
+ "ar": "تم رفض الطلب من قبل {{roleName}}{{commentAr}}"
158
+ },
159
+ "it_helpdesk_wf_role_outcome_by_user_rejected": {
160
+ "en": "{{roleName}} rejected the request by {{userName}}{{comment}}",
161
+ "ar": "تم رفض الطلب من قبل {{roleName}} بواسطة {{userName}}{{comment}}"
162
+ },
163
+ "it_helpdesk_wf_role_outcome_by_user_closed": {
164
+ "en": "{{roleName}} closed the request by {{userName}}{{comment}}",
165
+ "ar": "تم إغلاق الطلب من قبل {{roleName}} بواسطة {{userName}}{{comment}}"
166
+ },
167
+ "it_helpdesk_wf_routing_technician_rejected": {
168
+ "en": "Workflow routing completed - request rejected at technician level{{comment}}",
169
+ "ar": "تم استكمال مسار سير العمل - تم رفض الطلب على مستوى الفني{{comment}}"
170
+ },
171
+ "it_helpdesk_wf_hos_not_required_rejected": {
172
+ "en": "HOS notification not required, request rejected by {{userName}}{{comment}}",
173
+ "ar": "إشعار رئيس القسم غير مطلوب، تم رفض الطلب بواسطة {{userName}}{{comment}}"
174
+ },
175
+ "it_helpdesk_wf_hos_not_required_closed": {
176
+ "en": "HOS notification not required, request closed by {{userName}}{{comment}}",
177
+ "ar": "إشعار رئيس القسم غير مطلوب، تم إغلاق الطلب بواسطة {{userName}}{{comment}}"
178
+ },
179
+ "it_helpdesk_wf_l3_not_required_rejected": {
180
+ "en": "L3 assignment not required, request rejected by {{userName}}{{comment}}",
181
+ "ar": "تعيين المستوى الثالث (L3) غير مطلوب، تم رفض الطلب بواسطة {{userName}}{{comment}}"
182
+ },
183
+ "it_helpdesk_wf_l3_not_required_closed": {
184
+ "en": "L3 assignment not required, request closed by {{userName}}{{comment}}",
185
+ "ar": "تعيين المستوى الثالث (L3) غير مطلوب، تم إغلاق الطلب بواسطة {{userName}}{{comment}}"
186
+ },
187
+ "request_assigned": {
188
+ "en": "Request Assigned",
189
+ "ar": "تم تعيين الطلب"
190
+ },
191
+ "request_pending_from": {
192
+ "en": "Request Pending from {{name}}",
193
+ "ar": "الطلب قيد الانتظار من {{name}}"
194
+ },
195
+ "request_pending": {
196
+ "en": "Request Pending",
197
+ "ar": "الطلب قيد الانتظار"
134
198
  }
135
199
  },
136
200
  "chatTemplates": {
@@ -262,6 +326,10 @@
262
326
  "Pending": "قيد الانتظار",
263
327
  "Not Yet Started": "لم يبدأ بعد"
264
328
  },
329
+ "roleLabels": {
330
+ "L3 Technical Support": "دعم المستوى الثالث (L3) التقني",
331
+ "IT Technician": "فني تكنولوجيا المعلومات"
332
+ },
265
333
  "chatStatusLabels": {
266
334
  "Submitted": "تم التقديم",
267
335
  "Received": "تم الاستلام",
@@ -264,6 +264,47 @@ export type ItHelpdeskMuscatWorkflowParams = {
264
264
  comment?: string | null;
265
265
  } | {
266
266
  variant: 'routing_resolved';
267
+ } | {
268
+ variant: 'routing_l3_reassign';
269
+ userName: string;
270
+ userArabicName?: string | null;
271
+ comment?: string | null;
272
+ } | {
273
+ variant: 'routing_direct_approval';
274
+ roleName: string;
275
+ roleArabicName?: string | null;
276
+ comment?: string | null;
277
+ } | {
278
+ variant: 'routing_decision';
279
+ status: string;
280
+ roleName: string;
281
+ roleArabicName?: string | null;
282
+ comment?: string | null;
283
+ } | {
284
+ variant: 'l3_assignment_pending';
285
+ roleName: string;
286
+ roleArabicName?: string | null;
287
+ comment?: string | null;
288
+ } | {
289
+ variant: 'l3_role_approved' | 'l3_role_rejected';
290
+ roleName: string;
291
+ roleArabicName?: string | null;
292
+ comment?: string | null;
293
+ } | {
294
+ variant: 'role_outcome_by_user_rejected' | 'role_outcome_by_user_closed';
295
+ roleName: string;
296
+ roleArabicName?: string | null;
297
+ userName: string;
298
+ userArabicName?: string | null;
299
+ comment?: string | null;
300
+ } | {
301
+ variant: 'routing_technician_rejected';
302
+ comment?: string | null;
303
+ } | {
304
+ variant: 'hos_not_required_rejected' | 'hos_not_required_closed' | 'l3_not_required_rejected' | 'l3_not_required_closed';
305
+ userName: string;
306
+ userArabicName?: string | null;
307
+ comment?: string | null;
267
308
  };
268
309
  export declare function buildItHelpdeskMuscatWorkflowLog(params: ItHelpdeskMuscatWorkflowParams): BilingualText;
269
310
  export type LogisticsActualReturnParams = {
@@ -67,6 +67,7 @@ const CHAT_TEMPLATES = workflow_chat_i18n_json_1.default.chatTemplates;
67
67
  const NOTIFICATION_TEMPLATES = workflow_chat_i18n_json_1.default.notificationTemplates ?? {};
68
68
  const WORKFLOW_STATUS_LABELS = workflow_chat_i18n_json_1.default.workflowStatusLabels;
69
69
  const CHAT_STATUS_LABELS = workflow_chat_i18n_json_1.default.chatStatusLabels;
70
+ const ROLE_LABELS = workflow_chat_i18n_json_1.default.roleLabels ?? {};
70
71
  function getWorkflowChatI18nCatalog() {
71
72
  return workflow_chat_i18n_json_1.default;
72
73
  }
@@ -325,6 +326,19 @@ function buildRequestApprovedAssignedWorkflowFields(params) {
325
326
  }
326
327
  /** Standard workflow log UPDATE fields for all CAA module repositories. */
327
328
  function buildWorkflowLogUpdateFields(params) {
329
+ if (params.approvalStatus === 'Closed') {
330
+ const closed = buildClosedWorkflowMessage({
331
+ roleName: params.roleName,
332
+ roleArabicName: params.roleArabicName,
333
+ comment: params.comment,
334
+ });
335
+ return {
336
+ content: closed.en,
337
+ content_ar: closed.ar,
338
+ status: params.workflowStatus,
339
+ status_ar: resolveWorkflowStatusAr(params.workflowStatus),
340
+ };
341
+ }
328
342
  const isApprovedLike = params.approvalStatus === 'Approved' || params.approvalStatus === 'Assigned';
329
343
  if (isApprovedLike) {
330
344
  const assignment = tryParseRequestAssignedComment(params.comment);
@@ -457,6 +471,23 @@ function formatColonCommentSuffix(comment) {
457
471
  const text = comment?.trim();
458
472
  return text ? `: ${text}` : '';
459
473
  }
474
+ function formatBilingualColonCommentSuffix(comment) {
475
+ const text = comment?.trim();
476
+ if (!text)
477
+ return { en: '', ar: '' };
478
+ const statusKey = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
479
+ const arText = resolveChatStatusAr(statusKey) ?? text;
480
+ return { en: `: ${text}`, ar: `: ${arText}` };
481
+ }
482
+ function splitWorkflowColonComment(content) {
483
+ const idx = content.indexOf(': ');
484
+ if (idx <= 0)
485
+ return { base: content.trim() };
486
+ return {
487
+ base: content.slice(0, idx).trim(),
488
+ comment: content.slice(idx + 2).trim() || undefined,
489
+ };
490
+ }
460
491
  function formatReassignCommentSuffix(comment) {
461
492
  const text = comment?.trim();
462
493
  return text ? `:${text}` : '';
@@ -724,6 +755,65 @@ function buildItHelpdeskMuscatWorkflowLog(params) {
724
755
  return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_rejected', { comment }, { comment });
725
756
  case 'routing_resolved':
726
757
  return workflowTemplate('it_helpdesk_wf_routing_resolved');
758
+ case 'routing_l3_reassign': {
759
+ const userEn = params.userName.trim();
760
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
761
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_l3_reassign', { userName: userEn, comment }, { userName: userAr, comment });
762
+ }
763
+ case 'routing_direct_approval': {
764
+ const roleEn = params.roleName.trim();
765
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
766
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_direct_approval', { roleName: roleEn, comment }, { roleName: roleAr, comment });
767
+ }
768
+ case 'routing_decision': {
769
+ const roleEn = params.roleName.trim();
770
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
771
+ const statusEn = params.status.toLowerCase();
772
+ const statusAr = resolveItHelpdeskGenericStatusAr(params.status);
773
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_decision', { status: statusEn, statusAr, roleName: roleEn, comment }, { status: statusEn, statusAr, roleName: roleAr, comment });
774
+ }
775
+ case 'l3_assignment_pending': {
776
+ const roleEn = params.roleName.trim();
777
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
778
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_l3_assignment_pending', { roleName: roleEn, comment }, { roleName: roleAr, comment });
779
+ }
780
+ case 'l3_role_approved':
781
+ case 'l3_role_rejected': {
782
+ const roleEn = params.roleName.trim();
783
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName ?? ROLE_LABELS[roleEn] ?? null);
784
+ const biComment = formatBilingualColonCommentSuffix(params.comment);
785
+ const key = params.variant === 'l3_role_approved'
786
+ ? 'it_helpdesk_wf_l3_role_approved'
787
+ : 'it_helpdesk_wf_l3_role_rejected';
788
+ return renderSynchronizedWorkflowMessage(key, { roleName: roleEn, comment: biComment.en }, { roleName: roleAr, commentAr: biComment.ar });
789
+ }
790
+ case 'role_outcome_by_user_rejected':
791
+ case 'role_outcome_by_user_closed': {
792
+ const roleEn = params.roleName.trim();
793
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
794
+ const userEn = params.userName.trim();
795
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
796
+ const key = params.variant === 'role_outcome_by_user_rejected'
797
+ ? 'it_helpdesk_wf_role_outcome_by_user_rejected'
798
+ : 'it_helpdesk_wf_role_outcome_by_user_closed';
799
+ return renderSynchronizedWorkflowMessage(key, { roleName: roleEn, userName: userEn, comment }, { roleName: roleAr, userName: userAr, comment });
800
+ }
801
+ case 'routing_technician_rejected':
802
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_technician_rejected', { comment }, { comment });
803
+ case 'hos_not_required_rejected':
804
+ case 'hos_not_required_closed':
805
+ case 'l3_not_required_rejected':
806
+ case 'l3_not_required_closed': {
807
+ const userEn = params.userName.trim();
808
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
809
+ const keyMap = {
810
+ hos_not_required_rejected: 'it_helpdesk_wf_hos_not_required_rejected',
811
+ hos_not_required_closed: 'it_helpdesk_wf_hos_not_required_closed',
812
+ l3_not_required_rejected: 'it_helpdesk_wf_l3_not_required_rejected',
813
+ l3_not_required_closed: 'it_helpdesk_wf_l3_not_required_closed',
814
+ };
815
+ return renderSynchronizedWorkflowMessage(keyMap[params.variant], { userName: userEn, comment }, { userName: userAr, comment });
816
+ }
727
817
  default:
728
818
  return workflowTemplate('request_submitted');
729
819
  }
@@ -815,19 +905,23 @@ function resolveArabicWorkflowActorName(actors, parsedEnglishName) {
815
905
  /** Arabic role/user label for chat templates. */
816
906
  function resolveArabicChatRoleName(actors, parsedEnglishRole) {
817
907
  const parsed = parsedEnglishRole?.trim();
818
- if (actors?.roleName?.trim()) {
819
- const actorRole = actors.roleName.trim();
820
- if (!parsed || parsed.toLowerCase() === actorRole.toLowerCase()) {
821
- return resolveBilingualName(actorRole, actors.roleArabicName);
908
+ if (parsed) {
909
+ if (actors?.roleName?.trim() &&
910
+ parsed.toLowerCase() === actors.roleName.trim().toLowerCase()) {
911
+ return resolveBilingualName(parsed, actors.roleArabicName);
822
912
  }
913
+ const catalogAr = ROLE_LABELS[parsed];
914
+ if (catalogAr)
915
+ return catalogAr;
916
+ return parsed;
823
917
  }
824
- if (actors?.roleArabicName?.trim() && parsed) {
825
- return resolveBilingualName(parsed, actors.roleArabicName);
918
+ if (actors?.roleName?.trim()) {
919
+ return resolveBilingualName(actors.roleName, actors.roleArabicName);
826
920
  }
827
921
  if (actors?.userName?.trim()) {
828
922
  return resolveBilingualName(actors.userName, actors.userArabicName);
829
923
  }
830
- return resolveBilingualName(parsed, actors?.roleArabicName);
924
+ return '';
831
925
  }
832
926
  function escapeRegexLiteral(value) {
833
927
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -908,6 +1002,14 @@ function buildArabicWorkflowTemplateParams(params, actors) {
908
1002
  const normalizedStatus = params.status.charAt(0).toUpperCase() + params.status.slice(1).toLowerCase();
909
1003
  arParams.statusAr = resolveItHelpdeskGenericStatusAr(normalizedStatus);
910
1004
  }
1005
+ if (params.userName !== undefined) {
1006
+ arParams.userName = actors?.userName?.trim()
1007
+ ? resolveBilingualName(actors.userName, actors.userArabicName)
1008
+ : resolveBilingualName(params.userName.trim(), actors?.userArabicName);
1009
+ }
1010
+ if (params.commentAr !== undefined) {
1011
+ arParams.commentAr = params.commentAr;
1012
+ }
911
1013
  return arParams;
912
1014
  }
913
1015
  function countTemplatePlaceholders(templateEn) {
@@ -916,6 +1018,128 @@ function countTemplatePlaceholders(templateEn) {
916
1018
  function listWorkflowTemplatesBySpecificity() {
917
1019
  return Object.entries(WORKFLOW_TEMPLATES).sort(([, a], [, b]) => countTemplatePlaceholders(b.en) - countTemplatePlaceholders(a.en));
918
1020
  }
1021
+ /** Parse legacy IT Helpdesk Muscat EN workflow strings before generic template matching. */
1022
+ function tryResolveItHelpdeskMuscatLegacyWorkflowAr(content, actors) {
1023
+ const { base, comment } = splitWorkflowColonComment(content);
1024
+ let m = base.match(/^Workflow routed to L3 assignment path - request reassigned by (.+)$/i);
1025
+ if (m) {
1026
+ return buildItHelpdeskMuscatWorkflowLog({
1027
+ variant: 'routing_l3_reassign',
1028
+ userName: m[1].trim(),
1029
+ userArabicName: actors?.userArabicName,
1030
+ comment,
1031
+ }).ar;
1032
+ }
1033
+ m = base.match(/^Workflow routed to direct resolution path - request approved by (.+)$/i);
1034
+ if (m) {
1035
+ return buildItHelpdeskMuscatWorkflowLog({
1036
+ variant: 'routing_direct_approval',
1037
+ roleName: m[1].trim(),
1038
+ roleArabicName: actors?.roleArabicName,
1039
+ comment,
1040
+ }).ar;
1041
+ }
1042
+ m = base.match(/^Workflow decision made - (\w+) by (.+)$/i);
1043
+ if (m) {
1044
+ return buildItHelpdeskMuscatWorkflowLog({
1045
+ variant: 'routing_decision',
1046
+ status: m[1],
1047
+ roleName: m[2].trim(),
1048
+ roleArabicName: actors?.roleArabicName,
1049
+ comment,
1050
+ }).ar;
1051
+ }
1052
+ m = base.match(/^L3 Technical Support assignment pending - request reassigned by (.+)$/i);
1053
+ if (m) {
1054
+ return buildItHelpdeskMuscatWorkflowLog({
1055
+ variant: 'l3_assignment_pending',
1056
+ roleName: m[1].trim(),
1057
+ roleArabicName: actors?.roleArabicName,
1058
+ comment,
1059
+ }).ar;
1060
+ }
1061
+ m = base.match(/^L3 Technical Support approved the request$/i);
1062
+ if (m) {
1063
+ return buildItHelpdeskMuscatWorkflowLog({
1064
+ variant: 'l3_role_approved',
1065
+ roleName: 'L3 Technical Support',
1066
+ comment,
1067
+ }).ar;
1068
+ }
1069
+ m = base.match(/^L3 Technical Support rejected the request$/i);
1070
+ if (m) {
1071
+ return buildItHelpdeskMuscatWorkflowLog({
1072
+ variant: 'l3_role_rejected',
1073
+ roleName: 'L3 Technical Support',
1074
+ comment,
1075
+ }).ar;
1076
+ }
1077
+ m = base.match(/^(.+?) rejected the request by (.+)$/i);
1078
+ if (m && !m[1].toLowerCase().includes('workflow')) {
1079
+ return buildItHelpdeskMuscatWorkflowLog({
1080
+ variant: 'role_outcome_by_user_rejected',
1081
+ roleName: m[1].trim(),
1082
+ roleArabicName: actors?.roleArabicName,
1083
+ userName: m[2].trim(),
1084
+ userArabicName: actors?.userArabicName,
1085
+ comment,
1086
+ }).ar;
1087
+ }
1088
+ m = base.match(/^(.+?) closed the request by (.+)$/i);
1089
+ if (m) {
1090
+ return buildItHelpdeskMuscatWorkflowLog({
1091
+ variant: 'role_outcome_by_user_closed',
1092
+ roleName: m[1].trim(),
1093
+ roleArabicName: actors?.roleArabicName,
1094
+ userName: m[2].trim(),
1095
+ userArabicName: actors?.userArabicName,
1096
+ comment,
1097
+ }).ar;
1098
+ }
1099
+ if (/^Workflow routing completed - request rejected at technician level$/i.test(base)) {
1100
+ return buildItHelpdeskMuscatWorkflowLog({
1101
+ variant: 'routing_technician_rejected',
1102
+ comment,
1103
+ }).ar;
1104
+ }
1105
+ m = base.match(/^HOS notification not required, request rejected by (.+)$/i);
1106
+ if (m) {
1107
+ return buildItHelpdeskMuscatWorkflowLog({
1108
+ variant: 'hos_not_required_rejected',
1109
+ userName: m[1].trim(),
1110
+ userArabicName: actors?.userArabicName,
1111
+ comment,
1112
+ }).ar;
1113
+ }
1114
+ m = base.match(/^HOS notification not required, request closed by (.+)$/i);
1115
+ if (m) {
1116
+ return buildItHelpdeskMuscatWorkflowLog({
1117
+ variant: 'hos_not_required_closed',
1118
+ userName: m[1].trim(),
1119
+ userArabicName: actors?.userArabicName,
1120
+ comment,
1121
+ }).ar;
1122
+ }
1123
+ m = base.match(/^L3 assignment not required, request rejected by (.+)$/i);
1124
+ if (m) {
1125
+ return buildItHelpdeskMuscatWorkflowLog({
1126
+ variant: 'l3_not_required_rejected',
1127
+ userName: m[1].trim(),
1128
+ userArabicName: actors?.userArabicName,
1129
+ comment,
1130
+ }).ar;
1131
+ }
1132
+ m = base.match(/^L3 assignment not required, request closed by (.+)$/i);
1133
+ if (m) {
1134
+ return buildItHelpdeskMuscatWorkflowLog({
1135
+ variant: 'l3_not_required_closed',
1136
+ userName: m[1].trim(),
1137
+ userArabicName: actors?.userArabicName,
1138
+ comment,
1139
+ }).ar;
1140
+ }
1141
+ return null;
1142
+ }
919
1143
  function resolveWorkflowContentAr(content, actors) {
920
1144
  if (content == null || content.trim() === '')
921
1145
  return null;
@@ -924,6 +1148,9 @@ function resolveWorkflowContentAr(content, actors) {
924
1148
  if (embeddedAssignment) {
925
1149
  return buildAssignmentWorkflowFieldsFromActors(embeddedAssignment, actors).ar;
926
1150
  }
1151
+ const itHelpdeskLegacyAr = tryResolveItHelpdeskMuscatLegacyWorkflowAr(normalized, actors);
1152
+ if (itHelpdeskLegacyAr)
1153
+ return itHelpdeskLegacyAr;
927
1154
  for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
928
1155
  if (tpl.en === normalized || tpl.en.toLowerCase() === normalized.toLowerCase())
929
1156
  return tpl.ar;
@@ -992,6 +1219,25 @@ function resolveWorkflowContentAr(content, actors) {
992
1219
  const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
993
1220
  return interpolate(WORKFLOW_TEMPLATES.request_approval_pending_from.ar, { name: nameAr });
994
1221
  }
1222
+ // Assign Tasks to Employee workflow (legacy EN strings)
1223
+ if (normalized === 'Request Assigned') {
1224
+ return WORKFLOW_TEMPLATES.request_assigned?.ar ?? null;
1225
+ }
1226
+ if (normalized === 'Request Pending') {
1227
+ return WORKFLOW_TEMPLATES.request_pending?.ar ?? null;
1228
+ }
1229
+ const assignTasksPendingPrefix = WORKFLOW_TEMPLATES.request_pending_from?.en.split('{{name}}')[0].trim();
1230
+ if (assignTasksPendingPrefix && normalized.startsWith(assignTasksPendingPrefix)) {
1231
+ const namePartEn = normalized.slice(assignTasksPendingPrefix.length).trim();
1232
+ const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
1233
+ return interpolate(WORKFLOW_TEMPLATES.request_pending_from.ar, { name: nameAr });
1234
+ }
1235
+ if (normalized.toLowerCase() === 'notification sent pending') {
1236
+ return WORKFLOW_TEMPLATES.notification_sent_pending?.ar ?? null;
1237
+ }
1238
+ if (normalized.toLowerCase() === 'notification sent successfully') {
1239
+ return WORKFLOW_TEMPLATES.notification_sent_successfully?.ar ?? null;
1240
+ }
995
1241
  if (normalized === 'Approval Pending from HR Section' ||
996
1242
  normalized === 'Approval Pending from Records Department') {
997
1243
  return WORKFLOW_TEMPLATES.request_approval_pending?.ar ?? null;
@@ -1043,6 +1289,28 @@ function enrichWorkflowLog(log) {
1043
1289
  content_ar = resolvedAr;
1044
1290
  }
1045
1291
  }
1292
+ const inProgressPrefix = WORKFLOW_TEMPLATES.request_approval_in_progress_from?.en.split('{{name}}')[0].trim();
1293
+ if (content &&
1294
+ inProgressPrefix &&
1295
+ content.startsWith(inProgressPrefix) &&
1296
+ existingAr &&
1297
+ resolvedAr &&
1298
+ existingAr !== resolvedAr) {
1299
+ const namePartEn = content.slice(inProgressPrefix.length).trim();
1300
+ if (namePartEn && existingAr.includes(namePartEn)) {
1301
+ content_ar = resolvedAr;
1302
+ }
1303
+ }
1304
+ if (content &&
1305
+ existingAr &&
1306
+ resolvedAr &&
1307
+ existingAr !== resolvedAr &&
1308
+ /[A-Za-z]/.test(existingAr) &&
1309
+ (actors?.deptArabicName?.trim() ||
1310
+ actors?.sectionArabicName?.trim() ||
1311
+ actors?.roleArabicName?.trim())) {
1312
+ content_ar = resolvedAr;
1313
+ }
1046
1314
  if (content &&
1047
1315
  existingAr &&
1048
1316
  resolvedAr &&
@@ -1059,6 +1327,15 @@ function enrichWorkflowLog(log) {
1059
1327
  existingAr.includes(actors.roleName.trim())) {
1060
1328
  content_ar = resolvedAr;
1061
1329
  }
1330
+ const itHelpdeskMixedEnglish = /routed to L3|assignment path|Workflow decision made|L3 Technical Support approved|L3 assignment not required|HOS notification not required|routing completed/i;
1331
+ if (content &&
1332
+ existingAr &&
1333
+ resolvedAr &&
1334
+ existingAr !== resolvedAr &&
1335
+ (itHelpdeskMixedEnglish.test(content) ||
1336
+ /routed to|routing completed|assignment path|\sby\s/i.test(existingAr))) {
1337
+ content_ar = resolvedAr;
1338
+ }
1062
1339
  if ((content_ar == null || content_ar === '') && content) {
1063
1340
  const notificationAr = resolveWorkflowContentAr(content, actors);
1064
1341
  if (notificationAr)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platform-modules/civil-aviation-authority",
3
- "version": "2.3.296",
3
+ "version": "2.3.298",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -131,6 +131,70 @@
131
131
  "it_helpdesk_wf_routing_resolved": {
132
132
  "en": "Workflow routing completed - request resolved at L3",
133
133
  "ar": "تم استكمال مسار سير العمل - تم حل الطلب في المستوى الثالث (L3)"
134
+ },
135
+ "it_helpdesk_wf_routing_l3_reassign": {
136
+ "en": "Workflow routed to L3 assignment path - request reassigned by {{userName}}{{comment}}",
137
+ "ar": "تم توجيه سير العمل إلى مسار تعيين المستوى الثالث (L3) - تمت إعادة تعيين الطلب بواسطة {{userName}}{{comment}}"
138
+ },
139
+ "it_helpdesk_wf_routing_direct_approval": {
140
+ "en": "Workflow routed to direct resolution path - request approved by {{roleName}}{{comment}}",
141
+ "ar": "تم توجيه سير العمل إلى مسار الحل المباشر - تمت الموافقة على الطلب من قبل {{roleName}}{{comment}}"
142
+ },
143
+ "it_helpdesk_wf_routing_decision": {
144
+ "en": "Workflow decision made - {{status}} by {{roleName}}{{comment}}",
145
+ "ar": "تم اتخاذ قرار سير العمل - {{statusAr}} الطلب من قبل {{roleName}}{{comment}}"
146
+ },
147
+ "it_helpdesk_wf_l3_assignment_pending": {
148
+ "en": "L3 Technical Support assignment pending - request reassigned by {{roleName}}{{comment}}",
149
+ "ar": "تعيين دعم المستوى الثالث (L3) قيد الانتظار - تمت إعادة تعيين الطلب من قبل {{roleName}}{{comment}}"
150
+ },
151
+ "it_helpdesk_wf_l3_role_approved": {
152
+ "en": "{{roleName}} approved the request{{comment}}",
153
+ "ar": "تمت الموافقة على الطلب من قبل {{roleName}}{{commentAr}}"
154
+ },
155
+ "it_helpdesk_wf_l3_role_rejected": {
156
+ "en": "{{roleName}} rejected the request{{comment}}",
157
+ "ar": "تم رفض الطلب من قبل {{roleName}}{{commentAr}}"
158
+ },
159
+ "it_helpdesk_wf_role_outcome_by_user_rejected": {
160
+ "en": "{{roleName}} rejected the request by {{userName}}{{comment}}",
161
+ "ar": "تم رفض الطلب من قبل {{roleName}} بواسطة {{userName}}{{comment}}"
162
+ },
163
+ "it_helpdesk_wf_role_outcome_by_user_closed": {
164
+ "en": "{{roleName}} closed the request by {{userName}}{{comment}}",
165
+ "ar": "تم إغلاق الطلب من قبل {{roleName}} بواسطة {{userName}}{{comment}}"
166
+ },
167
+ "it_helpdesk_wf_routing_technician_rejected": {
168
+ "en": "Workflow routing completed - request rejected at technician level{{comment}}",
169
+ "ar": "تم استكمال مسار سير العمل - تم رفض الطلب على مستوى الفني{{comment}}"
170
+ },
171
+ "it_helpdesk_wf_hos_not_required_rejected": {
172
+ "en": "HOS notification not required, request rejected by {{userName}}{{comment}}",
173
+ "ar": "إشعار رئيس القسم غير مطلوب، تم رفض الطلب بواسطة {{userName}}{{comment}}"
174
+ },
175
+ "it_helpdesk_wf_hos_not_required_closed": {
176
+ "en": "HOS notification not required, request closed by {{userName}}{{comment}}",
177
+ "ar": "إشعار رئيس القسم غير مطلوب، تم إغلاق الطلب بواسطة {{userName}}{{comment}}"
178
+ },
179
+ "it_helpdesk_wf_l3_not_required_rejected": {
180
+ "en": "L3 assignment not required, request rejected by {{userName}}{{comment}}",
181
+ "ar": "تعيين المستوى الثالث (L3) غير مطلوب، تم رفض الطلب بواسطة {{userName}}{{comment}}"
182
+ },
183
+ "it_helpdesk_wf_l3_not_required_closed": {
184
+ "en": "L3 assignment not required, request closed by {{userName}}{{comment}}",
185
+ "ar": "تعيين المستوى الثالث (L3) غير مطلوب، تم إغلاق الطلب بواسطة {{userName}}{{comment}}"
186
+ },
187
+ "request_assigned": {
188
+ "en": "Request Assigned",
189
+ "ar": "تم تعيين الطلب"
190
+ },
191
+ "request_pending_from": {
192
+ "en": "Request Pending from {{name}}",
193
+ "ar": "الطلب قيد الانتظار من {{name}}"
194
+ },
195
+ "request_pending": {
196
+ "en": "Request Pending",
197
+ "ar": "الطلب قيد الانتظار"
134
198
  }
135
199
  },
136
200
  "chatTemplates": {
@@ -262,6 +326,10 @@
262
326
  "Pending": "قيد الانتظار",
263
327
  "Not Yet Started": "لم يبدأ بعد"
264
328
  },
329
+ "roleLabels": {
330
+ "L3 Technical Support": "دعم المستوى الثالث (L3) التقني",
331
+ "IT Technician": "فني تكنولوجيا المعلومات"
332
+ },
265
333
  "chatStatusLabels": {
266
334
  "Submitted": "تم التقديم",
267
335
  "Received": "تم الاستلام",
@@ -65,6 +65,7 @@ const CHAT_TEMPLATES = catalog.chatTemplates as Record<string, BilingualText>;
65
65
  const NOTIFICATION_TEMPLATES = (catalog as { notificationTemplates?: Record<string, BilingualText> }).notificationTemplates ?? {};
66
66
  const WORKFLOW_STATUS_LABELS = catalog.workflowStatusLabels as Record<string, string>;
67
67
  const CHAT_STATUS_LABELS = catalog.chatStatusLabels as Record<string, string>;
68
+ const ROLE_LABELS = (catalog as { roleLabels?: Record<string, string> }).roleLabels ?? {};
68
69
 
69
70
  export function getWorkflowChatI18nCatalog(): WorkflowChatI18nCatalog {
70
71
  return catalog as WorkflowChatI18nCatalog;
@@ -475,6 +476,20 @@ export function buildWorkflowLogUpdateFields(params: {
475
476
  roleArabicName?: string | null;
476
477
  comment?: string | null;
477
478
  }): { content: string; content_ar: string; status: string; status_ar: string | null } {
479
+ if (params.approvalStatus === 'Closed') {
480
+ const closed = buildClosedWorkflowMessage({
481
+ roleName: params.roleName,
482
+ roleArabicName: params.roleArabicName,
483
+ comment: params.comment,
484
+ });
485
+ return {
486
+ content: closed.en,
487
+ content_ar: closed.ar,
488
+ status: params.workflowStatus,
489
+ status_ar: resolveWorkflowStatusAr(params.workflowStatus),
490
+ };
491
+ }
492
+
478
493
  const isApprovedLike =
479
494
  params.approvalStatus === 'Approved' || params.approvalStatus === 'Assigned';
480
495
 
@@ -642,6 +657,23 @@ function formatColonCommentSuffix(comment?: string | null): string {
642
657
  return text ? `: ${text}` : '';
643
658
  }
644
659
 
660
+ function formatBilingualColonCommentSuffix(comment?: string | null): { en: string; ar: string } {
661
+ const text = comment?.trim();
662
+ if (!text) return { en: '', ar: '' };
663
+ const statusKey = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
664
+ const arText = resolveChatStatusAr(statusKey) ?? text;
665
+ return { en: `: ${text}`, ar: `: ${arText}` };
666
+ }
667
+
668
+ function splitWorkflowColonComment(content: string): { base: string; comment?: string } {
669
+ const idx = content.indexOf(': ');
670
+ if (idx <= 0) return { base: content.trim() };
671
+ return {
672
+ base: content.slice(0, idx).trim(),
673
+ comment: content.slice(idx + 2).trim() || undefined,
674
+ };
675
+ }
676
+
645
677
  function formatReassignCommentSuffix(comment?: string | null): string {
646
678
  const text = comment?.trim();
647
679
  return text ? `:${text}` : '';
@@ -986,7 +1018,53 @@ export type ItHelpdeskMuscatWorkflowParams =
986
1018
  roleArabicName?: string | null;
987
1019
  comment?: string | null;
988
1020
  }
989
- | { variant: 'routing_resolved' };
1021
+ | { variant: 'routing_resolved' }
1022
+ | {
1023
+ variant: 'routing_l3_reassign';
1024
+ userName: string;
1025
+ userArabicName?: string | null;
1026
+ comment?: string | null;
1027
+ }
1028
+ | {
1029
+ variant: 'routing_direct_approval';
1030
+ roleName: string;
1031
+ roleArabicName?: string | null;
1032
+ comment?: string | null;
1033
+ }
1034
+ | {
1035
+ variant: 'routing_decision';
1036
+ status: string;
1037
+ roleName: string;
1038
+ roleArabicName?: string | null;
1039
+ comment?: string | null;
1040
+ }
1041
+ | {
1042
+ variant: 'l3_assignment_pending';
1043
+ roleName: string;
1044
+ roleArabicName?: string | null;
1045
+ comment?: string | null;
1046
+ }
1047
+ | {
1048
+ variant: 'l3_role_approved' | 'l3_role_rejected';
1049
+ roleName: string;
1050
+ roleArabicName?: string | null;
1051
+ comment?: string | null;
1052
+ }
1053
+ | {
1054
+ variant: 'role_outcome_by_user_rejected' | 'role_outcome_by_user_closed';
1055
+ roleName: string;
1056
+ roleArabicName?: string | null;
1057
+ userName: string;
1058
+ userArabicName?: string | null;
1059
+ comment?: string | null;
1060
+ }
1061
+ | { variant: 'routing_technician_rejected'; comment?: string | null }
1062
+ | {
1063
+ variant: 'hos_not_required_rejected' | 'hos_not_required_closed' | 'l3_not_required_rejected' | 'l3_not_required_closed';
1064
+ userName: string;
1065
+ userArabicName?: string | null;
1066
+ comment?: string | null;
1067
+ };
990
1068
 
991
1069
  function resolveItHelpdeskGenericStatusAr(status: string): string {
992
1070
  switch (status.trim()) {
@@ -1151,6 +1229,102 @@ export function buildItHelpdeskMuscatWorkflowLog(params: ItHelpdeskMuscatWorkflo
1151
1229
  );
1152
1230
  case 'routing_resolved':
1153
1231
  return workflowTemplate('it_helpdesk_wf_routing_resolved');
1232
+ case 'routing_l3_reassign': {
1233
+ const userEn = params.userName.trim();
1234
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
1235
+ return renderSynchronizedWorkflowMessage(
1236
+ 'it_helpdesk_wf_routing_l3_reassign',
1237
+ { userName: userEn, comment },
1238
+ { userName: userAr, comment },
1239
+ );
1240
+ }
1241
+ case 'routing_direct_approval': {
1242
+ const roleEn = params.roleName.trim();
1243
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1244
+ return renderSynchronizedWorkflowMessage(
1245
+ 'it_helpdesk_wf_routing_direct_approval',
1246
+ { roleName: roleEn, comment },
1247
+ { roleName: roleAr, comment },
1248
+ );
1249
+ }
1250
+ case 'routing_decision': {
1251
+ const roleEn = params.roleName.trim();
1252
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1253
+ const statusEn = params.status.toLowerCase();
1254
+ const statusAr = resolveItHelpdeskGenericStatusAr(params.status);
1255
+ return renderSynchronizedWorkflowMessage(
1256
+ 'it_helpdesk_wf_routing_decision',
1257
+ { status: statusEn, statusAr, roleName: roleEn, comment },
1258
+ { status: statusEn, statusAr, roleName: roleAr, comment },
1259
+ );
1260
+ }
1261
+ case 'l3_assignment_pending': {
1262
+ const roleEn = params.roleName.trim();
1263
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1264
+ return renderSynchronizedWorkflowMessage(
1265
+ 'it_helpdesk_wf_l3_assignment_pending',
1266
+ { roleName: roleEn, comment },
1267
+ { roleName: roleAr, comment },
1268
+ );
1269
+ }
1270
+ case 'l3_role_approved':
1271
+ case 'l3_role_rejected': {
1272
+ const roleEn = params.roleName.trim();
1273
+ const roleAr = resolveBilingualName(
1274
+ roleEn,
1275
+ params.roleArabicName ?? ROLE_LABELS[roleEn] ?? null,
1276
+ );
1277
+ const biComment = formatBilingualColonCommentSuffix(params.comment);
1278
+ const key =
1279
+ params.variant === 'l3_role_approved'
1280
+ ? 'it_helpdesk_wf_l3_role_approved'
1281
+ : 'it_helpdesk_wf_l3_role_rejected';
1282
+ return renderSynchronizedWorkflowMessage(
1283
+ key,
1284
+ { roleName: roleEn, comment: biComment.en },
1285
+ { roleName: roleAr, commentAr: biComment.ar },
1286
+ );
1287
+ }
1288
+ case 'role_outcome_by_user_rejected':
1289
+ case 'role_outcome_by_user_closed': {
1290
+ const roleEn = params.roleName.trim();
1291
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1292
+ const userEn = params.userName.trim();
1293
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
1294
+ const key =
1295
+ params.variant === 'role_outcome_by_user_rejected'
1296
+ ? 'it_helpdesk_wf_role_outcome_by_user_rejected'
1297
+ : 'it_helpdesk_wf_role_outcome_by_user_closed';
1298
+ return renderSynchronizedWorkflowMessage(
1299
+ key,
1300
+ { roleName: roleEn, userName: userEn, comment },
1301
+ { roleName: roleAr, userName: userAr, comment },
1302
+ );
1303
+ }
1304
+ case 'routing_technician_rejected':
1305
+ return renderSynchronizedWorkflowMessage(
1306
+ 'it_helpdesk_wf_routing_technician_rejected',
1307
+ { comment },
1308
+ { comment },
1309
+ );
1310
+ case 'hos_not_required_rejected':
1311
+ case 'hos_not_required_closed':
1312
+ case 'l3_not_required_rejected':
1313
+ case 'l3_not_required_closed': {
1314
+ const userEn = params.userName.trim();
1315
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
1316
+ const keyMap = {
1317
+ hos_not_required_rejected: 'it_helpdesk_wf_hos_not_required_rejected',
1318
+ hos_not_required_closed: 'it_helpdesk_wf_hos_not_required_closed',
1319
+ l3_not_required_rejected: 'it_helpdesk_wf_l3_not_required_rejected',
1320
+ l3_not_required_closed: 'it_helpdesk_wf_l3_not_required_closed',
1321
+ } as const;
1322
+ return renderSynchronizedWorkflowMessage(
1323
+ keyMap[params.variant],
1324
+ { userName: userEn, comment },
1325
+ { userName: userAr, comment },
1326
+ );
1327
+ }
1154
1328
  default:
1155
1329
  return workflowTemplate('request_submitted');
1156
1330
  }
@@ -1287,19 +1461,24 @@ function resolveArabicChatRoleName(
1287
1461
  parsedEnglishRole?: string,
1288
1462
  ): string {
1289
1463
  const parsed = parsedEnglishRole?.trim();
1290
- if (actors?.roleName?.trim()) {
1291
- const actorRole = actors.roleName.trim();
1292
- if (!parsed || parsed.toLowerCase() === actorRole.toLowerCase()) {
1293
- return resolveBilingualName(actorRole, actors.roleArabicName);
1464
+ if (parsed) {
1465
+ if (
1466
+ actors?.roleName?.trim() &&
1467
+ parsed.toLowerCase() === actors.roleName.trim().toLowerCase()
1468
+ ) {
1469
+ return resolveBilingualName(parsed, actors.roleArabicName);
1294
1470
  }
1471
+ const catalogAr = ROLE_LABELS[parsed];
1472
+ if (catalogAr) return catalogAr;
1473
+ return parsed;
1295
1474
  }
1296
- if (actors?.roleArabicName?.trim() && parsed) {
1297
- return resolveBilingualName(parsed, actors.roleArabicName);
1475
+ if (actors?.roleName?.trim()) {
1476
+ return resolveBilingualName(actors.roleName, actors.roleArabicName);
1298
1477
  }
1299
1478
  if (actors?.userName?.trim()) {
1300
1479
  return resolveBilingualName(actors.userName, actors.userArabicName);
1301
1480
  }
1302
- return resolveBilingualName(parsed, actors?.roleArabicName);
1481
+ return '';
1303
1482
  }
1304
1483
 
1305
1484
  function escapeRegexLiteral(value: string): string {
@@ -1399,6 +1578,14 @@ function buildArabicWorkflowTemplateParams(
1399
1578
  params.status.charAt(0).toUpperCase() + params.status.slice(1).toLowerCase();
1400
1579
  arParams.statusAr = resolveItHelpdeskGenericStatusAr(normalizedStatus);
1401
1580
  }
1581
+ if (params.userName !== undefined) {
1582
+ arParams.userName = actors?.userName?.trim()
1583
+ ? resolveBilingualName(actors.userName, actors.userArabicName)
1584
+ : resolveBilingualName(params.userName.trim(), actors?.userArabicName);
1585
+ }
1586
+ if (params.commentAr !== undefined) {
1587
+ arParams.commentAr = params.commentAr;
1588
+ }
1402
1589
  return arParams;
1403
1590
  }
1404
1591
 
@@ -1412,6 +1599,146 @@ function listWorkflowTemplatesBySpecificity(): Array<[string, BilingualText]> {
1412
1599
  );
1413
1600
  }
1414
1601
 
1602
+ /** Parse legacy IT Helpdesk Muscat EN workflow strings before generic template matching. */
1603
+ function tryResolveItHelpdeskMuscatLegacyWorkflowAr(
1604
+ content: string,
1605
+ actors?: BilingualActorNames,
1606
+ ): string | null {
1607
+ const { base, comment } = splitWorkflowColonComment(content);
1608
+
1609
+ let m = base.match(/^Workflow routed to L3 assignment path - request reassigned by (.+)$/i);
1610
+ if (m) {
1611
+ return buildItHelpdeskMuscatWorkflowLog({
1612
+ variant: 'routing_l3_reassign',
1613
+ userName: m[1].trim(),
1614
+ userArabicName: actors?.userArabicName,
1615
+ comment,
1616
+ }).ar;
1617
+ }
1618
+
1619
+ m = base.match(/^Workflow routed to direct resolution path - request approved by (.+)$/i);
1620
+ if (m) {
1621
+ return buildItHelpdeskMuscatWorkflowLog({
1622
+ variant: 'routing_direct_approval',
1623
+ roleName: m[1].trim(),
1624
+ roleArabicName: actors?.roleArabicName,
1625
+ comment,
1626
+ }).ar;
1627
+ }
1628
+
1629
+ m = base.match(/^Workflow decision made - (\w+) by (.+)$/i);
1630
+ if (m) {
1631
+ return buildItHelpdeskMuscatWorkflowLog({
1632
+ variant: 'routing_decision',
1633
+ status: m[1],
1634
+ roleName: m[2].trim(),
1635
+ roleArabicName: actors?.roleArabicName,
1636
+ comment,
1637
+ }).ar;
1638
+ }
1639
+
1640
+ m = base.match(/^L3 Technical Support assignment pending - request reassigned by (.+)$/i);
1641
+ if (m) {
1642
+ return buildItHelpdeskMuscatWorkflowLog({
1643
+ variant: 'l3_assignment_pending',
1644
+ roleName: m[1].trim(),
1645
+ roleArabicName: actors?.roleArabicName,
1646
+ comment,
1647
+ }).ar;
1648
+ }
1649
+
1650
+ m = base.match(/^L3 Technical Support approved the request$/i);
1651
+ if (m) {
1652
+ return buildItHelpdeskMuscatWorkflowLog({
1653
+ variant: 'l3_role_approved',
1654
+ roleName: 'L3 Technical Support',
1655
+ comment,
1656
+ }).ar;
1657
+ }
1658
+
1659
+ m = base.match(/^L3 Technical Support rejected the request$/i);
1660
+ if (m) {
1661
+ return buildItHelpdeskMuscatWorkflowLog({
1662
+ variant: 'l3_role_rejected',
1663
+ roleName: 'L3 Technical Support',
1664
+ comment,
1665
+ }).ar;
1666
+ }
1667
+
1668
+ m = base.match(/^(.+?) rejected the request by (.+)$/i);
1669
+ if (m && !m[1].toLowerCase().includes('workflow')) {
1670
+ return buildItHelpdeskMuscatWorkflowLog({
1671
+ variant: 'role_outcome_by_user_rejected',
1672
+ roleName: m[1].trim(),
1673
+ roleArabicName: actors?.roleArabicName,
1674
+ userName: m[2].trim(),
1675
+ userArabicName: actors?.userArabicName,
1676
+ comment,
1677
+ }).ar;
1678
+ }
1679
+
1680
+ m = base.match(/^(.+?) closed the request by (.+)$/i);
1681
+ if (m) {
1682
+ return buildItHelpdeskMuscatWorkflowLog({
1683
+ variant: 'role_outcome_by_user_closed',
1684
+ roleName: m[1].trim(),
1685
+ roleArabicName: actors?.roleArabicName,
1686
+ userName: m[2].trim(),
1687
+ userArabicName: actors?.userArabicName,
1688
+ comment,
1689
+ }).ar;
1690
+ }
1691
+
1692
+ if (/^Workflow routing completed - request rejected at technician level$/i.test(base)) {
1693
+ return buildItHelpdeskMuscatWorkflowLog({
1694
+ variant: 'routing_technician_rejected',
1695
+ comment,
1696
+ }).ar;
1697
+ }
1698
+
1699
+ m = base.match(/^HOS notification not required, request rejected by (.+)$/i);
1700
+ if (m) {
1701
+ return buildItHelpdeskMuscatWorkflowLog({
1702
+ variant: 'hos_not_required_rejected',
1703
+ userName: m[1].trim(),
1704
+ userArabicName: actors?.userArabicName,
1705
+ comment,
1706
+ }).ar;
1707
+ }
1708
+
1709
+ m = base.match(/^HOS notification not required, request closed by (.+)$/i);
1710
+ if (m) {
1711
+ return buildItHelpdeskMuscatWorkflowLog({
1712
+ variant: 'hos_not_required_closed',
1713
+ userName: m[1].trim(),
1714
+ userArabicName: actors?.userArabicName,
1715
+ comment,
1716
+ }).ar;
1717
+ }
1718
+
1719
+ m = base.match(/^L3 assignment not required, request rejected by (.+)$/i);
1720
+ if (m) {
1721
+ return buildItHelpdeskMuscatWorkflowLog({
1722
+ variant: 'l3_not_required_rejected',
1723
+ userName: m[1].trim(),
1724
+ userArabicName: actors?.userArabicName,
1725
+ comment,
1726
+ }).ar;
1727
+ }
1728
+
1729
+ m = base.match(/^L3 assignment not required, request closed by (.+)$/i);
1730
+ if (m) {
1731
+ return buildItHelpdeskMuscatWorkflowLog({
1732
+ variant: 'l3_not_required_closed',
1733
+ userName: m[1].trim(),
1734
+ userArabicName: actors?.userArabicName,
1735
+ comment,
1736
+ }).ar;
1737
+ }
1738
+
1739
+ return null;
1740
+ }
1741
+
1415
1742
  export function resolveWorkflowContentAr(
1416
1743
  content: string | null | undefined,
1417
1744
  actors?: BilingualActorNames,
@@ -1424,6 +1751,9 @@ export function resolveWorkflowContentAr(
1424
1751
  return buildAssignmentWorkflowFieldsFromActors(embeddedAssignment, actors).ar;
1425
1752
  }
1426
1753
 
1754
+ const itHelpdeskLegacyAr = tryResolveItHelpdeskMuscatLegacyWorkflowAr(normalized, actors);
1755
+ if (itHelpdeskLegacyAr) return itHelpdeskLegacyAr;
1756
+
1427
1757
  for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
1428
1758
  if (tpl.en === normalized || tpl.en.toLowerCase() === normalized.toLowerCase()) return tpl.ar;
1429
1759
  }
@@ -1492,6 +1822,27 @@ export function resolveWorkflowContentAr(
1492
1822
  const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
1493
1823
  return interpolate(WORKFLOW_TEMPLATES.request_approval_pending_from.ar, { name: nameAr });
1494
1824
  }
1825
+
1826
+ // Assign Tasks to Employee workflow (legacy EN strings)
1827
+ if (normalized === 'Request Assigned') {
1828
+ return WORKFLOW_TEMPLATES.request_assigned?.ar ?? null;
1829
+ }
1830
+ if (normalized === 'Request Pending') {
1831
+ return WORKFLOW_TEMPLATES.request_pending?.ar ?? null;
1832
+ }
1833
+ const assignTasksPendingPrefix = WORKFLOW_TEMPLATES.request_pending_from?.en.split('{{name}}')[0].trim();
1834
+ if (assignTasksPendingPrefix && normalized.startsWith(assignTasksPendingPrefix)) {
1835
+ const namePartEn = normalized.slice(assignTasksPendingPrefix.length).trim();
1836
+ const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
1837
+ return interpolate(WORKFLOW_TEMPLATES.request_pending_from.ar, { name: nameAr });
1838
+ }
1839
+ if (normalized.toLowerCase() === 'notification sent pending') {
1840
+ return WORKFLOW_TEMPLATES.notification_sent_pending?.ar ?? null;
1841
+ }
1842
+ if (normalized.toLowerCase() === 'notification sent successfully') {
1843
+ return WORKFLOW_TEMPLATES.notification_sent_successfully?.ar ?? null;
1844
+ }
1845
+
1495
1846
  if (
1496
1847
  normalized === 'Approval Pending from HR Section' ||
1497
1848
  normalized === 'Approval Pending from Records Department'
@@ -1557,6 +1908,32 @@ export function enrichWorkflowLog(log: Record<string, unknown>): Record<string,
1557
1908
  content_ar = resolvedAr;
1558
1909
  }
1559
1910
  }
1911
+ const inProgressPrefix = WORKFLOW_TEMPLATES.request_approval_in_progress_from?.en.split('{{name}}')[0].trim();
1912
+ if (
1913
+ content &&
1914
+ inProgressPrefix &&
1915
+ content.startsWith(inProgressPrefix) &&
1916
+ existingAr &&
1917
+ resolvedAr &&
1918
+ existingAr !== resolvedAr
1919
+ ) {
1920
+ const namePartEn = content.slice(inProgressPrefix.length).trim();
1921
+ if (namePartEn && existingAr.includes(namePartEn)) {
1922
+ content_ar = resolvedAr;
1923
+ }
1924
+ }
1925
+ if (
1926
+ content &&
1927
+ existingAr &&
1928
+ resolvedAr &&
1929
+ existingAr !== resolvedAr &&
1930
+ /[A-Za-z]/.test(existingAr) &&
1931
+ (actors?.deptArabicName?.trim() ||
1932
+ actors?.sectionArabicName?.trim() ||
1933
+ actors?.roleArabicName?.trim())
1934
+ ) {
1935
+ content_ar = resolvedAr;
1936
+ }
1560
1937
  if (
1561
1938
  content &&
1562
1939
  existingAr &&
@@ -1577,6 +1954,18 @@ export function enrichWorkflowLog(log: Record<string, unknown>): Record<string,
1577
1954
  ) {
1578
1955
  content_ar = resolvedAr;
1579
1956
  }
1957
+ const itHelpdeskMixedEnglish =
1958
+ /routed to L3|assignment path|Workflow decision made|L3 Technical Support approved|L3 assignment not required|HOS notification not required|routing completed/i;
1959
+ if (
1960
+ content &&
1961
+ existingAr &&
1962
+ resolvedAr &&
1963
+ existingAr !== resolvedAr &&
1964
+ (itHelpdeskMixedEnglish.test(content) ||
1965
+ /routed to|routing completed|assignment path|\sby\s/i.test(existingAr))
1966
+ ) {
1967
+ content_ar = resolvedAr;
1968
+ }
1580
1969
  if ((content_ar == null || content_ar === '') && content) {
1581
1970
  const notificationAr = resolveWorkflowContentAr(content, actors);
1582
1971
  if (notificationAr) content_ar = notificationAr;