@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.
Files changed (50) hide show
  1. package/dist/admin.cjs +3624 -2642
  2. package/dist/admin.js +2415 -1433
  3. package/dist/api.cjs +41 -37
  4. package/dist/api.js +9 -5
  5. package/dist/chunk-3VZAAQUV.js +106 -0
  6. package/dist/{chunk-UHT4INVW.js → chunk-77YWLP4X.js} +625 -252
  7. package/dist/{chunk-BXYZDMTZ.cjs → chunk-7L556RGL.cjs} +3 -94
  8. package/dist/{chunk-T3HPSF7K.js → chunk-AYWDQFJZ.js} +3 -0
  9. package/dist/{chunk-URN7GS23.js → chunk-BWOITUNI.js} +203 -1062
  10. package/dist/{chunk-CNAHVRRU.cjs → chunk-CJEOH6QC.cjs} +676 -312
  11. package/dist/{chunk-O2642WVS.js → chunk-DTUL3Q5L.js} +17 -3
  12. package/dist/{chunk-YSYX4GUD.cjs → chunk-EWXG4E74.cjs} +213 -1082
  13. package/dist/chunk-GWDI752Q.js +83 -0
  14. package/dist/chunk-H3IVLIPR.cjs +86 -0
  15. package/dist/chunk-JLDHK2QE.cjs +109 -0
  16. package/dist/chunk-KAOHOHCA.js +901 -0
  17. package/dist/{chunk-2KUCQVAQ.js → chunk-MWEK7N3F.js} +2 -91
  18. package/dist/{chunk-V25RDUOU.cjs → chunk-MXABMVF7.cjs} +38 -4
  19. package/dist/chunk-OVTOP42W.js +199 -0
  20. package/dist/{chunk-73WVHINE.cjs → chunk-RHYVNRYG.cjs} +18 -4
  21. package/dist/{chunk-L3525VXI.js → chunk-RXTUSF3R.js} +36 -2
  22. package/dist/{chunk-77I3KS7V.cjs → chunk-UEE4PTTB.cjs} +3 -0
  23. package/dist/chunk-WFMVD567.cjs +209 -0
  24. package/dist/chunk-YVP4NQII.cjs +914 -0
  25. package/dist/{email-queue-6NJY6HNM.cjs → email-queue-VTYL2KFA.cjs} +7 -6
  26. package/dist/{email-queue-OI2HSCGE.js → email-queue-YPRSVG56.js} +3 -2
  27. package/dist/email-service-ATCPJFWG.cjs +28 -0
  28. package/dist/email-service-DPKVQOKP.js +3 -0
  29. package/dist/{emit-order-notification-trigger-TNEZOYEE.cjs → emit-order-notification-trigger-WI4HGMKH.cjs} +4 -4
  30. package/dist/{emit-order-notification-trigger-CZU3V2WE.js → emit-order-notification-trigger-XVG2B5ZF.js} +2 -2
  31. package/dist/{erp-order-invoice-3GXDE443.js → erp-order-invoice-3UGWLICU.js} +2 -1
  32. package/dist/{erp-order-invoice-EYYNR526.cjs → erp-order-invoice-KJYJNWMG.cjs} +6 -5
  33. package/dist/index.cjs +366 -316
  34. package/dist/index.d.cts +61 -21
  35. package/dist/index.d.ts +61 -21
  36. package/dist/index.js +79 -29
  37. package/dist/migrations/1783000000000-AddOrderPendingProcessingCompletedTriggers.ts +56 -0
  38. package/dist/migrations/1783100000000-AddSmsToOrderNotificationBindingsChannelEnum.ts +17 -0
  39. package/dist/migrations/1783200000000-AddOrderAssigneeColumns.ts +30 -0
  40. package/dist/order-assignees-handlers-LKMAGLG6.js +150 -0
  41. package/dist/order-assignees-handlers-NPKD64P6.cjs +152 -0
  42. package/dist/order-completion-otp-handlers-IMMQGHDR.cjs +363 -0
  43. package/dist/order-completion-otp-handlers-WGM7HWG4.js +361 -0
  44. package/dist/order-notification-dispatcher-5TUFCDU7.cjs +35 -0
  45. package/dist/order-notification-dispatcher-JKFQJYGG.js +10 -0
  46. package/dist/sms-notification-templates-handlers-SJLO7JGZ.js +47 -0
  47. package/dist/sms-notification-templates-handlers-YSB3L2PK.cjs +49 -0
  48. package/package.json +1 -1
  49. package/dist/order-notification-dispatcher-KJVGLK5E.cjs +0 -24
  50. 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;