@platform-modules/civil-aviation-authority 2.3.309 → 2.3.311

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 (34) hide show
  1. package/dist/data-source.js +2 -0
  2. package/dist/i18n/workflow-chat-i18n.json +8 -0
  3. package/dist/i18n/workflow-chat-message.builder.d.ts +1 -1
  4. package/dist/i18n/workflow-chat-message.builder.js +21 -3
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/models/CancellationRequestModel.d.ts +1 -0
  8. package/dist/models/CancellationRequestModel.js +4 -0
  9. package/dist/models/ChatbotQnsAnsMappingModel.d.ts +8 -0
  10. package/dist/models/ChatbotQnsAnsMappingModel.js +40 -0
  11. package/dist/models/ComplaintLostPropertyReportRequestModel.d.ts +3 -0
  12. package/dist/models/ComplaintLostPropertyReportRequestModel.js +12 -0
  13. package/dist/models/LegalComplaintRequestModel.d.ts +3 -3
  14. package/dist/models/LegalComplaintRequestModel.js +2 -8
  15. package/dist/models/MediaPublicationsRequestModel.d.ts +26 -1
  16. package/dist/models/MediaPublicationsRequestModel.js +70 -2
  17. package/dist/models/VipHallRequestModel.d.ts +4 -2
  18. package/dist/models/VipHallRequestModel.js +9 -7
  19. package/package.json +1 -1
  20. package/sql/caa_api_payload_changes_2026_07_batch2.sql +66 -0
  21. package/sql/create_chatbot_qns_ans_mapping.sql +25 -0
  22. package/sql/sla_reports_admin_procedures.sql +25 -6
  23. package/sql/sla_reports_approvals_workbook.sql +47 -0
  24. package/sql/user_role_department_section_validation.sql +214 -0
  25. package/src/data-source.ts +2 -0
  26. package/src/i18n/workflow-chat-i18n.json +8 -0
  27. package/src/i18n/workflow-chat-message.builder.ts +27 -3
  28. package/src/index.ts +1 -0
  29. package/src/models/CancellationRequestModel.ts +3 -0
  30. package/src/models/ChatbotQnsAnsMappingModel.ts +21 -0
  31. package/src/models/ComplaintLostPropertyReportRequestModel.ts +9 -0
  32. package/src/models/LegalComplaintRequestModel.ts +4 -11
  33. package/src/models/MediaPublicationsRequestModel.ts +62 -2
  34. package/src/models/VipHallRequestModel.ts +10 -7
@@ -256,6 +256,7 @@ const SlaMyRequestsViewModel_1 = require("./models/SlaMyRequestsViewModel");
256
256
  const SlaApprovalsViewModel_1 = require("./models/SlaApprovalsViewModel");
257
257
  const ServiceSlaApprovalModel_1 = require("./models/ServiceSlaApprovalModel");
258
258
  const SlaConfigModel_1 = require("./models/SlaConfigModel");
259
+ const ChatbotQnsAnsMappingModel_1 = require("./models/ChatbotQnsAnsMappingModel");
259
260
  const CSRMBusinessImpactRatingMasterModel_1 = require("./models/CSRMBusinessImpactRatingMasterModel");
260
261
  const CSRMLikelihoodMasterModel_1 = require("./models/CSRMLikelihoodMasterModel");
261
262
  const LocationModel_1 = require("./models/LocationModel");
@@ -579,6 +580,7 @@ exports.AppDataSource = new typeorm_1.DataSource({
579
580
  SlaApprovalsViewModel_1.SlaApprovalsView,
580
581
  ServiceSlaApprovalModel_1.ServiceSlaApproval,
581
582
  SlaConfigModel_1.SlaConfig,
583
+ ChatbotQnsAnsMappingModel_1.ChatbotQnsAnsMapping,
582
584
  CSRMBusinessImpactRatingMasterModel_1.CSRMBusinessImpactRatingMaster,
583
585
  CSRMLikelihoodMasterModel_1.CSRMLikelihoodMaster,
584
586
  LocationModel_1.Location,
@@ -309,6 +309,14 @@
309
309
  "logistics_actual_return_updated": {
310
310
  "en": "{{roleName}} updated the Vehicle Actual Return Date, Time, and Reason. Actual Return Date: {{date}}, Actual Return Time: {{time}}, Reason: {{reason}}",
311
311
  "ar": "قام {{roleName}} بتحديث تاريخ الإرجاع الفعلي للمركبة والوقت والسبب. تاريخ الإرجاع الفعلي: {{date}}، وقت الإرجاع الفعلي: {{time}}، السبب: {{reason}}"
312
+ },
313
+ "pending_with_user": {
314
+ "en": "Pending with {{userName}} ({{employeeId}})",
315
+ "ar": "قيد الانتظار لدى {{userName}} ({{employeeId}})"
316
+ },
317
+ "pending_with_role": {
318
+ "en": "Pending with {{roleName}}",
319
+ "ar": "قيد الانتظار لدى {{roleName}}"
312
320
  }
313
321
  },
314
322
  "notificationTemplates": {
@@ -348,7 +348,7 @@ export declare function buildItHelpdeskCloseNotificationMessage(params: {
348
348
  status: 'Approved' | 'Rejected';
349
349
  }): BilingualText;
350
350
  export declare function resolveWorkflowStatusAr(status: string | null | undefined): string | null;
351
- export declare function resolveChatStatusAr(status: string | null | undefined): string | null;
351
+ export declare function resolveChatStatusAr(status: string | null | undefined, actors?: BilingualActorNames): string | null;
352
352
  /** Pull bilingual actor fields from joined relations on workflow/chat rows. */
353
353
  export declare function extractBilingualActorsFromRecord(record: Record<string, unknown>): BilingualActorNames;
354
354
  export declare function resolveWorkflowContentAr(content: string | null | undefined, actors?: BilingualActorNames): string | null;
@@ -951,10 +951,28 @@ function resolveWorkflowStatusAr(status) {
951
951
  return null;
952
952
  return WORKFLOW_STATUS_LABELS[status] ?? status;
953
953
  }
954
- function resolveChatStatusAr(status) {
954
+ function resolveChatStatusAr(status, actors) {
955
955
  if (status == null || status === '')
956
956
  return null;
957
- return CHAT_STATUS_LABELS[status] ?? status;
957
+ const labeled = CHAT_STATUS_LABELS[status];
958
+ if (labeled)
959
+ return labeled;
960
+ const normalized = status.trim();
961
+ const pendingWithUserTpl = CHAT_TEMPLATES.pending_with_user;
962
+ if (pendingWithUserTpl) {
963
+ const params = matchTemplateMessage(pendingWithUserTpl.en, normalized);
964
+ if (params) {
965
+ return interpolate(pendingWithUserTpl.ar, buildArabicChatTemplateParams(params, actors));
966
+ }
967
+ }
968
+ const pendingWithRoleTpl = CHAT_TEMPLATES.pending_with_role;
969
+ if (pendingWithRoleTpl) {
970
+ const params = matchTemplateMessage(pendingWithRoleTpl.en, normalized);
971
+ if (params) {
972
+ return interpolate(pendingWithRoleTpl.ar, buildArabicChatTemplateParams(params, actors));
973
+ }
974
+ }
975
+ return status;
958
976
  }
959
977
  // ─── Legacy row enrichment (read API) ────────────────────────────────────────
960
978
  /** Pull bilingual actor fields from joined relations on workflow/chat rows. */
@@ -1599,7 +1617,7 @@ function enrichChatMessage(chat) {
1599
1617
  return {
1600
1618
  ...chat,
1601
1619
  message_ar: chat.message_ar ?? resolveChatMessageAr(message, actors),
1602
- status_ar: chat.status_ar ?? resolveChatStatusAr(status),
1620
+ status_ar: chat.status_ar ?? resolveChatStatusAr(status, actors),
1603
1621
  };
1604
1622
  }
1605
1623
  /** @deprecated Use enrichWorkflowLog */
package/dist/index.d.ts CHANGED
@@ -414,6 +414,7 @@ export * from './models/RequestTenderAnalysisRequestModel';
414
414
  export * from './models/ContractServiceRequestModel';
415
415
  export * from './models/ContractServiceRequestDocumentModel';
416
416
  export * from './models/SlaConfigModel';
417
+ export * from './models/ChatbotQnsAnsMappingModel';
417
418
  export * from './models/SlaRequestModel';
418
419
  export * from './models/SlaMyRequestsViewModel';
419
420
  export * from './models/SlaApprovalsViewModel';
package/dist/index.js CHANGED
@@ -603,6 +603,7 @@ __exportStar(require("./models/RequestTenderAnalysisRequestModel"), exports);
603
603
  __exportStar(require("./models/ContractServiceRequestModel"), exports);
604
604
  __exportStar(require("./models/ContractServiceRequestDocumentModel"), exports);
605
605
  __exportStar(require("./models/SlaConfigModel"), exports);
606
+ __exportStar(require("./models/ChatbotQnsAnsMappingModel"), exports);
606
607
  __exportStar(require("./models/SlaRequestModel"), exports);
607
608
  __exportStar(require("./models/SlaMyRequestsViewModel"), exports);
608
609
  __exportStar(require("./models/SlaApprovalsViewModel"), exports);
@@ -16,6 +16,7 @@ export declare class CancellationRequests extends BaseModel {
16
16
  status: CancellationRequestStatus;
17
17
  workflow_execution_id: string | null;
18
18
  old_request_number: number;
19
+ decision_number: string | null;
19
20
  reason_for_cancellation: string;
20
21
  description: string;
21
22
  /** Optional user IDs for “affected participants” notification fan-out */
@@ -56,6 +56,10 @@ __decorate([
56
56
  (0, typeorm_1.Column)({ type: "integer", nullable: false }),
57
57
  __metadata("design:type", Number)
58
58
  ], CancellationRequests.prototype, "old_request_number", void 0);
59
+ __decorate([
60
+ (0, typeorm_1.Column)({ type: "varchar", length: 255, nullable: true }),
61
+ __metadata("design:type", Object)
62
+ ], CancellationRequests.prototype, "decision_number", void 0);
59
63
  __decorate([
60
64
  (0, typeorm_1.Column)({ type: "varchar", length: 500, nullable: false }),
61
65
  __metadata("design:type", String)
@@ -0,0 +1,8 @@
1
+ import { BaseModel } from "./BaseModel";
2
+ export declare class ChatbotQnsAnsMapping extends BaseModel {
3
+ service_id: number;
4
+ sub_service_id: number;
5
+ question: Record<string, unknown> | unknown[];
6
+ answer: Record<string, unknown> | unknown[];
7
+ constructor();
8
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ChatbotQnsAnsMapping = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ const BaseModel_1 = require("./BaseModel");
15
+ let ChatbotQnsAnsMapping = class ChatbotQnsAnsMapping extends BaseModel_1.BaseModel {
16
+ constructor() {
17
+ super();
18
+ }
19
+ };
20
+ exports.ChatbotQnsAnsMapping = ChatbotQnsAnsMapping;
21
+ __decorate([
22
+ (0, typeorm_1.Column)({ type: "int", nullable: false }),
23
+ __metadata("design:type", Number)
24
+ ], ChatbotQnsAnsMapping.prototype, "service_id", void 0);
25
+ __decorate([
26
+ (0, typeorm_1.Column)({ type: "int", nullable: false }),
27
+ __metadata("design:type", Number)
28
+ ], ChatbotQnsAnsMapping.prototype, "sub_service_id", void 0);
29
+ __decorate([
30
+ (0, typeorm_1.Column)({ type: "jsonb", nullable: false }),
31
+ __metadata("design:type", Object)
32
+ ], ChatbotQnsAnsMapping.prototype, "question", void 0);
33
+ __decorate([
34
+ (0, typeorm_1.Column)({ type: "jsonb", nullable: false }),
35
+ __metadata("design:type", Object)
36
+ ], ChatbotQnsAnsMapping.prototype, "answer", void 0);
37
+ exports.ChatbotQnsAnsMapping = ChatbotQnsAnsMapping = __decorate([
38
+ (0, typeorm_1.Entity)({ name: "chatbot_qns_ans_mapping" }),
39
+ __metadata("design:paramtypes", [])
40
+ ], ChatbotQnsAnsMapping);
@@ -17,6 +17,9 @@ export declare class ComplaintLostPropertyReportRequest extends BaseModel {
17
17
  location_where_item_was_lost: string;
18
18
  item_description: string;
19
19
  description: string | null;
20
+ lost_item_classification: string | null;
21
+ caa_property_type: string | null;
22
+ lost_card: string | null;
20
23
  created_by: number;
21
24
  reference_number: string | null;
22
25
  constructor();
@@ -74,6 +74,18 @@ __decorate([
74
74
  (0, typeorm_1.Column)({ type: "text", nullable: true }),
75
75
  __metadata("design:type", Object)
76
76
  ], ComplaintLostPropertyReportRequest.prototype, "description", void 0);
77
+ __decorate([
78
+ (0, typeorm_1.Column)({ type: "varchar", length: 100, nullable: true }),
79
+ __metadata("design:type", Object)
80
+ ], ComplaintLostPropertyReportRequest.prototype, "lost_item_classification", void 0);
81
+ __decorate([
82
+ (0, typeorm_1.Column)({ type: "varchar", length: 255, nullable: true }),
83
+ __metadata("design:type", Object)
84
+ ], ComplaintLostPropertyReportRequest.prototype, "caa_property_type", void 0);
85
+ __decorate([
86
+ (0, typeorm_1.Column)({ type: "varchar", length: 10, nullable: true }),
87
+ __metadata("design:type", Object)
88
+ ], ComplaintLostPropertyReportRequest.prototype, "lost_card", void 0);
77
89
  __decorate([
78
90
  (0, typeorm_1.Column)({ type: "int", nullable: false }),
79
91
  __metadata("design:type", Number)
@@ -21,7 +21,7 @@ export interface LegalComplaintIncidentInput {
21
21
  }
22
22
  /** Step 3 — Complainant: Name / Position / Employee ID / Directorate / Department / Section. */
23
23
  export interface LegalComplaintComplainantInput {
24
- name: string;
24
+ name?: string | null;
25
25
  position?: string | null;
26
26
  employee_id?: string | null;
27
27
  directorate?: string | null;
@@ -50,7 +50,7 @@ export interface LegalComplaintStructuredCreateFields {
50
50
  incident_events: string | null;
51
51
  incident_other_details: string | null;
52
52
  incident_description: string;
53
- complainant_name: string;
53
+ complainant_name: string | null;
54
54
  complainant_position: string | null;
55
55
  complainant_employee_id: string | null;
56
56
  complainant_directorate: string | null;
@@ -94,7 +94,7 @@ export declare class LegalComplaintRequest extends BaseModel {
94
94
  /** “Any other details related to complaint”. */
95
95
  incident_other_details: string | null;
96
96
  incident_description: string;
97
- complainant_name: string;
97
+ complainant_name: string | null;
98
98
  complainant_position: string | null;
99
99
  complainant_employee_id: string | null;
100
100
  complainant_directorate: string | null;
@@ -126,12 +126,6 @@ function parseLegalComplaintCreateFields(body) {
126
126
  complainant_section_id = sec.id;
127
127
  complainant_section_name = sec.name;
128
128
  }
129
- if (!complainant_name) {
130
- return {
131
- ok: false,
132
- message: 'complainant.name is required (or legacy complainant_details text)',
133
- };
134
- }
135
129
  const sub = body.complained_employee ?? body.complainedEmployee;
136
130
  let complained_employee_name = '';
137
131
  let complained_employee_position = null;
@@ -238,8 +232,8 @@ __decorate([
238
232
  __metadata("design:type", String)
239
233
  ], LegalComplaintRequest.prototype, "incident_description", void 0);
240
234
  __decorate([
241
- (0, typeorm_1.Column)({ type: 'varchar', length: 255, nullable: false }),
242
- __metadata("design:type", String)
235
+ (0, typeorm_1.Column)({ type: 'varchar', length: 255, nullable: true }),
236
+ __metadata("design:type", Object)
243
237
  ], LegalComplaintRequest.prototype, "complainant_name", void 0);
244
238
  __decorate([
245
239
  (0, typeorm_1.Column)({ type: 'varchar', length: 255, nullable: true }),
@@ -15,6 +15,14 @@ export declare enum MediaPublicationsRequestStatus {
15
15
  APPROVED = "Approved",
16
16
  REJECTED = "Rejected"
17
17
  }
18
+ export interface MediaPublicationMaterialLine {
19
+ item_type?: string | null;
20
+ item_code?: string | null;
21
+ description?: string | null;
22
+ quantity?: number | null;
23
+ unit_price?: number | null;
24
+ cost?: number | null;
25
+ }
18
26
  export declare class MediaPublicationsRequests extends BaseModel {
19
27
  req_user_department_id: number | null;
20
28
  req_user_section_id: number | null;
@@ -25,14 +33,31 @@ export declare class MediaPublicationsRequests extends BaseModel {
25
33
  requestor_name: string | null;
26
34
  requestor_department: string | null;
27
35
  requestor_designation: string | null;
36
+ job_title: string | null;
37
+ directorate: string | null;
38
+ department: string | null;
39
+ extension_number: string | null;
28
40
  contact_number: string | null;
41
+ phone_number: string | null;
29
42
  email: string | null;
30
43
  request_type: MediaPublicationsRequestType;
31
- item_code: string;
44
+ item_code: string | null;
45
+ item_type: string | null;
32
46
  description: string;
33
47
  cost: number;
34
48
  publication_date: Date;
35
49
  event_type: MediaPublicationsEventType;
50
+ publication_type: string[] | null;
51
+ publication_format: string[] | null;
52
+ purpose_of_publication: string | null;
53
+ intended_use: string | null;
54
+ target_audience: string | null;
55
+ is_new_or_update: string | null;
56
+ approved_budget_available: string | null;
57
+ estimated_quantity: number | null;
58
+ required_languages: string[] | null;
59
+ technical_requirements: string | null;
60
+ material_lines: MediaPublicationMaterialLine[] | null;
36
61
  quantity: number | null;
37
62
  unit_price: number | null;
38
63
  status: MediaPublicationsRequestStatus;
@@ -70,10 +70,30 @@ __decorate([
70
70
  (0, typeorm_1.Column)({ type: 'varchar', length: 500, nullable: true }),
71
71
  __metadata("design:type", Object)
72
72
  ], MediaPublicationsRequests.prototype, "requestor_designation", void 0);
73
+ __decorate([
74
+ (0, typeorm_1.Column)({ type: 'varchar', length: 500, nullable: true }),
75
+ __metadata("design:type", Object)
76
+ ], MediaPublicationsRequests.prototype, "job_title", void 0);
77
+ __decorate([
78
+ (0, typeorm_1.Column)({ type: 'varchar', length: 500, nullable: true }),
79
+ __metadata("design:type", Object)
80
+ ], MediaPublicationsRequests.prototype, "directorate", void 0);
81
+ __decorate([
82
+ (0, typeorm_1.Column)({ type: 'varchar', length: 500, nullable: true }),
83
+ __metadata("design:type", Object)
84
+ ], MediaPublicationsRequests.prototype, "department", void 0);
85
+ __decorate([
86
+ (0, typeorm_1.Column)({ type: 'varchar', length: 50, nullable: true }),
87
+ __metadata("design:type", Object)
88
+ ], MediaPublicationsRequests.prototype, "extension_number", void 0);
73
89
  __decorate([
74
90
  (0, typeorm_1.Column)({ type: 'varchar', length: 100, nullable: true }),
75
91
  __metadata("design:type", Object)
76
92
  ], MediaPublicationsRequests.prototype, "contact_number", void 0);
93
+ __decorate([
94
+ (0, typeorm_1.Column)({ type: 'varchar', length: 100, nullable: true }),
95
+ __metadata("design:type", Object)
96
+ ], MediaPublicationsRequests.prototype, "phone_number", void 0);
77
97
  __decorate([
78
98
  (0, typeorm_1.Column)({ type: 'varchar', length: 320, nullable: true }),
79
99
  __metadata("design:type", Object)
@@ -88,9 +108,13 @@ __decorate([
88
108
  __metadata("design:type", String)
89
109
  ], MediaPublicationsRequests.prototype, "request_type", void 0);
90
110
  __decorate([
91
- (0, typeorm_1.Column)({ type: 'varchar', length: 120, nullable: false }),
92
- __metadata("design:type", String)
111
+ (0, typeorm_1.Column)({ type: 'varchar', length: 120, nullable: true }),
112
+ __metadata("design:type", Object)
93
113
  ], MediaPublicationsRequests.prototype, "item_code", void 0);
114
+ __decorate([
115
+ (0, typeorm_1.Column)({ type: 'varchar', length: 255, nullable: true }),
116
+ __metadata("design:type", Object)
117
+ ], MediaPublicationsRequests.prototype, "item_type", void 0);
94
118
  __decorate([
95
119
  (0, typeorm_1.Column)({ type: 'text', nullable: false }),
96
120
  __metadata("design:type", String)
@@ -112,6 +136,50 @@ __decorate([
112
136
  }),
113
137
  __metadata("design:type", String)
114
138
  ], MediaPublicationsRequests.prototype, "event_type", void 0);
139
+ __decorate([
140
+ (0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
141
+ __metadata("design:type", Object)
142
+ ], MediaPublicationsRequests.prototype, "publication_type", void 0);
143
+ __decorate([
144
+ (0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
145
+ __metadata("design:type", Object)
146
+ ], MediaPublicationsRequests.prototype, "publication_format", void 0);
147
+ __decorate([
148
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
149
+ __metadata("design:type", Object)
150
+ ], MediaPublicationsRequests.prototype, "purpose_of_publication", void 0);
151
+ __decorate([
152
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
153
+ __metadata("design:type", Object)
154
+ ], MediaPublicationsRequests.prototype, "intended_use", void 0);
155
+ __decorate([
156
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
157
+ __metadata("design:type", Object)
158
+ ], MediaPublicationsRequests.prototype, "target_audience", void 0);
159
+ __decorate([
160
+ (0, typeorm_1.Column)({ type: 'varchar', length: 50, nullable: true }),
161
+ __metadata("design:type", Object)
162
+ ], MediaPublicationsRequests.prototype, "is_new_or_update", void 0);
163
+ __decorate([
164
+ (0, typeorm_1.Column)({ type: 'varchar', length: 50, nullable: true }),
165
+ __metadata("design:type", Object)
166
+ ], MediaPublicationsRequests.prototype, "approved_budget_available", void 0);
167
+ __decorate([
168
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
169
+ __metadata("design:type", Object)
170
+ ], MediaPublicationsRequests.prototype, "estimated_quantity", void 0);
171
+ __decorate([
172
+ (0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
173
+ __metadata("design:type", Object)
174
+ ], MediaPublicationsRequests.prototype, "required_languages", void 0);
175
+ __decorate([
176
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
177
+ __metadata("design:type", Object)
178
+ ], MediaPublicationsRequests.prototype, "technical_requirements", void 0);
179
+ __decorate([
180
+ (0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
181
+ __metadata("design:type", Object)
182
+ ], MediaPublicationsRequests.prototype, "material_lines", void 0);
115
183
  __decorate([
116
184
  (0, typeorm_1.Column)({ type: 'int', nullable: true }),
117
185
  __metadata("design:type", Object)
@@ -13,8 +13,10 @@ export declare enum VipHallInternalExternal {
13
13
  export declare enum VipHallMealType {
14
14
  BREAKFAST = "Breakfast",
15
15
  LUNCH = "Lunch",
16
- DINNER = "Dinner"
16
+ DINNER = "Dinner",
17
+ SNACKS = "Snacks"
17
18
  }
19
+ export declare const VIP_HALL_MEAL_TYPE_VALUES: readonly [VipHallMealType.BREAKFAST, VipHallMealType.LUNCH, VipHallMealType.DINNER, VipHallMealType.SNACKS];
18
20
  export declare class VipHallRequests extends BaseModel {
19
21
  req_user_department_id: number | null;
20
22
  req_user_section_id: number | null;
@@ -34,7 +36,7 @@ export declare class VipHallRequests extends BaseModel {
34
36
  /** Purpose of the meeting (mandatory in API validation). */
35
37
  meeting_purpose: string;
36
38
  meal_required: boolean;
37
- meal_type: VipHallMealType | null;
39
+ meal_type: string[] | null;
38
40
  network_support_required: boolean;
39
41
  media_coverage_required: boolean;
40
42
  project_maintenance_support_required: boolean;
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.VipHallRequests = exports.VipHallMealType = exports.VipHallInternalExternal = exports.VipHallRequestStatus = void 0;
12
+ exports.VipHallRequests = exports.VIP_HALL_MEAL_TYPE_VALUES = exports.VipHallMealType = exports.VipHallInternalExternal = exports.VipHallRequestStatus = void 0;
13
13
  const typeorm_1 = require("typeorm");
14
14
  const BaseModel_1 = require("./BaseModel");
15
15
  var VipHallRequestStatus;
@@ -30,7 +30,14 @@ var VipHallMealType;
30
30
  VipHallMealType["BREAKFAST"] = "Breakfast";
31
31
  VipHallMealType["LUNCH"] = "Lunch";
32
32
  VipHallMealType["DINNER"] = "Dinner";
33
+ VipHallMealType["SNACKS"] = "Snacks";
33
34
  })(VipHallMealType || (exports.VipHallMealType = VipHallMealType = {}));
35
+ exports.VIP_HALL_MEAL_TYPE_VALUES = [
36
+ VipHallMealType.BREAKFAST,
37
+ VipHallMealType.LUNCH,
38
+ VipHallMealType.DINNER,
39
+ VipHallMealType.SNACKS,
40
+ ];
34
41
  let VipHallRequests = class VipHallRequests extends BaseModel_1.BaseModel {
35
42
  };
36
43
  exports.VipHallRequests = VipHallRequests;
@@ -109,12 +116,7 @@ __decorate([
109
116
  __metadata("design:type", Boolean)
110
117
  ], VipHallRequests.prototype, "meal_required", void 0);
111
118
  __decorate([
112
- (0, typeorm_1.Column)({
113
- type: "enum",
114
- enum: VipHallMealType,
115
- enumName: "vip_hall_meal_type_en",
116
- nullable: true,
117
- }),
119
+ (0, typeorm_1.Column)({ type: "jsonb", nullable: true }),
118
120
  __metadata("design:type", Object)
119
121
  ], VipHallRequests.prototype, "meal_type", void 0);
120
122
  __decorate([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platform-modules/civil-aviation-authority",
3
- "version": "2.3.309",
3
+ "version": "2.3.311",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -0,0 +1,66 @@
1
+ -- CAA API payload changes batch 2 (Jul 2026)
2
+ -- Run after deploying updated shared_models, services, and Conductor workers.
3
+
4
+ -- 1. Complaint / lost property report
5
+ ALTER TABLE complaint_lost_property_report_requests
6
+ ADD COLUMN IF NOT EXISTS lost_item_classification VARCHAR(100),
7
+ ADD COLUMN IF NOT EXISTS caa_property_type VARCHAR(255),
8
+ ADD COLUMN IF NOT EXISTS lost_card VARCHAR(10);
9
+
10
+ -- 2. Cancellation request
11
+ ALTER TABLE cancellation_requests
12
+ ADD COLUMN IF NOT EXISTS decision_number VARCHAR(255);
13
+
14
+ -- 3. VIP hall — meal_type multi-select (jsonb array)
15
+ DO $$
16
+ BEGIN
17
+ IF EXISTS (
18
+ SELECT 1 FROM information_schema.columns
19
+ WHERE table_name = 'vip_hall_requests' AND column_name = 'meal_type'
20
+ ) THEN
21
+ BEGIN
22
+ ALTER TABLE vip_hall_requests
23
+ ALTER COLUMN meal_type TYPE jsonb
24
+ USING CASE
25
+ WHEN meal_type IS NULL THEN NULL
26
+ WHEN pg_typeof(meal_type)::text = 'jsonb' THEN meal_type
27
+ ELSE to_jsonb(ARRAY[meal_type::text])
28
+ END;
29
+ EXCEPTION WHEN OTHERS THEN
30
+ ALTER TABLE vip_hall_requests
31
+ ADD COLUMN IF NOT EXISTS meal_type_jsonb jsonb;
32
+ UPDATE vip_hall_requests
33
+ SET meal_type_jsonb = to_jsonb(ARRAY[meal_type::text])
34
+ WHERE meal_type IS NOT NULL;
35
+ ALTER TABLE vip_hall_requests DROP COLUMN IF EXISTS meal_type;
36
+ ALTER TABLE vip_hall_requests RENAME COLUMN meal_type_jsonb TO meal_type;
37
+ END;
38
+ END IF;
39
+ END $$;
40
+
41
+ -- 4. Legal complaint — complainant name optional
42
+ ALTER TABLE legal_complaint_requests
43
+ ALTER COLUMN complainant_name DROP NOT NULL;
44
+
45
+ -- 5. Media publications — expanded payload fields
46
+ ALTER TABLE media_publications_requests
47
+ ADD COLUMN IF NOT EXISTS job_title VARCHAR(500),
48
+ ADD COLUMN IF NOT EXISTS directorate VARCHAR(500),
49
+ ADD COLUMN IF NOT EXISTS department VARCHAR(500),
50
+ ADD COLUMN IF NOT EXISTS extension_number VARCHAR(50),
51
+ ADD COLUMN IF NOT EXISTS phone_number VARCHAR(100),
52
+ ADD COLUMN IF NOT EXISTS item_type VARCHAR(255),
53
+ ADD COLUMN IF NOT EXISTS publication_type jsonb,
54
+ ADD COLUMN IF NOT EXISTS publication_format jsonb,
55
+ ADD COLUMN IF NOT EXISTS purpose_of_publication TEXT,
56
+ ADD COLUMN IF NOT EXISTS intended_use TEXT,
57
+ ADD COLUMN IF NOT EXISTS target_audience TEXT,
58
+ ADD COLUMN IF NOT EXISTS is_new_or_update VARCHAR(50),
59
+ ADD COLUMN IF NOT EXISTS approved_budget_available VARCHAR(50),
60
+ ADD COLUMN IF NOT EXISTS estimated_quantity INT,
61
+ ADD COLUMN IF NOT EXISTS required_languages jsonb,
62
+ ADD COLUMN IF NOT EXISTS technical_requirements TEXT,
63
+ ADD COLUMN IF NOT EXISTS material_lines jsonb;
64
+
65
+ ALTER TABLE media_publications_requests
66
+ ALTER COLUMN item_code DROP NOT NULL;
@@ -0,0 +1,25 @@
1
+ -- Chatbot Q&A mapping master table (User Service)
2
+ -- PostgreSQL
3
+
4
+ CREATE TABLE IF NOT EXISTS chatbot_qns_ans_mapping (
5
+ id SERIAL PRIMARY KEY,
6
+ service_id INTEGER NOT NULL,
7
+ sub_service_id INTEGER NOT NULL,
8
+ question JSONB NOT NULL,
9
+ answer JSONB NOT NULL,
10
+ created_by INTEGER NULL,
11
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
12
+ updated_by INTEGER NULL,
13
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
14
+ is_deleted BOOLEAN DEFAULT FALSE
15
+ );
16
+
17
+ CREATE INDEX IF NOT EXISTS idx_chatbot_qns_ans_mapping_service_sub_service
18
+ ON chatbot_qns_ans_mapping (service_id, sub_service_id)
19
+ WHERE is_deleted = FALSE;
20
+
21
+ CREATE INDEX IF NOT EXISTS idx_chatbot_qns_ans_mapping_question_gin
22
+ ON chatbot_qns_ans_mapping USING GIN (question);
23
+
24
+ CREATE INDEX IF NOT EXISTS idx_chatbot_qns_ans_mapping_answer_gin
25
+ ON chatbot_qns_ans_mapping USING GIN (answer);
@@ -19,13 +19,15 @@ $$;
19
19
  -- Admin list views: served by Reports_Service TypeORM (sla-my-requests-view.query.ts, sla-approvals-view.query.ts)
20
20
 
21
21
  -- ---------------------------------------------------------------------------
22
- -- ADMIN — my requests Excel (optional p_target_user_id)
22
+ -- ADMIN — my requests Excel (optional p_target_user_ids INT[])
23
23
  -- Per sub-service Requests + Approvals sheets (same as user my-requests download)
24
24
  -- ---------------------------------------------------------------------------
25
25
  DROP FUNCTION IF EXISTS sp_sla_admin_my_requests_excel_export(INT, INT[], INT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT);
26
+ DROP FUNCTION IF EXISTS sp_sla_admin_my_requests_excel_export(INT, INT[], INT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT, TEXT);
27
+ DROP FUNCTION IF EXISTS sp_sla_admin_my_requests_excel_export(INT[], INT[], INT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT, TEXT);
26
28
 
27
29
  CREATE OR REPLACE FUNCTION sp_sla_admin_my_requests_excel_export(
28
- p_target_user_id INT DEFAULT NULL,
30
+ p_target_user_ids INT[] DEFAULT NULL,
29
31
  p_service_ids INT[] DEFAULT NULL,
30
32
  p_sub_service_ids INT[] DEFAULT NULL,
31
33
  p_statuses TEXT[] DEFAULT ARRAY['Pending','In Progress','Approved','Rejected'],
@@ -65,10 +67,21 @@ DECLARE
65
67
  v_k TEXT;
66
68
  v_sheet_order INT := 0;
67
69
  v_user_filter TEXT;
70
+ v_user_scope TEXT;
68
71
  BEGIN
72
+ IF p_target_user_ids IS NULL OR cardinality(p_target_user_ids) = 0 THEN
73
+ v_user_scope := 'TRUE';
74
+ ELSE
75
+ -- Validated positive ints from app; format as int array literal for ANY()
76
+ v_user_scope := format(
77
+ 'v.user_id = ANY (ARRAY[%s]::int[])',
78
+ array_to_string(p_target_user_ids, ',')
79
+ );
80
+ END IF;
81
+
69
82
  v_user_filter := format(
70
83
  '(%s) AND (%s)',
71
- CASE WHEN p_target_user_id IS NULL THEN 'TRUE' ELSE format('v.user_id = %s', p_target_user_id) END,
84
+ v_user_scope,
72
85
  COALESCE(NULLIF(trim(p_extra_filter_sql), ''), 'TRUE')
73
86
  );
74
87
 
@@ -256,15 +269,21 @@ $$;
256
269
 
257
270
  -- ---------------------------------------------------------------------------
258
271
  -- ADMIN — approvals Excel
259
- -- Per sub-service Requests + Approvals; optional approver_user_id (NULL = all queues)
272
+ -- Per sub-service Requests + Approvals; optional approver user id(s) (NULL/empty = all queues)
260
273
  -- Does not exclude own requests (unlike user approvals download)
261
274
  -- ---------------------------------------------------------------------------
262
275
  DROP FUNCTION IF EXISTS sp_sla_admin_approvals_excel_export(
263
276
  INT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
264
277
  );
278
+ DROP FUNCTION IF EXISTS sp_sla_admin_approvals_excel_export(
279
+ INT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT, TEXT
280
+ );
281
+ DROP FUNCTION IF EXISTS sp_sla_admin_approvals_excel_export(
282
+ INT[], INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT, TEXT
283
+ );
265
284
 
266
285
  CREATE OR REPLACE FUNCTION sp_sla_admin_approvals_excel_export(
267
- p_target_approver_user_id INT DEFAULT NULL,
286
+ p_target_approver_user_ids INT[] DEFAULT NULL,
268
287
  p_service_ids INT[] DEFAULT NULL,
269
288
  p_sub_service_ids INT[] DEFAULT NULL,
270
289
  p_request_statuses TEXT[] DEFAULT ARRAY['Pending','In Progress','Approved','Rejected'],
@@ -283,7 +302,7 @@ LANGUAGE plpgsql
283
302
  STABLE
284
303
  AS $$
285
304
  DECLARE
286
- v_exists TEXT := sla_approval_queue_exists_sql_optional(p_target_approver_user_id);
305
+ v_exists TEXT := sla_approval_queue_exists_sql_any(p_target_approver_user_ids);
287
306
  v_own TEXT := COALESCE(NULLIF(trim(p_extra_filter_sql), ''), 'TRUE');
288
307
  BEGIN
289
308
  RETURN QUERY
@@ -96,6 +96,53 @@ AS $$
96
96
  END;
97
97
  $$;
98
98
 
99
+ -- Admin: any of multiple approver user ids (NULL/empty = all queues)
100
+ CREATE OR REPLACE FUNCTION sla_approval_queue_exists_sql_any(p_user_ids INT[])
101
+ RETURNS TEXT
102
+ LANGUAGE plpgsql
103
+ STABLE
104
+ AS $$
105
+ DECLARE
106
+ v_id INT;
107
+ v_parts TEXT[] := ARRAY[]::TEXT[];
108
+ v_match TEXT;
109
+ BEGIN
110
+ IF p_user_ids IS NULL OR cardinality(p_user_ids) = 0 THEN
111
+ RETURN sla_approval_queue_exists_sql_optional(NULL);
112
+ END IF;
113
+
114
+ IF cardinality(p_user_ids) = 1 THEN
115
+ RETURN sla_approval_queue_exists_sql(p_user_ids[1]);
116
+ END IF;
117
+
118
+ FOREACH v_id IN ARRAY p_user_ids LOOP
119
+ IF v_id IS NOT NULL AND v_id > 0 THEN
120
+ v_parts := array_append(v_parts, '(' || sla_approval_matches_user('appr', v_id) || ')');
121
+ END IF;
122
+ END LOOP;
123
+
124
+ IF cardinality(v_parts) = 0 THEN
125
+ RETURN sla_approval_queue_exists_sql_optional(NULL);
126
+ END IF;
127
+
128
+ v_match := array_to_string(v_parts, ' OR ');
129
+
130
+ RETURN format(
131
+ 'EXISTS (
132
+ SELECT 1
133
+ FROM sla_approval appr
134
+ WHERE appr.request_id = sr.request_id
135
+ AND COALESCE(appr.is_deleted, false) = false
136
+ AND (appr.service_id = sr.service_id OR appr.service_id IS NULL)
137
+ AND (appr.sub_service_id = sr.sub_service_id OR appr.sub_service_id IS NULL)
138
+ AND (%s)
139
+ AND (appr.approval_status = ANY($1) OR appr.approval_status IS NULL)
140
+ )',
141
+ v_match
142
+ );
143
+ END;
144
+ $$;
145
+
99
146
  DROP FUNCTION IF EXISTS sp_sla_approvals_workbook_excel_export(
100
147
  TEXT, TEXT, INT[], INT[], TEXT[], TEXT[], DATE, DATE, TEXT, TEXT, TEXT
101
148
  );
@@ -0,0 +1,214 @@
1
+ -- =============================================================================
2
+ -- User Role – Department / Section Data Validation
3
+ -- =============================================================================
4
+ -- Purpose:
5
+ -- Identify user_role rows whose department_id and/or section_id reference
6
+ -- master records that are missing or soft-deleted (is_deleted = true).
7
+ --
8
+ -- Database: PostgreSQL (CAA / FM User Service schema)
9
+ -- Tables: user_role, departments, sections
10
+ --
11
+ -- Usage:
12
+ -- psql -U <user> -d <database> -f shared_models/sql/user_role_department_section_validation.sql
13
+ --
14
+ -- Notes:
15
+ -- * department_id / section_id may be NULL on user_role; NULL is treated as
16
+ -- "not assigned" and is not flagged unless an ID is present but invalid.
17
+ -- * Soft-deleted user_role rows are included by default (see filter comment).
18
+ -- =============================================================================
19
+
20
+ -- -----------------------------------------------------------------------------
21
+ -- 1) Detailed invalid records with reason
22
+ -- -----------------------------------------------------------------------------
23
+ WITH validated AS (
24
+ SELECT
25
+ ur.id AS user_role_id,
26
+ ur.role_id,
27
+ ur.user_id,
28
+ ur.department_id,
29
+ ur.section_id,
30
+ ur.is_active,
31
+ COALESCE(ur.is_deleted, false) AS user_role_is_deleted,
32
+ ur.created_at,
33
+ ur.updated_at,
34
+
35
+ r.name AS role_name,
36
+ u.employee_name AS user_name,
37
+ u.email AS user_email,
38
+
39
+ d.id AS department_master_id,
40
+ d.department_name,
41
+ COALESCE(d.is_deleted, false) AS department_is_deleted,
42
+
43
+ s.id AS section_master_id,
44
+ s.section_name,
45
+ COALESCE(s.is_deleted, false) AS section_is_deleted,
46
+
47
+ CASE
48
+ WHEN ur.department_id IS NULL THEN 'NOT_APPLICABLE'
49
+ WHEN d.id IS NULL THEN 'DEPARTMENT_NOT_FOUND'
50
+ WHEN COALESCE(d.is_deleted, false) = true THEN 'DEPARTMENT_DELETED'
51
+ ELSE 'VALID'
52
+ END AS department_status,
53
+
54
+ CASE
55
+ WHEN ur.section_id IS NULL THEN 'NOT_APPLICABLE'
56
+ WHEN s.id IS NULL THEN 'SECTION_NOT_FOUND'
57
+ WHEN COALESCE(s.is_deleted, false) = true THEN 'SECTION_DELETED'
58
+ ELSE 'VALID'
59
+ END AS section_status
60
+
61
+ FROM user_role ur
62
+ LEFT JOIN role r
63
+ ON r.id = ur.role_id
64
+ LEFT JOIN users u
65
+ ON u.id = ur.user_id
66
+ LEFT JOIN departments d
67
+ ON d.id = ur.department_id
68
+ LEFT JOIN sections s
69
+ ON s.id = ur.section_id
70
+ -- Uncomment to restrict to active user_role rows only:
71
+ -- WHERE COALESCE(ur.is_deleted, false) = false
72
+ )
73
+ SELECT
74
+ user_role_id,
75
+ role_id,
76
+ role_name,
77
+ user_id,
78
+ user_name,
79
+ user_email,
80
+ department_id,
81
+ department_name,
82
+ section_id,
83
+ section_name,
84
+ is_active,
85
+ user_role_is_deleted,
86
+ department_status,
87
+ section_status,
88
+ CASE
89
+ WHEN department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
90
+ AND section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
91
+ THEN 'INVALID_DEPARTMENT_AND_SECTION'
92
+ WHEN department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
93
+ THEN 'INVALID_DEPARTMENT'
94
+ WHEN section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
95
+ THEN 'INVALID_SECTION'
96
+ END AS invalid_reason,
97
+ CASE
98
+ WHEN department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
99
+ AND section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
100
+ THEN department_status || ' | ' || section_status
101
+ WHEN department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
102
+ THEN department_status
103
+ WHEN section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
104
+ THEN section_status
105
+ END AS invalid_reason_detail,
106
+ created_at,
107
+ updated_at
108
+ FROM validated
109
+ WHERE
110
+ department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
111
+ OR section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
112
+ ORDER BY
113
+ invalid_reason,
114
+ user_role_id;
115
+
116
+
117
+ -- -----------------------------------------------------------------------------
118
+ -- 2) Summary counts by invalidity category
119
+ -- -----------------------------------------------------------------------------
120
+ WITH validated AS (
121
+ SELECT
122
+ ur.id AS user_role_id,
123
+ CASE
124
+ WHEN ur.department_id IS NULL THEN 'NOT_APPLICABLE'
125
+ WHEN d.id IS NULL THEN 'DEPARTMENT_NOT_FOUND'
126
+ WHEN COALESCE(d.is_deleted, false) = true THEN 'DEPARTMENT_DELETED'
127
+ ELSE 'VALID'
128
+ END AS department_status,
129
+ CASE
130
+ WHEN ur.section_id IS NULL THEN 'NOT_APPLICABLE'
131
+ WHEN s.id IS NULL THEN 'SECTION_NOT_FOUND'
132
+ WHEN COALESCE(s.is_deleted, false) = true THEN 'SECTION_DELETED'
133
+ ELSE 'VALID'
134
+ END AS section_status
135
+ FROM user_role ur
136
+ LEFT JOIN departments d ON d.id = ur.department_id
137
+ LEFT JOIN sections s ON s.id = ur.section_id
138
+ -- Uncomment to restrict to active user_role rows only:
139
+ -- WHERE COALESCE(ur.is_deleted, false) = false
140
+ ),
141
+ invalid AS (
142
+ SELECT
143
+ user_role_id,
144
+ CASE
145
+ WHEN department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
146
+ AND section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
147
+ THEN 'INVALID_DEPARTMENT_AND_SECTION'
148
+ WHEN department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
149
+ THEN 'INVALID_DEPARTMENT'
150
+ WHEN section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
151
+ THEN 'INVALID_SECTION'
152
+ END AS invalid_reason,
153
+ department_status,
154
+ section_status
155
+ FROM validated
156
+ WHERE
157
+ department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
158
+ OR section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
159
+ )
160
+ SELECT
161
+ invalid_reason,
162
+ COUNT(*) AS record_count
163
+ FROM invalid
164
+ GROUP BY invalid_reason
165
+
166
+ UNION ALL
167
+
168
+ SELECT
169
+ 'TOTAL_INVALID_RECORDS' AS invalid_reason,
170
+ COUNT(*) AS record_count
171
+ FROM invalid
172
+
173
+ ORDER BY
174
+ CASE invalid_reason
175
+ WHEN 'INVALID_DEPARTMENT' THEN 1
176
+ WHEN 'INVALID_SECTION' THEN 2
177
+ WHEN 'INVALID_DEPARTMENT_AND_SECTION' THEN 3
178
+ WHEN 'TOTAL_INVALID_RECORDS' THEN 4
179
+ ELSE 5
180
+ END;
181
+
182
+
183
+ -- -----------------------------------------------------------------------------
184
+ -- 3) Breakdown by underlying master-table status (optional drill-down)
185
+ -- -----------------------------------------------------------------------------
186
+ WITH validated AS (
187
+ SELECT
188
+ ur.id AS user_role_id,
189
+ CASE
190
+ WHEN ur.department_id IS NULL THEN 'NOT_APPLICABLE'
191
+ WHEN d.id IS NULL THEN 'DEPARTMENT_NOT_FOUND'
192
+ WHEN COALESCE(d.is_deleted, false) = true THEN 'DEPARTMENT_DELETED'
193
+ ELSE 'VALID'
194
+ END AS department_status,
195
+ CASE
196
+ WHEN ur.section_id IS NULL THEN 'NOT_APPLICABLE'
197
+ WHEN s.id IS NULL THEN 'SECTION_NOT_FOUND'
198
+ WHEN COALESCE(s.is_deleted, false) = true THEN 'SECTION_DELETED'
199
+ ELSE 'VALID'
200
+ END AS section_status
201
+ FROM user_role ur
202
+ LEFT JOIN departments d ON d.id = ur.department_id
203
+ LEFT JOIN sections s ON s.id = ur.section_id
204
+ )
205
+ SELECT
206
+ department_status,
207
+ section_status,
208
+ COUNT(*) AS record_count
209
+ FROM validated
210
+ WHERE
211
+ department_status IN ('DEPARTMENT_NOT_FOUND', 'DEPARTMENT_DELETED')
212
+ OR section_status IN ('SECTION_NOT_FOUND', 'SECTION_DELETED')
213
+ GROUP BY department_status, section_status
214
+ ORDER BY record_count DESC, department_status, section_status;
@@ -254,6 +254,7 @@ import { SlaMyRequestsView } from './models/SlaMyRequestsViewModel';
254
254
  import { SlaApprovalsView } from './models/SlaApprovalsViewModel';
255
255
  import { ServiceSlaApproval } from './models/ServiceSlaApprovalModel';
256
256
  import { SlaConfig } from './models/SlaConfigModel';
257
+ import { ChatbotQnsAnsMapping } from './models/ChatbotQnsAnsMappingModel';
257
258
  import { CSRMBusinessImpactRatingMaster } from './models/CSRMBusinessImpactRatingMasterModel';
258
259
  import { CSRMLikelihoodMaster } from './models/CSRMLikelihoodMasterModel';
259
260
  import { Location } from './models/LocationModel';
@@ -579,6 +580,7 @@ export const AppDataSource = new DataSource({
579
580
  SlaApprovalsView,
580
581
  ServiceSlaApproval,
581
582
  SlaConfig,
583
+ ChatbotQnsAnsMapping,
582
584
  CSRMBusinessImpactRatingMaster,
583
585
  CSRMLikelihoodMaster,
584
586
  Location,
@@ -309,6 +309,14 @@
309
309
  "logistics_actual_return_updated": {
310
310
  "en": "{{roleName}} updated the Vehicle Actual Return Date, Time, and Reason. Actual Return Date: {{date}}, Actual Return Time: {{time}}, Reason: {{reason}}",
311
311
  "ar": "قام {{roleName}} بتحديث تاريخ الإرجاع الفعلي للمركبة والوقت والسبب. تاريخ الإرجاع الفعلي: {{date}}، وقت الإرجاع الفعلي: {{time}}، السبب: {{reason}}"
312
+ },
313
+ "pending_with_user": {
314
+ "en": "Pending with {{userName}} ({{employeeId}})",
315
+ "ar": "قيد الانتظار لدى {{userName}} ({{employeeId}})"
316
+ },
317
+ "pending_with_role": {
318
+ "en": "Pending with {{roleName}}",
319
+ "ar": "قيد الانتظار لدى {{roleName}}"
312
320
  }
313
321
  },
314
322
  "notificationTemplates": {
@@ -1545,9 +1545,33 @@ export function resolveWorkflowStatusAr(status: string | null | undefined): stri
1545
1545
  return WORKFLOW_STATUS_LABELS[status] ?? status;
1546
1546
  }
1547
1547
 
1548
- export function resolveChatStatusAr(status: string | null | undefined): string | null {
1548
+ export function resolveChatStatusAr(
1549
+ status: string | null | undefined,
1550
+ actors?: BilingualActorNames,
1551
+ ): string | null {
1549
1552
  if (status == null || status === '') return null;
1550
- return CHAT_STATUS_LABELS[status] ?? status;
1553
+
1554
+ const labeled = CHAT_STATUS_LABELS[status];
1555
+ if (labeled) return labeled;
1556
+
1557
+ const normalized = status.trim();
1558
+ const pendingWithUserTpl = CHAT_TEMPLATES.pending_with_user;
1559
+ if (pendingWithUserTpl) {
1560
+ const params = matchTemplateMessage(pendingWithUserTpl.en, normalized);
1561
+ if (params) {
1562
+ return interpolate(pendingWithUserTpl.ar, buildArabicChatTemplateParams(params, actors));
1563
+ }
1564
+ }
1565
+
1566
+ const pendingWithRoleTpl = CHAT_TEMPLATES.pending_with_role;
1567
+ if (pendingWithRoleTpl) {
1568
+ const params = matchTemplateMessage(pendingWithRoleTpl.en, normalized);
1569
+ if (params) {
1570
+ return interpolate(pendingWithRoleTpl.ar, buildArabicChatTemplateParams(params, actors));
1571
+ }
1572
+ }
1573
+
1574
+ return status;
1551
1575
  }
1552
1576
 
1553
1577
  // ─── Legacy row enrichment (read API) ────────────────────────────────────────
@@ -2310,7 +2334,7 @@ export function enrichChatMessage(chat: Record<string, unknown>): Record<string,
2310
2334
  return {
2311
2335
  ...chat,
2312
2336
  message_ar: chat.message_ar ?? resolveChatMessageAr(message, actors),
2313
- status_ar: chat.status_ar ?? resolveChatStatusAr(status),
2337
+ status_ar: chat.status_ar ?? resolveChatStatusAr(status, actors),
2314
2338
  };
2315
2339
  }
2316
2340
 
package/src/index.ts CHANGED
@@ -504,6 +504,7 @@ export * from './models/RequestTenderAnalysisRequestModel';
504
504
  export * from './models/ContractServiceRequestModel';
505
505
  export * from './models/ContractServiceRequestDocumentModel';
506
506
  export * from './models/SlaConfigModel';
507
+ export * from './models/ChatbotQnsAnsMappingModel';
507
508
  export * from './models/SlaRequestModel';
508
509
  export * from './models/SlaMyRequestsViewModel';
509
510
  export * from './models/SlaApprovalsViewModel';
@@ -36,6 +36,9 @@ export class CancellationRequests extends BaseModel {
36
36
  @Column({ type: "integer", nullable: false })
37
37
  old_request_number: number;
38
38
 
39
+ @Column({ type: "varchar", length: 255, nullable: true })
40
+ decision_number: string | null;
41
+
39
42
  @Column({ type: "varchar", length: 500, nullable: false })
40
43
  reason_for_cancellation: string;
41
44
 
@@ -0,0 +1,21 @@
1
+ import { Column, Entity } from "typeorm";
2
+ import { BaseModel } from "./BaseModel";
3
+
4
+ @Entity({ name: "chatbot_qns_ans_mapping" })
5
+ export class ChatbotQnsAnsMapping extends BaseModel {
6
+ @Column({ type: "int", nullable: false })
7
+ service_id: number;
8
+
9
+ @Column({ type: "int", nullable: false })
10
+ sub_service_id: number;
11
+
12
+ @Column({ type: "jsonb", nullable: false })
13
+ question: Record<string, unknown> | unknown[];
14
+
15
+ @Column({ type: "jsonb", nullable: false })
16
+ answer: Record<string, unknown> | unknown[];
17
+
18
+ constructor() {
19
+ super();
20
+ }
21
+ }
@@ -48,6 +48,15 @@ export class ComplaintLostPropertyReportRequest extends BaseModel {
48
48
  @Column({ type: "text", nullable: true })
49
49
  description: string | null;
50
50
 
51
+ @Column({ type: "varchar", length: 100, nullable: true })
52
+ lost_item_classification: string | null;
53
+
54
+ @Column({ type: "varchar", length: 255, nullable: true })
55
+ caa_property_type: string | null;
56
+
57
+ @Column({ type: "varchar", length: 10, nullable: true })
58
+ lost_card: string | null;
59
+
51
60
  @Column({ type: "int", nullable: false })
52
61
  created_by: number;
53
62
 
@@ -25,7 +25,7 @@ export interface LegalComplaintIncidentInput {
25
25
 
26
26
  /** Step 3 — Complainant: Name / Position / Employee ID / Directorate / Department / Section. */
27
27
  export interface LegalComplaintComplainantInput {
28
- name: string;
28
+ name?: string | null;
29
29
  position?: string | null;
30
30
  employee_id?: string | null;
31
31
  directorate?: string | null;
@@ -56,7 +56,7 @@ export interface LegalComplaintStructuredCreateFields {
56
56
  incident_events: string | null;
57
57
  incident_other_details: string | null;
58
58
  incident_description: string;
59
- complainant_name: string;
59
+ complainant_name: string | null;
60
60
  complainant_position: string | null;
61
61
  complainant_employee_id: string | null;
62
62
  complainant_directorate: string | null;
@@ -193,13 +193,6 @@ export function parseLegalComplaintCreateFields(body: Record<string, any>): Lega
193
193
  complainant_section_name = sec.name;
194
194
  }
195
195
 
196
- if (!complainant_name) {
197
- return {
198
- ok: false,
199
- message: 'complainant.name is required (or legacy complainant_details text)',
200
- };
201
- }
202
-
203
196
  const sub = body.complained_employee ?? body.complainedEmployee;
204
197
  let complained_employee_name = '';
205
198
  let complained_employee_position: string | null = null;
@@ -297,8 +290,8 @@ export class LegalComplaintRequest extends BaseModel {
297
290
  @Column({ type: 'text', nullable: false })
298
291
  incident_description: string;
299
292
 
300
- @Column({ type: 'varchar', length: 255, nullable: false })
301
- complainant_name: string;
293
+ @Column({ type: 'varchar', length: 255, nullable: true })
294
+ complainant_name: string | null;
302
295
 
303
296
  @Column({ type: 'varchar', length: 255, nullable: true })
304
297
  complainant_position: string | null;
@@ -20,6 +20,15 @@ export enum MediaPublicationsRequestStatus {
20
20
  REJECTED = 'Rejected',
21
21
  }
22
22
 
23
+ export interface MediaPublicationMaterialLine {
24
+ item_type?: string | null;
25
+ item_code?: string | null;
26
+ description?: string | null;
27
+ quantity?: number | null;
28
+ unit_price?: number | null;
29
+ cost?: number | null;
30
+ }
31
+
23
32
  @Entity({ name: 'media_publications_requests' })
24
33
  export class MediaPublicationsRequests extends BaseModel {
25
34
  @Column({ type: 'int', nullable: true })
@@ -49,9 +58,24 @@ export class MediaPublicationsRequests extends BaseModel {
49
58
  @Column({ type: 'varchar', length: 500, nullable: true })
50
59
  requestor_designation: string | null;
51
60
 
61
+ @Column({ type: 'varchar', length: 500, nullable: true })
62
+ job_title: string | null;
63
+
64
+ @Column({ type: 'varchar', length: 500, nullable: true })
65
+ directorate: string | null;
66
+
67
+ @Column({ type: 'varchar', length: 500, nullable: true })
68
+ department: string | null;
69
+
70
+ @Column({ type: 'varchar', length: 50, nullable: true })
71
+ extension_number: string | null;
72
+
52
73
  @Column({ type: 'varchar', length: 100, nullable: true })
53
74
  contact_number: string | null;
54
75
 
76
+ @Column({ type: 'varchar', length: 100, nullable: true })
77
+ phone_number: string | null;
78
+
55
79
  @Column({ type: 'varchar', length: 320, nullable: true })
56
80
  email: string | null;
57
81
 
@@ -63,8 +87,11 @@ export class MediaPublicationsRequests extends BaseModel {
63
87
  })
64
88
  request_type: MediaPublicationsRequestType;
65
89
 
66
- @Column({ type: 'varchar', length: 120, nullable: false })
67
- item_code: string;
90
+ @Column({ type: 'varchar', length: 120, nullable: true })
91
+ item_code: string | null;
92
+
93
+ @Column({ type: 'varchar', length: 255, nullable: true })
94
+ item_type: string | null;
68
95
 
69
96
  @Column({ type: 'text', nullable: false })
70
97
  description: string;
@@ -83,6 +110,39 @@ export class MediaPublicationsRequests extends BaseModel {
83
110
  })
84
111
  event_type: MediaPublicationsEventType;
85
112
 
113
+ @Column({ type: 'jsonb', nullable: true })
114
+ publication_type: string[] | null;
115
+
116
+ @Column({ type: 'jsonb', nullable: true })
117
+ publication_format: string[] | null;
118
+
119
+ @Column({ type: 'text', nullable: true })
120
+ purpose_of_publication: string | null;
121
+
122
+ @Column({ type: 'text', nullable: true })
123
+ intended_use: string | null;
124
+
125
+ @Column({ type: 'text', nullable: true })
126
+ target_audience: string | null;
127
+
128
+ @Column({ type: 'varchar', length: 50, nullable: true })
129
+ is_new_or_update: string | null;
130
+
131
+ @Column({ type: 'varchar', length: 50, nullable: true })
132
+ approved_budget_available: string | null;
133
+
134
+ @Column({ type: 'int', nullable: true })
135
+ estimated_quantity: number | null;
136
+
137
+ @Column({ type: 'jsonb', nullable: true })
138
+ required_languages: string[] | null;
139
+
140
+ @Column({ type: 'text', nullable: true })
141
+ technical_requirements: string | null;
142
+
143
+ @Column({ type: 'jsonb', nullable: true })
144
+ material_lines: MediaPublicationMaterialLine[] | null;
145
+
86
146
  @Column({ type: 'int', nullable: true })
87
147
  quantity: number | null;
88
148
 
@@ -18,8 +18,16 @@ export enum VipHallMealType {
18
18
  BREAKFAST = "Breakfast",
19
19
  LUNCH = "Lunch",
20
20
  DINNER = "Dinner",
21
+ SNACKS = "Snacks",
21
22
  }
22
23
 
24
+ export const VIP_HALL_MEAL_TYPE_VALUES = [
25
+ VipHallMealType.BREAKFAST,
26
+ VipHallMealType.LUNCH,
27
+ VipHallMealType.DINNER,
28
+ VipHallMealType.SNACKS,
29
+ ] as const;
30
+
23
31
  @Entity({ name: "vip_hall_requests" })
24
32
  export class VipHallRequests extends BaseModel {
25
33
  @Column({ type: "integer", nullable: true })
@@ -82,13 +90,8 @@ export class VipHallRequests extends BaseModel {
82
90
  @Column({ type: "boolean", default: false, nullable: false })
83
91
  meal_required: boolean;
84
92
 
85
- @Column({
86
- type: "enum",
87
- enum: VipHallMealType,
88
- enumName: "vip_hall_meal_type_en",
89
- nullable: true,
90
- })
91
- meal_type: VipHallMealType | null;
93
+ @Column({ type: "jsonb", nullable: true })
94
+ meal_type: string[] | null;
92
95
 
93
96
  @Column({ type: "boolean", default: false, nullable: false })
94
97
  network_support_required: boolean;