@platform-modules/civil-aviation-authority 2.3.291 → 2.3.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data-source.js +4 -0
- package/dist/i18n/workflow-chat-i18n.json +54 -42
- package/dist/i18n/workflow-chat-message.builder.d.ts +21 -0
- package/dist/i18n/workflow-chat-message.builder.js +270 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/models/ContractServiceRequestDocumentModel.d.ts +13 -0
- package/dist/models/ContractServiceRequestDocumentModel.js +60 -0
- package/dist/models/ContractServiceRequestModel.d.ts +2 -0
- package/dist/models/ContractServiceRequestModel.js +8 -0
- package/dist/models/FollowUpReportItemModel.d.ts +20 -0
- package/dist/models/FollowUpReportItemModel.js +90 -0
- package/dist/models/FollowUpReportRequestModel.d.ts +12 -1
- package/dist/models/FollowUpReportRequestModel.js +27 -1
- package/dist/models/RespondToEnquiriesAttachmentModel.d.ts +2 -1
- package/dist/models/RespondToEnquiriesAttachmentModel.js +7 -2
- package/package.json +1 -1
- package/sql/caa_api_payload_changes_2026_07.sql +40 -0
- package/src/data-source.ts +4 -0
- package/src/i18n/workflow-chat-i18n.json +54 -42
- package/src/i18n/workflow-chat-message.builder.ts +333 -31
- package/src/index.ts +4 -1
- package/src/models/ContractServiceRequestDocumentModel.ts +35 -0
- package/src/models/ContractServiceRequestModel.ts +6 -0
- package/src/models/FollowUpReportItemModel.ts +59 -0
- package/src/models/FollowUpReportRequestModel.ts +24 -0
- package/src/models/RespondToEnquiriesAttachmentModel.ts +6 -1
|
@@ -344,6 +344,81 @@ export function buildSynchronizedChatMessage(ctx: ChatMessageContext): Bilingual
|
|
|
344
344
|
|
|
345
345
|
// ─── Backward-compatible aliases used by Cancellation pilot ───────────────────
|
|
346
346
|
|
|
347
|
+
export type RequestApprovedAssignedWorkflowParams = {
|
|
348
|
+
assignedUserName: string;
|
|
349
|
+
roleName: string;
|
|
350
|
+
roleArabicName?: string | null;
|
|
351
|
+
actorUserName: string;
|
|
352
|
+
employeeId: string | number;
|
|
353
|
+
assigneeLabel?: 'user' | 'technician';
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/** Parse legacy English-only assignment comments used as workflow log suffixes. */
|
|
357
|
+
export function tryParseRequestAssignedComment(
|
|
358
|
+
comment?: string | null,
|
|
359
|
+
): Omit<RequestApprovedAssignedWorkflowParams, 'roleArabicName'> | null {
|
|
360
|
+
const text = comment?.trim();
|
|
361
|
+
if (!text) return null;
|
|
362
|
+
|
|
363
|
+
const technicianMatch = text.match(
|
|
364
|
+
/^Request assigned to technician (.+?) by (.+?) \((.+?) - (.+?)\)$/,
|
|
365
|
+
);
|
|
366
|
+
if (technicianMatch) {
|
|
367
|
+
return {
|
|
368
|
+
assignedUserName: technicianMatch[1].trim(),
|
|
369
|
+
roleName: technicianMatch[2].trim(),
|
|
370
|
+
actorUserName: technicianMatch[3].trim(),
|
|
371
|
+
employeeId: technicianMatch[4].trim(),
|
|
372
|
+
assigneeLabel: 'technician',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const userMatch = text.match(/^Request assigned to (.+?) by (.+?) \((.+?) - (.+?)\)$/);
|
|
377
|
+
if (userMatch) {
|
|
378
|
+
return {
|
|
379
|
+
assignedUserName: userMatch[1].trim(),
|
|
380
|
+
roleName: userMatch[2].trim(),
|
|
381
|
+
actorUserName: userMatch[3].trim(),
|
|
382
|
+
employeeId: userMatch[4].trim(),
|
|
383
|
+
assigneeLabel: 'user',
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Bilingual workflow content for approved assignment steps (HR, IT, Tender, etc.). */
|
|
391
|
+
export function buildRequestApprovedAssignedWorkflowFields(
|
|
392
|
+
params: RequestApprovedAssignedWorkflowParams,
|
|
393
|
+
): BilingualText {
|
|
394
|
+
const assignedUserEn = params.assignedUserName?.trim() || 'User';
|
|
395
|
+
const roleEn = params.roleName?.trim() || 'Approver';
|
|
396
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
397
|
+
const actorEn = params.actorUserName?.trim() || 'User';
|
|
398
|
+
const employeeId = String(params.employeeId ?? 'N/A');
|
|
399
|
+
|
|
400
|
+
const templateKey =
|
|
401
|
+
params.assigneeLabel === 'technician'
|
|
402
|
+
? 'request_approved_assigned_to_technician'
|
|
403
|
+
: 'request_approved_assigned_to_user';
|
|
404
|
+
|
|
405
|
+
return renderSynchronizedWorkflowMessage(
|
|
406
|
+
templateKey,
|
|
407
|
+
{
|
|
408
|
+
assignedUserName: assignedUserEn,
|
|
409
|
+
roleName: roleEn,
|
|
410
|
+
actorUserName: actorEn,
|
|
411
|
+
employeeId,
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
assignedUserName: assignedUserEn,
|
|
415
|
+
roleName: roleAr,
|
|
416
|
+
actorUserName: actorEn,
|
|
417
|
+
employeeId,
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
347
422
|
/** Standard workflow log UPDATE fields for all CAA module repositories. */
|
|
348
423
|
export function buildWorkflowLogUpdateFields(params: {
|
|
349
424
|
approvalStatus: string;
|
|
@@ -352,6 +427,23 @@ export function buildWorkflowLogUpdateFields(params: {
|
|
|
352
427
|
roleArabicName?: string | null;
|
|
353
428
|
comment?: string | null;
|
|
354
429
|
}): { content: string; content_ar: string; status: string; status_ar: string | null } {
|
|
430
|
+
if (params.approvalStatus === 'Approved') {
|
|
431
|
+
const assignment = tryParseRequestAssignedComment(params.comment);
|
|
432
|
+
if (assignment) {
|
|
433
|
+
const bilingual = buildRequestApprovedAssignedWorkflowFields({
|
|
434
|
+
...assignment,
|
|
435
|
+
roleName: params.roleName?.trim() || assignment.roleName,
|
|
436
|
+
roleArabicName: params.roleArabicName,
|
|
437
|
+
});
|
|
438
|
+
return {
|
|
439
|
+
content: bilingual.en,
|
|
440
|
+
content_ar: bilingual.ar,
|
|
441
|
+
status: params.workflowStatus,
|
|
442
|
+
status_ar: resolveWorkflowStatusAr(params.workflowStatus),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
355
447
|
let action: WorkflowMessageAction;
|
|
356
448
|
if (params.approvalStatus === 'Approved') action = 'approved';
|
|
357
449
|
else if (params.approvalStatus === 'Rejected') action = 'rejected';
|
|
@@ -399,6 +491,19 @@ export function toChatPersistFields(
|
|
|
399
491
|
};
|
|
400
492
|
}
|
|
401
493
|
|
|
494
|
+
/** User-typed chat — never auto-translate; message_ar stays null. */
|
|
495
|
+
export function buildUserEnteredChatPersistFields(
|
|
496
|
+
message: string,
|
|
497
|
+
status: string,
|
|
498
|
+
): { message: string; message_ar: null; status: string; status_ar: string | null } {
|
|
499
|
+
return {
|
|
500
|
+
message,
|
|
501
|
+
message_ar: null,
|
|
502
|
+
status,
|
|
503
|
+
status_ar: resolveChatStatusAr(status),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
402
507
|
export function buildApprovalWorkflowContent(params: {
|
|
403
508
|
status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
|
|
404
509
|
roleName?: string | null;
|
|
@@ -832,6 +937,23 @@ export type ItHelpdeskMuscatWorkflowParams =
|
|
|
832
937
|
}
|
|
833
938
|
| { variant: 'routing_resolved' };
|
|
834
939
|
|
|
940
|
+
function resolveItHelpdeskGenericStatusAr(status: string): string {
|
|
941
|
+
switch (status.trim()) {
|
|
942
|
+
case 'Approved':
|
|
943
|
+
return 'تمت الموافقة على';
|
|
944
|
+
case 'Rejected':
|
|
945
|
+
return 'تم رفض';
|
|
946
|
+
case 'Closed':
|
|
947
|
+
return 'تم إغلاق';
|
|
948
|
+
case 'Assigned':
|
|
949
|
+
return 'تم تعيين';
|
|
950
|
+
case 'Reassigned':
|
|
951
|
+
return 'تمت إعادة تعيين';
|
|
952
|
+
default:
|
|
953
|
+
return resolveChatStatusAr(status) ?? status.toLowerCase();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
835
957
|
export function buildItHelpdeskMuscatWorkflowLog(params: ItHelpdeskMuscatWorkflowParams): BilingualText {
|
|
836
958
|
const comment = formatColonCommentSuffix(
|
|
837
959
|
'comment' in params ? params.comment : undefined,
|
|
@@ -954,7 +1076,7 @@ export function buildItHelpdeskMuscatWorkflowLog(params: ItHelpdeskMuscatWorkflo
|
|
|
954
1076
|
const approverEn = params.approverRoleName.trim();
|
|
955
1077
|
const approverAr = resolveBilingualName(approverEn, params.approverRoleArabicName);
|
|
956
1078
|
const statusEn = params.status.toLowerCase();
|
|
957
|
-
const statusAr =
|
|
1079
|
+
const statusAr = resolveItHelpdeskGenericStatusAr(params.status);
|
|
958
1080
|
return renderSynchronizedWorkflowMessage(
|
|
959
1081
|
'it_helpdesk_wf_status_by_role',
|
|
960
1082
|
{ status: statusEn, statusAr, approverRoleName: approverEn, comment },
|
|
@@ -1113,13 +1235,130 @@ function resolveArabicChatRoleName(
|
|
|
1113
1235
|
actors: BilingualActorNames | undefined,
|
|
1114
1236
|
parsedEnglishRole?: string,
|
|
1115
1237
|
): string {
|
|
1238
|
+
const parsed = parsedEnglishRole?.trim();
|
|
1116
1239
|
if (actors?.roleName?.trim()) {
|
|
1117
|
-
|
|
1240
|
+
const actorRole = actors.roleName.trim();
|
|
1241
|
+
if (!parsed || parsed.toLowerCase() === actorRole.toLowerCase()) {
|
|
1242
|
+
return resolveBilingualName(actorRole, actors.roleArabicName);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (actors?.roleArabicName?.trim() && parsed) {
|
|
1246
|
+
return resolveBilingualName(parsed, actors.roleArabicName);
|
|
1118
1247
|
}
|
|
1119
1248
|
if (actors?.userName?.trim()) {
|
|
1120
1249
|
return resolveBilingualName(actors.userName, actors.userArabicName);
|
|
1121
1250
|
}
|
|
1122
|
-
return resolveBilingualName(
|
|
1251
|
+
return resolveBilingualName(parsed, actors?.roleArabicName);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function escapeRegexLiteral(value: string): string {
|
|
1255
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/** Match a templated EN message and extract placeholder values (full-structure match). */
|
|
1259
|
+
function matchTemplateMessage(
|
|
1260
|
+
templateEn: string,
|
|
1261
|
+
message: string,
|
|
1262
|
+
): Record<string, string> | null {
|
|
1263
|
+
if (!/\{\{\w+\}\}/.test(templateEn)) return null;
|
|
1264
|
+
|
|
1265
|
+
const captures: Array<{ key: string; groupIndex: number }> = [];
|
|
1266
|
+
let regexBody = '';
|
|
1267
|
+
let cursor = 0;
|
|
1268
|
+
let groupIndex = 0;
|
|
1269
|
+
const tokenRe = /\{\{(\w+)\}\}/g;
|
|
1270
|
+
let tokenMatch: RegExpExecArray | null;
|
|
1271
|
+
|
|
1272
|
+
while ((tokenMatch = tokenRe.exec(templateEn)) !== null) {
|
|
1273
|
+
regexBody += escapeRegexLiteral(templateEn.slice(cursor, tokenMatch.index));
|
|
1274
|
+
const key = tokenMatch[1];
|
|
1275
|
+
const afterToken = templateEn.slice(tokenMatch.index + tokenMatch[0].length);
|
|
1276
|
+
|
|
1277
|
+
if (key === 'comment') {
|
|
1278
|
+
groupIndex += 1;
|
|
1279
|
+
captures.push({ key, groupIndex });
|
|
1280
|
+
regexBody += '(.*)';
|
|
1281
|
+
cursor = tokenMatch.index + tokenMatch[0].length;
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
if (afterToken.startsWith('{{comment}}')) {
|
|
1286
|
+
groupIndex += 1;
|
|
1287
|
+
captures.push({ key, groupIndex });
|
|
1288
|
+
groupIndex += 1;
|
|
1289
|
+
captures.push({ key: 'comment', groupIndex });
|
|
1290
|
+
regexBody += '([^:]+?)(: .*)?';
|
|
1291
|
+
tokenRe.lastIndex = tokenMatch.index + tokenMatch[0].length + '{{comment}}'.length;
|
|
1292
|
+
cursor = tokenRe.lastIndex;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
groupIndex += 1;
|
|
1297
|
+
captures.push({ key, groupIndex });
|
|
1298
|
+
regexBody += '(.+?)';
|
|
1299
|
+
cursor = tokenMatch.index + tokenMatch[0].length;
|
|
1300
|
+
}
|
|
1301
|
+
regexBody += escapeRegexLiteral(templateEn.slice(cursor));
|
|
1302
|
+
|
|
1303
|
+
const match = message.match(new RegExp(`^${regexBody}$`, 's'));
|
|
1304
|
+
if (!match) return null;
|
|
1305
|
+
|
|
1306
|
+
const params: Record<string, string> = {};
|
|
1307
|
+
captures.forEach(({ key, groupIndex: idx }) => {
|
|
1308
|
+
params[key] = match[idx] ?? '';
|
|
1309
|
+
});
|
|
1310
|
+
return params;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function buildArabicChatTemplateParams(
|
|
1314
|
+
params: Record<string, string>,
|
|
1315
|
+
actors?: BilingualActorNames,
|
|
1316
|
+
): Record<string, string> {
|
|
1317
|
+
const arParams: Record<string, string> = { ...params };
|
|
1318
|
+
if (params.roleName !== undefined) {
|
|
1319
|
+
arParams.roleName = resolveArabicChatRoleName(actors, params.roleName.trim());
|
|
1320
|
+
}
|
|
1321
|
+
if (params.userName !== undefined) {
|
|
1322
|
+
arParams.userName = actors?.userName?.trim()
|
|
1323
|
+
? resolveBilingualName(actors.userName, actors.userArabicName)
|
|
1324
|
+
: resolveBilingualName(params.userName.trim(), actors?.userArabicName);
|
|
1325
|
+
}
|
|
1326
|
+
return arParams;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function buildArabicWorkflowTemplateParams(
|
|
1330
|
+
params: Record<string, string>,
|
|
1331
|
+
actors?: BilingualActorNames,
|
|
1332
|
+
): Record<string, string> {
|
|
1333
|
+
const arParams: Record<string, string> = { ...params };
|
|
1334
|
+
if (params.roleName !== undefined) {
|
|
1335
|
+
arParams.roleName = resolveArabicChatRoleName(actors, params.roleName.trim());
|
|
1336
|
+
}
|
|
1337
|
+
if (params.approverRoleName !== undefined) {
|
|
1338
|
+
arParams.approverRoleName = resolveArabicChatRoleName(actors, params.approverRoleName.trim());
|
|
1339
|
+
}
|
|
1340
|
+
if (params.assignedRoleName !== undefined) {
|
|
1341
|
+
arParams.assignedRoleName = resolveArabicChatRoleName(actors, params.assignedRoleName.trim());
|
|
1342
|
+
}
|
|
1343
|
+
if (params.name !== undefined) {
|
|
1344
|
+
arParams.name = resolveArabicWorkflowActorName(actors, params.name.trim());
|
|
1345
|
+
}
|
|
1346
|
+
if (params.status !== undefined && params.statusAr === undefined) {
|
|
1347
|
+
const normalizedStatus =
|
|
1348
|
+
params.status.charAt(0).toUpperCase() + params.status.slice(1).toLowerCase();
|
|
1349
|
+
arParams.statusAr = resolveItHelpdeskGenericStatusAr(normalizedStatus);
|
|
1350
|
+
}
|
|
1351
|
+
return arParams;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function countTemplatePlaceholders(templateEn: string): number {
|
|
1355
|
+
return (templateEn.match(/\{\{\w+\}\}/g) || []).length;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function listWorkflowTemplatesBySpecificity(): Array<[string, BilingualText]> {
|
|
1359
|
+
return Object.entries(WORKFLOW_TEMPLATES).sort(
|
|
1360
|
+
([, a], [, b]) => countTemplatePlaceholders(b.en) - countTemplatePlaceholders(a.en),
|
|
1361
|
+
);
|
|
1123
1362
|
}
|
|
1124
1363
|
|
|
1125
1364
|
export function resolveWorkflowContentAr(
|
|
@@ -1130,7 +1369,14 @@ export function resolveWorkflowContentAr(
|
|
|
1130
1369
|
const normalized = content.trim();
|
|
1131
1370
|
|
|
1132
1371
|
for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
|
|
1133
|
-
if (tpl.en === normalized) return tpl.ar;
|
|
1372
|
+
if (tpl.en === normalized || tpl.en.toLowerCase() === normalized.toLowerCase()) return tpl.ar;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
for (const [, tpl] of listWorkflowTemplatesBySpecificity()) {
|
|
1376
|
+
if (!/\{\{\w+\}\}/.test(tpl.en)) continue;
|
|
1377
|
+
const params = matchTemplateMessage(tpl.en, normalized);
|
|
1378
|
+
if (!params) continue;
|
|
1379
|
+
return interpolate(tpl.ar, buildArabicWorkflowTemplateParams(params, actors));
|
|
1134
1380
|
}
|
|
1135
1381
|
|
|
1136
1382
|
const commentIdx = normalized.indexOf(': ');
|
|
@@ -1168,6 +1414,31 @@ export function resolveWorkflowContentAr(
|
|
|
1168
1414
|
return interpolate(WORKFLOW_TEMPLATES.request_approval_pending_from.ar, { name: nameAr });
|
|
1169
1415
|
}
|
|
1170
1416
|
|
|
1417
|
+
// Legacy HR worker strings (pre-i18n catalog)
|
|
1418
|
+
if (normalized === 'Send Notification') {
|
|
1419
|
+
return WORKFLOW_TEMPLATES.notification_sent_pending?.ar ?? null;
|
|
1420
|
+
}
|
|
1421
|
+
const notificationSuccessVariants = [
|
|
1422
|
+
'notification sent successfully',
|
|
1423
|
+
'notification sent sucessfully',
|
|
1424
|
+
'notification sent',
|
|
1425
|
+
];
|
|
1426
|
+
if (notificationSuccessVariants.includes(normalized.toLowerCase())) {
|
|
1427
|
+
return WORKFLOW_TEMPLATES.notification_sent_successfully?.ar ?? null;
|
|
1428
|
+
}
|
|
1429
|
+
const legacyPendingPrefix = 'Approval pending from ';
|
|
1430
|
+
if (normalized.startsWith(legacyPendingPrefix)) {
|
|
1431
|
+
const namePartEn = normalized.slice(legacyPendingPrefix.length).trim();
|
|
1432
|
+
const nameAr = resolveArabicWorkflowActorName(actors, namePartEn);
|
|
1433
|
+
return interpolate(WORKFLOW_TEMPLATES.request_approval_pending_from.ar, { name: nameAr });
|
|
1434
|
+
}
|
|
1435
|
+
if (
|
|
1436
|
+
normalized === 'Approval Pending from HR Section' ||
|
|
1437
|
+
normalized === 'Approval Pending from Records Department'
|
|
1438
|
+
) {
|
|
1439
|
+
return WORKFLOW_TEMPLATES.request_approval_pending?.ar ?? null;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1171
1442
|
return null;
|
|
1172
1443
|
}
|
|
1173
1444
|
|
|
@@ -1182,46 +1453,77 @@ export function resolveChatMessageAr(
|
|
|
1182
1453
|
if (tpl.en === normalized) return tpl.ar;
|
|
1183
1454
|
}
|
|
1184
1455
|
|
|
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
|
-
|
|
1202
1456
|
for (const tpl of Object.values(CHAT_TEMPLATES)) {
|
|
1203
|
-
if (
|
|
1204
|
-
const
|
|
1205
|
-
if (!
|
|
1206
|
-
|
|
1207
|
-
const commentIdx = rest.lastIndexOf(' - ');
|
|
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 });
|
|
1457
|
+
if (!/\{\{\w+\}\}/.test(tpl.en)) continue;
|
|
1458
|
+
const params = matchTemplateMessage(tpl.en, normalized);
|
|
1459
|
+
if (!params) continue;
|
|
1460
|
+
return interpolate(tpl.ar, buildArabicChatTemplateParams(params, actors));
|
|
1212
1461
|
}
|
|
1213
1462
|
|
|
1214
1463
|
return null;
|
|
1215
1464
|
}
|
|
1216
1465
|
|
|
1466
|
+
/** True when message matches a known system chat template (safe to resolve message_ar). */
|
|
1467
|
+
export function isSystemGeneratedChatMessage(
|
|
1468
|
+
message: string | null | undefined,
|
|
1469
|
+
actors?: BilingualActorNames,
|
|
1470
|
+
): boolean {
|
|
1471
|
+
return resolveChatMessageAr(message, actors) != null;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1217
1474
|
/** Generic workflow log enricher — use in all module repositories. */
|
|
1218
1475
|
export function enrichWorkflowLog(log: Record<string, unknown>): Record<string, unknown> {
|
|
1219
1476
|
const status = log.status != null ? String(log.status) : null;
|
|
1220
1477
|
const content = log.content != null ? String(log.content) : null;
|
|
1221
1478
|
const actors = extractBilingualActorsFromRecord(log);
|
|
1479
|
+
const resolvedAr = resolveWorkflowContentAr(content, actors);
|
|
1480
|
+
const existingAr =
|
|
1481
|
+
log.content_ar != null && String(log.content_ar).trim() !== ''
|
|
1482
|
+
? String(log.content_ar)
|
|
1483
|
+
: null;
|
|
1484
|
+
// Re-resolve when stored AR still embeds the English actor parsed from `content`.
|
|
1485
|
+
const pendingPrefix = WORKFLOW_TEMPLATES.request_approval_pending_from?.en.split('{{name}}')[0].trim();
|
|
1486
|
+
let content_ar = existingAr ?? resolvedAr;
|
|
1487
|
+
if (
|
|
1488
|
+
content &&
|
|
1489
|
+
pendingPrefix &&
|
|
1490
|
+
content.startsWith(pendingPrefix) &&
|
|
1491
|
+
existingAr &&
|
|
1492
|
+
resolvedAr &&
|
|
1493
|
+
existingAr !== resolvedAr
|
|
1494
|
+
) {
|
|
1495
|
+
const namePartEn = content.slice(pendingPrefix.length).trim();
|
|
1496
|
+
if (namePartEn && existingAr.includes(namePartEn)) {
|
|
1497
|
+
content_ar = resolvedAr;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (
|
|
1501
|
+
content &&
|
|
1502
|
+
existingAr &&
|
|
1503
|
+
resolvedAr &&
|
|
1504
|
+
existingAr !== resolvedAr &&
|
|
1505
|
+
/Request assigned to/i.test(existingAr)
|
|
1506
|
+
) {
|
|
1507
|
+
content_ar = resolvedAr;
|
|
1508
|
+
}
|
|
1509
|
+
if (
|
|
1510
|
+
content &&
|
|
1511
|
+
existingAr &&
|
|
1512
|
+
resolvedAr &&
|
|
1513
|
+
existingAr !== resolvedAr &&
|
|
1514
|
+
actors?.roleName?.trim() &&
|
|
1515
|
+
actors?.roleArabicName?.trim() &&
|
|
1516
|
+
existingAr.includes(actors.roleName.trim())
|
|
1517
|
+
) {
|
|
1518
|
+
content_ar = resolvedAr;
|
|
1519
|
+
}
|
|
1520
|
+
if ((content_ar == null || content_ar === '') && content) {
|
|
1521
|
+
const notificationAr = resolveWorkflowContentAr(content, actors);
|
|
1522
|
+
if (notificationAr) content_ar = notificationAr;
|
|
1523
|
+
}
|
|
1222
1524
|
return {
|
|
1223
1525
|
...log,
|
|
1224
|
-
content_ar
|
|
1526
|
+
content_ar,
|
|
1225
1527
|
status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
|
|
1226
1528
|
};
|
|
1227
1529
|
}
|
package/src/index.ts
CHANGED
|
@@ -425,6 +425,7 @@ export * from './models/MaintenanceAttachmentModel';
|
|
|
425
425
|
export * from './models/MaintenanceChatModel';
|
|
426
426
|
export * from './models/MaintenanceWorkflowModel';
|
|
427
427
|
export * from './models/FollowUpReportRequestModel';
|
|
428
|
+
export * from './models/FollowUpReportItemModel';
|
|
428
429
|
export * from './models/FollowUpReportApprovalModel';
|
|
429
430
|
export * from './models/FollowUpReportAttachmentModel';
|
|
430
431
|
export * from './models/FollowUpReportChatModel';
|
|
@@ -497,7 +498,9 @@ export * from './models/RespondToEnquiriesAttachmentModel';
|
|
|
497
498
|
export {RespondToEnquiriesRequestAttachment} from './models/RespondToEnquiriesAttachmentModel';
|
|
498
499
|
export * from './models/RequestATenderRequestModel';
|
|
499
500
|
export * from './models/RequestTenderAnalysisRequestModel';
|
|
500
|
-
export * from './models/ContractServiceRequestModel';
|
|
501
|
+
export * from './models/ContractServiceRequestModel';
|
|
502
|
+
export * from './models/ContractServiceRequestDocumentModel';
|
|
503
|
+
export * from './models/SlaConfigModel';
|
|
501
504
|
export * from './models/SlaRequestModel';
|
|
502
505
|
export * from './models/SlaMyRequestsViewModel';
|
|
503
506
|
export * from './models/SlaApprovalsViewModel';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Column, Entity } from 'typeorm';
|
|
2
|
+
import { BaseModel } from './BaseModel';
|
|
3
|
+
|
|
4
|
+
@Entity({ name: 'contract_service_request_documents' })
|
|
5
|
+
export class ContractServiceRequestDocument extends BaseModel {
|
|
6
|
+
@Column({ type: 'integer', nullable: false })
|
|
7
|
+
request_id: number;
|
|
8
|
+
|
|
9
|
+
@Column({ type: 'integer', nullable: true })
|
|
10
|
+
service_id: number | null;
|
|
11
|
+
|
|
12
|
+
@Column({ type: 'integer', nullable: true })
|
|
13
|
+
sub_service_id: number | null;
|
|
14
|
+
|
|
15
|
+
@Column({ type: 'varchar', length: 500, nullable: false })
|
|
16
|
+
document_name: string;
|
|
17
|
+
|
|
18
|
+
@Column({ type: 'text', nullable: true })
|
|
19
|
+
description: string | null;
|
|
20
|
+
|
|
21
|
+
@Column({ type: 'varchar', length: 500, nullable: false })
|
|
22
|
+
file_url: string;
|
|
23
|
+
|
|
24
|
+
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
25
|
+
file_name: string | null;
|
|
26
|
+
|
|
27
|
+
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
28
|
+
file_type: string | null;
|
|
29
|
+
|
|
30
|
+
@Column({ type: 'bigint', nullable: true })
|
|
31
|
+
file_size: number | null;
|
|
32
|
+
|
|
33
|
+
@Column({ type: 'integer', nullable: false })
|
|
34
|
+
uploaded_by: number;
|
|
35
|
+
}
|
|
@@ -43,6 +43,12 @@ export class ContractServiceRequest extends BaseModel {
|
|
|
43
43
|
@Column({ type: "varchar", length: 255, nullable: false })
|
|
44
44
|
company_name: string;
|
|
45
45
|
|
|
46
|
+
@Column({ type: "varchar", length: 30, nullable: true })
|
|
47
|
+
company_phone_number: string | null;
|
|
48
|
+
|
|
49
|
+
@Column({ type: "text", nullable: true })
|
|
50
|
+
notes: string | null;
|
|
51
|
+
|
|
46
52
|
@Column({
|
|
47
53
|
type: "enum",
|
|
48
54
|
enum: RespondToEnquiriesRequestStatus,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Column, Entity } from 'typeorm';
|
|
2
|
+
import { BaseModel } from './BaseModel';
|
|
3
|
+
import { FollowUpReportSubjectClassification } from './FollowUpReportRequestModel';
|
|
4
|
+
|
|
5
|
+
@Entity({ name: 'followup_report_items' })
|
|
6
|
+
export class FollowUpReportItem extends BaseModel {
|
|
7
|
+
@Column({ type: 'integer', nullable: false })
|
|
8
|
+
request_id: number;
|
|
9
|
+
|
|
10
|
+
@Column({ type: 'integer', nullable: true })
|
|
11
|
+
service_id: number | null;
|
|
12
|
+
|
|
13
|
+
@Column({ type: 'integer', nullable: true })
|
|
14
|
+
sub_service_id: number | null;
|
|
15
|
+
|
|
16
|
+
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
17
|
+
reference_type: string | null;
|
|
18
|
+
|
|
19
|
+
@Column({ type: 'date', nullable: true })
|
|
20
|
+
letter_date: Date | null;
|
|
21
|
+
|
|
22
|
+
@Column({ type: 'varchar', length: 500, nullable: true })
|
|
23
|
+
subject: string | null;
|
|
24
|
+
|
|
25
|
+
@Column({
|
|
26
|
+
type: 'enum',
|
|
27
|
+
enum: FollowUpReportSubjectClassification,
|
|
28
|
+
enumName: 'maintenance_subject_classification_en',
|
|
29
|
+
nullable: true,
|
|
30
|
+
})
|
|
31
|
+
subject_classification: FollowUpReportSubjectClassification | null;
|
|
32
|
+
|
|
33
|
+
@Column({ type: 'varchar', length: 500, nullable: true })
|
|
34
|
+
general_manager_comment: string | null;
|
|
35
|
+
|
|
36
|
+
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
37
|
+
response_date_target: string | null;
|
|
38
|
+
|
|
39
|
+
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
40
|
+
action_status: string | null;
|
|
41
|
+
|
|
42
|
+
@Column({ type: 'text', nullable: true })
|
|
43
|
+
action_taken: string | null;
|
|
44
|
+
|
|
45
|
+
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
46
|
+
delay_period: string | null;
|
|
47
|
+
|
|
48
|
+
@Column({ type: 'varchar', length: 500, nullable: true })
|
|
49
|
+
file_url: string | null;
|
|
50
|
+
|
|
51
|
+
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
52
|
+
file_name: string | null;
|
|
53
|
+
|
|
54
|
+
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
55
|
+
file_type: string | null;
|
|
56
|
+
|
|
57
|
+
@Column({ type: 'bigint', nullable: true })
|
|
58
|
+
file_size: number | null;
|
|
59
|
+
}
|
|
@@ -8,8 +8,17 @@ export enum FollowUpReportSubjectClassification {
|
|
|
8
8
|
CONFIDENTIAL = 'Confidential',
|
|
9
9
|
INFORMATIONAL = 'Informational',
|
|
10
10
|
NORMAL = 'Normal',
|
|
11
|
+
IMPORTANT = 'Important',
|
|
12
|
+
HIGHLY_CONFIDENTIAL = 'Highly Confidential',
|
|
13
|
+
RESTRICTED = 'Restricted',
|
|
14
|
+
LIMITED = 'Limited',
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
/** Allowed API/DB values for follow-up report subject classification. */
|
|
18
|
+
export const FOLLOW_UP_REPORT_SUBJECT_CLASSIFICATION_VALUES = Object.values(
|
|
19
|
+
FollowUpReportSubjectClassification,
|
|
20
|
+
) as FollowUpReportSubjectClassification[];
|
|
21
|
+
|
|
13
22
|
/** Concerned department under General Manager of Support Services */
|
|
14
23
|
export enum FollowUpReportConcernedDepartment {
|
|
15
24
|
IT = 'IT',
|
|
@@ -78,6 +87,21 @@ export class FollowUpReportRequests extends BaseModel {
|
|
|
78
87
|
@Column({ type: 'date', nullable: false })
|
|
79
88
|
date_to: Date;
|
|
80
89
|
|
|
90
|
+
@Column({ type: 'varchar', length: 500, nullable: true })
|
|
91
|
+
general_manager_comment: string | null;
|
|
92
|
+
|
|
93
|
+
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
94
|
+
response_date_target: string | null;
|
|
95
|
+
|
|
96
|
+
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
97
|
+
action_status: string | null;
|
|
98
|
+
|
|
99
|
+
@Column({ type: 'text', nullable: true })
|
|
100
|
+
action_taken: string | null;
|
|
101
|
+
|
|
102
|
+
@Column({ type: 'varchar', length: 100, nullable: true })
|
|
103
|
+
delay_period: string | null;
|
|
104
|
+
|
|
81
105
|
@Column({ type: 'date', nullable: false, default: () => 'CURRENT_DATE' })
|
|
82
106
|
request_submission_date: Date;
|
|
83
107
|
|
|
@@ -30,6 +30,9 @@ export class RespondToEnquiriesRequestAttachment extends BaseModel {
|
|
|
30
30
|
@Column({ type: "text", nullable: true })
|
|
31
31
|
description: string;
|
|
32
32
|
|
|
33
|
+
@Column({ type: "varchar", length: 500, nullable: true })
|
|
34
|
+
document_name: string | null;
|
|
35
|
+
|
|
33
36
|
constructor(
|
|
34
37
|
request_id: number,
|
|
35
38
|
uploaded_by: number,
|
|
@@ -39,7 +42,8 @@ export class RespondToEnquiriesRequestAttachment extends BaseModel {
|
|
|
39
42
|
file_name?: string,
|
|
40
43
|
file_type?: string,
|
|
41
44
|
file_size?: number,
|
|
42
|
-
description?: string
|
|
45
|
+
description?: string,
|
|
46
|
+
document_name?: string
|
|
43
47
|
) {
|
|
44
48
|
super();
|
|
45
49
|
this.request_id = request_id;
|
|
@@ -51,5 +55,6 @@ export class RespondToEnquiriesRequestAttachment extends BaseModel {
|
|
|
51
55
|
this.file_type = file_type || "";
|
|
52
56
|
this.file_size = file_size || null;
|
|
53
57
|
this.description = description || "";
|
|
58
|
+
this.document_name = document_name || null;
|
|
54
59
|
}
|
|
55
60
|
}
|