@platform-modules/civil-aviation-authority 2.3.281 → 2.3.290

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.
Files changed (253) hide show
  1. package/dist/i18n/workflow-chat-i18n.json +162 -1
  2. package/dist/i18n/workflow-chat-message.builder.d.ts +182 -3
  3. package/dist/i18n/workflow-chat-message.builder.js +469 -17
  4. package/dist/models/AccessCardChatModel.d.ts +2 -0
  5. package/dist/models/AccessCardChatModel.js +8 -0
  6. package/dist/models/AccessCardWorkflowModel.d.ts +2 -0
  7. package/dist/models/AccessCardWorkflowModel.js +8 -0
  8. package/dist/models/AccommodationChatModel.d.ts +2 -0
  9. package/dist/models/AccommodationChatModel.js +8 -0
  10. package/dist/models/AccommodationWorkflowModel.d.ts +2 -0
  11. package/dist/models/AccommodationWorkflowModel.js +8 -0
  12. package/dist/models/AirportEntryPermitChatModel.d.ts +2 -0
  13. package/dist/models/AirportEntryPermitChatModel.js +8 -0
  14. package/dist/models/AirportEntryPermitWorkflowModel.d.ts +2 -0
  15. package/dist/models/AirportEntryPermitWorkflowModel.js +8 -0
  16. package/dist/models/AnnualTrainingPlanChatModel.d.ts +2 -0
  17. package/dist/models/AnnualTrainingPlanChatModel.js +8 -0
  18. package/dist/models/AnnualTrainingPlanWorkflowModel.d.ts +2 -0
  19. package/dist/models/AnnualTrainingPlanWorkflowModel.js +8 -0
  20. package/dist/models/AssignTasksEmpChatModel.d.ts +2 -0
  21. package/dist/models/AssignTasksEmpChatModel.js +8 -0
  22. package/dist/models/AssignTasksEmpWorkflowModel.d.ts +2 -0
  23. package/dist/models/AssignTasksEmpWorkflowModel.js +8 -0
  24. package/dist/models/ComplaintLostPropertyReportChatModel.d.ts +2 -0
  25. package/dist/models/ComplaintLostPropertyReportChatModel.js +8 -0
  26. package/dist/models/ComplaintLostPropertyReportWorkflowModel.d.ts +2 -0
  27. package/dist/models/ComplaintLostPropertyReportWorkflowModel.js +8 -0
  28. package/dist/models/DutyMissionChatModel.d.ts +2 -0
  29. package/dist/models/DutyMissionChatModel.js +8 -0
  30. package/dist/models/DutyMissionWorkflowModel.d.ts +2 -0
  31. package/dist/models/DutyMissionWorkflowModel.js +8 -0
  32. package/dist/models/EventSupportChatModel.d.ts +2 -0
  33. package/dist/models/EventSupportChatModel.js +8 -0
  34. package/dist/models/EventSupportWorkflowModel.d.ts +2 -0
  35. package/dist/models/EventSupportWorkflowModel.js +8 -0
  36. package/dist/models/FollowUpReportChatModel.d.ts +2 -0
  37. package/dist/models/FollowUpReportChatModel.js +8 -0
  38. package/dist/models/FollowUpReportWorkflowModel.d.ts +2 -0
  39. package/dist/models/FollowUpReportWorkflowModel.js +8 -0
  40. package/dist/models/FoodChatModel.d.ts +2 -0
  41. package/dist/models/FoodChatModel.js +8 -0
  42. package/dist/models/FoodWorkflowModel.d.ts +2 -0
  43. package/dist/models/FoodWorkflowModel.js +8 -0
  44. package/dist/models/HallChatModel.d.ts +2 -0
  45. package/dist/models/HallChatModel.js +8 -0
  46. package/dist/models/HallWorkflowModel.d.ts +2 -0
  47. package/dist/models/HallWorkflowModel.js +8 -0
  48. package/dist/models/HotelChatModel.d.ts +2 -0
  49. package/dist/models/HotelChatModel.js +8 -0
  50. package/dist/models/HotelWorkFlowModel.d.ts +2 -0
  51. package/dist/models/HotelWorkFlowModel.js +8 -0
  52. package/dist/models/HousingContractCancelChatModel.d.ts +2 -0
  53. package/dist/models/HousingContractCancelChatModel.js +8 -0
  54. package/dist/models/HousingContractCancelWorkflowModel.d.ts +2 -0
  55. package/dist/models/HousingContractCancelWorkflowModel.js +8 -0
  56. package/dist/models/HousingContractRenewalChatModel.d.ts +2 -0
  57. package/dist/models/HousingContractRenewalChatModel.js +8 -0
  58. package/dist/models/HousingContractRenewalWorkflowModel.d.ts +2 -0
  59. package/dist/models/HousingContractRenewalWorkflowModel.js +8 -0
  60. package/dist/models/HrServiceChatModel.d.ts +2 -0
  61. package/dist/models/HrServiceChatModel.js +8 -0
  62. package/dist/models/HrServiceWorkflowModel.d.ts +2 -0
  63. package/dist/models/HrServiceWorkflowModel.js +8 -0
  64. package/dist/models/ITRequestChatModel.d.ts +2 -0
  65. package/dist/models/ITRequestChatModel.js +8 -0
  66. package/dist/models/ImportExportMaterialChatModel.d.ts +2 -0
  67. package/dist/models/ImportExportMaterialChatModel.js +8 -0
  68. package/dist/models/ImportExportMaterialWorkflowModel.d.ts +2 -0
  69. package/dist/models/ImportExportMaterialWorkflowModel.js +8 -0
  70. package/dist/models/InformationSecurityChatModel.d.ts +2 -0
  71. package/dist/models/InformationSecurityChatModel.js +8 -0
  72. package/dist/models/InformationSecurityWorkflowModel.d.ts +2 -0
  73. package/dist/models/InformationSecurityWorkflowModel.js +8 -0
  74. package/dist/models/ItWorkflowModel.d.ts +2 -0
  75. package/dist/models/ItWorkflowModel.js +8 -0
  76. package/dist/models/LegalComplaintChatModel.d.ts +2 -0
  77. package/dist/models/LegalComplaintChatModel.js +8 -0
  78. package/dist/models/LegalComplaintWorkflowModel.d.ts +2 -0
  79. package/dist/models/LegalComplaintWorkflowModel.js +8 -0
  80. package/dist/models/LegalConsultationChatModel.d.ts +2 -0
  81. package/dist/models/LegalConsultationChatModel.js +8 -0
  82. package/dist/models/LegalConsultationWorkflowModel.d.ts +2 -0
  83. package/dist/models/LegalConsultationWorkflowModel.js +8 -0
  84. package/dist/models/LegalContractReviewChatModel.d.ts +2 -0
  85. package/dist/models/LegalContractReviewChatModel.js +8 -0
  86. package/dist/models/LegalContractReviewWorkflowModel.d.ts +2 -0
  87. package/dist/models/LegalContractReviewWorkflowModel.js +8 -0
  88. package/dist/models/LogisticsChatModel.d.ts +2 -0
  89. package/dist/models/LogisticsChatModel.js +8 -0
  90. package/dist/models/LogisticsVehicleMaintenanceChatModel.d.ts +2 -0
  91. package/dist/models/LogisticsVehicleMaintenanceChatModel.js +8 -0
  92. package/dist/models/LogisticsVehicleMaintenanceWorkflowModel.d.ts +2 -0
  93. package/dist/models/LogisticsVehicleMaintenanceWorkflowModel.js +8 -0
  94. package/dist/models/LogisticsWorkflowModel.d.ts +2 -0
  95. package/dist/models/LogisticsWorkflowModel.js +8 -0
  96. package/dist/models/MaintenanceChatModel.d.ts +2 -0
  97. package/dist/models/MaintenanceChatModel.js +8 -0
  98. package/dist/models/MaintenanceExternalStationsChatModel.d.ts +2 -0
  99. package/dist/models/MaintenanceExternalStationsChatModel.js +8 -0
  100. package/dist/models/MaintenanceExternalStationsWorkflowModel.d.ts +2 -0
  101. package/dist/models/MaintenanceExternalStationsWorkflowModel.js +8 -0
  102. package/dist/models/MaintenanceWorkflowModel.d.ts +2 -0
  103. package/dist/models/MaintenanceWorkflowModel.js +8 -0
  104. package/dist/models/MediaPublicationsChatModel.d.ts +2 -0
  105. package/dist/models/MediaPublicationsChatModel.js +8 -0
  106. package/dist/models/MediaPublicationsWorkflowModel.d.ts +2 -0
  107. package/dist/models/MediaPublicationsWorkflowModel.js +8 -0
  108. package/dist/models/MultiPurposeHallChatModel.d.ts +2 -0
  109. package/dist/models/MultiPurposeHallChatModel.js +8 -0
  110. package/dist/models/MultiPurposeHallWorkflowModel.d.ts +2 -0
  111. package/dist/models/MultiPurposeHallWorkflowModel.js +8 -0
  112. package/dist/models/RequestForCoverageChatModel.d.ts +2 -0
  113. package/dist/models/RequestForCoverageChatModel.js +8 -0
  114. package/dist/models/RequestForCoverageWorkflowModel.d.ts +2 -0
  115. package/dist/models/RequestForCoverageWorkflowModel.js +8 -0
  116. package/dist/models/RequestForPresidentsCoverageChatModel.d.ts +2 -0
  117. package/dist/models/RequestForPresidentsCoverageChatModel.js +8 -0
  118. package/dist/models/RequestForPresidentsCoverageWorkflowModel.d.ts +2 -0
  119. package/dist/models/RequestForPresidentsCoverageWorkflowModel.js +8 -0
  120. package/dist/models/ResidentialUnitRentalChatModel.d.ts +2 -0
  121. package/dist/models/ResidentialUnitRentalChatModel.js +8 -0
  122. package/dist/models/ResidentialUnitRentalWorkflowModel.d.ts +2 -0
  123. package/dist/models/ResidentialUnitRentalWorkflowModel.js +8 -0
  124. package/dist/models/RespondToEnquiriesChatModel.d.ts +2 -0
  125. package/dist/models/RespondToEnquiriesChatModel.js +8 -0
  126. package/dist/models/RespondToEnquiriesWorkflowModel.d.ts +2 -0
  127. package/dist/models/RespondToEnquiriesWorkflowModel.js +8 -0
  128. package/dist/models/ScholarshipChatModel.d.ts +2 -0
  129. package/dist/models/ScholarshipChatModel.js +8 -0
  130. package/dist/models/ScholarshipWorkflowModel.d.ts +2 -0
  131. package/dist/models/ScholarshipWorkflowModel.js +8 -0
  132. package/dist/models/SecurityAwarenessChatModel.d.ts +2 -0
  133. package/dist/models/SecurityAwarenessChatModel.js +8 -0
  134. package/dist/models/SecurityAwarenessWorkflowModel.d.ts +2 -0
  135. package/dist/models/SecurityAwarenessWorkflowModel.js +8 -0
  136. package/dist/models/SecurityThreatChatModel.d.ts +2 -0
  137. package/dist/models/SecurityThreatChatModel.js +8 -0
  138. package/dist/models/SecurityThreatWorkflowModel.d.ts +2 -0
  139. package/dist/models/SecurityThreatWorkflowModel.js +8 -0
  140. package/dist/models/ServiceExtensionAfter60ChatModel.d.ts +2 -0
  141. package/dist/models/ServiceExtensionAfter60ChatModel.js +8 -0
  142. package/dist/models/ServiceExtensionAfter60WorkflowModel.d.ts +2 -0
  143. package/dist/models/ServiceExtensionAfter60WorkflowModel.js +8 -0
  144. package/dist/models/StudyLeaveChatModel.d.ts +2 -0
  145. package/dist/models/StudyLeaveChatModel.js +8 -0
  146. package/dist/models/StudyLeaveWorkflowModel.d.ts +2 -0
  147. package/dist/models/StudyLeaveWorkflowModel.js +8 -0
  148. package/dist/models/TrainingChatModel.d.ts +2 -0
  149. package/dist/models/TrainingChatModel.js +8 -0
  150. package/dist/models/TrainingRoomBookingChatModel.d.ts +2 -0
  151. package/dist/models/TrainingRoomBookingChatModel.js +8 -0
  152. package/dist/models/TrainingRoomBookingWorkflowModel.d.ts +2 -0
  153. package/dist/models/TrainingRoomBookingWorkflowModel.js +8 -0
  154. package/dist/models/TrainingWorkflowModel.d.ts +2 -0
  155. package/dist/models/TrainingWorkflowModel.js +8 -0
  156. package/dist/models/VipHallChatModel.d.ts +2 -0
  157. package/dist/models/VipHallChatModel.js +8 -0
  158. package/dist/models/VipHallWorkflowModel.d.ts +2 -0
  159. package/dist/models/VipHallWorkflowModel.js +8 -0
  160. package/dist/models/VpnRequestChatModel.d.ts +2 -0
  161. package/dist/models/VpnRequestChatModel.js +8 -0
  162. package/dist/models/VpnWorkflowModel.d.ts +2 -0
  163. package/dist/models/VpnWorkflowModel.js +8 -0
  164. package/dist/models/WorkingHoursExtensionChatModel.d.ts +2 -0
  165. package/dist/models/WorkingHoursExtensionChatModel.js +8 -0
  166. package/dist/models/WorkingHoursExtensionWorkflowModel.d.ts +2 -0
  167. package/dist/models/WorkingHoursExtensionWorkflowModel.js +8 -0
  168. package/package.json +1 -1
  169. package/sql/add_all_workflow_chat_arabic_columns.sql +1 -0
  170. package/src/i18n/workflow-chat-i18n.json +162 -1
  171. package/src/i18n/workflow-chat-message.builder.ts +760 -17
  172. package/src/models/AccessCardChatModel.ts +8 -2
  173. package/src/models/AccessCardWorkflowModel.ts +8 -2
  174. package/src/models/AccommodationChatModel.ts +8 -2
  175. package/src/models/AccommodationWorkflowModel.ts +8 -2
  176. package/src/models/AirportEntryPermitChatModel.ts +8 -2
  177. package/src/models/AirportEntryPermitWorkflowModel.ts +8 -2
  178. package/src/models/AnnualTrainingPlanChatModel.ts +8 -2
  179. package/src/models/AnnualTrainingPlanWorkflowModel.ts +8 -2
  180. package/src/models/AssignTasksEmpChatModel.ts +8 -2
  181. package/src/models/AssignTasksEmpWorkflowModel.ts +8 -2
  182. package/src/models/ComplaintLostPropertyReportChatModel.ts +8 -2
  183. package/src/models/ComplaintLostPropertyReportWorkflowModel.ts +8 -2
  184. package/src/models/DutyMissionChatModel.ts +8 -2
  185. package/src/models/DutyMissionWorkflowModel.ts +8 -2
  186. package/src/models/EventSupportChatModel.ts +8 -2
  187. package/src/models/EventSupportWorkflowModel.ts +8 -2
  188. package/src/models/FollowUpReportChatModel.ts +8 -2
  189. package/src/models/FollowUpReportWorkflowModel.ts +8 -2
  190. package/src/models/FoodChatModel.ts +8 -2
  191. package/src/models/FoodWorkflowModel.ts +8 -2
  192. package/src/models/HallChatModel.ts +8 -2
  193. package/src/models/HallWorkflowModel.ts +8 -2
  194. package/src/models/HotelChatModel.ts +8 -2
  195. package/src/models/HotelWorkFlowModel.ts +8 -2
  196. package/src/models/HousingContractCancelChatModel.ts +8 -2
  197. package/src/models/HousingContractCancelWorkflowModel.ts +8 -2
  198. package/src/models/HousingContractRenewalChatModel.ts +8 -2
  199. package/src/models/HousingContractRenewalWorkflowModel.ts +8 -2
  200. package/src/models/HrServiceChatModel.ts +8 -2
  201. package/src/models/HrServiceWorkflowModel.ts +8 -2
  202. package/src/models/ITRequestChatModel.ts +8 -2
  203. package/src/models/ImportExportMaterialChatModel.ts +8 -2
  204. package/src/models/ImportExportMaterialWorkflowModel.ts +8 -2
  205. package/src/models/InformationSecurityChatModel.ts +8 -2
  206. package/src/models/InformationSecurityWorkflowModel.ts +8 -2
  207. package/src/models/ItWorkflowModel.ts +8 -2
  208. package/src/models/LegalComplaintChatModel.ts +8 -2
  209. package/src/models/LegalComplaintWorkflowModel.ts +8 -2
  210. package/src/models/LegalConsultationChatModel.ts +8 -2
  211. package/src/models/LegalConsultationWorkflowModel.ts +8 -2
  212. package/src/models/LegalContractReviewChatModel.ts +8 -2
  213. package/src/models/LegalContractReviewWorkflowModel.ts +8 -2
  214. package/src/models/LogisticsChatModel.ts +8 -2
  215. package/src/models/LogisticsVehicleMaintenanceChatModel.ts +8 -2
  216. package/src/models/LogisticsVehicleMaintenanceWorkflowModel.ts +8 -2
  217. package/src/models/LogisticsWorkflowModel.ts +8 -2
  218. package/src/models/MaintenanceChatModel.ts +8 -2
  219. package/src/models/MaintenanceExternalStationsChatModel.ts +8 -2
  220. package/src/models/MaintenanceExternalStationsWorkflowModel.ts +8 -2
  221. package/src/models/MaintenanceWorkflowModel.ts +8 -2
  222. package/src/models/MediaPublicationsChatModel.ts +8 -2
  223. package/src/models/MediaPublicationsWorkflowModel.ts +8 -2
  224. package/src/models/MultiPurposeHallChatModel.ts +8 -2
  225. package/src/models/MultiPurposeHallWorkflowModel.ts +8 -2
  226. package/src/models/RequestForCoverageChatModel.ts +8 -2
  227. package/src/models/RequestForCoverageWorkflowModel.ts +8 -2
  228. package/src/models/RequestForPresidentsCoverageChatModel.ts +8 -2
  229. package/src/models/RequestForPresidentsCoverageWorkflowModel.ts +8 -2
  230. package/src/models/ResidentialUnitRentalChatModel.ts +8 -2
  231. package/src/models/ResidentialUnitRentalWorkflowModel.ts +8 -2
  232. package/src/models/RespondToEnquiriesChatModel.ts +8 -2
  233. package/src/models/RespondToEnquiriesWorkflowModel.ts +8 -2
  234. package/src/models/ScholarshipChatModel.ts +8 -2
  235. package/src/models/ScholarshipWorkflowModel.ts +8 -2
  236. package/src/models/SecurityAwarenessChatModel.ts +8 -2
  237. package/src/models/SecurityAwarenessWorkflowModel.ts +8 -2
  238. package/src/models/SecurityThreatChatModel.ts +8 -2
  239. package/src/models/SecurityThreatWorkflowModel.ts +8 -2
  240. package/src/models/ServiceExtensionAfter60ChatModel.ts +8 -2
  241. package/src/models/ServiceExtensionAfter60WorkflowModel.ts +8 -2
  242. package/src/models/StudyLeaveChatModel.ts +8 -2
  243. package/src/models/StudyLeaveWorkflowModel.ts +8 -2
  244. package/src/models/TrainingChatModel.ts +8 -2
  245. package/src/models/TrainingRoomBookingChatModel.ts +8 -2
  246. package/src/models/TrainingRoomBookingWorkflowModel.ts +8 -2
  247. package/src/models/TrainingWorkflowModel.ts +8 -2
  248. package/src/models/VipHallChatModel.ts +8 -2
  249. package/src/models/VipHallWorkflowModel.ts +8 -2
  250. package/src/models/VpnRequestChatModel.ts +8 -2
  251. package/src/models/VpnWorkflowModel.ts +8 -2
  252. package/src/models/WorkingHoursExtensionChatModel.ts +8 -2
  253. package/src/models/WorkingHoursExtensionWorkflowModel.ts +8 -2
@@ -11,6 +11,7 @@ export type BilingualText = { en: string; ar: string };
11
11
  export type WorkflowChatI18nCatalog = {
12
12
  workflowTemplates: Record<string, BilingualText>;
13
13
  chatTemplates: Record<string, BilingualText>;
14
+ notificationTemplates?: Record<string, BilingualText>;
14
15
  workflowStatusLabels: Record<string, string>;
15
16
  chatStatusLabels: Record<string, string>;
16
17
  };
@@ -41,6 +42,7 @@ export type WorkflowMessageAction =
41
42
 
42
43
  export type ChatMessageAction =
43
44
  | 'cancellation_submitted'
45
+ | 'request_submitted'
44
46
  | 'request_received'
45
47
  | 'approved'
46
48
  | 'rejected'
@@ -60,6 +62,7 @@ export type ChatMessageContext = BilingualActorNames & {
60
62
 
61
63
  const WORKFLOW_TEMPLATES = catalog.workflowTemplates as Record<string, BilingualText>;
62
64
  const CHAT_TEMPLATES = catalog.chatTemplates as Record<string, BilingualText>;
65
+ const NOTIFICATION_TEMPLATES = (catalog as { notificationTemplates?: Record<string, BilingualText> }).notificationTemplates ?? {};
63
66
  const WORKFLOW_STATUS_LABELS = catalog.workflowStatusLabels as Record<string, string>;
64
67
  const CHAT_STATUS_LABELS = catalog.chatStatusLabels as Record<string, string>;
65
68
 
@@ -305,6 +308,16 @@ export function buildSynchronizedChatMessage(ctx: ChatMessageContext): Bilingual
305
308
  { userName: userAr, employeeId },
306
309
  );
307
310
  }
311
+ case 'request_submitted': {
312
+ const userEn = ctx.userName?.trim() || 'Unknown User';
313
+ const userAr = resolveBilingualName(userEn, ctx.userArabicName);
314
+ const employeeId = String(ctx.employeeId ?? 'N/A');
315
+ return chatTemplate(
316
+ 'request_submitted_by',
317
+ { userName: userEn, employeeId },
318
+ { userName: userAr, employeeId },
319
+ );
320
+ }
308
321
  case 'request_received':
309
322
  return chatTemplate('request_received');
310
323
  case 'approved':
@@ -331,6 +344,61 @@ export function buildSynchronizedChatMessage(ctx: ChatMessageContext): Bilingual
331
344
 
332
345
  // ─── Backward-compatible aliases used by Cancellation pilot ───────────────────
333
346
 
347
+ /** Standard workflow log UPDATE fields for all CAA module repositories. */
348
+ export function buildWorkflowLogUpdateFields(params: {
349
+ approvalStatus: string;
350
+ workflowStatus: string;
351
+ roleName?: string | null;
352
+ roleArabicName?: string | null;
353
+ comment?: string | null;
354
+ }): { content: string; content_ar: string; status: string; status_ar: string | null } {
355
+ let action: WorkflowMessageAction;
356
+ if (params.approvalStatus === 'Approved') action = 'approved';
357
+ else if (params.approvalStatus === 'Rejected') action = 'rejected';
358
+ else if (params.approvalStatus === 'Returned for Modification') action = 'returned_for_modification';
359
+ else action = 'approval_pending_update';
360
+
361
+ const bilingual = buildSynchronizedWorkflowMessage({
362
+ action,
363
+ roleName: params.roleName,
364
+ roleArabicName: params.roleArabicName,
365
+ comment: params.comment,
366
+ });
367
+
368
+ return {
369
+ content: bilingual.en,
370
+ content_ar: bilingual.ar,
371
+ status: params.workflowStatus,
372
+ status_ar: resolveWorkflowStatusAr(params.workflowStatus),
373
+ };
374
+ }
375
+
376
+ /** Bilingual workflow insert fields from builder output. */
377
+ export function toWorkflowPersistFields(
378
+ bilingual: BilingualText,
379
+ status: string,
380
+ ): { content: string; content_ar: string; status: string; status_ar: string | null } {
381
+ return {
382
+ content: bilingual.en,
383
+ content_ar: bilingual.ar,
384
+ status,
385
+ status_ar: resolveWorkflowStatusAr(status),
386
+ };
387
+ }
388
+
389
+ /** Bilingual chat insert fields from builder output. */
390
+ export function toChatPersistFields(
391
+ bilingual: BilingualText,
392
+ status: string,
393
+ ): { message: string; message_ar: string; status: string; status_ar: string | null } {
394
+ return {
395
+ message: bilingual.en,
396
+ message_ar: bilingual.ar,
397
+ status,
398
+ status_ar: resolveChatStatusAr(status),
399
+ };
400
+ }
401
+
334
402
  export function buildApprovalWorkflowContent(params: {
335
403
  status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
336
404
  roleName?: string | null;
@@ -378,6 +446,22 @@ export function buildApprovalChatMessage(params: {
378
446
  roleArabicName?: string | null;
379
447
  comment?: string | null;
380
448
  }): BilingualText {
449
+ if (params.status === 'Closed') {
450
+ return buildClosedChatMessage({
451
+ roleName: params.roleName,
452
+ roleArabicName: params.roleArabicName,
453
+ comment: params.comment,
454
+ });
455
+ }
456
+ if (params.status === 'Request For Change' || params.status === 'RFC') {
457
+ return buildRequestForChangeChatMessage({
458
+ roleName: params.roleName,
459
+ roleArabicName: params.roleArabicName,
460
+ comment: params.comment,
461
+ variant: 'by',
462
+ });
463
+ }
464
+
381
465
  let action: ChatMessageAction = 'approved';
382
466
  if (params.status === 'Rejected') action = 'rejected';
383
467
  else if (params.status === 'Returned for Modification') action = 'returned_for_modification';
@@ -390,6 +474,589 @@ export function buildApprovalChatMessage(params: {
390
474
  });
391
475
  }
392
476
 
477
+ // ─── Specialized chat/workflow builders ─────────────────────────────────────
478
+
479
+ function formatDotCommentSuffix(comment?: string | null): string {
480
+ const text = comment?.trim();
481
+ return text ? `. Comment: ${text}` : '';
482
+ }
483
+
484
+ function formatColonCommentSuffix(comment?: string | null): string {
485
+ const text = comment?.trim();
486
+ return text ? `: ${text}` : '';
487
+ }
488
+
489
+ function formatReassignCommentSuffix(comment?: string | null): string {
490
+ const text = comment?.trim();
491
+ return text ? `:${text}` : '';
492
+ }
493
+
494
+ export function buildClosedChatMessage(params: {
495
+ roleName?: string | null;
496
+ roleArabicName?: string | null;
497
+ comment?: string | null;
498
+ }): BilingualText {
499
+ const roleEn = params.roleName?.trim() || 'Unknown Role';
500
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
501
+ const comment = formatDotCommentSuffix(params.comment);
502
+ return renderSynchronizedChatMessage(
503
+ 'request_closed_by_role',
504
+ { roleName: roleEn, comment },
505
+ { roleName: roleAr, comment },
506
+ );
507
+ }
508
+
509
+ export function buildClosedWorkflowMessage(params: {
510
+ roleName?: string | null;
511
+ roleArabicName?: string | null;
512
+ comment?: string | null;
513
+ }): BilingualText {
514
+ const comment = formatWorkflowCommentSuffix(params.comment);
515
+ const roleEn = params.roleName?.trim();
516
+ if (roleEn) {
517
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
518
+ return renderSynchronizedWorkflowMessage(
519
+ 'request_closed_by',
520
+ { name: roleEn, comment },
521
+ { name: roleAr, comment },
522
+ );
523
+ }
524
+ return renderSynchronizedWorkflowMessage('request_closed', { comment }, { comment });
525
+ }
526
+
527
+ export function buildRequestForChangeChatMessage(params: {
528
+ roleName?: string | null;
529
+ roleArabicName?: string | null;
530
+ comment?: string | null;
531
+ variant?: 'by' | 'requested_by';
532
+ }): BilingualText {
533
+ const roleEn = params.roleName?.trim() || 'Unknown Role';
534
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
535
+ const comment =
536
+ params.variant === 'requested_by'
537
+ ? formatChatCommentSuffix(params.comment)
538
+ : formatColonCommentSuffix(params.comment);
539
+ const key =
540
+ params.variant === 'requested_by'
541
+ ? 'request_for_change_requested_by_role'
542
+ : 'request_for_change_by_role';
543
+ return renderSynchronizedChatMessage(key, { roleName: roleEn, comment }, { roleName: roleAr, comment });
544
+ }
545
+
546
+ export function buildAttachmentUploadedChatMessage(fileName: string): BilingualText {
547
+ const name = fileName?.trim() || 'file';
548
+ return renderSynchronizedChatMessage('attachment_uploaded', { fileName: name }, { fileName: name });
549
+ }
550
+
551
+ /** User-typed chat text, or bilingual [Attachment] placeholder when empty. */
552
+ export function buildUserOrAttachmentChatMessage(userMessage?: string | null): BilingualText {
553
+ const text = userMessage?.trim();
554
+ if (text) return { en: text, ar: text };
555
+ return chatTemplate('attachment_placeholder');
556
+ }
557
+
558
+ export function buildTaskInProgressChatMessage(params: {
559
+ roleName?: string | null;
560
+ roleArabicName?: string | null;
561
+ }): BilingualText {
562
+ const roleEn = params.roleName?.trim() || 'Employee';
563
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
564
+ return renderSynchronizedChatMessage(
565
+ 'task_status_in_progress_by_role',
566
+ { roleName: roleEn },
567
+ { roleName: roleAr },
568
+ );
569
+ }
570
+
571
+ export function buildTaskCompletedChatMessage(params: {
572
+ roleName?: string | null;
573
+ roleArabicName?: string | null;
574
+ comment?: string | null;
575
+ }): BilingualText {
576
+ const roleEn = params.roleName?.trim() || 'Employee';
577
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
578
+ const comment = formatDotCommentSuffix(params.comment);
579
+ return renderSynchronizedChatMessage(
580
+ 'task_completed_by_role',
581
+ { roleName: roleEn, comment },
582
+ { roleName: roleAr, comment },
583
+ );
584
+ }
585
+
586
+ export function buildTaskStatusUpdatedChatMessage(status: string, statusAr?: string | null): BilingualText {
587
+ const statusEn = status?.trim() || 'Updated';
588
+ const statusArResolved = statusAr?.trim() || resolveChatStatusAr(statusEn) || statusEn;
589
+ return renderSynchronizedChatMessage(
590
+ 'task_status_updated',
591
+ { status: statusEn },
592
+ { status: statusArResolved },
593
+ );
594
+ }
595
+
596
+ export function buildItHelpdeskAssignedChatMessage(params: {
597
+ level: number;
598
+ assignedRoleName?: string | null;
599
+ assignedRoleArabicName?: string | null;
600
+ assignedUserName?: string | null;
601
+ assignedUserArabicName?: string | null;
602
+ comment?: string | null;
603
+ }): BilingualText {
604
+ const assignedRoleEn = params.assignedRoleName?.trim() || 'Role';
605
+ const assignedRoleAr = resolveBilingualName(assignedRoleEn, params.assignedRoleArabicName);
606
+ const assignedUserEn = params.assignedUserName?.trim() || 'User';
607
+ const assignedUserAr = resolveBilingualName(assignedUserEn, params.assignedUserArabicName);
608
+ const comment = params.comment?.trim() ? ` - ${params.comment.trim()}` : '';
609
+ const templateKey = params.level === 1 ? 'it_helpdesk_hos_assigned' : 'it_helpdesk_technician_assigned';
610
+ const enParams: Record<string, string> = {
611
+ assignedRoleName: assignedRoleEn,
612
+ assignedUserName: assignedUserEn,
613
+ comment,
614
+ };
615
+ const arParams: Record<string, string> = {
616
+ assignedRoleName: assignedRoleAr,
617
+ assignedUserName: assignedUserAr,
618
+ comment,
619
+ };
620
+ if (params.level !== 1) {
621
+ enParams.level = String(params.level);
622
+ arParams.level = String(params.level);
623
+ }
624
+ return renderSynchronizedChatMessage(templateKey, enParams, arParams);
625
+ }
626
+
627
+ export function buildItHelpdeskReassignedChatMessage(params: {
628
+ level: number;
629
+ approverRoleName?: string | null;
630
+ approverRoleArabicName?: string | null;
631
+ reassignedRoleName?: string | null;
632
+ reassignedRoleArabicName?: string | null;
633
+ comment?: string | null;
634
+ }): BilingualText {
635
+ const approverEn = params.approverRoleName?.trim() || 'Role';
636
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
637
+ const comment = formatReassignCommentSuffix(params.comment);
638
+ if (params.level === 1) {
639
+ const reassignedEn = params.reassignedRoleName?.trim() || 'Role';
640
+ const reassignedAr = resolveBilingualName(reassignedEn, params.reassignedRoleArabicName);
641
+ return renderSynchronizedChatMessage(
642
+ 'it_helpdesk_reassigned_to_role',
643
+ { approverRoleName: approverEn, reassignedRoleName: reassignedEn, comment },
644
+ { approverRoleName: approverAr, reassignedRoleName: reassignedAr, comment },
645
+ );
646
+ }
647
+ if (params.level === 2) {
648
+ return renderSynchronizedChatMessage(
649
+ 'it_helpdesk_reassigned_to_l3',
650
+ { approverRoleName: approverEn, comment },
651
+ { approverRoleName: approverAr, comment },
652
+ );
653
+ }
654
+ const level = String(params.level);
655
+ return renderSynchronizedChatMessage(
656
+ 'it_helpdesk_reassigned_generic',
657
+ { approverRoleName: approverEn, level, comment },
658
+ { approverRoleName: approverAr, level, comment },
659
+ );
660
+ }
661
+
662
+ export function buildRequestSubmittedWithRefChatMessage(params: {
663
+ requestLabel: string;
664
+ requestLabelAr?: string | null;
665
+ requestId: number | string;
666
+ userName?: string | null;
667
+ userArabicName?: string | null;
668
+ employeeId?: string | number | null;
669
+ }): BilingualText {
670
+ const userEn = params.userName?.trim() || 'Unknown User';
671
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
672
+ const labelEn = params.requestLabel?.trim() || 'Request';
673
+ const labelAr = params.requestLabelAr?.trim() || labelEn;
674
+ const requestId = String(params.requestId);
675
+ const employeeId = String(params.employeeId ?? 'N/A');
676
+ return renderSynchronizedChatMessage(
677
+ 'request_submitted_with_ref',
678
+ { requestLabel: labelEn, requestId, userName: userEn, employeeId },
679
+ { requestLabel: labelAr, requestId, userName: userAr, employeeId },
680
+ );
681
+ }
682
+
683
+ export function buildSecurityAccessApprovalChatMessage(params: {
684
+ productLabel: string;
685
+ productLabelAr?: string | null;
686
+ requestId: number | string;
687
+ approvalLevel: number | string;
688
+ decision: 'approve' | 'reject';
689
+ comment?: string | null;
690
+ }): BilingualText {
691
+ const statusWord = params.decision === 'approve' ? 'approved' : 'rejected';
692
+ const statusWordAr = params.decision === 'approve' ? 'الموافقة على' : 'رفض';
693
+ const comment = params.comment?.trim() ? ` Comment: ${params.comment.trim()}` : '';
694
+ const labelEn = params.productLabel?.trim() || 'Security Access';
695
+ const labelAr = params.productLabelAr?.trim() || labelEn;
696
+ return renderSynchronizedChatMessage(
697
+ 'security_access_approval_step',
698
+ {
699
+ productLabel: labelEn,
700
+ requestId: String(params.requestId),
701
+ statusWord,
702
+ approvalLevel: String(params.approvalLevel),
703
+ comment,
704
+ statusWordAr,
705
+ },
706
+ {
707
+ productLabel: labelAr,
708
+ requestId: String(params.requestId),
709
+ statusWord,
710
+ approvalLevel: String(params.approvalLevel),
711
+ comment,
712
+ statusWordAr,
713
+ },
714
+ );
715
+ }
716
+
717
+ function renderSynchronizedNotificationMessage(
718
+ templateKey: string,
719
+ enParams: Record<string, string>,
720
+ arParams: Record<string, string>,
721
+ ): BilingualText {
722
+ const tpl = NOTIFICATION_TEMPLATES[templateKey];
723
+ if (!tpl) return { en: templateKey, ar: templateKey };
724
+ return {
725
+ en: interpolate(tpl.en, enParams),
726
+ ar: interpolate(tpl.ar, arParams),
727
+ };
728
+ }
729
+
730
+ function normalizeDisplayValue(value: unknown): string {
731
+ if (value === undefined || value === null || String(value).trim() === '') return 'Not specified';
732
+ return String(value);
733
+ }
734
+
735
+ function normalizeDisplayDate(value: unknown): string {
736
+ if (value === undefined || value === null || String(value).trim() === '') return 'Not specified';
737
+ const d = new Date(value as string | number | Date);
738
+ if (!isNaN(d.getTime())) return d.toISOString().slice(0, 10);
739
+ return String(value);
740
+ }
741
+
742
+ export function buildItHelpdeskReassignModifiers(params: {
743
+ sectionFrom?: number | null;
744
+ sectionTo?: number | null;
745
+ departmentFrom?: number | null;
746
+ departmentTo?: number | null;
747
+ }): { en: string; ar: string } {
748
+ let en = '';
749
+ let ar = '';
750
+ if (
751
+ params.sectionFrom != null &&
752
+ params.sectionTo != null &&
753
+ params.sectionFrom !== params.sectionTo
754
+ ) {
755
+ en += ` (Section modified from ${params.sectionFrom} to ${params.sectionTo})`;
756
+ ar += ` (تم تعديل القسم من ${params.sectionFrom} إلى ${params.sectionTo})`;
757
+ }
758
+ if (
759
+ params.departmentFrom != null &&
760
+ params.departmentTo != null &&
761
+ params.departmentFrom !== params.departmentTo
762
+ ) {
763
+ en += ` (Department modified from ${params.departmentFrom} to ${params.departmentTo})`;
764
+ ar += ` (تم تعديل الإدارة من ${params.departmentFrom} إلى ${params.departmentTo})`;
765
+ }
766
+ return { en, ar };
767
+ }
768
+
769
+ export type ItHelpdeskMuscatWorkflowParams =
770
+ | {
771
+ variant: 'hos_assigned';
772
+ approverRoleName: string;
773
+ approverRoleArabicName?: string | null;
774
+ assignedRoleName: string;
775
+ assignedRoleArabicName?: string | null;
776
+ comment?: string | null;
777
+ }
778
+ | {
779
+ variant: 'technician_assigned';
780
+ level: number;
781
+ approverRoleName: string;
782
+ approverRoleArabicName?: string | null;
783
+ comment?: string | null;
784
+ }
785
+ | {
786
+ variant: 'reassigned_technician' | 'reassigned_l3' | 'reassigned_generic';
787
+ approverRoleName: string;
788
+ approverRoleArabicName?: string | null;
789
+ level?: number;
790
+ comment?: string | null;
791
+ modifiers?: { en: string; ar: string };
792
+ }
793
+ | {
794
+ variant: 'closed_level1' | 'closed_level2';
795
+ approverRoleName: string;
796
+ approverRoleArabicName?: string | null;
797
+ comment?: string | null;
798
+ }
799
+ | {
800
+ variant: 'l3_approved' | 'l3_rejected';
801
+ roleName: string;
802
+ roleArabicName?: string | null;
803
+ userName: string;
804
+ userArabicName?: string | null;
805
+ comment?: string | null;
806
+ }
807
+ | {
808
+ variant: 'level_approved';
809
+ level: number;
810
+ approverRoleName: string;
811
+ approverRoleArabicName?: string | null;
812
+ comment?: string | null;
813
+ }
814
+ | {
815
+ variant: 'rejected' | 'l3_step_rejected';
816
+ approverRoleName: string;
817
+ approverRoleArabicName?: string | null;
818
+ comment?: string | null;
819
+ }
820
+ | {
821
+ variant: 'generic_status';
822
+ approverRoleName: string;
823
+ approverRoleArabicName?: string | null;
824
+ status: string;
825
+ comment?: string | null;
826
+ }
827
+ | {
828
+ variant: 'technician_completed' | 'routing_rejected';
829
+ roleName: string;
830
+ roleArabicName?: string | null;
831
+ comment?: string | null;
832
+ }
833
+ | { variant: 'routing_resolved' };
834
+
835
+ export function buildItHelpdeskMuscatWorkflowLog(params: ItHelpdeskMuscatWorkflowParams): BilingualText {
836
+ const comment = formatColonCommentSuffix(
837
+ 'comment' in params ? params.comment : undefined,
838
+ );
839
+ const modifiers = 'modifiers' in params && params.modifiers ? params.modifiers : { en: '', ar: '' };
840
+
841
+ switch (params.variant) {
842
+ case 'hos_assigned': {
843
+ const approverEn = params.approverRoleName.trim();
844
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
845
+ const assignedEn = params.assignedRoleName.trim();
846
+ const assignedAr = resolveBilingualName(assignedEn, params.assignedRoleArabicName);
847
+ return renderSynchronizedWorkflowMessage(
848
+ 'it_helpdesk_wf_hos_assigned',
849
+ { approverRoleName: approverEn, assignedRoleName: assignedEn, comment },
850
+ { approverRoleName: approverAr, assignedRoleName: assignedAr, comment },
851
+ );
852
+ }
853
+ case 'technician_assigned': {
854
+ const approverEn = params.approverRoleName.trim();
855
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
856
+ return renderSynchronizedWorkflowMessage(
857
+ 'it_helpdesk_wf_technician_assigned',
858
+ { level: String(params.level), approverRoleName: approverEn, comment },
859
+ { level: String(params.level), approverRoleName: approverAr, comment },
860
+ );
861
+ }
862
+ case 'reassigned_technician': {
863
+ const approverEn = params.approverRoleName.trim();
864
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
865
+ return renderSynchronizedWorkflowMessage(
866
+ 'it_helpdesk_wf_reassigned_technician',
867
+ { approverRoleName: approverEn, comment, modifiers: modifiers.en },
868
+ { approverRoleName: approverAr, comment, modifiers: modifiers.ar },
869
+ );
870
+ }
871
+ case 'reassigned_l3': {
872
+ const approverEn = params.approverRoleName.trim();
873
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
874
+ return renderSynchronizedWorkflowMessage(
875
+ 'it_helpdesk_wf_reassigned_l3',
876
+ { approverRoleName: approverEn, comment, modifiers: modifiers.en },
877
+ { approverRoleName: approverAr, comment, modifiers: modifiers.ar },
878
+ );
879
+ }
880
+ case 'reassigned_generic': {
881
+ const approverEn = params.approverRoleName.trim();
882
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
883
+ const level = String(params.level ?? '');
884
+ return renderSynchronizedWorkflowMessage(
885
+ 'it_helpdesk_wf_reassigned_generic',
886
+ { approverRoleName: approverEn, level, comment, modifiers: modifiers.en },
887
+ { approverRoleName: approverAr, level, comment, modifiers: modifiers.ar },
888
+ );
889
+ }
890
+ case 'closed_level1': {
891
+ const approverEn = params.approverRoleName.trim();
892
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
893
+ return renderSynchronizedWorkflowMessage(
894
+ 'it_helpdesk_wf_closed_request',
895
+ { approverRoleName: approverEn, comment },
896
+ { approverRoleName: approverAr, comment },
897
+ );
898
+ }
899
+ case 'closed_level2': {
900
+ const approverEn = params.approverRoleName.trim();
901
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
902
+ return renderSynchronizedWorkflowMessage(
903
+ 'it_helpdesk_wf_closed_request_cap',
904
+ { approverRoleName: approverEn, comment },
905
+ { approverRoleName: approverAr, comment },
906
+ );
907
+ }
908
+ case 'l3_approved': {
909
+ const roleEn = params.roleName.trim();
910
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
911
+ const userEn = params.userName.trim();
912
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
913
+ return renderSynchronizedWorkflowMessage(
914
+ 'it_helpdesk_wf_l3_approved_by',
915
+ { roleName: roleEn, userName: userEn, comment },
916
+ { roleName: roleAr, userName: userAr, comment },
917
+ );
918
+ }
919
+ case 'l3_rejected': {
920
+ const roleEn = params.roleName.trim();
921
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
922
+ const userEn = params.userName.trim();
923
+ const userAr = resolveBilingualName(userEn, params.userArabicName);
924
+ return renderSynchronizedWorkflowMessage(
925
+ 'it_helpdesk_wf_l3_rejected_by',
926
+ { roleName: roleEn, userName: userEn, comment },
927
+ { roleName: roleAr, userName: userAr, comment },
928
+ );
929
+ }
930
+ case 'level_approved': {
931
+ const approverEn = params.approverRoleName.trim();
932
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
933
+ return renderSynchronizedWorkflowMessage(
934
+ 'it_helpdesk_wf_level_approved',
935
+ { level: String(params.level), approverRoleName: approverEn, comment },
936
+ { level: String(params.level), approverRoleName: approverAr, comment },
937
+ );
938
+ }
939
+ case 'rejected':
940
+ case 'l3_step_rejected': {
941
+ const key =
942
+ params.variant === 'l3_step_rejected'
943
+ ? 'it_helpdesk_wf_l3_step_rejected'
944
+ : 'it_helpdesk_wf_rejected';
945
+ const roleEn = params.approverRoleName.trim();
946
+ const roleAr = resolveBilingualName(roleEn, params.approverRoleArabicName);
947
+ return renderSynchronizedWorkflowMessage(
948
+ key,
949
+ { approverRoleName: roleEn, roleName: roleEn, comment },
950
+ { approverRoleName: roleAr, roleName: roleAr, comment },
951
+ );
952
+ }
953
+ case 'generic_status': {
954
+ const approverEn = params.approverRoleName.trim();
955
+ const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
956
+ const statusEn = params.status.toLowerCase();
957
+ const statusAr = resolveChatStatusAr(params.status) ?? statusEn;
958
+ return renderSynchronizedWorkflowMessage(
959
+ 'it_helpdesk_wf_status_by_role',
960
+ { status: statusEn, statusAr, approverRoleName: approverEn, comment },
961
+ { status: statusEn, statusAr, approverRoleName: approverAr, comment },
962
+ );
963
+ }
964
+ case 'technician_completed': {
965
+ const roleEn = params.roleName.trim();
966
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
967
+ return renderSynchronizedWorkflowMessage(
968
+ 'it_helpdesk_wf_technician_completed',
969
+ { roleName: roleEn, comment },
970
+ { roleName: roleAr, comment },
971
+ );
972
+ }
973
+ case 'routing_rejected':
974
+ return renderSynchronizedWorkflowMessage(
975
+ 'it_helpdesk_wf_routing_rejected',
976
+ { comment },
977
+ { comment },
978
+ );
979
+ case 'routing_resolved':
980
+ return workflowTemplate('it_helpdesk_wf_routing_resolved');
981
+ default:
982
+ return workflowTemplate('request_submitted');
983
+ }
984
+ }
985
+
986
+ export type LogisticsActualReturnParams = {
987
+ requestId?: number | string | null;
988
+ requestTypeLabel?: string | null;
989
+ actual_return_date?: string | Date | null;
990
+ actual_vehicle_return_time?: string | null;
991
+ reason?: string | null;
992
+ };
993
+
994
+ export function buildLogisticsActualReturnChatMessage(params: {
995
+ roleName: string;
996
+ roleArabicName?: string | null;
997
+ returnDetails: LogisticsActualReturnParams;
998
+ }): BilingualText {
999
+ const roleEn = params.roleName.trim() || 'Unknown Role';
1000
+ const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
1001
+ const date = normalizeDisplayDate(params.returnDetails.actual_return_date);
1002
+ const time = normalizeDisplayValue(params.returnDetails.actual_vehicle_return_time);
1003
+ const reason = normalizeDisplayValue(params.returnDetails.reason);
1004
+ return renderSynchronizedChatMessage(
1005
+ 'logistics_actual_return_updated',
1006
+ { roleName: roleEn, date, time, reason },
1007
+ { roleName: roleAr, date, time, reason },
1008
+ );
1009
+ }
1010
+
1011
+ export function buildLogisticsActualReturnNotificationBody(
1012
+ params: LogisticsActualReturnParams,
1013
+ ): BilingualText {
1014
+ const requestLabel = params.requestTypeLabel
1015
+ ? `${params.requestTypeLabel} Request`
1016
+ : 'Vehicle Request';
1017
+ const requestLabelAr = params.requestTypeLabel
1018
+ ? `طلب ${params.requestTypeLabel}`
1019
+ : 'طلب المركبة';
1020
+ const requestId = normalizeDisplayValue(params.requestId);
1021
+ const en = [
1022
+ `${requestLabel} #${requestId}`,
1023
+ `Actual Return Date: ${normalizeDisplayDate(params.actual_return_date)}`,
1024
+ `Actual Return Time: ${normalizeDisplayValue(params.actual_vehicle_return_time)}`,
1025
+ `Reason: ${normalizeDisplayValue(params.reason)}`,
1026
+ ].join('\n');
1027
+ const ar = [
1028
+ `${requestLabelAr} رقم ${requestId}`,
1029
+ `تاريخ الإرجاع الفعلي: ${normalizeDisplayDate(params.actual_return_date)}`,
1030
+ `وقت الإرجاع الفعلي: ${normalizeDisplayValue(params.actual_vehicle_return_time)}`,
1031
+ `السبب: ${normalizeDisplayValue(params.reason)}`,
1032
+ ].join('\n');
1033
+ return { en, ar };
1034
+ }
1035
+
1036
+ export function buildLogisticsActualReturnNotificationTitle(
1037
+ params: LogisticsActualReturnParams,
1038
+ ): BilingualText {
1039
+ const requestLabel = params.requestTypeLabel || 'Vehicle';
1040
+ const requestId = String(params.requestId ?? '');
1041
+ return renderSynchronizedNotificationMessage(
1042
+ 'logistics_actual_return_title',
1043
+ { requestLabel, requestId },
1044
+ { requestLabel, requestId },
1045
+ );
1046
+ }
1047
+
1048
+ export function buildItHelpdeskCloseNotificationMessage(params: {
1049
+ requestId: number | string;
1050
+ status: 'Approved' | 'Rejected';
1051
+ }): BilingualText {
1052
+ const requestId = String(params.requestId);
1053
+ const key =
1054
+ params.status === 'Rejected'
1055
+ ? 'it_helpdesk_notif_request_rejected'
1056
+ : 'it_helpdesk_notif_request_closed';
1057
+ return renderSynchronizedNotificationMessage(key, { requestId }, { requestId });
1058
+ }
1059
+
393
1060
  // ─── Status labels ───────────────────────────────────────────────────────────
394
1061
 
395
1062
  export function resolveWorkflowStatusAr(status: string | null | undefined): string | null {
@@ -404,7 +1071,61 @@ export function resolveChatStatusAr(status: string | null | undefined): string |
404
1071
 
405
1072
  // ─── Legacy row enrichment (read API) ────────────────────────────────────────
406
1073
 
407
- export function resolveWorkflowContentAr(content: string | null | undefined): string | null {
1074
+ /** Pull bilingual actor fields from joined relations on workflow/chat rows. */
1075
+ export function extractBilingualActorsFromRecord(
1076
+ record: Record<string, unknown>,
1077
+ ): BilingualActorNames {
1078
+ const role = (record.approver_role ?? record.role) as Record<string, unknown> | undefined;
1079
+ const user = (record.approver_user ?? record.user) as Record<string, unknown> | undefined;
1080
+ const dept = (record.approver_department ?? record.department) as Record<string, unknown> | undefined;
1081
+ const section = (record.approver_section ?? record.section) as Record<string, unknown> | undefined;
1082
+
1083
+ return {
1084
+ roleName: role?.name != null ? String(role.name) : null,
1085
+ roleArabicName: role?.arabic_name != null ? String(role.arabic_name) : null,
1086
+ deptName: dept?.department_name != null ? String(dept.department_name) : null,
1087
+ deptArabicName: dept?.department_arabic_name != null ? String(dept.department_arabic_name) : null,
1088
+ sectionName: section?.section_name != null ? String(section.section_name) : null,
1089
+ sectionArabicName: section?.section_arabic_name != null ? String(section.section_arabic_name) : null,
1090
+ userName: user?.employee_name != null ? String(user.employee_name) : null,
1091
+ userArabicName: user?.employee_arabic_name != null ? String(user.employee_arabic_name) : null,
1092
+ employeeId: user?.employee_id != null ? String(user.employee_id) : null,
1093
+ };
1094
+ }
1095
+
1096
+ /** Arabic actor label for workflow templates (role, dept+section, or user). */
1097
+ function resolveArabicWorkflowActorName(
1098
+ actors: BilingualActorNames | undefined,
1099
+ parsedEnglishName?: string,
1100
+ ): string {
1101
+ if (actors) {
1102
+ const { ar } = resolveActorDisplayNames(actors);
1103
+ if (ar) return ar;
1104
+ if (actors.userName?.trim()) {
1105
+ return resolveBilingualName(actors.userName, actors.userArabicName);
1106
+ }
1107
+ }
1108
+ return parsedEnglishName?.trim() || '';
1109
+ }
1110
+
1111
+ /** Arabic role/user label for chat templates. */
1112
+ function resolveArabicChatRoleName(
1113
+ actors: BilingualActorNames | undefined,
1114
+ parsedEnglishRole?: string,
1115
+ ): string {
1116
+ if (actors?.roleName?.trim()) {
1117
+ return resolveBilingualName(actors.roleName, actors.roleArabicName);
1118
+ }
1119
+ if (actors?.userName?.trim()) {
1120
+ return resolveBilingualName(actors.userName, actors.userArabicName);
1121
+ }
1122
+ return resolveBilingualName(parsedEnglishRole, actors?.roleArabicName);
1123
+ }
1124
+
1125
+ export function resolveWorkflowContentAr(
1126
+ content: string | null | undefined,
1127
+ actors?: BilingualActorNames,
1128
+ ): string | null {
408
1129
  if (content == null || content.trim() === '') return null;
409
1130
  const normalized = content.trim();
410
1131
 
@@ -424,8 +1145,9 @@ export function resolveWorkflowContentAr(content: string | null | undefined): st
424
1145
  if (withoutComment.includes('{{name}}')) {
425
1146
  const staticPrefix = withoutComment.replace('{{name}}', '').trim();
426
1147
  if (baseEn.startsWith(staticPrefix)) {
427
- const namePart = baseEn.slice(staticPrefix.length).trim();
428
- return interpolate(tpl.ar, { name: namePart, comment: commentSuffix });
1148
+ const namePartEn = baseEn.slice(staticPrefix.length).trim();
1149
+ const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
1150
+ return interpolate(tpl.ar, { name: nameAr, comment: commentSuffix });
429
1151
  }
430
1152
  } else if (withoutComment.trim() === baseEn) {
431
1153
  return interpolate(tpl.ar, { comment: commentSuffix });
@@ -435,19 +1157,24 @@ export function resolveWorkflowContentAr(content: string | null | undefined): st
435
1157
 
436
1158
  const inProgressPrefix = WORKFLOW_TEMPLATES.request_approval_in_progress_from?.en.split('{{name}}')[0].trim();
437
1159
  if (inProgressPrefix && normalized.startsWith(inProgressPrefix)) {
438
- const namePart = normalized.slice(inProgressPrefix.length).trim();
439
- return interpolate(WORKFLOW_TEMPLATES.request_approval_in_progress_from.ar, { name: namePart });
1160
+ const namePartEn = normalized.slice(inProgressPrefix.length).trim();
1161
+ const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
1162
+ return interpolate(WORKFLOW_TEMPLATES.request_approval_in_progress_from.ar, { name: nameAr });
440
1163
  }
441
1164
  const pendingPrefix = WORKFLOW_TEMPLATES.request_approval_pending_from?.en.split('{{name}}')[0].trim();
442
1165
  if (pendingPrefix && normalized.startsWith(pendingPrefix)) {
443
- const namePart = normalized.slice(pendingPrefix.length).trim();
444
- return interpolate(WORKFLOW_TEMPLATES.request_approval_pending_from.ar, { name: namePart });
1166
+ const namePartEn = normalized.slice(pendingPrefix.length).trim();
1167
+ const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
1168
+ return interpolate(WORKFLOW_TEMPLATES.request_approval_pending_from.ar, { name: nameAr });
445
1169
  }
446
1170
 
447
1171
  return null;
448
1172
  }
449
1173
 
450
- export function resolveChatMessageAr(message: string | null | undefined): string | null {
1174
+ export function resolveChatMessageAr(
1175
+ message: string | null | undefined,
1176
+ actors?: BilingualActorNames,
1177
+ ): string | null {
451
1178
  if (message == null || message.trim() === '') return null;
452
1179
  const normalized = message.trim();
453
1180
 
@@ -455,19 +1182,33 @@ export function resolveChatMessageAr(message: string | null | undefined): string
455
1182
  if (tpl.en === normalized) return tpl.ar;
456
1183
  }
457
1184
 
1185
+ for (const tplKey of ['request_submitted_by', 'cancellation_submitted_by'] as const) {
1186
+ const submittedTpl = CHAT_TEMPLATES[tplKey];
1187
+ if (!submittedTpl) continue;
1188
+ const prefix = submittedTpl.en.split('{{userName}}')[0];
1189
+ if (!normalized.startsWith(prefix)) continue;
1190
+ const afterPrefix = normalized.slice(prefix.length);
1191
+ const parenMatch = afterPrefix.match(/^(.+?) \((.+)\)$/);
1192
+ if (parenMatch) {
1193
+ const userEn = parenMatch[1].trim();
1194
+ const employeeId = parenMatch[2].trim();
1195
+ const userAr = actors?.userName?.trim()
1196
+ ? resolveBilingualName(actors.userName, actors.userArabicName)
1197
+ : resolveBilingualName(userEn, actors?.userArabicName);
1198
+ return interpolate(submittedTpl.ar, { userName: userAr, employeeId });
1199
+ }
1200
+ }
1201
+
458
1202
  for (const tpl of Object.values(CHAT_TEMPLATES)) {
459
1203
  if (!tpl.en.includes('{{roleName}}')) continue;
460
1204
  const prefix = tpl.en.split('{{roleName}}')[0];
461
1205
  if (!normalized.startsWith(prefix)) continue;
462
1206
  const rest = normalized.slice(prefix.length);
463
1207
  const commentIdx = rest.lastIndexOf(' - ');
464
- if (commentIdx >= 0) {
465
- return interpolate(tpl.ar, {
466
- roleName: rest.slice(0, commentIdx),
467
- comment: rest.slice(commentIdx),
468
- });
469
- }
470
- return interpolate(tpl.ar, { roleName: rest, comment: '' });
1208
+ const roleNameEn = commentIdx >= 0 ? rest.slice(0, commentIdx) : rest;
1209
+ const commentSuffix = commentIdx >= 0 ? rest.slice(commentIdx) : '';
1210
+ const roleNameAr = resolveArabicChatRoleName(actors, roleNameEn.trim());
1211
+ return interpolate(tpl.ar, { roleName: roleNameAr, comment: commentSuffix });
471
1212
  }
472
1213
 
473
1214
  return null;
@@ -477,9 +1218,10 @@ export function resolveChatMessageAr(message: string | null | undefined): string
477
1218
  export function enrichWorkflowLog(log: Record<string, unknown>): Record<string, unknown> {
478
1219
  const status = log.status != null ? String(log.status) : null;
479
1220
  const content = log.content != null ? String(log.content) : null;
1221
+ const actors = extractBilingualActorsFromRecord(log);
480
1222
  return {
481
1223
  ...log,
482
- content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
1224
+ content_ar: log.content_ar ?? resolveWorkflowContentAr(content, actors),
483
1225
  status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
484
1226
  };
485
1227
  }
@@ -488,9 +1230,10 @@ export function enrichWorkflowLog(log: Record<string, unknown>): Record<string,
488
1230
  export function enrichChatMessage(chat: Record<string, unknown>): Record<string, unknown> {
489
1231
  const status = chat.status != null ? String(chat.status) : null;
490
1232
  const message = chat.message != null ? String(chat.message) : null;
1233
+ const actors = extractBilingualActorsFromRecord(chat);
491
1234
  return {
492
1235
  ...chat,
493
- message_ar: chat.message_ar ?? resolveChatMessageAr(message),
1236
+ message_ar: chat.message_ar ?? resolveChatMessageAr(message, actors),
494
1237
  status_ar: chat.status_ar ?? resolveChatStatusAr(status),
495
1238
  };
496
1239
  }