@platform-modules/civil-aviation-authority 2.3.277 → 2.3.279
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/.env +1 -1
- package/dist/helpers/cancellation-bilingual-persist.helper.d.ts +37 -0
- package/dist/helpers/cancellation-bilingual-persist.helper.js +77 -0
- package/dist/i18n/workflow-chat-i18n.d.ts +23 -7
- package/dist/i18n/workflow-chat-i18n.js +141 -52
- package/dist/i18n/workflow-chat-i18n.json +22 -9
- package/package.json +1 -1
- package/src/i18n/workflow-chat-i18n.json +22 -9
- package/src/i18n/workflow-chat-i18n.ts +213 -55
package/.env
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EntityManager } from 'typeorm';
|
|
2
|
+
import { CancellationMessageType } from '../models/CancellationChatModel';
|
|
3
|
+
import { CancellationWorkFlowStatus } from '../models/CancellationWorkflowModel';
|
|
4
|
+
export type CancellationWorkflowRowInput = {
|
|
5
|
+
request_id: number;
|
|
6
|
+
service_id?: number | null;
|
|
7
|
+
sub_service_id?: number | null;
|
|
8
|
+
content: string;
|
|
9
|
+
content_ar?: string | null;
|
|
10
|
+
status: CancellationWorkFlowStatus;
|
|
11
|
+
status_ar?: string | null;
|
|
12
|
+
user_id?: number | null;
|
|
13
|
+
role_id?: number | null;
|
|
14
|
+
department_id?: number | null;
|
|
15
|
+
section_id?: number | null;
|
|
16
|
+
step_order?: number | null;
|
|
17
|
+
created_by?: number | null;
|
|
18
|
+
};
|
|
19
|
+
export type CancellationChatRowInput = {
|
|
20
|
+
request_id: number;
|
|
21
|
+
service_id?: number | null;
|
|
22
|
+
sub_service_id?: number | null;
|
|
23
|
+
user_id: number;
|
|
24
|
+
role_id?: number | null;
|
|
25
|
+
message: string;
|
|
26
|
+
message_ar?: string | null;
|
|
27
|
+
messageType?: CancellationMessageType;
|
|
28
|
+
status?: string | null;
|
|
29
|
+
status_ar?: string | null;
|
|
30
|
+
created_by?: number | string | null;
|
|
31
|
+
};
|
|
32
|
+
/** Force-persist Arabic workflow columns (guards against stale TypeORM entity metadata). */
|
|
33
|
+
export declare function patchCancellationWorkflowArabic(manager: EntityManager, workflowId: number, contentAr: string | null, statusAr: string | null): Promise<void>;
|
|
34
|
+
/** Force-persist Arabic chat columns (guards against stale TypeORM entity metadata). */
|
|
35
|
+
export declare function patchCancellationChatArabic(manager: EntityManager, chatId: number, messageAr: string | null, statusAr: string | null): Promise<void>;
|
|
36
|
+
export declare function insertCancellationWorkflowRow(manager: EntityManager, input: CancellationWorkflowRowInput): Promise<number>;
|
|
37
|
+
export declare function insertCancellationChatRow(manager: EntityManager, input: CancellationChatRowInput): Promise<number>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.patchCancellationWorkflowArabic = patchCancellationWorkflowArabic;
|
|
4
|
+
exports.patchCancellationChatArabic = patchCancellationChatArabic;
|
|
5
|
+
exports.insertCancellationWorkflowRow = insertCancellationWorkflowRow;
|
|
6
|
+
exports.insertCancellationChatRow = insertCancellationChatRow;
|
|
7
|
+
const CancellationChatModel_1 = require("../models/CancellationChatModel");
|
|
8
|
+
const CancellationWorkflowModel_1 = require("../models/CancellationWorkflowModel");
|
|
9
|
+
const workflow_chat_i18n_1 = require("../i18n/workflow-chat-i18n");
|
|
10
|
+
function resolveWorkflowRowId(insertResult) {
|
|
11
|
+
const id = insertResult.raw?.[0]?.id ?? insertResult.identifiers?.[0]?.id;
|
|
12
|
+
if (!id) {
|
|
13
|
+
throw new Error('Failed to insert cancellation workflow row');
|
|
14
|
+
}
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
function resolveChatRowId(insertResult) {
|
|
18
|
+
const id = insertResult.raw?.[0]?.id ?? insertResult.identifiers?.[0]?.id;
|
|
19
|
+
if (!id) {
|
|
20
|
+
throw new Error('Failed to insert cancellation chat row');
|
|
21
|
+
}
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
/** Force-persist Arabic workflow columns (guards against stale TypeORM entity metadata). */
|
|
25
|
+
async function patchCancellationWorkflowArabic(manager, workflowId, contentAr, statusAr) {
|
|
26
|
+
await manager.query(`UPDATE cancellation_workflows
|
|
27
|
+
SET content_ar = $1,
|
|
28
|
+
status_ar = $2,
|
|
29
|
+
updated_at = CURRENT_TIMESTAMP
|
|
30
|
+
WHERE id = $3`, [contentAr, statusAr, workflowId]);
|
|
31
|
+
}
|
|
32
|
+
/** Force-persist Arabic chat columns (guards against stale TypeORM entity metadata). */
|
|
33
|
+
async function patchCancellationChatArabic(manager, chatId, messageAr, statusAr) {
|
|
34
|
+
await manager.query(`UPDATE cancellation_chats
|
|
35
|
+
SET message_ar = $1,
|
|
36
|
+
status_ar = $2,
|
|
37
|
+
updated_at = CURRENT_TIMESTAMP
|
|
38
|
+
WHERE id = $3`, [messageAr, statusAr, chatId]);
|
|
39
|
+
}
|
|
40
|
+
async function insertCancellationWorkflowRow(manager, input) {
|
|
41
|
+
const contentAr = input.content_ar ?? null;
|
|
42
|
+
const statusAr = input.status_ar ?? (0, workflow_chat_i18n_1.resolveWorkflowStatusAr)(input.status);
|
|
43
|
+
const insertResult = await manager
|
|
44
|
+
.createQueryBuilder()
|
|
45
|
+
.insert()
|
|
46
|
+
.into(CancellationWorkflowModel_1.CancellationWorkFlow)
|
|
47
|
+
.values({
|
|
48
|
+
...input,
|
|
49
|
+
content_ar: contentAr,
|
|
50
|
+
status_ar: statusAr,
|
|
51
|
+
})
|
|
52
|
+
.returning('id')
|
|
53
|
+
.execute();
|
|
54
|
+
const id = resolveWorkflowRowId(insertResult);
|
|
55
|
+
await patchCancellationWorkflowArabic(manager, id, contentAr, statusAr);
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
async function insertCancellationChatRow(manager, input) {
|
|
59
|
+
const messageAr = input.message_ar ?? null;
|
|
60
|
+
const statusAr = input.status_ar ?? (0, workflow_chat_i18n_1.resolveChatStatusAr)(input.status ?? null);
|
|
61
|
+
const messageType = input.messageType ?? CancellationChatModel_1.CancellationMessageType.TEXT;
|
|
62
|
+
const insertResult = await manager
|
|
63
|
+
.createQueryBuilder()
|
|
64
|
+
.insert()
|
|
65
|
+
.into(CancellationChatModel_1.CancellationRequestChat)
|
|
66
|
+
.values({
|
|
67
|
+
...input,
|
|
68
|
+
message_ar: messageAr,
|
|
69
|
+
status_ar: statusAr,
|
|
70
|
+
messageType,
|
|
71
|
+
})
|
|
72
|
+
.returning('id')
|
|
73
|
+
.execute();
|
|
74
|
+
const id = resolveChatRowId(insertResult);
|
|
75
|
+
await patchCancellationChatArabic(manager, id, messageAr, statusAr);
|
|
76
|
+
return id;
|
|
77
|
+
}
|
|
@@ -10,14 +10,24 @@ type WorkflowChatI18nCatalog = {
|
|
|
10
10
|
};
|
|
11
11
|
/** Read-only view of the shared workflow/chat translation catalog. */
|
|
12
12
|
export declare function getWorkflowChatI18nCatalog(): WorkflowChatI18nCatalog;
|
|
13
|
-
export declare function buildBilingualFromTemplate(templateCatalog: Record<string, BilingualText>, key: string, params?: Record<string, string>): BilingualText;
|
|
14
|
-
export declare function workflowTemplate(key: string, params?: Record<string, string>): BilingualText;
|
|
15
|
-
export declare function chatTemplate(key: string, params?: Record<string, string>): BilingualText;
|
|
13
|
+
export declare function buildBilingualFromTemplate(templateCatalog: Record<string, BilingualText>, key: string, params?: Record<string, string>, arParams?: Record<string, string>): BilingualText;
|
|
14
|
+
export declare function workflowTemplate(key: string, params?: Record<string, string>, arParams?: Record<string, string>): BilingualText;
|
|
15
|
+
export declare function chatTemplate(key: string, params?: Record<string, string>, arParams?: Record<string, string>): BilingualText;
|
|
16
16
|
export declare function resolveWorkflowStatusAr(status: string | null | undefined): string | null;
|
|
17
17
|
export declare function resolveChatStatusAr(status: string | null | undefined): string | null;
|
|
18
|
+
/** Arabic DB value when present; otherwise English so AR text stays structurally complete. */
|
|
19
|
+
export declare function resolveBilingualName(englishName?: string | null, arabicName?: string | null): string;
|
|
20
|
+
export declare function pickArabicName(arabicName?: string | null, fallback?: string): string;
|
|
18
21
|
export declare function pickLocalizedName(englishName?: string | null, arabicName?: string | null, fallback?: string): string;
|
|
22
|
+
/** Parallel EN/AR dept-section labels (Arabic columns preferred; English fallback per field). */
|
|
23
|
+
export declare function joinDeptSectionBilingualLabels(deptEn?: string | null, deptAr?: string | null, sectEn?: string | null, sectAr?: string | null): {
|
|
24
|
+
en: string;
|
|
25
|
+
ar: string;
|
|
26
|
+
};
|
|
27
|
+
/** @deprecated Use joinDeptSectionBilingualLabels */
|
|
28
|
+
export declare function joinDeptSectionArabicLabels(deptArabic?: string | null, sectionArabic?: string | null): string;
|
|
19
29
|
export declare function buildApprovalWorkflowContent(params: {
|
|
20
|
-
status: 'Approved' | 'Rejected' | 'Pending' | string;
|
|
30
|
+
status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
|
|
21
31
|
roleName?: string | null;
|
|
22
32
|
roleArabicName?: string | null;
|
|
23
33
|
comment?: string | null;
|
|
@@ -26,15 +36,21 @@ export declare function buildHumanApprovalWorkflowContent(params: {
|
|
|
26
36
|
isFirst: boolean;
|
|
27
37
|
roleName?: string | null;
|
|
28
38
|
roleArabicName?: string | null;
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
deptName?: string | null;
|
|
40
|
+
deptArabicName?: string | null;
|
|
41
|
+
sectionName?: string | null;
|
|
42
|
+
sectionArabicName?: string | null;
|
|
31
43
|
}): BilingualText;
|
|
32
44
|
export declare function buildApprovalChatMessage(params: {
|
|
33
|
-
status: 'Approved' | 'Rejected' | string;
|
|
45
|
+
status: 'Approved' | 'Rejected' | 'Returned for Modification' | string;
|
|
34
46
|
roleName?: string | null;
|
|
35
47
|
roleArabicName?: string | null;
|
|
36
48
|
comment?: string | null;
|
|
37
49
|
}): BilingualText;
|
|
50
|
+
/** Resolve Arabic workflow content from a stored English content string (legacy rows). */
|
|
51
|
+
export declare function resolveWorkflowContentAr(content: string | null | undefined): string | null;
|
|
52
|
+
/** Resolve Arabic chat message from a stored English message string (legacy rows). */
|
|
53
|
+
export declare function resolveChatMessageAr(message: string | null | undefined): string | null;
|
|
38
54
|
export declare function enrichCancellationWorkflowLog(log: Record<string, unknown>): Record<string, unknown>;
|
|
39
55
|
export declare function enrichCancellationChatMessage(chat: Record<string, unknown>): Record<string, unknown>;
|
|
40
56
|
export {};
|
|
@@ -9,10 +9,16 @@ exports.workflowTemplate = workflowTemplate;
|
|
|
9
9
|
exports.chatTemplate = chatTemplate;
|
|
10
10
|
exports.resolveWorkflowStatusAr = resolveWorkflowStatusAr;
|
|
11
11
|
exports.resolveChatStatusAr = resolveChatStatusAr;
|
|
12
|
+
exports.resolveBilingualName = resolveBilingualName;
|
|
13
|
+
exports.pickArabicName = pickArabicName;
|
|
12
14
|
exports.pickLocalizedName = pickLocalizedName;
|
|
15
|
+
exports.joinDeptSectionBilingualLabels = joinDeptSectionBilingualLabels;
|
|
16
|
+
exports.joinDeptSectionArabicLabels = joinDeptSectionArabicLabels;
|
|
13
17
|
exports.buildApprovalWorkflowContent = buildApprovalWorkflowContent;
|
|
14
18
|
exports.buildHumanApprovalWorkflowContent = buildHumanApprovalWorkflowContent;
|
|
15
19
|
exports.buildApprovalChatMessage = buildApprovalChatMessage;
|
|
20
|
+
exports.resolveWorkflowContentAr = resolveWorkflowContentAr;
|
|
21
|
+
exports.resolveChatMessageAr = resolveChatMessageAr;
|
|
16
22
|
exports.enrichCancellationWorkflowLog = enrichCancellationWorkflowLog;
|
|
17
23
|
exports.enrichCancellationChatMessage = enrichCancellationChatMessage;
|
|
18
24
|
const workflow_chat_i18n_json_1 = __importDefault(require("./workflow-chat-i18n.json"));
|
|
@@ -27,21 +33,22 @@ function getWorkflowChatI18nCatalog() {
|
|
|
27
33
|
function interpolate(template, params = {}) {
|
|
28
34
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => params[key] ?? '');
|
|
29
35
|
}
|
|
30
|
-
function buildBilingualFromTemplate(templateCatalog, key, params = {}) {
|
|
36
|
+
function buildBilingualFromTemplate(templateCatalog, key, params = {}, arParams) {
|
|
31
37
|
const tpl = templateCatalog[key];
|
|
32
38
|
if (!tpl) {
|
|
33
39
|
return { en: key, ar: key };
|
|
34
40
|
}
|
|
41
|
+
const arabicParams = arParams ?? params;
|
|
35
42
|
return {
|
|
36
43
|
en: interpolate(tpl.en, params),
|
|
37
|
-
ar: interpolate(tpl.ar,
|
|
44
|
+
ar: interpolate(tpl.ar, arabicParams),
|
|
38
45
|
};
|
|
39
46
|
}
|
|
40
|
-
function workflowTemplate(key, params = {}) {
|
|
41
|
-
return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params);
|
|
47
|
+
function workflowTemplate(key, params = {}, arParams) {
|
|
48
|
+
return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params, arParams);
|
|
42
49
|
}
|
|
43
|
-
function chatTemplate(key, params = {}) {
|
|
44
|
-
return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params);
|
|
50
|
+
function chatTemplate(key, params = {}, arParams) {
|
|
51
|
+
return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params, arParams);
|
|
45
52
|
}
|
|
46
53
|
function resolveWorkflowStatusAr(status) {
|
|
47
54
|
if (status == null || status === '')
|
|
@@ -53,6 +60,16 @@ function resolveChatStatusAr(status) {
|
|
|
53
60
|
return null;
|
|
54
61
|
return CHAT_STATUS_LABELS[status] ?? status;
|
|
55
62
|
}
|
|
63
|
+
/** Arabic DB value when present; otherwise English so AR text stays structurally complete. */
|
|
64
|
+
function resolveBilingualName(englishName, arabicName) {
|
|
65
|
+
const ar = arabicName?.trim();
|
|
66
|
+
if (ar)
|
|
67
|
+
return ar;
|
|
68
|
+
return englishName?.trim() || '';
|
|
69
|
+
}
|
|
70
|
+
function pickArabicName(arabicName, fallback = '') {
|
|
71
|
+
return arabicName?.trim() || fallback;
|
|
72
|
+
}
|
|
56
73
|
function pickLocalizedName(englishName, arabicName, fallback = '') {
|
|
57
74
|
const ar = arabicName?.trim();
|
|
58
75
|
if (ar)
|
|
@@ -62,53 +79,69 @@ function pickLocalizedName(englishName, arabicName, fallback = '') {
|
|
|
62
79
|
return en;
|
|
63
80
|
return fallback;
|
|
64
81
|
}
|
|
82
|
+
function joinDeptSectionLabels(dept, section) {
|
|
83
|
+
return [dept, section].map((s) => s?.trim()).filter(Boolean).join(' - ');
|
|
84
|
+
}
|
|
85
|
+
/** Parallel EN/AR dept-section labels (Arabic columns preferred; English fallback per field). */
|
|
86
|
+
function joinDeptSectionBilingualLabels(deptEn, deptAr, sectEn, sectAr) {
|
|
87
|
+
const en = joinDeptSectionLabels(deptEn, sectEn);
|
|
88
|
+
const ar = joinDeptSectionLabels(resolveBilingualName(deptEn, deptAr), resolveBilingualName(sectEn, sectAr));
|
|
89
|
+
return { en, ar };
|
|
90
|
+
}
|
|
91
|
+
/** @deprecated Use joinDeptSectionBilingualLabels */
|
|
92
|
+
function joinDeptSectionArabicLabels(deptArabic, sectionArabic) {
|
|
93
|
+
return joinDeptSectionLabels(deptArabic, sectionArabic);
|
|
94
|
+
}
|
|
95
|
+
function formatWorkflowCommentSuffix(comment) {
|
|
96
|
+
const text = comment?.trim();
|
|
97
|
+
return text ? `: ${text}` : '';
|
|
98
|
+
}
|
|
99
|
+
function formatChatCommentSuffix(comment) {
|
|
100
|
+
const text = comment?.trim();
|
|
101
|
+
return text ? ` - ${text}` : '';
|
|
102
|
+
}
|
|
103
|
+
function buildWorkflowApprovalByName(templateKey, nameEn, nameAr, comment) {
|
|
104
|
+
const commentSuffix = formatWorkflowCommentSuffix(comment);
|
|
105
|
+
return workflowTemplate(templateKey, { name: nameEn, comment: commentSuffix }, { name: nameAr, comment: commentSuffix });
|
|
106
|
+
}
|
|
107
|
+
function buildWorkflowApprovalWithoutName(templateKey, comment) {
|
|
108
|
+
const commentSuffix = formatWorkflowCommentSuffix(comment);
|
|
109
|
+
return workflowTemplate(templateKey, { comment: commentSuffix }, { comment: commentSuffix });
|
|
110
|
+
}
|
|
65
111
|
function buildApprovalWorkflowContent(params) {
|
|
66
112
|
const nameEn = params.roleName?.trim() || '';
|
|
67
|
-
const nameAr =
|
|
68
|
-
let base;
|
|
113
|
+
const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
|
|
69
114
|
if (params.status === 'Approved') {
|
|
70
|
-
|
|
71
|
-
?
|
|
72
|
-
:
|
|
73
|
-
if (nameEn) {
|
|
74
|
-
base.ar = workflowTemplate('request_approved_by', { name: nameAr }).ar;
|
|
75
|
-
}
|
|
115
|
+
return nameEn
|
|
116
|
+
? buildWorkflowApprovalByName('request_approved_by', nameEn, nameAr, params.comment)
|
|
117
|
+
: buildWorkflowApprovalWithoutName('request_approved', params.comment);
|
|
76
118
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
?
|
|
80
|
-
:
|
|
81
|
-
if (nameEn) {
|
|
82
|
-
base.ar = workflowTemplate('request_rejected_by', { name: nameAr }).ar;
|
|
83
|
-
}
|
|
119
|
+
if (params.status === 'Rejected') {
|
|
120
|
+
return nameEn
|
|
121
|
+
? buildWorkflowApprovalByName('request_rejected_by', nameEn, nameAr, params.comment)
|
|
122
|
+
: buildWorkflowApprovalWithoutName('request_rejected', params.comment);
|
|
84
123
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
?
|
|
88
|
-
:
|
|
89
|
-
if (nameEn) {
|
|
90
|
-
base.ar = workflowTemplate('request_approval_pending_from', { name: nameAr }).ar;
|
|
91
|
-
}
|
|
124
|
+
if (params.status === 'Returned for Modification') {
|
|
125
|
+
return nameEn
|
|
126
|
+
? buildWorkflowApprovalByName('request_returned_for_modification_by', nameEn, nameAr, params.comment)
|
|
127
|
+
: buildWorkflowApprovalWithoutName('request_returned_for_modification', params.comment);
|
|
92
128
|
}
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
ar: `${base.ar}: ${comment}`,
|
|
99
|
-
};
|
|
129
|
+
const commentSuffix = formatWorkflowCommentSuffix(params.comment);
|
|
130
|
+
if (nameEn) {
|
|
131
|
+
return workflowTemplate('request_approval_pending_from', { name: nameEn, comment: commentSuffix }, { name: nameAr, comment: commentSuffix });
|
|
132
|
+
}
|
|
133
|
+
return workflowTemplate('request_approval_pending', { comment: commentSuffix }, { comment: commentSuffix });
|
|
100
134
|
}
|
|
101
135
|
function buildHumanApprovalWorkflowContent(params) {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
};
|
|
136
|
+
const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
|
|
137
|
+
if (params.roleName?.trim()) {
|
|
138
|
+
const nameEn = params.roleName.trim();
|
|
139
|
+
const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
|
|
140
|
+
return workflowTemplate(key, { name: nameEn }, { name: nameAr });
|
|
141
|
+
}
|
|
142
|
+
const { en, ar } = joinDeptSectionBilingualLabels(params.deptName, params.deptArabicName, params.sectionName, params.sectionArabicName);
|
|
143
|
+
if (en) {
|
|
144
|
+
return workflowTemplate(key, { name: en }, { name: ar });
|
|
112
145
|
}
|
|
113
146
|
return params.isFirst
|
|
114
147
|
? workflowTemplate('request_approval_in_progress')
|
|
@@ -116,25 +149,81 @@ function buildHumanApprovalWorkflowContent(params) {
|
|
|
116
149
|
}
|
|
117
150
|
function buildApprovalChatMessage(params) {
|
|
118
151
|
const roleEn = params.roleName?.trim() || 'Unknown Role';
|
|
119
|
-
const roleAr =
|
|
120
|
-
const commentSuffix =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
153
|
+
const commentSuffix = formatChatCommentSuffix(params.comment);
|
|
154
|
+
let key;
|
|
155
|
+
if (params.status === 'Approved') {
|
|
156
|
+
key = 'request_approved_by_role';
|
|
157
|
+
}
|
|
158
|
+
else if (params.status === 'Rejected') {
|
|
159
|
+
key = 'request_rejected_by_role';
|
|
160
|
+
}
|
|
161
|
+
else if (params.status === 'Returned for Modification') {
|
|
162
|
+
key = 'request_returned_for_modification_by_role';
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
key = 'request_approved_by_role';
|
|
166
|
+
}
|
|
167
|
+
return chatTemplate(key, { roleName: roleEn, comment: commentSuffix }, { roleName: roleAr, comment: commentSuffix });
|
|
168
|
+
}
|
|
169
|
+
/** Resolve Arabic workflow content from a stored English content string (legacy rows). */
|
|
170
|
+
function resolveWorkflowContentAr(content) {
|
|
171
|
+
if (content == null || content.trim() === '')
|
|
172
|
+
return null;
|
|
173
|
+
const normalized = content.trim();
|
|
174
|
+
for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
|
|
175
|
+
if (tpl.en === normalized)
|
|
176
|
+
return tpl.ar;
|
|
177
|
+
}
|
|
178
|
+
const commentIdx = normalized.indexOf(': ');
|
|
179
|
+
if (commentIdx > 0) {
|
|
180
|
+
const baseEn = normalized.slice(0, commentIdx).trim();
|
|
181
|
+
const commentText = normalized.slice(commentIdx + 2).trim();
|
|
182
|
+
const commentSuffix = formatWorkflowCommentSuffix(commentText);
|
|
183
|
+
for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
|
|
184
|
+
if (!tpl.en.includes('{{comment}}'))
|
|
185
|
+
continue;
|
|
186
|
+
const withoutComment = tpl.en.replace('{{comment}}', '');
|
|
187
|
+
if (withoutComment.includes('{{name}}')) {
|
|
188
|
+
const staticPrefix = withoutComment.replace('{{name}}', '').trim();
|
|
189
|
+
if (baseEn.startsWith(staticPrefix)) {
|
|
190
|
+
const namePart = baseEn.slice(staticPrefix.length).trim();
|
|
191
|
+
return interpolate(tpl.ar, { name: namePart, comment: commentSuffix });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (withoutComment.trim() === baseEn) {
|
|
195
|
+
return interpolate(tpl.ar, { comment: commentSuffix });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
/** Resolve Arabic chat message from a stored English message string (legacy rows). */
|
|
202
|
+
function resolveChatMessageAr(message) {
|
|
203
|
+
if (message == null || message.trim() === '')
|
|
204
|
+
return null;
|
|
205
|
+
const normalized = message.trim();
|
|
206
|
+
for (const tpl of Object.values(CHAT_TEMPLATES)) {
|
|
207
|
+
if (tpl.en === normalized)
|
|
208
|
+
return tpl.ar;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
126
211
|
}
|
|
127
212
|
function enrichCancellationWorkflowLog(log) {
|
|
128
213
|
const status = log.status != null ? String(log.status) : null;
|
|
214
|
+
const content = log.content != null ? String(log.content) : null;
|
|
129
215
|
return {
|
|
130
216
|
...log,
|
|
217
|
+
content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
|
|
131
218
|
status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
|
|
132
219
|
};
|
|
133
220
|
}
|
|
134
221
|
function enrichCancellationChatMessage(chat) {
|
|
135
222
|
const status = chat.status != null ? String(chat.status) : null;
|
|
223
|
+
const message = chat.message != null ? String(chat.message) : null;
|
|
136
224
|
return {
|
|
137
225
|
...chat,
|
|
226
|
+
message_ar: chat.message_ar ?? resolveChatMessageAr(message),
|
|
138
227
|
status_ar: chat.status_ar ?? resolveChatStatusAr(status),
|
|
139
228
|
};
|
|
140
229
|
}
|
|
@@ -29,20 +29,28 @@
|
|
|
29
29
|
"ar": "في انتظار موافقة {{name}}"
|
|
30
30
|
},
|
|
31
31
|
"request_approved_by": {
|
|
32
|
-
"en": "Request Approved by {{name}}",
|
|
33
|
-
"ar": "تمت الموافقة على الطلب من قبل {{name}}"
|
|
32
|
+
"en": "Request Approved by {{name}}{{comment}}",
|
|
33
|
+
"ar": "تمت الموافقة على الطلب من قبل {{name}}{{comment}}"
|
|
34
34
|
},
|
|
35
35
|
"request_rejected_by": {
|
|
36
|
-
"en": "Request Rejected by {{name}}",
|
|
37
|
-
"ar": "تم رفض الطلب من قبل {{name}}"
|
|
36
|
+
"en": "Request Rejected by {{name}}{{comment}}",
|
|
37
|
+
"ar": "تم رفض الطلب من قبل {{name}}{{comment}}"
|
|
38
38
|
},
|
|
39
39
|
"request_approved": {
|
|
40
|
-
"en": "Request Approved",
|
|
41
|
-
"ar": "تمت الموافقة على الطلب"
|
|
40
|
+
"en": "Request Approved{{comment}}",
|
|
41
|
+
"ar": "تمت الموافقة على الطلب{{comment}}"
|
|
42
42
|
},
|
|
43
43
|
"request_rejected": {
|
|
44
|
-
"en": "Request Rejected",
|
|
45
|
-
"ar": "تم رفض الطلب"
|
|
44
|
+
"en": "Request Rejected{{comment}}",
|
|
45
|
+
"ar": "تم رفض الطلب{{comment}}"
|
|
46
|
+
},
|
|
47
|
+
"request_returned_for_modification_by": {
|
|
48
|
+
"en": "Request Returned for Modification by {{name}}{{comment}}",
|
|
49
|
+
"ar": "تم إرجاع الطلب للتعديل من قبل {{name}}{{comment}}"
|
|
50
|
+
},
|
|
51
|
+
"request_returned_for_modification": {
|
|
52
|
+
"en": "Request Returned for Modification{{comment}}",
|
|
53
|
+
"ar": "تم إرجاع الطلب للتعديل{{comment}}"
|
|
46
54
|
}
|
|
47
55
|
},
|
|
48
56
|
"chatTemplates": {
|
|
@@ -61,6 +69,10 @@
|
|
|
61
69
|
"request_rejected_by_role": {
|
|
62
70
|
"en": "Request Rejected by: {{roleName}}{{comment}}",
|
|
63
71
|
"ar": "تم رفض الطلب من قبل: {{roleName}}{{comment}}"
|
|
72
|
+
},
|
|
73
|
+
"request_returned_for_modification_by_role": {
|
|
74
|
+
"en": "Request Returned for Modification by: {{roleName}}{{comment}}",
|
|
75
|
+
"ar": "تم إرجاع الطلب للتعديل من قبل: {{roleName}}{{comment}}"
|
|
64
76
|
}
|
|
65
77
|
},
|
|
66
78
|
"workflowStatusLabels": {
|
|
@@ -73,6 +85,7 @@
|
|
|
73
85
|
"Received": "تم الاستلام",
|
|
74
86
|
"Approved": "موافق عليه",
|
|
75
87
|
"Rejected": "مرفوض",
|
|
76
|
-
"Pending": "قيد الانتظار"
|
|
88
|
+
"Pending": "قيد الانتظار",
|
|
89
|
+
"Returned for Modification": "أُعيد للتعديل"
|
|
77
90
|
}
|
|
78
91
|
}
|
package/package.json
CHANGED
|
@@ -29,20 +29,28 @@
|
|
|
29
29
|
"ar": "في انتظار موافقة {{name}}"
|
|
30
30
|
},
|
|
31
31
|
"request_approved_by": {
|
|
32
|
-
"en": "Request Approved by {{name}}",
|
|
33
|
-
"ar": "تمت الموافقة على الطلب من قبل {{name}}"
|
|
32
|
+
"en": "Request Approved by {{name}}{{comment}}",
|
|
33
|
+
"ar": "تمت الموافقة على الطلب من قبل {{name}}{{comment}}"
|
|
34
34
|
},
|
|
35
35
|
"request_rejected_by": {
|
|
36
|
-
"en": "Request Rejected by {{name}}",
|
|
37
|
-
"ar": "تم رفض الطلب من قبل {{name}}"
|
|
36
|
+
"en": "Request Rejected by {{name}}{{comment}}",
|
|
37
|
+
"ar": "تم رفض الطلب من قبل {{name}}{{comment}}"
|
|
38
38
|
},
|
|
39
39
|
"request_approved": {
|
|
40
|
-
"en": "Request Approved",
|
|
41
|
-
"ar": "تمت الموافقة على الطلب"
|
|
40
|
+
"en": "Request Approved{{comment}}",
|
|
41
|
+
"ar": "تمت الموافقة على الطلب{{comment}}"
|
|
42
42
|
},
|
|
43
43
|
"request_rejected": {
|
|
44
|
-
"en": "Request Rejected",
|
|
45
|
-
"ar": "تم رفض الطلب"
|
|
44
|
+
"en": "Request Rejected{{comment}}",
|
|
45
|
+
"ar": "تم رفض الطلب{{comment}}"
|
|
46
|
+
},
|
|
47
|
+
"request_returned_for_modification_by": {
|
|
48
|
+
"en": "Request Returned for Modification by {{name}}{{comment}}",
|
|
49
|
+
"ar": "تم إرجاع الطلب للتعديل من قبل {{name}}{{comment}}"
|
|
50
|
+
},
|
|
51
|
+
"request_returned_for_modification": {
|
|
52
|
+
"en": "Request Returned for Modification{{comment}}",
|
|
53
|
+
"ar": "تم إرجاع الطلب للتعديل{{comment}}"
|
|
46
54
|
}
|
|
47
55
|
},
|
|
48
56
|
"chatTemplates": {
|
|
@@ -61,6 +69,10 @@
|
|
|
61
69
|
"request_rejected_by_role": {
|
|
62
70
|
"en": "Request Rejected by: {{roleName}}{{comment}}",
|
|
63
71
|
"ar": "تم رفض الطلب من قبل: {{roleName}}{{comment}}"
|
|
72
|
+
},
|
|
73
|
+
"request_returned_for_modification_by_role": {
|
|
74
|
+
"en": "Request Returned for Modification by: {{roleName}}{{comment}}",
|
|
75
|
+
"ar": "تم إرجاع الطلب للتعديل من قبل: {{roleName}}{{comment}}"
|
|
64
76
|
}
|
|
65
77
|
},
|
|
66
78
|
"workflowStatusLabels": {
|
|
@@ -73,6 +85,7 @@
|
|
|
73
85
|
"Received": "تم الاستلام",
|
|
74
86
|
"Approved": "موافق عليه",
|
|
75
87
|
"Rejected": "مرفوض",
|
|
76
|
-
"Pending": "قيد الانتظار"
|
|
88
|
+
"Pending": "قيد الانتظار",
|
|
89
|
+
"Returned for Modification": "أُعيد للتعديل"
|
|
77
90
|
}
|
|
78
91
|
}
|
|
@@ -26,24 +26,34 @@ function interpolate(template: string, params: Record<string, string> = {}): str
|
|
|
26
26
|
export function buildBilingualFromTemplate(
|
|
27
27
|
templateCatalog: Record<string, BilingualText>,
|
|
28
28
|
key: string,
|
|
29
|
-
params: Record<string, string> = {}
|
|
29
|
+
params: Record<string, string> = {},
|
|
30
|
+
arParams?: Record<string, string>,
|
|
30
31
|
): BilingualText {
|
|
31
32
|
const tpl = templateCatalog[key];
|
|
32
33
|
if (!tpl) {
|
|
33
34
|
return { en: key, ar: key };
|
|
34
35
|
}
|
|
36
|
+
const arabicParams = arParams ?? params;
|
|
35
37
|
return {
|
|
36
38
|
en: interpolate(tpl.en, params),
|
|
37
|
-
ar: interpolate(tpl.ar,
|
|
39
|
+
ar: interpolate(tpl.ar, arabicParams),
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
export function workflowTemplate(
|
|
42
|
-
|
|
43
|
+
export function workflowTemplate(
|
|
44
|
+
key: string,
|
|
45
|
+
params: Record<string, string> = {},
|
|
46
|
+
arParams?: Record<string, string>,
|
|
47
|
+
): BilingualText {
|
|
48
|
+
return buildBilingualFromTemplate(WORKFLOW_TEMPLATES, key, params, arParams);
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
export function chatTemplate(
|
|
46
|
-
|
|
51
|
+
export function chatTemplate(
|
|
52
|
+
key: string,
|
|
53
|
+
params: Record<string, string> = {},
|
|
54
|
+
arParams?: Record<string, string>,
|
|
55
|
+
): BilingualText {
|
|
56
|
+
return buildBilingualFromTemplate(CHAT_TEMPLATES, key, params, arParams);
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
export function resolveWorkflowStatusAr(status: string | null | undefined): string | null {
|
|
@@ -56,6 +66,20 @@ export function resolveChatStatusAr(status: string | null | undefined): string |
|
|
|
56
66
|
return CHAT_STATUS_LABELS[status] ?? status;
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
/** Arabic DB value when present; otherwise English so AR text stays structurally complete. */
|
|
70
|
+
export function resolveBilingualName(
|
|
71
|
+
englishName?: string | null,
|
|
72
|
+
arabicName?: string | null,
|
|
73
|
+
): string {
|
|
74
|
+
const ar = arabicName?.trim();
|
|
75
|
+
if (ar) return ar;
|
|
76
|
+
return englishName?.trim() || '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function pickArabicName(arabicName?: string | null, fallback = ''): string {
|
|
80
|
+
return arabicName?.trim() || fallback;
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
export function pickLocalizedName(
|
|
60
84
|
englishName?: string | null,
|
|
61
85
|
arabicName?: string | null,
|
|
@@ -68,66 +92,140 @@ export function pickLocalizedName(
|
|
|
68
92
|
return fallback;
|
|
69
93
|
}
|
|
70
94
|
|
|
95
|
+
function joinDeptSectionLabels(
|
|
96
|
+
dept?: string | null,
|
|
97
|
+
section?: string | null,
|
|
98
|
+
): string {
|
|
99
|
+
return [dept, section].map((s) => s?.trim()).filter(Boolean).join(' - ');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Parallel EN/AR dept-section labels (Arabic columns preferred; English fallback per field). */
|
|
103
|
+
export function joinDeptSectionBilingualLabels(
|
|
104
|
+
deptEn?: string | null,
|
|
105
|
+
deptAr?: string | null,
|
|
106
|
+
sectEn?: string | null,
|
|
107
|
+
sectAr?: string | null,
|
|
108
|
+
): { en: string; ar: string } {
|
|
109
|
+
const en = joinDeptSectionLabels(deptEn, sectEn);
|
|
110
|
+
const ar = joinDeptSectionLabels(
|
|
111
|
+
resolveBilingualName(deptEn, deptAr),
|
|
112
|
+
resolveBilingualName(sectEn, sectAr),
|
|
113
|
+
);
|
|
114
|
+
return { en, ar };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @deprecated Use joinDeptSectionBilingualLabels */
|
|
118
|
+
export function joinDeptSectionArabicLabels(
|
|
119
|
+
deptArabic?: string | null,
|
|
120
|
+
sectionArabic?: string | null,
|
|
121
|
+
): string {
|
|
122
|
+
return joinDeptSectionLabels(deptArabic, sectionArabic);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatWorkflowCommentSuffix(comment?: string | null): string {
|
|
126
|
+
const text = comment?.trim();
|
|
127
|
+
return text ? `: ${text}` : '';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function formatChatCommentSuffix(comment?: string | null): string {
|
|
131
|
+
const text = comment?.trim();
|
|
132
|
+
return text ? ` - ${text}` : '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildWorkflowApprovalByName(
|
|
136
|
+
templateKey: 'request_approved_by' | 'request_rejected_by' | 'request_returned_for_modification_by',
|
|
137
|
+
nameEn: string,
|
|
138
|
+
nameAr: string,
|
|
139
|
+
comment?: string | null,
|
|
140
|
+
): BilingualText {
|
|
141
|
+
const commentSuffix = formatWorkflowCommentSuffix(comment);
|
|
142
|
+
return workflowTemplate(
|
|
143
|
+
templateKey,
|
|
144
|
+
{ name: nameEn, comment: commentSuffix },
|
|
145
|
+
{ name: nameAr, comment: commentSuffix },
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildWorkflowApprovalWithoutName(
|
|
150
|
+
templateKey: 'request_approved' | 'request_rejected' | 'request_returned_for_modification',
|
|
151
|
+
comment?: string | null,
|
|
152
|
+
): BilingualText {
|
|
153
|
+
const commentSuffix = formatWorkflowCommentSuffix(comment);
|
|
154
|
+
return workflowTemplate(
|
|
155
|
+
templateKey,
|
|
156
|
+
{ comment: commentSuffix },
|
|
157
|
+
{ comment: commentSuffix },
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
71
161
|
export function buildApprovalWorkflowContent(params: {
|
|
72
|
-
status: 'Approved' | 'Rejected' | 'Pending' | string;
|
|
162
|
+
status: 'Approved' | 'Rejected' | 'Returned for Modification' | 'Pending' | string;
|
|
73
163
|
roleName?: string | null;
|
|
74
164
|
roleArabicName?: string | null;
|
|
75
165
|
comment?: string | null;
|
|
76
166
|
}): BilingualText {
|
|
77
167
|
const nameEn = params.roleName?.trim() || '';
|
|
78
|
-
const nameAr =
|
|
168
|
+
const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
|
|
79
169
|
|
|
80
|
-
let base: BilingualText;
|
|
81
170
|
if (params.status === 'Approved') {
|
|
82
|
-
|
|
83
|
-
?
|
|
84
|
-
:
|
|
85
|
-
if (nameEn) {
|
|
86
|
-
base.ar = workflowTemplate('request_approved_by', { name: nameAr }).ar;
|
|
87
|
-
}
|
|
88
|
-
} else if (params.status === 'Rejected') {
|
|
89
|
-
base = nameEn
|
|
90
|
-
? workflowTemplate('request_rejected_by', { name: nameEn })
|
|
91
|
-
: workflowTemplate('request_rejected');
|
|
92
|
-
if (nameEn) {
|
|
93
|
-
base.ar = workflowTemplate('request_rejected_by', { name: nameAr }).ar;
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
base = nameEn
|
|
97
|
-
? workflowTemplate('request_approval_pending_from', { name: nameEn })
|
|
98
|
-
: workflowTemplate('request_approval_pending');
|
|
99
|
-
if (nameEn) {
|
|
100
|
-
base.ar = workflowTemplate('request_approval_pending_from', { name: nameAr }).ar;
|
|
101
|
-
}
|
|
171
|
+
return nameEn
|
|
172
|
+
? buildWorkflowApprovalByName('request_approved_by', nameEn, nameAr, params.comment)
|
|
173
|
+
: buildWorkflowApprovalWithoutName('request_approved', params.comment);
|
|
102
174
|
}
|
|
103
175
|
|
|
104
|
-
|
|
105
|
-
|
|
176
|
+
if (params.status === 'Rejected') {
|
|
177
|
+
return nameEn
|
|
178
|
+
? buildWorkflowApprovalByName('request_rejected_by', nameEn, nameAr, params.comment)
|
|
179
|
+
: buildWorkflowApprovalWithoutName('request_rejected', params.comment);
|
|
180
|
+
}
|
|
106
181
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
182
|
+
if (params.status === 'Returned for Modification') {
|
|
183
|
+
return nameEn
|
|
184
|
+
? buildWorkflowApprovalByName('request_returned_for_modification_by', nameEn, nameAr, params.comment)
|
|
185
|
+
: buildWorkflowApprovalWithoutName('request_returned_for_modification', params.comment);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const commentSuffix = formatWorkflowCommentSuffix(params.comment);
|
|
189
|
+
if (nameEn) {
|
|
190
|
+
return workflowTemplate(
|
|
191
|
+
'request_approval_pending_from',
|
|
192
|
+
{ name: nameEn, comment: commentSuffix },
|
|
193
|
+
{ name: nameAr, comment: commentSuffix },
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return workflowTemplate(
|
|
198
|
+
'request_approval_pending',
|
|
199
|
+
{ comment: commentSuffix },
|
|
200
|
+
{ comment: commentSuffix },
|
|
201
|
+
);
|
|
111
202
|
}
|
|
112
203
|
|
|
113
204
|
export function buildHumanApprovalWorkflowContent(params: {
|
|
114
205
|
isFirst: boolean;
|
|
115
206
|
roleName?: string | null;
|
|
116
207
|
roleArabicName?: string | null;
|
|
117
|
-
|
|
118
|
-
|
|
208
|
+
deptName?: string | null;
|
|
209
|
+
deptArabicName?: string | null;
|
|
210
|
+
sectionName?: string | null;
|
|
211
|
+
sectionArabicName?: string | null;
|
|
119
212
|
}): BilingualText {
|
|
120
|
-
const
|
|
121
|
-
const nameAr = params.roleName
|
|
122
|
-
? pickLocalizedName(params.roleName, params.roleArabicName, nameEn)
|
|
123
|
-
: params.deptSectionAr?.trim() || params.deptSectionEn?.trim() || nameEn;
|
|
213
|
+
const key = params.isFirst ? 'request_approval_in_progress_from' : 'request_approval_pending_from';
|
|
124
214
|
|
|
125
|
-
if (
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
215
|
+
if (params.roleName?.trim()) {
|
|
216
|
+
const nameEn = params.roleName.trim();
|
|
217
|
+
const nameAr = resolveBilingualName(nameEn, params.roleArabicName);
|
|
218
|
+
return workflowTemplate(key, { name: nameEn }, { name: nameAr });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const { en, ar } = joinDeptSectionBilingualLabels(
|
|
222
|
+
params.deptName,
|
|
223
|
+
params.deptArabicName,
|
|
224
|
+
params.sectionName,
|
|
225
|
+
params.sectionArabicName,
|
|
226
|
+
);
|
|
227
|
+
if (en) {
|
|
228
|
+
return workflowTemplate(key, { name: en }, { name: ar });
|
|
131
229
|
}
|
|
132
230
|
|
|
133
231
|
return params.isFirst
|
|
@@ -136,34 +234,94 @@ export function buildHumanApprovalWorkflowContent(params: {
|
|
|
136
234
|
}
|
|
137
235
|
|
|
138
236
|
export function buildApprovalChatMessage(params: {
|
|
139
|
-
status: 'Approved' | 'Rejected' | string;
|
|
237
|
+
status: 'Approved' | 'Rejected' | 'Returned for Modification' | string;
|
|
140
238
|
roleName?: string | null;
|
|
141
239
|
roleArabicName?: string | null;
|
|
142
240
|
comment?: string | null;
|
|
143
241
|
}): BilingualText {
|
|
144
242
|
const roleEn = params.roleName?.trim() || 'Unknown Role';
|
|
145
|
-
const roleAr =
|
|
146
|
-
const commentSuffix =
|
|
147
|
-
const key = params.status === 'Approved' ? 'request_approved_by_role' : 'request_rejected_by_role';
|
|
243
|
+
const roleAr = resolveBilingualName(roleEn, params.roleArabicName);
|
|
244
|
+
const commentSuffix = formatChatCommentSuffix(params.comment);
|
|
148
245
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
246
|
+
let key: string;
|
|
247
|
+
if (params.status === 'Approved') {
|
|
248
|
+
key = 'request_approved_by_role';
|
|
249
|
+
} else if (params.status === 'Rejected') {
|
|
250
|
+
key = 'request_rejected_by_role';
|
|
251
|
+
} else if (params.status === 'Returned for Modification') {
|
|
252
|
+
key = 'request_returned_for_modification_by_role';
|
|
253
|
+
} else {
|
|
254
|
+
key = 'request_approved_by_role';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return chatTemplate(
|
|
258
|
+
key,
|
|
259
|
+
{ roleName: roleEn, comment: commentSuffix },
|
|
260
|
+
{ roleName: roleAr, comment: commentSuffix },
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Resolve Arabic workflow content from a stored English content string (legacy rows). */
|
|
265
|
+
export function resolveWorkflowContentAr(content: string | null | undefined): string | null {
|
|
266
|
+
if (content == null || content.trim() === '') return null;
|
|
267
|
+
const normalized = content.trim();
|
|
268
|
+
|
|
269
|
+
for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
|
|
270
|
+
if (tpl.en === normalized) return tpl.ar;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const commentIdx = normalized.indexOf(': ');
|
|
274
|
+
if (commentIdx > 0) {
|
|
275
|
+
const baseEn = normalized.slice(0, commentIdx).trim();
|
|
276
|
+
const commentText = normalized.slice(commentIdx + 2).trim();
|
|
277
|
+
const commentSuffix = formatWorkflowCommentSuffix(commentText);
|
|
278
|
+
|
|
279
|
+
for (const tpl of Object.values(WORKFLOW_TEMPLATES)) {
|
|
280
|
+
if (!tpl.en.includes('{{comment}}')) continue;
|
|
281
|
+
const withoutComment = tpl.en.replace('{{comment}}', '');
|
|
282
|
+
if (withoutComment.includes('{{name}}')) {
|
|
283
|
+
const staticPrefix = withoutComment.replace('{{name}}', '').trim();
|
|
284
|
+
if (baseEn.startsWith(staticPrefix)) {
|
|
285
|
+
const namePart = baseEn.slice(staticPrefix.length).trim();
|
|
286
|
+
return interpolate(tpl.ar, { name: namePart, comment: commentSuffix });
|
|
287
|
+
}
|
|
288
|
+
} else if (withoutComment.trim() === baseEn) {
|
|
289
|
+
return interpolate(tpl.ar, { comment: commentSuffix });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Resolve Arabic chat message from a stored English message string (legacy rows). */
|
|
298
|
+
export function resolveChatMessageAr(message: string | null | undefined): string | null {
|
|
299
|
+
if (message == null || message.trim() === '') return null;
|
|
300
|
+
const normalized = message.trim();
|
|
301
|
+
|
|
302
|
+
for (const tpl of Object.values(CHAT_TEMPLATES)) {
|
|
303
|
+
if (tpl.en === normalized) return tpl.ar;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return null;
|
|
153
307
|
}
|
|
154
308
|
|
|
155
309
|
export function enrichCancellationWorkflowLog(log: Record<string, unknown>): Record<string, unknown> {
|
|
156
310
|
const status = log.status != null ? String(log.status) : null;
|
|
311
|
+
const content = log.content != null ? String(log.content) : null;
|
|
157
312
|
return {
|
|
158
313
|
...log,
|
|
314
|
+
content_ar: log.content_ar ?? resolveWorkflowContentAr(content),
|
|
159
315
|
status_ar: log.status_ar ?? resolveWorkflowStatusAr(status),
|
|
160
316
|
};
|
|
161
317
|
}
|
|
162
318
|
|
|
163
319
|
export function enrichCancellationChatMessage(chat: Record<string, unknown>): Record<string, unknown> {
|
|
164
320
|
const status = chat.status != null ? String(chat.status) : null;
|
|
321
|
+
const message = chat.message != null ? String(chat.message) : null;
|
|
165
322
|
return {
|
|
166
323
|
...chat,
|
|
324
|
+
message_ar: chat.message_ar ?? resolveChatMessageAr(message),
|
|
167
325
|
status_ar: chat.status_ar ?? resolveChatStatusAr(status),
|
|
168
326
|
};
|
|
169
327
|
}
|