@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.
@@ -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 = resolveChatStatusAr(params.status) ?? statusEn;
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
- return resolveBilingualName(actors.roleName, actors.roleArabicName);
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(parsedEnglishRole, actors?.roleArabicName);
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 (!tpl.en.includes('{{roleName}}')) continue;
1204
- const prefix = tpl.en.split('{{roleName}}')[0];
1205
- if (!normalized.startsWith(prefix)) continue;
1206
- const rest = normalized.slice(prefix.length);
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: log.content_ar ?? resolveWorkflowContentAr(content, actors),
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';export * from './models/SlaConfigModel';
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
  }