@infuro/cms-core 1.0.60 → 1.0.62
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 +4179 -2665
- package/dist/admin.js +2990 -1476
- package/dist/api.cjs +41 -37
- package/dist/api.js +9 -5
- package/dist/{chunk-URN7GS23.js → chunk-6LV36PCL.js} +199 -1059
- package/dist/{chunk-O2642WVS.js → chunk-6XAVZE4I.js} +17 -3
- package/dist/{chunk-BXYZDMTZ.cjs → chunk-AXMP2U43.cjs} +3 -94
- package/dist/{chunk-T3HPSF7K.js → chunk-AYWDQFJZ.js} +3 -0
- package/dist/chunk-CTIEAHQL.cjs +96 -0
- package/dist/chunk-GWDI752Q.js +83 -0
- package/dist/chunk-GZGFNVTK.js +886 -0
- package/dist/chunk-H3IVLIPR.cjs +86 -0
- package/dist/{chunk-2KUCQVAQ.js → chunk-HMLVPIJR.js} +2 -91
- package/dist/{chunk-V25RDUOU.cjs → chunk-LRD4CGOG.cjs} +38 -4
- package/dist/{chunk-73WVHINE.cjs → chunk-LVQSG25Y.cjs} +18 -4
- package/dist/chunk-NMYGQWHV.js +93 -0
- package/dist/chunk-OVTOP42W.js +199 -0
- package/dist/{chunk-BSLNXKV2.cjs → chunk-QA5C477U.cjs} +927 -351
- package/dist/{chunk-JAEUYYGA.js → chunk-R6Q67HAS.js} +876 -291
- package/dist/{chunk-YSYX4GUD.cjs → chunk-TWTISTZB.cjs} +210 -1080
- package/dist/{chunk-77I3KS7V.cjs → chunk-UEE4PTTB.cjs} +3 -0
- package/dist/chunk-WFMVD567.cjs +209 -0
- package/dist/{chunk-L3525VXI.js → chunk-YW7EF7K7.js} +36 -2
- package/dist/chunk-ZPAPCGCR.cjs +899 -0
- package/dist/{email-queue-OI2HSCGE.js → email-queue-65PQAG4C.js} +3 -2
- package/dist/{email-queue-6NJY6HNM.cjs → email-queue-J7DXYNCE.cjs} +7 -6
- package/dist/email-service-F7HA4X63.js +3 -0
- package/dist/email-service-RDOTY3CO.cjs +28 -0
- package/dist/{emit-order-notification-trigger-TNEZOYEE.cjs → emit-order-notification-trigger-K7URAZHJ.cjs} +4 -4
- package/dist/{emit-order-notification-trigger-CZU3V2WE.js → emit-order-notification-trigger-MD7EBODC.js} +2 -2
- package/dist/{erp-order-invoice-EYYNR526.cjs → erp-order-invoice-C5GZCOBM.cjs} +6 -5
- package/dist/{erp-order-invoice-3GXDE443.js → erp-order-invoice-MYFCXOCY.js} +2 -1
- package/dist/index.cjs +638 -346
- package/dist/index.d.cts +53 -19
- package/dist/index.d.ts +53 -19
- package/dist/index.js +350 -58
- 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-3NTOGEA5.js +361 -0
- package/dist/order-completion-otp-handlers-4EOLYLAP.cjs +363 -0
- package/dist/order-notification-dispatcher-IYMOS4VY.js +10 -0
- package/dist/order-notification-dispatcher-X2XM5VUY.cjs +35 -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,361 @@
|
|
|
1
|
+
import { fireOrderNotificationTrigger } from './chunk-6XAVZE4I.js';
|
|
2
|
+
import { verifyAndConsumeOtpChallenge, generateNumericOtp, createOtpChallenge, queueSms } from './chunk-OVTOP42W.js';
|
|
3
|
+
import { queueWhatsApp } from './chunk-GWDI752Q.js';
|
|
4
|
+
import './chunk-AYWDQFJZ.js';
|
|
5
|
+
import { queueEmail } from './chunk-YW7EF7K7.js';
|
|
6
|
+
import './chunk-HMLVPIJR.js';
|
|
7
|
+
import './chunk-JC6DLWTE.js';
|
|
8
|
+
import './chunk-NMYGQWHV.js';
|
|
9
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
10
|
+
|
|
11
|
+
// src/api/order-completion-otp-handlers.ts
|
|
12
|
+
function json(data, init) {
|
|
13
|
+
return new Response(JSON.stringify(data), {
|
|
14
|
+
status: init?.status ?? 200,
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
...init?.headers || {}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__name(json, "json");
|
|
22
|
+
function maskEmail(email) {
|
|
23
|
+
if (!email) return "";
|
|
24
|
+
const parts = email.split("@");
|
|
25
|
+
if (parts.length !== 2) return "***";
|
|
26
|
+
const name = parts[0];
|
|
27
|
+
const domain = parts[1];
|
|
28
|
+
if (name.length <= 2) return `${name[0]}***@${domain}`;
|
|
29
|
+
return `${name.slice(0, 2)}***${name.slice(-1)}@${domain}`;
|
|
30
|
+
}
|
|
31
|
+
__name(maskEmail, "maskEmail");
|
|
32
|
+
function maskPhone(phone) {
|
|
33
|
+
if (!phone) return "";
|
|
34
|
+
const digits = String(phone).replace(/\D/g, "");
|
|
35
|
+
if (digits.length <= 4) return "***";
|
|
36
|
+
return `+${digits.slice(0, 2)} ***${digits.slice(-4)}`;
|
|
37
|
+
}
|
|
38
|
+
__name(maskPhone, "maskPhone");
|
|
39
|
+
async function getOtpChannelsConfig(dataSource, entityMap) {
|
|
40
|
+
if (!entityMap.configs) {
|
|
41
|
+
return {
|
|
42
|
+
email: true,
|
|
43
|
+
whatsapp: true,
|
|
44
|
+
sms: true,
|
|
45
|
+
push: false
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const rows = await dataSource.getRepository(entityMap.configs).find({
|
|
49
|
+
where: {
|
|
50
|
+
settings: "event_notifications",
|
|
51
|
+
deleted: false
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const map = Object.fromEntries(rows.map((r) => [
|
|
55
|
+
r.key,
|
|
56
|
+
r.value
|
|
57
|
+
]));
|
|
58
|
+
return {
|
|
59
|
+
email: map.otp_email_enabled !== "false" && map.otp_email_enabled !== "0",
|
|
60
|
+
whatsapp: map.otp_whatsapp_enabled !== "false" && map.otp_whatsapp_enabled !== "0",
|
|
61
|
+
sms: map.otp_sms_enabled !== "false" && map.otp_sms_enabled !== "0",
|
|
62
|
+
push: map.otp_push_enabled === "true" || map.otp_push_enabled === "1"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
__name(getOtpChannelsConfig, "getOtpChannelsConfig");
|
|
66
|
+
function createOrderCompletionOtpHandlers(dataSource, entityMap, getCms) {
|
|
67
|
+
return {
|
|
68
|
+
/** GET /api/orders/:id/completion-otp/channels */
|
|
69
|
+
async getChannels(req, orderId) {
|
|
70
|
+
if (!entityMap.orders) {
|
|
71
|
+
return json({
|
|
72
|
+
error: "Orders entity not found"
|
|
73
|
+
}, {
|
|
74
|
+
status: 500
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
78
|
+
const order = await orderRepo.findOne({
|
|
79
|
+
where: {
|
|
80
|
+
id: orderId,
|
|
81
|
+
deleted: false
|
|
82
|
+
},
|
|
83
|
+
relations: [
|
|
84
|
+
"contact"
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
if (!order) {
|
|
88
|
+
return json({
|
|
89
|
+
error: "Order not found"
|
|
90
|
+
}, {
|
|
91
|
+
status: 404
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const o = order;
|
|
95
|
+
const cfg = await getOtpChannelsConfig(dataSource, entityMap);
|
|
96
|
+
const customerEmail = o.contact?.email?.trim();
|
|
97
|
+
const customerPhone = o.contact?.phone?.trim();
|
|
98
|
+
const configuredChannels = [];
|
|
99
|
+
if (cfg.email) configuredChannels.push("email");
|
|
100
|
+
if (cfg.whatsapp) configuredChannels.push("whatsapp");
|
|
101
|
+
if (cfg.sms) configuredChannels.push("sms");
|
|
102
|
+
if (cfg.push) configuredChannels.push("push");
|
|
103
|
+
const activeChannels = [];
|
|
104
|
+
if (cfg.email && customerEmail) activeChannels.push("email");
|
|
105
|
+
if (cfg.whatsapp && customerPhone) activeChannels.push("whatsapp");
|
|
106
|
+
if (cfg.sms && customerPhone) activeChannels.push("sms");
|
|
107
|
+
if (cfg.push) activeChannels.push("push");
|
|
108
|
+
return json({
|
|
109
|
+
configuredChannels,
|
|
110
|
+
activeChannels,
|
|
111
|
+
maskedEmail: maskEmail(customerEmail),
|
|
112
|
+
maskedPhone: maskPhone(customerPhone),
|
|
113
|
+
rawEmail: customerEmail ?? null,
|
|
114
|
+
rawPhone: customerPhone ?? null
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
/** POST /api/orders/:id/completion-otp/send */
|
|
118
|
+
async sendOtp(req, orderId) {
|
|
119
|
+
if (!entityMap.orders) {
|
|
120
|
+
return json({
|
|
121
|
+
error: "Orders entity not found"
|
|
122
|
+
}, {
|
|
123
|
+
status: 500
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
let body = {};
|
|
127
|
+
try {
|
|
128
|
+
body = await req.json();
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
132
|
+
const order = await orderRepo.findOne({
|
|
133
|
+
where: {
|
|
134
|
+
id: orderId,
|
|
135
|
+
deleted: false
|
|
136
|
+
},
|
|
137
|
+
relations: [
|
|
138
|
+
"contact"
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
if (!order) {
|
|
142
|
+
return json({
|
|
143
|
+
error: "Order not found"
|
|
144
|
+
}, {
|
|
145
|
+
status: 404
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const o = order;
|
|
149
|
+
if (o.status === "completed") {
|
|
150
|
+
return json({
|
|
151
|
+
error: "Order is already completed"
|
|
152
|
+
}, {
|
|
153
|
+
status: 400
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (o.status === "cancelled") {
|
|
157
|
+
return json({
|
|
158
|
+
error: "Cannot complete a cancelled order"
|
|
159
|
+
}, {
|
|
160
|
+
status: 400
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const customerEmail = o.contact?.email?.trim();
|
|
164
|
+
const customerPhone = o.contact?.phone?.trim();
|
|
165
|
+
if (!customerEmail && !customerPhone) {
|
|
166
|
+
return json({
|
|
167
|
+
error: "Customer email and phone number are both missing for this order."
|
|
168
|
+
}, {
|
|
169
|
+
status: 400
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const cfg = await getOtpChannelsConfig(dataSource, entityMap);
|
|
173
|
+
const requestedChannels = Array.isArray(body.channels) && body.channels.length > 0 ? body.channels : [
|
|
174
|
+
...cfg.email ? [
|
|
175
|
+
"email"
|
|
176
|
+
] : [],
|
|
177
|
+
...cfg.whatsapp ? [
|
|
178
|
+
"whatsapp"
|
|
179
|
+
] : [],
|
|
180
|
+
...cfg.sms ? [
|
|
181
|
+
"sms"
|
|
182
|
+
] : [],
|
|
183
|
+
...cfg.push ? [
|
|
184
|
+
"push"
|
|
185
|
+
] : []
|
|
186
|
+
];
|
|
187
|
+
const targetChannels = [];
|
|
188
|
+
if (requestedChannels.includes("email") && customerEmail && cfg.email) targetChannels.push("email");
|
|
189
|
+
if (requestedChannels.includes("whatsapp") && customerPhone && cfg.whatsapp) targetChannels.push("whatsapp");
|
|
190
|
+
if (requestedChannels.includes("sms") && customerPhone && cfg.sms) targetChannels.push("sms");
|
|
191
|
+
if (requestedChannels.includes("push") && cfg.push) targetChannels.push("push");
|
|
192
|
+
if (targetChannels.length === 0) {
|
|
193
|
+
if (customerEmail) targetChannels.push("email");
|
|
194
|
+
else if (customerPhone) targetChannels.push("whatsapp");
|
|
195
|
+
}
|
|
196
|
+
const code = generateNumericOtp(6);
|
|
197
|
+
const challengeResult = await createOtpChallenge(dataSource, entityMap, {
|
|
198
|
+
purpose: "order_completion",
|
|
199
|
+
channel: targetChannels[0] || "email",
|
|
200
|
+
identifier: `order:${orderId}`,
|
|
201
|
+
code
|
|
202
|
+
});
|
|
203
|
+
if (!challengeResult.ok) {
|
|
204
|
+
return json({
|
|
205
|
+
error: challengeResult.error
|
|
206
|
+
}, {
|
|
207
|
+
status: challengeResult.status
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const cms = await getCms();
|
|
211
|
+
const customerName = o.contact?.name || "Valued Customer";
|
|
212
|
+
const channelsSent = [];
|
|
213
|
+
if (targetChannels.includes("email") && customerEmail) {
|
|
214
|
+
try {
|
|
215
|
+
const html = `
|
|
216
|
+
<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;">
|
|
217
|
+
<h2 style="color: #111827; font-size: 20px; font-weight: 600; margin-bottom: 16px;">Order Completion Verification Code</h2>
|
|
218
|
+
<p>Hi ${customerName},</p>
|
|
219
|
+
<p>Your service/delivery representative is ready to mark your Order <strong>#${o.orderNumber}</strong> as completed.</p>
|
|
220
|
+
<p>Please share the following verification code with them to confirm completion:</p>
|
|
221
|
+
<div style="background-color: #f3f4f6; border-radius: 8px; padding: 20px; text-align: center; margin: 24px 0;">
|
|
222
|
+
<span style="font-size: 32px; font-weight: 700; letter-spacing: 8px; color: #111827;">${code}</span>
|
|
223
|
+
</div>
|
|
224
|
+
<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>
|
|
225
|
+
</div>
|
|
226
|
+
`;
|
|
227
|
+
await queueEmail(cms, {
|
|
228
|
+
to: customerEmail,
|
|
229
|
+
subject: `Completion Code ${code} for Order #${o.orderNumber}`,
|
|
230
|
+
html,
|
|
231
|
+
text: `Your completion code for Order #${o.orderNumber} is ${code}. Valid for 10 minutes.`
|
|
232
|
+
});
|
|
233
|
+
channelsSent.push("email");
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error("[completion-otp] failed to queue email", err);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (targetChannels.includes("whatsapp") && customerPhone) {
|
|
239
|
+
try {
|
|
240
|
+
const sent = await queueWhatsApp(cms, {
|
|
241
|
+
to: customerPhone,
|
|
242
|
+
body: `Your completion code for Order #${o.orderNumber} is ${code}. Share this with your representative to complete your order. Valid for 10 minutes.`,
|
|
243
|
+
variables: {
|
|
244
|
+
code,
|
|
245
|
+
orderNumber: o.orderNumber,
|
|
246
|
+
customerName
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
if (sent) channelsSent.push("whatsapp");
|
|
250
|
+
} catch (err) {
|
|
251
|
+
console.error("[completion-otp] failed to queue WhatsApp", err);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (targetChannels.includes("sms") && customerPhone) {
|
|
255
|
+
try {
|
|
256
|
+
await queueSms(cms, {
|
|
257
|
+
to: customerPhone,
|
|
258
|
+
body: `Your verification code for Order #${o.orderNumber} is ${code}. Valid for 10 minutes.`,
|
|
259
|
+
otpCode: code,
|
|
260
|
+
variables: {
|
|
261
|
+
code,
|
|
262
|
+
orderNumber: o.orderNumber
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
channelsSent.push("sms");
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.error("[completion-otp] failed to queue SMS", err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return json({
|
|
271
|
+
ok: true,
|
|
272
|
+
message: "Verification code sent to customer",
|
|
273
|
+
channelsSent: channelsSent.length > 0 ? channelsSent : targetChannels,
|
|
274
|
+
maskedEmail: maskEmail(customerEmail),
|
|
275
|
+
maskedPhone: maskPhone(customerPhone)
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
/** POST /api/orders/:id/completion-otp/verify */
|
|
279
|
+
async verifyOtp(req, orderId) {
|
|
280
|
+
if (!entityMap.orders) {
|
|
281
|
+
return json({
|
|
282
|
+
error: "Orders entity not found"
|
|
283
|
+
}, {
|
|
284
|
+
status: 500
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
let body = {};
|
|
288
|
+
try {
|
|
289
|
+
body = await req.json();
|
|
290
|
+
} catch {
|
|
291
|
+
return json({
|
|
292
|
+
error: "Invalid request body"
|
|
293
|
+
}, {
|
|
294
|
+
status: 400
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
const code = String(body.code ?? "").trim();
|
|
298
|
+
if (!code || code.length < 4) {
|
|
299
|
+
return json({
|
|
300
|
+
error: "Please enter a valid verification code"
|
|
301
|
+
}, {
|
|
302
|
+
status: 400
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const verifyResult = await verifyAndConsumeOtpChallenge(dataSource, entityMap, {
|
|
306
|
+
purpose: "order_completion",
|
|
307
|
+
identifier: `order:${orderId}`,
|
|
308
|
+
code
|
|
309
|
+
});
|
|
310
|
+
if (!verifyResult.ok) {
|
|
311
|
+
return json({
|
|
312
|
+
error: verifyResult.error
|
|
313
|
+
}, {
|
|
314
|
+
status: verifyResult.status
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
318
|
+
const order = await orderRepo.findOne({
|
|
319
|
+
where: {
|
|
320
|
+
id: orderId,
|
|
321
|
+
deleted: false
|
|
322
|
+
},
|
|
323
|
+
relations: [
|
|
324
|
+
"contact"
|
|
325
|
+
]
|
|
326
|
+
});
|
|
327
|
+
if (!order) {
|
|
328
|
+
return json({
|
|
329
|
+
error: "Order not found"
|
|
330
|
+
}, {
|
|
331
|
+
status: 404
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const now = /* @__PURE__ */ new Date();
|
|
335
|
+
order.status = "completed";
|
|
336
|
+
order.completedAt = now;
|
|
337
|
+
order.updatedAt = now;
|
|
338
|
+
await orderRepo.save(order);
|
|
339
|
+
try {
|
|
340
|
+
await fireOrderNotificationTrigger("order_completed", orderId, {
|
|
341
|
+
dataSource,
|
|
342
|
+
entityMap
|
|
343
|
+
});
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn("[completion-otp] failed to fire order_completed notification trigger", err);
|
|
346
|
+
}
|
|
347
|
+
return json({
|
|
348
|
+
ok: true,
|
|
349
|
+
message: "Order verified and completed successfully",
|
|
350
|
+
order: {
|
|
351
|
+
id: order.id,
|
|
352
|
+
status: "completed",
|
|
353
|
+
completedAt: now
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
__name(createOrderCompletionOtpHandlers, "createOrderCompletionOtpHandlers");
|
|
360
|
+
|
|
361
|
+
export { createOrderCompletionOtpHandlers };
|