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

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,58 @@
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}}"
134
186
  }
135
187
  },
136
188
  "chatTemplates": {
@@ -262,6 +314,10 @@
262
314
  "Pending": "قيد الانتظار",
263
315
  "Not Yet Started": "لم يبدأ بعد"
264
316
  },
317
+ "roleLabels": {
318
+ "L3 Technical Support": "دعم المستوى الثالث (L3) التقني",
319
+ "IT Technician": "فني تكنولوجيا المعلومات"
320
+ },
265
321
  "chatStatusLabels": {
266
322
  "Submitted": "تم التقديم",
267
323
  "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
  }
@@ -457,6 +458,23 @@ function formatColonCommentSuffix(comment) {
457
458
  const text = comment?.trim();
458
459
  return text ? `: ${text}` : '';
459
460
  }
461
+ function formatBilingualColonCommentSuffix(comment) {
462
+ const text = comment?.trim();
463
+ if (!text)
464
+ return { en: '', ar: '' };
465
+ const statusKey = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
466
+ const arText = resolveChatStatusAr(statusKey) ?? text;
467
+ return { en: `: ${text}`, ar: `: ${arText}` };
468
+ }
469
+ function splitWorkflowColonComment(content) {
470
+ const idx = content.indexOf(': ');
471
+ if (idx <= 0)
472
+ return { base: content.trim() };
473
+ return {
474
+ base: content.slice(0, idx).trim(),
475
+ comment: content.slice(idx + 2).trim() || undefined,
476
+ };
477
+ }
460
478
  function formatReassignCommentSuffix(comment) {
461
479
  const text = comment?.trim();
462
480
  return text ? `:${text}` : '';
@@ -724,6 +742,65 @@ function buildItHelpdeskMuscatWorkflowLog(params) {
724
742
  return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_rejected', { comment }, { comment });
725
743
  case 'routing_resolved':
726
744
  return workflowTemplate('it_helpdesk_wf_routing_resolved');
745
+ case 'routing_l3_reassign': {
746
+ const userEn = params.userName.trim();
747
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
748
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_l3_reassign', { userName: userEn, comment }, { userName: userAr, comment });
749
+ }
750
+ case 'routing_direct_approval': {
751
+ const roleEn = params.roleName.trim();
752
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
753
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_direct_approval', { roleName: roleEn, comment }, { roleName: roleAr, comment });
754
+ }
755
+ case 'routing_decision': {
756
+ const roleEn = params.roleName.trim();
757
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
758
+ const statusEn = params.status.toLowerCase();
759
+ const statusAr = resolveItHelpdeskGenericStatusAr(params.status);
760
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_decision', { status: statusEn, statusAr, roleName: roleEn, comment }, { status: statusEn, statusAr, roleName: roleAr, comment });
761
+ }
762
+ case 'l3_assignment_pending': {
763
+ const roleEn = params.roleName.trim();
764
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
765
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_l3_assignment_pending', { roleName: roleEn, comment }, { roleName: roleAr, comment });
766
+ }
767
+ case 'l3_role_approved':
768
+ case 'l3_role_rejected': {
769
+ const roleEn = params.roleName.trim();
770
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName ?? ROLE_LABELS[roleEn] ?? null);
771
+ const biComment = formatBilingualColonCommentSuffix(params.comment);
772
+ const key = params.variant === 'l3_role_approved'
773
+ ? 'it_helpdesk_wf_l3_role_approved'
774
+ : 'it_helpdesk_wf_l3_role_rejected';
775
+ return renderSynchronizedWorkflowMessage(key, { roleName: roleEn, comment: biComment.en }, { roleName: roleAr, commentAr: biComment.ar });
776
+ }
777
+ case 'role_outcome_by_user_rejected':
778
+ case 'role_outcome_by_user_closed': {
779
+ const roleEn = params.roleName.trim();
780
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
781
+ const userEn = params.userName.trim();
782
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
783
+ const key = params.variant === 'role_outcome_by_user_rejected'
784
+ ? 'it_helpdesk_wf_role_outcome_by_user_rejected'
785
+ : 'it_helpdesk_wf_role_outcome_by_user_closed';
786
+ return renderSynchronizedWorkflowMessage(key, { roleName: roleEn, userName: userEn, comment }, { roleName: roleAr, userName: userAr, comment });
787
+ }
788
+ case 'routing_technician_rejected':
789
+ return renderSynchronizedWorkflowMessage('it_helpdesk_wf_routing_technician_rejected', { comment }, { comment });
790
+ case 'hos_not_required_rejected':
791
+ case 'hos_not_required_closed':
792
+ case 'l3_not_required_rejected':
793
+ case 'l3_not_required_closed': {
794
+ const userEn = params.userName.trim();
795
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
796
+ const keyMap = {
797
+ hos_not_required_rejected: 'it_helpdesk_wf_hos_not_required_rejected',
798
+ hos_not_required_closed: 'it_helpdesk_wf_hos_not_required_closed',
799
+ l3_not_required_rejected: 'it_helpdesk_wf_l3_not_required_rejected',
800
+ l3_not_required_closed: 'it_helpdesk_wf_l3_not_required_closed',
801
+ };
802
+ return renderSynchronizedWorkflowMessage(keyMap[params.variant], { userName: userEn, comment }, { userName: userAr, comment });
803
+ }
727
804
  default:
728
805
  return workflowTemplate('request_submitted');
729
806
  }
@@ -815,19 +892,23 @@ function resolveArabicWorkflowActorName(actors, parsedEnglishName) {
815
892
  /** Arabic role/user label for chat templates. */
816
893
  function resolveArabicChatRoleName(actors, parsedEnglishRole) {
817
894
  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);
895
+ if (parsed) {
896
+ if (actors?.roleName?.trim() &&
897
+ parsed.toLowerCase() === actors.roleName.trim().toLowerCase()) {
898
+ return resolveBilingualName(parsed, actors.roleArabicName);
822
899
  }
900
+ const catalogAr = ROLE_LABELS[parsed];
901
+ if (catalogAr)
902
+ return catalogAr;
903
+ return parsed;
823
904
  }
824
- if (actors?.roleArabicName?.trim() && parsed) {
825
- return resolveBilingualName(parsed, actors.roleArabicName);
905
+ if (actors?.roleName?.trim()) {
906
+ return resolveBilingualName(actors.roleName, actors.roleArabicName);
826
907
  }
827
908
  if (actors?.userName?.trim()) {
828
909
  return resolveBilingualName(actors.userName, actors.userArabicName);
829
910
  }
830
- return resolveBilingualName(parsed, actors?.roleArabicName);
911
+ return '';
831
912
  }
832
913
  function escapeRegexLiteral(value) {
833
914
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -908,6 +989,14 @@ function buildArabicWorkflowTemplateParams(params, actors) {
908
989
  const normalizedStatus = params.status.charAt(0).toUpperCase() + params.status.slice(1).toLowerCase();
909
990
  arParams.statusAr = resolveItHelpdeskGenericStatusAr(normalizedStatus);
910
991
  }
992
+ if (params.userName !== undefined) {
993
+ arParams.userName = actors?.userName?.trim()
994
+ ? resolveBilingualName(actors.userName, actors.userArabicName)
995
+ : resolveBilingualName(params.userName.trim(), actors?.userArabicName);
996
+ }
997
+ if (params.commentAr !== undefined) {
998
+ arParams.commentAr = params.commentAr;
999
+ }
911
1000
  return arParams;
912
1001
  }
913
1002
  function countTemplatePlaceholders(templateEn) {
@@ -916,6 +1005,128 @@ function countTemplatePlaceholders(templateEn) {
916
1005
  function listWorkflowTemplatesBySpecificity() {
917
1006
  return Object.entries(WORKFLOW_TEMPLATES).sort(([, a], [, b]) => countTemplatePlaceholders(b.en) - countTemplatePlaceholders(a.en));
918
1007
  }
1008
+ /** Parse legacy IT Helpdesk Muscat EN workflow strings before generic template matching. */
1009
+ function tryResolveItHelpdeskMuscatLegacyWorkflowAr(content, actors) {
1010
+ const { base, comment } = splitWorkflowColonComment(content);
1011
+ let m = base.match(/^Workflow routed to L3 assignment path - request reassigned by (.+)$/i);
1012
+ if (m) {
1013
+ return buildItHelpdeskMuscatWorkflowLog({
1014
+ variant: 'routing_l3_reassign',
1015
+ userName: m[1].trim(),
1016
+ userArabicName: actors?.userArabicName,
1017
+ comment,
1018
+ }).ar;
1019
+ }
1020
+ m = base.match(/^Workflow routed to direct resolution path - request approved by (.+)$/i);
1021
+ if (m) {
1022
+ return buildItHelpdeskMuscatWorkflowLog({
1023
+ variant: 'routing_direct_approval',
1024
+ roleName: m[1].trim(),
1025
+ roleArabicName: actors?.roleArabicName,
1026
+ comment,
1027
+ }).ar;
1028
+ }
1029
+ m = base.match(/^Workflow decision made - (\w+) by (.+)$/i);
1030
+ if (m) {
1031
+ return buildItHelpdeskMuscatWorkflowLog({
1032
+ variant: 'routing_decision',
1033
+ status: m[1],
1034
+ roleName: m[2].trim(),
1035
+ roleArabicName: actors?.roleArabicName,
1036
+ comment,
1037
+ }).ar;
1038
+ }
1039
+ m = base.match(/^L3 Technical Support assignment pending - request reassigned by (.+)$/i);
1040
+ if (m) {
1041
+ return buildItHelpdeskMuscatWorkflowLog({
1042
+ variant: 'l3_assignment_pending',
1043
+ roleName: m[1].trim(),
1044
+ roleArabicName: actors?.roleArabicName,
1045
+ comment,
1046
+ }).ar;
1047
+ }
1048
+ m = base.match(/^L3 Technical Support approved the request$/i);
1049
+ if (m) {
1050
+ return buildItHelpdeskMuscatWorkflowLog({
1051
+ variant: 'l3_role_approved',
1052
+ roleName: 'L3 Technical Support',
1053
+ comment,
1054
+ }).ar;
1055
+ }
1056
+ m = base.match(/^L3 Technical Support rejected the request$/i);
1057
+ if (m) {
1058
+ return buildItHelpdeskMuscatWorkflowLog({
1059
+ variant: 'l3_role_rejected',
1060
+ roleName: 'L3 Technical Support',
1061
+ comment,
1062
+ }).ar;
1063
+ }
1064
+ m = base.match(/^(.+?) rejected the request by (.+)$/i);
1065
+ if (m && !m[1].toLowerCase().includes('workflow')) {
1066
+ return buildItHelpdeskMuscatWorkflowLog({
1067
+ variant: 'role_outcome_by_user_rejected',
1068
+ roleName: m[1].trim(),
1069
+ roleArabicName: actors?.roleArabicName,
1070
+ userName: m[2].trim(),
1071
+ userArabicName: actors?.userArabicName,
1072
+ comment,
1073
+ }).ar;
1074
+ }
1075
+ m = base.match(/^(.+?) closed the request by (.+)$/i);
1076
+ if (m) {
1077
+ return buildItHelpdeskMuscatWorkflowLog({
1078
+ variant: 'role_outcome_by_user_closed',
1079
+ roleName: m[1].trim(),
1080
+ roleArabicName: actors?.roleArabicName,
1081
+ userName: m[2].trim(),
1082
+ userArabicName: actors?.userArabicName,
1083
+ comment,
1084
+ }).ar;
1085
+ }
1086
+ if (/^Workflow routing completed - request rejected at technician level$/i.test(base)) {
1087
+ return buildItHelpdeskMuscatWorkflowLog({
1088
+ variant: 'routing_technician_rejected',
1089
+ comment,
1090
+ }).ar;
1091
+ }
1092
+ m = base.match(/^HOS notification not required, request rejected by (.+)$/i);
1093
+ if (m) {
1094
+ return buildItHelpdeskMuscatWorkflowLog({
1095
+ variant: 'hos_not_required_rejected',
1096
+ userName: m[1].trim(),
1097
+ userArabicName: actors?.userArabicName,
1098
+ comment,
1099
+ }).ar;
1100
+ }
1101
+ m = base.match(/^HOS notification not required, request closed by (.+)$/i);
1102
+ if (m) {
1103
+ return buildItHelpdeskMuscatWorkflowLog({
1104
+ variant: 'hos_not_required_closed',
1105
+ userName: m[1].trim(),
1106
+ userArabicName: actors?.userArabicName,
1107
+ comment,
1108
+ }).ar;
1109
+ }
1110
+ m = base.match(/^L3 assignment not required, request rejected by (.+)$/i);
1111
+ if (m) {
1112
+ return buildItHelpdeskMuscatWorkflowLog({
1113
+ variant: 'l3_not_required_rejected',
1114
+ userName: m[1].trim(),
1115
+ userArabicName: actors?.userArabicName,
1116
+ comment,
1117
+ }).ar;
1118
+ }
1119
+ m = base.match(/^L3 assignment not required, request closed by (.+)$/i);
1120
+ if (m) {
1121
+ return buildItHelpdeskMuscatWorkflowLog({
1122
+ variant: 'l3_not_required_closed',
1123
+ userName: m[1].trim(),
1124
+ userArabicName: actors?.userArabicName,
1125
+ comment,
1126
+ }).ar;
1127
+ }
1128
+ return null;
1129
+ }
919
1130
  function resolveWorkflowContentAr(content, actors) {
920
1131
  if (content == null || content.trim() === '')
921
1132
  return null;
@@ -924,6 +1135,9 @@ function resolveWorkflowContentAr(content, actors) {
924
1135
  if (embeddedAssignment) {
925
1136
  return buildAssignmentWorkflowFieldsFromActors(embeddedAssignment, actors).ar;
926
1137
  }
1138
+ const itHelpdeskLegacyAr = tryResolveItHelpdeskMuscatLegacyWorkflowAr(normalized, actors);
1139
+ if (itHelpdeskLegacyAr)
1140
+ return itHelpdeskLegacyAr;
927
1141
  for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
928
1142
  if (tpl.en === normalized || tpl.en.toLowerCase() === normalized.toLowerCase())
929
1143
  return tpl.ar;
@@ -1059,6 +1273,15 @@ function enrichWorkflowLog(log) {
1059
1273
  existingAr.includes(actors.roleName.trim())) {
1060
1274
  content_ar = resolvedAr;
1061
1275
  }
1276
+ 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;
1277
+ if (content &&
1278
+ existingAr &&
1279
+ resolvedAr &&
1280
+ existingAr !== resolvedAr &&
1281
+ (itHelpdeskMixedEnglish.test(content) ||
1282
+ /routed to|routing completed|assignment path|\sby\s/i.test(existingAr))) {
1283
+ content_ar = resolvedAr;
1284
+ }
1062
1285
  if ((content_ar == null || content_ar === '') && content) {
1063
1286
  const notificationAr = resolveWorkflowContentAr(content, actors);
1064
1287
  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.297",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -131,6 +131,58 @@
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}}"
134
186
  }
135
187
  },
136
188
  "chatTemplates": {
@@ -262,6 +314,10 @@
262
314
  "Pending": "قيد الانتظار",
263
315
  "Not Yet Started": "لم يبدأ بعد"
264
316
  },
317
+ "roleLabels": {
318
+ "L3 Technical Support": "دعم المستوى الثالث (L3) التقني",
319
+ "IT Technician": "فني تكنولوجيا المعلومات"
320
+ },
265
321
  "chatStatusLabels": {
266
322
  "Submitted": "تم التقديم",
267
323
  "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;
@@ -642,6 +643,23 @@ function formatColonCommentSuffix(comment?: string | null): string {
642
643
  return text ? `: ${text}` : '';
643
644
  }
644
645
 
646
+ function formatBilingualColonCommentSuffix(comment?: string | null): { en: string; ar: string } {
647
+ const text = comment?.trim();
648
+ if (!text) return { en: '', ar: '' };
649
+ const statusKey = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
650
+ const arText = resolveChatStatusAr(statusKey) ?? text;
651
+ return { en: `: ${text}`, ar: `: ${arText}` };
652
+ }
653
+
654
+ function splitWorkflowColonComment(content: string): { base: string; comment?: string } {
655
+ const idx = content.indexOf(': ');
656
+ if (idx <= 0) return { base: content.trim() };
657
+ return {
658
+ base: content.slice(0, idx).trim(),
659
+ comment: content.slice(idx + 2).trim() || undefined,
660
+ };
661
+ }
662
+
645
663
  function formatReassignCommentSuffix(comment?: string | null): string {
646
664
  const text = comment?.trim();
647
665
  return text ? `:${text}` : '';
@@ -986,7 +1004,53 @@ export type ItHelpdeskMuscatWorkflowParams =
986
1004
  roleArabicName?: string | null;
987
1005
  comment?: string | null;
988
1006
  }
989
- | { variant: 'routing_resolved' };
1007
+ | { variant: 'routing_resolved' }
1008
+ | {
1009
+ variant: 'routing_l3_reassign';
1010
+ userName: string;
1011
+ userArabicName?: string | null;
1012
+ comment?: string | null;
1013
+ }
1014
+ | {
1015
+ variant: 'routing_direct_approval';
1016
+ roleName: string;
1017
+ roleArabicName?: string | null;
1018
+ comment?: string | null;
1019
+ }
1020
+ | {
1021
+ variant: 'routing_decision';
1022
+ status: string;
1023
+ roleName: string;
1024
+ roleArabicName?: string | null;
1025
+ comment?: string | null;
1026
+ }
1027
+ | {
1028
+ variant: 'l3_assignment_pending';
1029
+ roleName: string;
1030
+ roleArabicName?: string | null;
1031
+ comment?: string | null;
1032
+ }
1033
+ | {
1034
+ variant: 'l3_role_approved' | 'l3_role_rejected';
1035
+ roleName: string;
1036
+ roleArabicName?: string | null;
1037
+ comment?: string | null;
1038
+ }
1039
+ | {
1040
+ variant: 'role_outcome_by_user_rejected' | 'role_outcome_by_user_closed';
1041
+ roleName: string;
1042
+ roleArabicName?: string | null;
1043
+ userName: string;
1044
+ userArabicName?: string | null;
1045
+ comment?: string | null;
1046
+ }
1047
+ | { variant: 'routing_technician_rejected'; comment?: string | null }
1048
+ | {
1049
+ variant: 'hos_not_required_rejected' | 'hos_not_required_closed' | 'l3_not_required_rejected' | 'l3_not_required_closed';
1050
+ userName: string;
1051
+ userArabicName?: string | null;
1052
+ comment?: string | null;
1053
+ };
990
1054
 
991
1055
  function resolveItHelpdeskGenericStatusAr(status: string): string {
992
1056
  switch (status.trim()) {
@@ -1151,6 +1215,102 @@ export function buildItHelpdeskMuscatWorkflowLog(params: ItHelpdeskMuscatWorkflo
1151
1215
  );
1152
1216
  case 'routing_resolved':
1153
1217
  return workflowTemplate('it_helpdesk_wf_routing_resolved');
1218
+ case 'routing_l3_reassign': {
1219
+ const userEn = params.userName.trim();
1220
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
1221
+ return renderSynchronizedWorkflowMessage(
1222
+ 'it_helpdesk_wf_routing_l3_reassign',
1223
+ { userName: userEn, comment },
1224
+ { userName: userAr, comment },
1225
+ );
1226
+ }
1227
+ case 'routing_direct_approval': {
1228
+ const roleEn = params.roleName.trim();
1229
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1230
+ return renderSynchronizedWorkflowMessage(
1231
+ 'it_helpdesk_wf_routing_direct_approval',
1232
+ { roleName: roleEn, comment },
1233
+ { roleName: roleAr, comment },
1234
+ );
1235
+ }
1236
+ case 'routing_decision': {
1237
+ const roleEn = params.roleName.trim();
1238
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1239
+ const statusEn = params.status.toLowerCase();
1240
+ const statusAr = resolveItHelpdeskGenericStatusAr(params.status);
1241
+ return renderSynchronizedWorkflowMessage(
1242
+ 'it_helpdesk_wf_routing_decision',
1243
+ { status: statusEn, statusAr, roleName: roleEn, comment },
1244
+ { status: statusEn, statusAr, roleName: roleAr, comment },
1245
+ );
1246
+ }
1247
+ case 'l3_assignment_pending': {
1248
+ const roleEn = params.roleName.trim();
1249
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1250
+ return renderSynchronizedWorkflowMessage(
1251
+ 'it_helpdesk_wf_l3_assignment_pending',
1252
+ { roleName: roleEn, comment },
1253
+ { roleName: roleAr, comment },
1254
+ );
1255
+ }
1256
+ case 'l3_role_approved':
1257
+ case 'l3_role_rejected': {
1258
+ const roleEn = params.roleName.trim();
1259
+ const roleAr = resolveBilingualName(
1260
+ roleEn,
1261
+ params.roleArabicName ?? ROLE_LABELS[roleEn] ?? null,
1262
+ );
1263
+ const biComment = formatBilingualColonCommentSuffix(params.comment);
1264
+ const key =
1265
+ params.variant === 'l3_role_approved'
1266
+ ? 'it_helpdesk_wf_l3_role_approved'
1267
+ : 'it_helpdesk_wf_l3_role_rejected';
1268
+ return renderSynchronizedWorkflowMessage(
1269
+ key,
1270
+ { roleName: roleEn, comment: biComment.en },
1271
+ { roleName: roleAr, commentAr: biComment.ar },
1272
+ );
1273
+ }
1274
+ case 'role_outcome_by_user_rejected':
1275
+ case 'role_outcome_by_user_closed': {
1276
+ const roleEn = params.roleName.trim();
1277
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1278
+ const userEn = params.userName.trim();
1279
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
1280
+ const key =
1281
+ params.variant === 'role_outcome_by_user_rejected'
1282
+ ? 'it_helpdesk_wf_role_outcome_by_user_rejected'
1283
+ : 'it_helpdesk_wf_role_outcome_by_user_closed';
1284
+ return renderSynchronizedWorkflowMessage(
1285
+ key,
1286
+ { roleName: roleEn, userName: userEn, comment },
1287
+ { roleName: roleAr, userName: userAr, comment },
1288
+ );
1289
+ }
1290
+ case 'routing_technician_rejected':
1291
+ return renderSynchronizedWorkflowMessage(
1292
+ 'it_helpdesk_wf_routing_technician_rejected',
1293
+ { comment },
1294
+ { comment },
1295
+ );
1296
+ case 'hos_not_required_rejected':
1297
+ case 'hos_not_required_closed':
1298
+ case 'l3_not_required_rejected':
1299
+ case 'l3_not_required_closed': {
1300
+ const userEn = params.userName.trim();
1301
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
1302
+ const keyMap = {
1303
+ hos_not_required_rejected: 'it_helpdesk_wf_hos_not_required_rejected',
1304
+ hos_not_required_closed: 'it_helpdesk_wf_hos_not_required_closed',
1305
+ l3_not_required_rejected: 'it_helpdesk_wf_l3_not_required_rejected',
1306
+ l3_not_required_closed: 'it_helpdesk_wf_l3_not_required_closed',
1307
+ } as const;
1308
+ return renderSynchronizedWorkflowMessage(
1309
+ keyMap[params.variant],
1310
+ { userName: userEn, comment },
1311
+ { userName: userAr, comment },
1312
+ );
1313
+ }
1154
1314
  default:
1155
1315
  return workflowTemplate('request_submitted');
1156
1316
  }
@@ -1287,19 +1447,24 @@ function resolveArabicChatRoleName(
1287
1447
  parsedEnglishRole?: string,
1288
1448
  ): string {
1289
1449
  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);
1450
+ if (parsed) {
1451
+ if (
1452
+ actors?.roleName?.trim() &&
1453
+ parsed.toLowerCase() === actors.roleName.trim().toLowerCase()
1454
+ ) {
1455
+ return resolveBilingualName(parsed, actors.roleArabicName);
1294
1456
  }
1457
+ const catalogAr = ROLE_LABELS[parsed];
1458
+ if (catalogAr) return catalogAr;
1459
+ return parsed;
1295
1460
  }
1296
- if (actors?.roleArabicName?.trim() && parsed) {
1297
- return resolveBilingualName(parsed, actors.roleArabicName);
1461
+ if (actors?.roleName?.trim()) {
1462
+ return resolveBilingualName(actors.roleName, actors.roleArabicName);
1298
1463
  }
1299
1464
  if (actors?.userName?.trim()) {
1300
1465
  return resolveBilingualName(actors.userName, actors.userArabicName);
1301
1466
  }
1302
- return resolveBilingualName(parsed, actors?.roleArabicName);
1467
+ return '';
1303
1468
  }
1304
1469
 
1305
1470
  function escapeRegexLiteral(value: string): string {
@@ -1399,6 +1564,14 @@ function buildArabicWorkflowTemplateParams(
1399
1564
  params.status.charAt(0).toUpperCase() + params.status.slice(1).toLowerCase();
1400
1565
  arParams.statusAr = resolveItHelpdeskGenericStatusAr(normalizedStatus);
1401
1566
  }
1567
+ if (params.userName !== undefined) {
1568
+ arParams.userName = actors?.userName?.trim()
1569
+ ? resolveBilingualName(actors.userName, actors.userArabicName)
1570
+ : resolveBilingualName(params.userName.trim(), actors?.userArabicName);
1571
+ }
1572
+ if (params.commentAr !== undefined) {
1573
+ arParams.commentAr = params.commentAr;
1574
+ }
1402
1575
  return arParams;
1403
1576
  }
1404
1577
 
@@ -1412,6 +1585,146 @@ function listWorkflowTemplatesBySpecificity(): Array<[string, BilingualText]> {
1412
1585
  );
1413
1586
  }
1414
1587
 
1588
+ /** Parse legacy IT Helpdesk Muscat EN workflow strings before generic template matching. */
1589
+ function tryResolveItHelpdeskMuscatLegacyWorkflowAr(
1590
+ content: string,
1591
+ actors?: BilingualActorNames,
1592
+ ): string | null {
1593
+ const { base, comment } = splitWorkflowColonComment(content);
1594
+
1595
+ let m = base.match(/^Workflow routed to L3 assignment path - request reassigned by (.+)$/i);
1596
+ if (m) {
1597
+ return buildItHelpdeskMuscatWorkflowLog({
1598
+ variant: 'routing_l3_reassign',
1599
+ userName: m[1].trim(),
1600
+ userArabicName: actors?.userArabicName,
1601
+ comment,
1602
+ }).ar;
1603
+ }
1604
+
1605
+ m = base.match(/^Workflow routed to direct resolution path - request approved by (.+)$/i);
1606
+ if (m) {
1607
+ return buildItHelpdeskMuscatWorkflowLog({
1608
+ variant: 'routing_direct_approval',
1609
+ roleName: m[1].trim(),
1610
+ roleArabicName: actors?.roleArabicName,
1611
+ comment,
1612
+ }).ar;
1613
+ }
1614
+
1615
+ m = base.match(/^Workflow decision made - (\w+) by (.+)$/i);
1616
+ if (m) {
1617
+ return buildItHelpdeskMuscatWorkflowLog({
1618
+ variant: 'routing_decision',
1619
+ status: m[1],
1620
+ roleName: m[2].trim(),
1621
+ roleArabicName: actors?.roleArabicName,
1622
+ comment,
1623
+ }).ar;
1624
+ }
1625
+
1626
+ m = base.match(/^L3 Technical Support assignment pending - request reassigned by (.+)$/i);
1627
+ if (m) {
1628
+ return buildItHelpdeskMuscatWorkflowLog({
1629
+ variant: 'l3_assignment_pending',
1630
+ roleName: m[1].trim(),
1631
+ roleArabicName: actors?.roleArabicName,
1632
+ comment,
1633
+ }).ar;
1634
+ }
1635
+
1636
+ m = base.match(/^L3 Technical Support approved the request$/i);
1637
+ if (m) {
1638
+ return buildItHelpdeskMuscatWorkflowLog({
1639
+ variant: 'l3_role_approved',
1640
+ roleName: 'L3 Technical Support',
1641
+ comment,
1642
+ }).ar;
1643
+ }
1644
+
1645
+ m = base.match(/^L3 Technical Support rejected the request$/i);
1646
+ if (m) {
1647
+ return buildItHelpdeskMuscatWorkflowLog({
1648
+ variant: 'l3_role_rejected',
1649
+ roleName: 'L3 Technical Support',
1650
+ comment,
1651
+ }).ar;
1652
+ }
1653
+
1654
+ m = base.match(/^(.+?) rejected the request by (.+)$/i);
1655
+ if (m && !m[1].toLowerCase().includes('workflow')) {
1656
+ return buildItHelpdeskMuscatWorkflowLog({
1657
+ variant: 'role_outcome_by_user_rejected',
1658
+ roleName: m[1].trim(),
1659
+ roleArabicName: actors?.roleArabicName,
1660
+ userName: m[2].trim(),
1661
+ userArabicName: actors?.userArabicName,
1662
+ comment,
1663
+ }).ar;
1664
+ }
1665
+
1666
+ m = base.match(/^(.+?) closed the request by (.+)$/i);
1667
+ if (m) {
1668
+ return buildItHelpdeskMuscatWorkflowLog({
1669
+ variant: 'role_outcome_by_user_closed',
1670
+ roleName: m[1].trim(),
1671
+ roleArabicName: actors?.roleArabicName,
1672
+ userName: m[2].trim(),
1673
+ userArabicName: actors?.userArabicName,
1674
+ comment,
1675
+ }).ar;
1676
+ }
1677
+
1678
+ if (/^Workflow routing completed - request rejected at technician level$/i.test(base)) {
1679
+ return buildItHelpdeskMuscatWorkflowLog({
1680
+ variant: 'routing_technician_rejected',
1681
+ comment,
1682
+ }).ar;
1683
+ }
1684
+
1685
+ m = base.match(/^HOS notification not required, request rejected by (.+)$/i);
1686
+ if (m) {
1687
+ return buildItHelpdeskMuscatWorkflowLog({
1688
+ variant: 'hos_not_required_rejected',
1689
+ userName: m[1].trim(),
1690
+ userArabicName: actors?.userArabicName,
1691
+ comment,
1692
+ }).ar;
1693
+ }
1694
+
1695
+ m = base.match(/^HOS notification not required, request closed by (.+)$/i);
1696
+ if (m) {
1697
+ return buildItHelpdeskMuscatWorkflowLog({
1698
+ variant: 'hos_not_required_closed',
1699
+ userName: m[1].trim(),
1700
+ userArabicName: actors?.userArabicName,
1701
+ comment,
1702
+ }).ar;
1703
+ }
1704
+
1705
+ m = base.match(/^L3 assignment not required, request rejected by (.+)$/i);
1706
+ if (m) {
1707
+ return buildItHelpdeskMuscatWorkflowLog({
1708
+ variant: 'l3_not_required_rejected',
1709
+ userName: m[1].trim(),
1710
+ userArabicName: actors?.userArabicName,
1711
+ comment,
1712
+ }).ar;
1713
+ }
1714
+
1715
+ m = base.match(/^L3 assignment not required, request closed by (.+)$/i);
1716
+ if (m) {
1717
+ return buildItHelpdeskMuscatWorkflowLog({
1718
+ variant: 'l3_not_required_closed',
1719
+ userName: m[1].trim(),
1720
+ userArabicName: actors?.userArabicName,
1721
+ comment,
1722
+ }).ar;
1723
+ }
1724
+
1725
+ return null;
1726
+ }
1727
+
1415
1728
  export function resolveWorkflowContentAr(
1416
1729
  content: string | null | undefined,
1417
1730
  actors?: BilingualActorNames,
@@ -1424,6 +1737,9 @@ export function resolveWorkflowContentAr(
1424
1737
  return buildAssignmentWorkflowFieldsFromActors(embeddedAssignment, actors).ar;
1425
1738
  }
1426
1739
 
1740
+ const itHelpdeskLegacyAr = tryResolveItHelpdeskMuscatLegacyWorkflowAr(normalized, actors);
1741
+ if (itHelpdeskLegacyAr) return itHelpdeskLegacyAr;
1742
+
1427
1743
  for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
1428
1744
  if (tpl.en === normalized || tpl.en.toLowerCase() === normalized.toLowerCase()) return tpl.ar;
1429
1745
  }
@@ -1577,6 +1893,18 @@ export function enrichWorkflowLog(log: Record<string, unknown>): Record<string,
1577
1893
  ) {
1578
1894
  content_ar = resolvedAr;
1579
1895
  }
1896
+ const itHelpdeskMixedEnglish =
1897
+ /routed to L3|assignment path|Workflow decision made|L3 Technical Support approved|L3 assignment not required|HOS notification not required|routing completed/i;
1898
+ if (
1899
+ content &&
1900
+ existingAr &&
1901
+ resolvedAr &&
1902
+ existingAr !== resolvedAr &&
1903
+ (itHelpdeskMixedEnglish.test(content) ||
1904
+ /routed to|routing completed|assignment path|\sby\s/i.test(existingAr))
1905
+ ) {
1906
+ content_ar = resolvedAr;
1907
+ }
1580
1908
  if ((content_ar == null || content_ar === '') && content) {
1581
1909
  const notificationAr = resolveWorkflowContentAr(content, actors);
1582
1910
  if (notificationAr) content_ar = notificationAr;