@infuro/cms-core 1.0.61 → 1.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs +3624 -2642
- package/dist/admin.js +2415 -1433
- package/dist/api.cjs +41 -37
- package/dist/api.js +9 -5
- package/dist/chunk-3VZAAQUV.js +106 -0
- package/dist/{chunk-UHT4INVW.js → chunk-77YWLP4X.js} +625 -252
- package/dist/{chunk-BXYZDMTZ.cjs → chunk-7L556RGL.cjs} +3 -94
- package/dist/{chunk-T3HPSF7K.js → chunk-AYWDQFJZ.js} +3 -0
- package/dist/{chunk-URN7GS23.js → chunk-BWOITUNI.js} +203 -1062
- package/dist/{chunk-CNAHVRRU.cjs → chunk-CJEOH6QC.cjs} +676 -312
- package/dist/{chunk-O2642WVS.js → chunk-DTUL3Q5L.js} +17 -3
- package/dist/{chunk-YSYX4GUD.cjs → chunk-EWXG4E74.cjs} +213 -1082
- package/dist/chunk-GWDI752Q.js +83 -0
- package/dist/chunk-H3IVLIPR.cjs +86 -0
- package/dist/chunk-JLDHK2QE.cjs +109 -0
- package/dist/chunk-KAOHOHCA.js +901 -0
- package/dist/{chunk-2KUCQVAQ.js → chunk-MWEK7N3F.js} +2 -91
- package/dist/{chunk-V25RDUOU.cjs → chunk-MXABMVF7.cjs} +38 -4
- package/dist/chunk-OVTOP42W.js +199 -0
- package/dist/{chunk-73WVHINE.cjs → chunk-RHYVNRYG.cjs} +18 -4
- package/dist/{chunk-L3525VXI.js → chunk-RXTUSF3R.js} +36 -2
- package/dist/{chunk-77I3KS7V.cjs → chunk-UEE4PTTB.cjs} +3 -0
- package/dist/chunk-WFMVD567.cjs +209 -0
- package/dist/chunk-YVP4NQII.cjs +914 -0
- package/dist/{email-queue-6NJY6HNM.cjs → email-queue-VTYL2KFA.cjs} +7 -6
- package/dist/{email-queue-OI2HSCGE.js → email-queue-YPRSVG56.js} +3 -2
- package/dist/email-service-ATCPJFWG.cjs +28 -0
- package/dist/email-service-DPKVQOKP.js +3 -0
- package/dist/{emit-order-notification-trigger-TNEZOYEE.cjs → emit-order-notification-trigger-WI4HGMKH.cjs} +4 -4
- package/dist/{emit-order-notification-trigger-CZU3V2WE.js → emit-order-notification-trigger-XVG2B5ZF.js} +2 -2
- package/dist/{erp-order-invoice-3GXDE443.js → erp-order-invoice-3UGWLICU.js} +2 -1
- package/dist/{erp-order-invoice-EYYNR526.cjs → erp-order-invoice-KJYJNWMG.cjs} +6 -5
- package/dist/index.cjs +366 -316
- package/dist/index.d.cts +61 -21
- package/dist/index.d.ts +61 -21
- package/dist/index.js +79 -29
- package/dist/migrations/1783000000000-AddOrderPendingProcessingCompletedTriggers.ts +56 -0
- package/dist/migrations/1783100000000-AddSmsToOrderNotificationBindingsChannelEnum.ts +17 -0
- package/dist/migrations/1783200000000-AddOrderAssigneeColumns.ts +30 -0
- package/dist/order-assignees-handlers-LKMAGLG6.js +150 -0
- package/dist/order-assignees-handlers-NPKD64P6.cjs +152 -0
- package/dist/order-completion-otp-handlers-IMMQGHDR.cjs +363 -0
- package/dist/order-completion-otp-handlers-WGM7HWG4.js +361 -0
- package/dist/order-notification-dispatcher-5TUFCDU7.cjs +35 -0
- package/dist/order-notification-dispatcher-JKFQJYGG.js +10 -0
- package/dist/sms-notification-templates-handlers-SJLO7JGZ.js +47 -0
- package/dist/sms-notification-templates-handlers-YSB3L2PK.cjs +49 -0
- package/package.json +1 -1
- package/dist/order-notification-dispatcher-KJVGLK5E.cjs +0 -24
- package/dist/order-notification-dispatcher-ZSFKEYMP.js +0 -7
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
2
|
+
|
|
3
|
+
// src/api/order-assignees-handlers.ts
|
|
4
|
+
function json(data, init) {
|
|
5
|
+
return new Response(JSON.stringify(data), {
|
|
6
|
+
status: init?.status ?? 200,
|
|
7
|
+
headers: {
|
|
8
|
+
"Content-Type": "application/json",
|
|
9
|
+
...init?.headers || {}
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
__name(json, "json");
|
|
14
|
+
function createOrderAssigneesHandlers(dataSource, entityMap, getHydratedSessionUser) {
|
|
15
|
+
return {
|
|
16
|
+
/** GET /api/orders/:id/eligible-assignees */
|
|
17
|
+
async listEligibleAssignees(req, orderId) {
|
|
18
|
+
if (!entityMap.orders || !entityMap.users) {
|
|
19
|
+
return json({
|
|
20
|
+
error: "Order or User entity not configured"
|
|
21
|
+
}, {
|
|
22
|
+
status: 500
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
26
|
+
const order = await orderRepo.findOne({
|
|
27
|
+
where: {
|
|
28
|
+
id: orderId,
|
|
29
|
+
deleted: false
|
|
30
|
+
},
|
|
31
|
+
select: [
|
|
32
|
+
"id",
|
|
33
|
+
"vendorId",
|
|
34
|
+
"assignedUserId"
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
if (!order) {
|
|
38
|
+
return json({
|
|
39
|
+
error: "Order not found"
|
|
40
|
+
}, {
|
|
41
|
+
status: 404
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const orderVendorId = order.vendorId;
|
|
45
|
+
const session = await getHydratedSessionUser?.() ?? null;
|
|
46
|
+
const userVendorIds = session?.vendorIds ?? [];
|
|
47
|
+
const isVendorSession = Boolean(session?.isVendorPortal || !session?.isRBACAdmin && userVendorIds.length > 0);
|
|
48
|
+
Boolean(session?.isRBACAdmin && !session?.isVendorPortal);
|
|
49
|
+
if (isVendorSession && orderVendorId && !userVendorIds.includes(orderVendorId)) {
|
|
50
|
+
return json({
|
|
51
|
+
error: "Forbidden: Access denied to vendor order"
|
|
52
|
+
}, {
|
|
53
|
+
status: 403
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const assigneesMap = /* @__PURE__ */ new Map();
|
|
57
|
+
if (isVendorSession) {
|
|
58
|
+
const targetVendorId = orderVendorId ?? session?.activeVendorId ?? userVendorIds[0];
|
|
59
|
+
if (entityMap.vendor_users && targetVendorId) {
|
|
60
|
+
const vuRepo = dataSource.getRepository(entityMap.vendor_users);
|
|
61
|
+
const vendorUsers = await vuRepo.find({
|
|
62
|
+
where: {
|
|
63
|
+
vendorId: targetVendorId
|
|
64
|
+
},
|
|
65
|
+
relations: [
|
|
66
|
+
"user",
|
|
67
|
+
"vendorRole"
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
for (const vu of vendorUsers) {
|
|
71
|
+
const u = vu.user;
|
|
72
|
+
const r = vu.vendorRole;
|
|
73
|
+
if (u && u.id && !u.deleted && !u.blocked && u.inviteStatus === "active") {
|
|
74
|
+
const roleName = r?.name || (r?.isOwnerRole ? "Owner" : "Vendor Staff");
|
|
75
|
+
assigneesMap.set(u.id, {
|
|
76
|
+
id: u.id,
|
|
77
|
+
name: u.name || u.email?.split("@")[0] || `User #${u.id}`,
|
|
78
|
+
email: u.email || "",
|
|
79
|
+
role: roleName
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
if (entityMap.vendor_users) {
|
|
86
|
+
const vuRepo = dataSource.getRepository(entityMap.vendor_users);
|
|
87
|
+
const vendorUsers = await vuRepo.find({
|
|
88
|
+
relations: [
|
|
89
|
+
"user",
|
|
90
|
+
"vendorRole"
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
for (const vu of vendorUsers) {
|
|
94
|
+
const u = vu.user;
|
|
95
|
+
const r = vu.vendorRole;
|
|
96
|
+
if (u && u.id && !u.deleted && !u.blocked && u.inviteStatus === "active") {
|
|
97
|
+
const roleName = r?.name || (r?.isOwnerRole ? "Owner" : "Vendor Staff");
|
|
98
|
+
assigneesMap.set(u.id, {
|
|
99
|
+
id: u.id,
|
|
100
|
+
name: u.name || u.email?.split("@")[0] || `User #${u.id}`,
|
|
101
|
+
email: u.email || "",
|
|
102
|
+
role: roleName
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
108
|
+
const users = await userRepo.find({
|
|
109
|
+
where: {
|
|
110
|
+
deleted: false,
|
|
111
|
+
blocked: false,
|
|
112
|
+
inviteStatus: "active"
|
|
113
|
+
},
|
|
114
|
+
relations: [
|
|
115
|
+
"group"
|
|
116
|
+
],
|
|
117
|
+
order: {
|
|
118
|
+
name: "ASC"
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
for (const u of users) {
|
|
122
|
+
const groupName = u.group?.name ? String(u.group.name).trim() : "";
|
|
123
|
+
const isCustomer = groupName.toLowerCase() === "customer";
|
|
124
|
+
if (isCustomer) continue;
|
|
125
|
+
if (!assigneesMap.has(u.id)) {
|
|
126
|
+
let roleLabel = "Staff";
|
|
127
|
+
if (groupName) {
|
|
128
|
+
roleLabel = groupName;
|
|
129
|
+
} else if (u.adminAccess) {
|
|
130
|
+
roleLabel = "Administrator";
|
|
131
|
+
}
|
|
132
|
+
assigneesMap.set(u.id, {
|
|
133
|
+
id: u.id,
|
|
134
|
+
name: u.name || u.email?.split("@")[0] || `User #${u.id}`,
|
|
135
|
+
email: u.email || "",
|
|
136
|
+
role: roleLabel
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return json({
|
|
142
|
+
assignees: Array.from(assigneesMap.values()),
|
|
143
|
+
currentAssignedUserId: order.assignedUserId ?? null
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
__name(createOrderAssigneesHandlers, "createOrderAssigneesHandlers");
|
|
149
|
+
|
|
150
|
+
export { createOrderAssigneesHandlers };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
|
|
4
|
+
|
|
5
|
+
// src/api/order-assignees-handlers.ts
|
|
6
|
+
function json(data, init) {
|
|
7
|
+
return new Response(JSON.stringify(data), {
|
|
8
|
+
status: init?.status ?? 200,
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
...init?.headers || {}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
chunkUSNT2KNT_cjs.__name(json, "json");
|
|
16
|
+
function createOrderAssigneesHandlers(dataSource, entityMap, getHydratedSessionUser) {
|
|
17
|
+
return {
|
|
18
|
+
/** GET /api/orders/:id/eligible-assignees */
|
|
19
|
+
async listEligibleAssignees(req, orderId) {
|
|
20
|
+
if (!entityMap.orders || !entityMap.users) {
|
|
21
|
+
return json({
|
|
22
|
+
error: "Order or User entity not configured"
|
|
23
|
+
}, {
|
|
24
|
+
status: 500
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
28
|
+
const order = await orderRepo.findOne({
|
|
29
|
+
where: {
|
|
30
|
+
id: orderId,
|
|
31
|
+
deleted: false
|
|
32
|
+
},
|
|
33
|
+
select: [
|
|
34
|
+
"id",
|
|
35
|
+
"vendorId",
|
|
36
|
+
"assignedUserId"
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
if (!order) {
|
|
40
|
+
return json({
|
|
41
|
+
error: "Order not found"
|
|
42
|
+
}, {
|
|
43
|
+
status: 404
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const orderVendorId = order.vendorId;
|
|
47
|
+
const session = await getHydratedSessionUser?.() ?? null;
|
|
48
|
+
const userVendorIds = session?.vendorIds ?? [];
|
|
49
|
+
const isVendorSession = Boolean(session?.isVendorPortal || !session?.isRBACAdmin && userVendorIds.length > 0);
|
|
50
|
+
Boolean(session?.isRBACAdmin && !session?.isVendorPortal);
|
|
51
|
+
if (isVendorSession && orderVendorId && !userVendorIds.includes(orderVendorId)) {
|
|
52
|
+
return json({
|
|
53
|
+
error: "Forbidden: Access denied to vendor order"
|
|
54
|
+
}, {
|
|
55
|
+
status: 403
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const assigneesMap = /* @__PURE__ */ new Map();
|
|
59
|
+
if (isVendorSession) {
|
|
60
|
+
const targetVendorId = orderVendorId ?? session?.activeVendorId ?? userVendorIds[0];
|
|
61
|
+
if (entityMap.vendor_users && targetVendorId) {
|
|
62
|
+
const vuRepo = dataSource.getRepository(entityMap.vendor_users);
|
|
63
|
+
const vendorUsers = await vuRepo.find({
|
|
64
|
+
where: {
|
|
65
|
+
vendorId: targetVendorId
|
|
66
|
+
},
|
|
67
|
+
relations: [
|
|
68
|
+
"user",
|
|
69
|
+
"vendorRole"
|
|
70
|
+
]
|
|
71
|
+
});
|
|
72
|
+
for (const vu of vendorUsers) {
|
|
73
|
+
const u = vu.user;
|
|
74
|
+
const r = vu.vendorRole;
|
|
75
|
+
if (u && u.id && !u.deleted && !u.blocked && u.inviteStatus === "active") {
|
|
76
|
+
const roleName = r?.name || (r?.isOwnerRole ? "Owner" : "Vendor Staff");
|
|
77
|
+
assigneesMap.set(u.id, {
|
|
78
|
+
id: u.id,
|
|
79
|
+
name: u.name || u.email?.split("@")[0] || `User #${u.id}`,
|
|
80
|
+
email: u.email || "",
|
|
81
|
+
role: roleName
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
if (entityMap.vendor_users) {
|
|
88
|
+
const vuRepo = dataSource.getRepository(entityMap.vendor_users);
|
|
89
|
+
const vendorUsers = await vuRepo.find({
|
|
90
|
+
relations: [
|
|
91
|
+
"user",
|
|
92
|
+
"vendorRole"
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
for (const vu of vendorUsers) {
|
|
96
|
+
const u = vu.user;
|
|
97
|
+
const r = vu.vendorRole;
|
|
98
|
+
if (u && u.id && !u.deleted && !u.blocked && u.inviteStatus === "active") {
|
|
99
|
+
const roleName = r?.name || (r?.isOwnerRole ? "Owner" : "Vendor Staff");
|
|
100
|
+
assigneesMap.set(u.id, {
|
|
101
|
+
id: u.id,
|
|
102
|
+
name: u.name || u.email?.split("@")[0] || `User #${u.id}`,
|
|
103
|
+
email: u.email || "",
|
|
104
|
+
role: roleName
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
110
|
+
const users = await userRepo.find({
|
|
111
|
+
where: {
|
|
112
|
+
deleted: false,
|
|
113
|
+
blocked: false,
|
|
114
|
+
inviteStatus: "active"
|
|
115
|
+
},
|
|
116
|
+
relations: [
|
|
117
|
+
"group"
|
|
118
|
+
],
|
|
119
|
+
order: {
|
|
120
|
+
name: "ASC"
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
for (const u of users) {
|
|
124
|
+
const groupName = u.group?.name ? String(u.group.name).trim() : "";
|
|
125
|
+
const isCustomer = groupName.toLowerCase() === "customer";
|
|
126
|
+
if (isCustomer) continue;
|
|
127
|
+
if (!assigneesMap.has(u.id)) {
|
|
128
|
+
let roleLabel = "Staff";
|
|
129
|
+
if (groupName) {
|
|
130
|
+
roleLabel = groupName;
|
|
131
|
+
} else if (u.adminAccess) {
|
|
132
|
+
roleLabel = "Administrator";
|
|
133
|
+
}
|
|
134
|
+
assigneesMap.set(u.id, {
|
|
135
|
+
id: u.id,
|
|
136
|
+
name: u.name || u.email?.split("@")[0] || `User #${u.id}`,
|
|
137
|
+
email: u.email || "",
|
|
138
|
+
role: roleLabel
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return json({
|
|
144
|
+
assignees: Array.from(assigneesMap.values()),
|
|
145
|
+
currentAssignedUserId: order.assignedUserId ?? null
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
chunkUSNT2KNT_cjs.__name(createOrderAssigneesHandlers, "createOrderAssigneesHandlers");
|
|
151
|
+
|
|
152
|
+
exports.createOrderAssigneesHandlers = createOrderAssigneesHandlers;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkRHYVNRYG_cjs = require('./chunk-RHYVNRYG.cjs');
|
|
4
|
+
var chunkWFMVD567_cjs = require('./chunk-WFMVD567.cjs');
|
|
5
|
+
var chunkH3IVLIPR_cjs = require('./chunk-H3IVLIPR.cjs');
|
|
6
|
+
require('./chunk-UEE4PTTB.cjs');
|
|
7
|
+
var chunkMXABMVF7_cjs = require('./chunk-MXABMVF7.cjs');
|
|
8
|
+
require('./chunk-7L556RGL.cjs');
|
|
9
|
+
require('./chunk-UPLVMVRX.cjs');
|
|
10
|
+
require('./chunk-JLDHK2QE.cjs');
|
|
11
|
+
var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
|
|
12
|
+
|
|
13
|
+
// src/api/order-completion-otp-handlers.ts
|
|
14
|
+
function json(data, init) {
|
|
15
|
+
return new Response(JSON.stringify(data), {
|
|
16
|
+
status: init?.status ?? 200,
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
...init?.headers || {}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
chunkUSNT2KNT_cjs.__name(json, "json");
|
|
24
|
+
function maskEmail(email) {
|
|
25
|
+
if (!email) return "";
|
|
26
|
+
const parts = email.split("@");
|
|
27
|
+
if (parts.length !== 2) return "***";
|
|
28
|
+
const name = parts[0];
|
|
29
|
+
const domain = parts[1];
|
|
30
|
+
if (name.length <= 2) return `${name[0]}***@${domain}`;
|
|
31
|
+
return `${name.slice(0, 2)}***${name.slice(-1)}@${domain}`;
|
|
32
|
+
}
|
|
33
|
+
chunkUSNT2KNT_cjs.__name(maskEmail, "maskEmail");
|
|
34
|
+
function maskPhone(phone) {
|
|
35
|
+
if (!phone) return "";
|
|
36
|
+
const digits = String(phone).replace(/\D/g, "");
|
|
37
|
+
if (digits.length <= 4) return "***";
|
|
38
|
+
return `+${digits.slice(0, 2)} ***${digits.slice(-4)}`;
|
|
39
|
+
}
|
|
40
|
+
chunkUSNT2KNT_cjs.__name(maskPhone, "maskPhone");
|
|
41
|
+
async function getOtpChannelsConfig(dataSource, entityMap) {
|
|
42
|
+
if (!entityMap.configs) {
|
|
43
|
+
return {
|
|
44
|
+
email: true,
|
|
45
|
+
whatsapp: true,
|
|
46
|
+
sms: true,
|
|
47
|
+
push: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const rows = await dataSource.getRepository(entityMap.configs).find({
|
|
51
|
+
where: {
|
|
52
|
+
settings: "event_notifications",
|
|
53
|
+
deleted: false
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const map = Object.fromEntries(rows.map((r) => [
|
|
57
|
+
r.key,
|
|
58
|
+
r.value
|
|
59
|
+
]));
|
|
60
|
+
return {
|
|
61
|
+
email: map.otp_email_enabled !== "false" && map.otp_email_enabled !== "0",
|
|
62
|
+
whatsapp: map.otp_whatsapp_enabled !== "false" && map.otp_whatsapp_enabled !== "0",
|
|
63
|
+
sms: map.otp_sms_enabled !== "false" && map.otp_sms_enabled !== "0",
|
|
64
|
+
push: map.otp_push_enabled === "true" || map.otp_push_enabled === "1"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
chunkUSNT2KNT_cjs.__name(getOtpChannelsConfig, "getOtpChannelsConfig");
|
|
68
|
+
function createOrderCompletionOtpHandlers(dataSource, entityMap, getCms) {
|
|
69
|
+
return {
|
|
70
|
+
/** GET /api/orders/:id/completion-otp/channels */
|
|
71
|
+
async getChannels(req, orderId) {
|
|
72
|
+
if (!entityMap.orders) {
|
|
73
|
+
return json({
|
|
74
|
+
error: "Orders entity not found"
|
|
75
|
+
}, {
|
|
76
|
+
status: 500
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
80
|
+
const order = await orderRepo.findOne({
|
|
81
|
+
where: {
|
|
82
|
+
id: orderId,
|
|
83
|
+
deleted: false
|
|
84
|
+
},
|
|
85
|
+
relations: [
|
|
86
|
+
"contact"
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
if (!order) {
|
|
90
|
+
return json({
|
|
91
|
+
error: "Order not found"
|
|
92
|
+
}, {
|
|
93
|
+
status: 404
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const o = order;
|
|
97
|
+
const cfg = await getOtpChannelsConfig(dataSource, entityMap);
|
|
98
|
+
const customerEmail = o.contact?.email?.trim();
|
|
99
|
+
const customerPhone = o.contact?.phone?.trim();
|
|
100
|
+
const configuredChannels = [];
|
|
101
|
+
if (cfg.email) configuredChannels.push("email");
|
|
102
|
+
if (cfg.whatsapp) configuredChannels.push("whatsapp");
|
|
103
|
+
if (cfg.sms) configuredChannels.push("sms");
|
|
104
|
+
if (cfg.push) configuredChannels.push("push");
|
|
105
|
+
const activeChannels = [];
|
|
106
|
+
if (cfg.email && customerEmail) activeChannels.push("email");
|
|
107
|
+
if (cfg.whatsapp && customerPhone) activeChannels.push("whatsapp");
|
|
108
|
+
if (cfg.sms && customerPhone) activeChannels.push("sms");
|
|
109
|
+
if (cfg.push) activeChannels.push("push");
|
|
110
|
+
return json({
|
|
111
|
+
configuredChannels,
|
|
112
|
+
activeChannels,
|
|
113
|
+
maskedEmail: maskEmail(customerEmail),
|
|
114
|
+
maskedPhone: maskPhone(customerPhone),
|
|
115
|
+
rawEmail: customerEmail ?? null,
|
|
116
|
+
rawPhone: customerPhone ?? null
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
/** POST /api/orders/:id/completion-otp/send */
|
|
120
|
+
async sendOtp(req, orderId) {
|
|
121
|
+
if (!entityMap.orders) {
|
|
122
|
+
return json({
|
|
123
|
+
error: "Orders entity not found"
|
|
124
|
+
}, {
|
|
125
|
+
status: 500
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
let body = {};
|
|
129
|
+
try {
|
|
130
|
+
body = await req.json();
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
134
|
+
const order = await orderRepo.findOne({
|
|
135
|
+
where: {
|
|
136
|
+
id: orderId,
|
|
137
|
+
deleted: false
|
|
138
|
+
},
|
|
139
|
+
relations: [
|
|
140
|
+
"contact"
|
|
141
|
+
]
|
|
142
|
+
});
|
|
143
|
+
if (!order) {
|
|
144
|
+
return json({
|
|
145
|
+
error: "Order not found"
|
|
146
|
+
}, {
|
|
147
|
+
status: 404
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const o = order;
|
|
151
|
+
if (o.status === "completed") {
|
|
152
|
+
return json({
|
|
153
|
+
error: "Order is already completed"
|
|
154
|
+
}, {
|
|
155
|
+
status: 400
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (o.status === "cancelled") {
|
|
159
|
+
return json({
|
|
160
|
+
error: "Cannot complete a cancelled order"
|
|
161
|
+
}, {
|
|
162
|
+
status: 400
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const customerEmail = o.contact?.email?.trim();
|
|
166
|
+
const customerPhone = o.contact?.phone?.trim();
|
|
167
|
+
if (!customerEmail && !customerPhone) {
|
|
168
|
+
return json({
|
|
169
|
+
error: "Customer email and phone number are both missing for this order."
|
|
170
|
+
}, {
|
|
171
|
+
status: 400
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const cfg = await getOtpChannelsConfig(dataSource, entityMap);
|
|
175
|
+
const requestedChannels = Array.isArray(body.channels) && body.channels.length > 0 ? body.channels : [
|
|
176
|
+
...cfg.email ? [
|
|
177
|
+
"email"
|
|
178
|
+
] : [],
|
|
179
|
+
...cfg.whatsapp ? [
|
|
180
|
+
"whatsapp"
|
|
181
|
+
] : [],
|
|
182
|
+
...cfg.sms ? [
|
|
183
|
+
"sms"
|
|
184
|
+
] : [],
|
|
185
|
+
...cfg.push ? [
|
|
186
|
+
"push"
|
|
187
|
+
] : []
|
|
188
|
+
];
|
|
189
|
+
const targetChannels = [];
|
|
190
|
+
if (requestedChannels.includes("email") && customerEmail && cfg.email) targetChannels.push("email");
|
|
191
|
+
if (requestedChannels.includes("whatsapp") && customerPhone && cfg.whatsapp) targetChannels.push("whatsapp");
|
|
192
|
+
if (requestedChannels.includes("sms") && customerPhone && cfg.sms) targetChannels.push("sms");
|
|
193
|
+
if (requestedChannels.includes("push") && cfg.push) targetChannels.push("push");
|
|
194
|
+
if (targetChannels.length === 0) {
|
|
195
|
+
if (customerEmail) targetChannels.push("email");
|
|
196
|
+
else if (customerPhone) targetChannels.push("whatsapp");
|
|
197
|
+
}
|
|
198
|
+
const code = chunkWFMVD567_cjs.generateNumericOtp(6);
|
|
199
|
+
const challengeResult = await chunkWFMVD567_cjs.createOtpChallenge(dataSource, entityMap, {
|
|
200
|
+
purpose: "order_completion",
|
|
201
|
+
channel: targetChannels[0] || "email",
|
|
202
|
+
identifier: `order:${orderId}`,
|
|
203
|
+
code
|
|
204
|
+
});
|
|
205
|
+
if (!challengeResult.ok) {
|
|
206
|
+
return json({
|
|
207
|
+
error: challengeResult.error
|
|
208
|
+
}, {
|
|
209
|
+
status: challengeResult.status
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const cms = await getCms();
|
|
213
|
+
const customerName = o.contact?.name || "Valued Customer";
|
|
214
|
+
const channelsSent = [];
|
|
215
|
+
if (targetChannels.includes("email") && customerEmail) {
|
|
216
|
+
try {
|
|
217
|
+
const html = `
|
|
218
|
+
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; color: #1f2937; line-height: 1.6; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;">
|
|
219
|
+
<h2 style="color: #111827; font-size: 20px; font-weight: 600; margin-bottom: 16px;">Order Completion Verification Code</h2>
|
|
220
|
+
<p>Hi ${customerName},</p>
|
|
221
|
+
<p>Your service/delivery representative is ready to mark your Order <strong>#${o.orderNumber}</strong> as completed.</p>
|
|
222
|
+
<p>Please share the following verification code with them to confirm completion:</p>
|
|
223
|
+
<div style="background-color: #f3f4f6; border-radius: 8px; padding: 20px; text-align: center; margin: 24px 0;">
|
|
224
|
+
<span style="font-size: 32px; font-weight: 700; letter-spacing: 8px; color: #111827;">${code}</span>
|
|
225
|
+
</div>
|
|
226
|
+
<p style="color: #6b7280; font-size: 13px;">This code is valid for 10 minutes. If you did not authorize this completion, please contact support immediately.</p>
|
|
227
|
+
</div>
|
|
228
|
+
`;
|
|
229
|
+
await chunkMXABMVF7_cjs.queueEmail(cms, {
|
|
230
|
+
to: customerEmail,
|
|
231
|
+
subject: `Completion Code ${code} for Order #${o.orderNumber}`,
|
|
232
|
+
html,
|
|
233
|
+
text: `Your completion code for Order #${o.orderNumber} is ${code}. Valid for 10 minutes.`
|
|
234
|
+
});
|
|
235
|
+
channelsSent.push("email");
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error("[completion-otp] failed to queue email", err);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (targetChannels.includes("whatsapp") && customerPhone) {
|
|
241
|
+
try {
|
|
242
|
+
const sent = await chunkH3IVLIPR_cjs.queueWhatsApp(cms, {
|
|
243
|
+
to: customerPhone,
|
|
244
|
+
body: `Your completion code for Order #${o.orderNumber} is ${code}. Share this with your representative to complete your order. Valid for 10 minutes.`,
|
|
245
|
+
variables: {
|
|
246
|
+
code,
|
|
247
|
+
orderNumber: o.orderNumber,
|
|
248
|
+
customerName
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
if (sent) channelsSent.push("whatsapp");
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.error("[completion-otp] failed to queue WhatsApp", err);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (targetChannels.includes("sms") && customerPhone) {
|
|
257
|
+
try {
|
|
258
|
+
await chunkWFMVD567_cjs.queueSms(cms, {
|
|
259
|
+
to: customerPhone,
|
|
260
|
+
body: `Your verification code for Order #${o.orderNumber} is ${code}. Valid for 10 minutes.`,
|
|
261
|
+
otpCode: code,
|
|
262
|
+
variables: {
|
|
263
|
+
code,
|
|
264
|
+
orderNumber: o.orderNumber
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
channelsSent.push("sms");
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error("[completion-otp] failed to queue SMS", err);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return json({
|
|
273
|
+
ok: true,
|
|
274
|
+
message: "Verification code sent to customer",
|
|
275
|
+
channelsSent: channelsSent.length > 0 ? channelsSent : targetChannels,
|
|
276
|
+
maskedEmail: maskEmail(customerEmail),
|
|
277
|
+
maskedPhone: maskPhone(customerPhone)
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
/** POST /api/orders/:id/completion-otp/verify */
|
|
281
|
+
async verifyOtp(req, orderId) {
|
|
282
|
+
if (!entityMap.orders) {
|
|
283
|
+
return json({
|
|
284
|
+
error: "Orders entity not found"
|
|
285
|
+
}, {
|
|
286
|
+
status: 500
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
let body = {};
|
|
290
|
+
try {
|
|
291
|
+
body = await req.json();
|
|
292
|
+
} catch {
|
|
293
|
+
return json({
|
|
294
|
+
error: "Invalid request body"
|
|
295
|
+
}, {
|
|
296
|
+
status: 400
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const code = String(body.code ?? "").trim();
|
|
300
|
+
if (!code || code.length < 4) {
|
|
301
|
+
return json({
|
|
302
|
+
error: "Please enter a valid verification code"
|
|
303
|
+
}, {
|
|
304
|
+
status: 400
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const verifyResult = await chunkWFMVD567_cjs.verifyAndConsumeOtpChallenge(dataSource, entityMap, {
|
|
308
|
+
purpose: "order_completion",
|
|
309
|
+
identifier: `order:${orderId}`,
|
|
310
|
+
code
|
|
311
|
+
});
|
|
312
|
+
if (!verifyResult.ok) {
|
|
313
|
+
return json({
|
|
314
|
+
error: verifyResult.error
|
|
315
|
+
}, {
|
|
316
|
+
status: verifyResult.status
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
320
|
+
const order = await orderRepo.findOne({
|
|
321
|
+
where: {
|
|
322
|
+
id: orderId,
|
|
323
|
+
deleted: false
|
|
324
|
+
},
|
|
325
|
+
relations: [
|
|
326
|
+
"contact"
|
|
327
|
+
]
|
|
328
|
+
});
|
|
329
|
+
if (!order) {
|
|
330
|
+
return json({
|
|
331
|
+
error: "Order not found"
|
|
332
|
+
}, {
|
|
333
|
+
status: 404
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const now = /* @__PURE__ */ new Date();
|
|
337
|
+
order.status = "completed";
|
|
338
|
+
order.completedAt = now;
|
|
339
|
+
order.updatedAt = now;
|
|
340
|
+
await orderRepo.save(order);
|
|
341
|
+
try {
|
|
342
|
+
await chunkRHYVNRYG_cjs.fireOrderNotificationTrigger("order_completed", orderId, {
|
|
343
|
+
dataSource,
|
|
344
|
+
entityMap
|
|
345
|
+
});
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.warn("[completion-otp] failed to fire order_completed notification trigger", err);
|
|
348
|
+
}
|
|
349
|
+
return json({
|
|
350
|
+
ok: true,
|
|
351
|
+
message: "Order verified and completed successfully",
|
|
352
|
+
order: {
|
|
353
|
+
id: order.id,
|
|
354
|
+
status: "completed",
|
|
355
|
+
completedAt: now
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
chunkUSNT2KNT_cjs.__name(createOrderCompletionOtpHandlers, "createOrderCompletionOtpHandlers");
|
|
362
|
+
|
|
363
|
+
exports.createOrderCompletionOtpHandlers = createOrderCompletionOtpHandlers;
|