@infuro/cms-core 1.0.44 → 1.0.46
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/README.md +19 -2
- package/dist/admin.cjs +1413 -620
- package/dist/admin.d.cts +8 -2
- package/dist/admin.d.ts +8 -2
- package/dist/admin.js +1414 -621
- package/dist/api.cjs +40 -37
- package/dist/api.d.cts +3 -1
- package/dist/api.d.ts +3 -1
- package/dist/api.js +8 -5
- package/dist/auth.cjs +23 -23
- package/dist/auth.d.cts +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +2 -2
- package/dist/chunk-2KUCQVAQ.js +936 -0
- package/dist/chunk-3K5AIEIZ.js +256 -0
- package/dist/{chunk-I6NH72MO.js → chunk-57O3HZPG.js} +9 -1
- package/dist/chunk-7PMHZRF3.cjs +872 -0
- package/dist/chunk-BXYZDMTZ.cjs +953 -0
- package/dist/chunk-CQHXW4TR.js +107 -0
- package/dist/{chunk-NNQBWDCI.js → chunk-CRYKVJTO.js} +55 -959
- package/dist/chunk-DBPSJYLZ.js +47 -0
- package/dist/{chunk-TAHX46EI.cjs → chunk-FBKDMRQL.cjs} +9 -1
- package/dist/chunk-GUSHM5HN.js +862 -0
- package/dist/chunk-HY3AXLOI.cjs +265 -0
- package/dist/chunk-L3525VXI.js +243 -0
- package/dist/{chunk-ZMRQ72F6.cjs → chunk-NBY4NVTY.cjs} +3 -3
- package/dist/chunk-OY2YTBMP.cjs +50 -0
- package/dist/{chunk-BLR6H5GL.js → chunk-SF2XKMCI.js} +3 -3
- package/dist/{chunk-22FFHLTO.cjs → chunk-TCSGFAWB.cjs} +56 -970
- package/dist/{chunk-P2IWWBJV.cjs → chunk-UDVLFVEC.cjs} +1314 -601
- package/dist/{chunk-SQGBHYJF.js → chunk-UKC3HYXI.js} +1245 -548
- package/dist/chunk-V25RDUOU.cjs +248 -0
- package/dist/chunk-XMRMZ6NQ.cjs +109 -0
- package/dist/cli.cjs +9 -4
- package/dist/cli.js +9 -4
- package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
- package/dist/email-queue-OI2HSCGE.js +4 -0
- package/dist/erp-order-invoice-3GXDE443.js +3 -0
- package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
- package/dist/index.cjs +426 -359
- package/dist/index.d.cts +308 -20
- package/dist/index.d.ts +308 -20
- package/dist/index.js +31 -110
- package/dist/migrations/1781800000000-CustomerPhoneNullable.ts +53 -0
- package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
- package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
- package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
- package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
- package/dist/order-inventory-2MYRKEPC.cjs +39 -0
- package/dist/order-inventory-76V4XIGW.js +2 -0
- package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
- package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
- package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
- package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
- package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
- package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
- package/package.json +5 -4
- package/src/admin/admin.css +27 -24
- package/dist/chunk-5ELSW2UP.js +0 -150
- package/dist/chunk-PNOKNSG2.cjs +0 -222
- package/dist/chunk-WEMDMVHQ.js +0 -216
- package/dist/chunk-XW5ATPRW.cjs +0 -155
- package/dist/email-queue-CGZBVVAS.js +0 -2
- package/dist/erp-order-invoice-3YISSOWW.js +0 -3
- package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
- package/dist/order-notification-dispatcher-RU7BTGGX.cjs +0 -18
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
|
|
4
|
+
|
|
5
|
+
// src/lib/order-inventory.ts
|
|
6
|
+
var STOCK_HELD = /* @__PURE__ */ new Set([
|
|
7
|
+
"confirmed",
|
|
8
|
+
"completed"
|
|
9
|
+
]);
|
|
10
|
+
function orderStatusHoldsStock(status) {
|
|
11
|
+
return STOCK_HELD.has(String(status ?? "").trim().toLowerCase());
|
|
12
|
+
}
|
|
13
|
+
chunkUSNT2KNT_cjs.__name(orderStatusHoldsStock, "orderStatusHoldsStock");
|
|
14
|
+
function orderInventorySnapshotFromRow(order) {
|
|
15
|
+
return {
|
|
16
|
+
status: order.status ?? "pending",
|
|
17
|
+
lines: (order.items ?? []).map((i) => {
|
|
18
|
+
const meta = i.metadata && typeof i.metadata === "object" ? i.metadata : null;
|
|
19
|
+
const metaVid = meta?.variantId ?? meta?.variant_id;
|
|
20
|
+
const fromCol = i.variantId != null ? Number(i.variantId) : NaN;
|
|
21
|
+
const fromMeta = metaVid != null && String(metaVid).trim() !== "" ? Number(metaVid) : NaN;
|
|
22
|
+
const variantId = Number.isFinite(fromCol) && fromCol > 0 ? fromCol : Number.isFinite(fromMeta) && fromMeta > 0 ? fromMeta : null;
|
|
23
|
+
return {
|
|
24
|
+
productId: Number(i.productId),
|
|
25
|
+
quantity: Math.max(0, Math.floor(Number(i.quantity) || 0)),
|
|
26
|
+
variantId
|
|
27
|
+
};
|
|
28
|
+
}).filter((l) => l.productId > 0 && l.quantity > 0)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
chunkUSNT2KNT_cjs.__name(orderInventorySnapshotFromRow, "orderInventorySnapshotFromRow");
|
|
32
|
+
function lineKey(productId, variantId) {
|
|
33
|
+
const vid = variantId != null && Number.isFinite(Number(variantId)) && Number(variantId) > 0 ? Number(variantId) : 0;
|
|
34
|
+
return `${productId}:${vid}`;
|
|
35
|
+
}
|
|
36
|
+
chunkUSNT2KNT_cjs.__name(lineKey, "lineKey");
|
|
37
|
+
function mergeInventoryLines(lines) {
|
|
38
|
+
const m = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const productId = Number(line.productId);
|
|
41
|
+
const quantity = Math.max(0, Math.floor(Number(line.quantity) || 0));
|
|
42
|
+
if (!Number.isFinite(productId) || productId <= 0 || quantity <= 0) continue;
|
|
43
|
+
const variantId = line.variantId != null && Number.isFinite(Number(line.variantId)) && Number(line.variantId) > 0 ? Number(line.variantId) : null;
|
|
44
|
+
const key = lineKey(productId, variantId);
|
|
45
|
+
const prev = m.get(key);
|
|
46
|
+
if (prev) {
|
|
47
|
+
prev.quantity += quantity;
|
|
48
|
+
} else {
|
|
49
|
+
m.set(key, {
|
|
50
|
+
productId,
|
|
51
|
+
quantity,
|
|
52
|
+
variantId
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return m;
|
|
57
|
+
}
|
|
58
|
+
chunkUSNT2KNT_cjs.__name(mergeInventoryLines, "mergeInventoryLines");
|
|
59
|
+
function productRepo(ds, entityMap) {
|
|
60
|
+
const target = entityMap.products;
|
|
61
|
+
if (!target) throw new Error("products entity not configured");
|
|
62
|
+
return ds.getRepository(target);
|
|
63
|
+
}
|
|
64
|
+
chunkUSNT2KNT_cjs.__name(productRepo, "productRepo");
|
|
65
|
+
function variantRepo(ds, entityMap) {
|
|
66
|
+
const target = entityMap.product_variants;
|
|
67
|
+
if (!target) return null;
|
|
68
|
+
return ds.getRepository(target);
|
|
69
|
+
}
|
|
70
|
+
chunkUSNT2KNT_cjs.__name(variantRepo, "variantRepo");
|
|
71
|
+
async function validateInventoryForConfirmedOrderLines(ds, entityMap, lines) {
|
|
72
|
+
const merged = mergeInventoryLines(lines);
|
|
73
|
+
const byProduct = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const line of merged.values()) {
|
|
75
|
+
byProduct.set(line.productId, (byProduct.get(line.productId) ?? 0) + line.quantity);
|
|
76
|
+
}
|
|
77
|
+
const pRepo = productRepo(ds, entityMap);
|
|
78
|
+
const vRepo = variantRepo(ds, entityMap);
|
|
79
|
+
for (const [productId, qty] of byProduct) {
|
|
80
|
+
const row = await pRepo.findOne({
|
|
81
|
+
where: {
|
|
82
|
+
id: productId,
|
|
83
|
+
deleted: false
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (!row) return `Product #${productId} not found`;
|
|
87
|
+
if (row.type === "service") continue;
|
|
88
|
+
const name = row.name ?? String(productId);
|
|
89
|
+
const productQty = Number(row.quantity ?? 0);
|
|
90
|
+
if (productQty < qty) {
|
|
91
|
+
return `Insufficient stock for ${name}: available ${productQty}, required ${qty}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const line of merged.values()) {
|
|
95
|
+
if (!line.variantId || !vRepo) continue;
|
|
96
|
+
const row = await pRepo.findOne({
|
|
97
|
+
where: {
|
|
98
|
+
id: line.productId,
|
|
99
|
+
deleted: false
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (!row) continue;
|
|
103
|
+
if (row.type === "service") continue;
|
|
104
|
+
const name = row.name ?? String(line.productId);
|
|
105
|
+
const variant = await vRepo.findOne({
|
|
106
|
+
where: {
|
|
107
|
+
id: line.variantId,
|
|
108
|
+
productId: line.productId
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (!variant) return `Variant #${line.variantId} not found for ${name}`;
|
|
112
|
+
const inv = Number(variant.inventory ?? 0);
|
|
113
|
+
if (inv < line.quantity) {
|
|
114
|
+
return `Insufficient variant stock for ${name}: available ${inv}, required ${line.quantity}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
chunkUSNT2KNT_cjs.__name(validateInventoryForConfirmedOrderLines, "validateInventoryForConfirmedOrderLines");
|
|
120
|
+
async function validateInventoryForOrderBecomingConfirmed(ds, entityMap, prevSnap, nextLines) {
|
|
121
|
+
const mergedNext = mergeInventoryLines(nextLines);
|
|
122
|
+
const prevByKey = prevSnap && orderStatusHoldsStock(prevSnap.status) ? mergeInventoryLines(prevSnap.lines) : /* @__PURE__ */ new Map();
|
|
123
|
+
const nextByProduct = /* @__PURE__ */ new Map();
|
|
124
|
+
for (const line of mergedNext.values()) {
|
|
125
|
+
nextByProduct.set(line.productId, (nextByProduct.get(line.productId) ?? 0) + line.quantity);
|
|
126
|
+
}
|
|
127
|
+
const prevByProduct = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const line of prevByKey.values()) {
|
|
129
|
+
prevByProduct.set(line.productId, (prevByProduct.get(line.productId) ?? 0) + line.quantity);
|
|
130
|
+
}
|
|
131
|
+
const pRepo = productRepo(ds, entityMap);
|
|
132
|
+
const vRepo = variantRepo(ds, entityMap);
|
|
133
|
+
for (const [productId, need] of nextByProduct) {
|
|
134
|
+
const row = await pRepo.findOne({
|
|
135
|
+
where: {
|
|
136
|
+
id: productId,
|
|
137
|
+
deleted: false
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
if (!row) return `Product #${productId} not found`;
|
|
141
|
+
if (row.type === "service") continue;
|
|
142
|
+
const name = row.name ?? String(productId);
|
|
143
|
+
const available = Number(row.quantity ?? 0) + (prevByProduct.get(productId) ?? 0);
|
|
144
|
+
if (available < need) {
|
|
145
|
+
return `Insufficient stock for ${name}: available ${available}, required ${need}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const line of mergedNext.values()) {
|
|
149
|
+
if (!line.variantId || !vRepo) continue;
|
|
150
|
+
const row = await pRepo.findOne({
|
|
151
|
+
where: {
|
|
152
|
+
id: line.productId,
|
|
153
|
+
deleted: false
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
if (!row || row.type === "service") continue;
|
|
157
|
+
const name = row.name ?? String(line.productId);
|
|
158
|
+
const key = lineKey(line.productId, line.variantId);
|
|
159
|
+
const variant = await vRepo.findOne({
|
|
160
|
+
where: {
|
|
161
|
+
id: line.variantId,
|
|
162
|
+
productId: line.productId
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
if (!variant) return `Variant #${line.variantId} not found for ${name}`;
|
|
166
|
+
let inv = Number(variant.inventory ?? 0);
|
|
167
|
+
inv += prevByKey.get(key)?.quantity ?? 0;
|
|
168
|
+
if (inv < line.quantity) {
|
|
169
|
+
return `Insufficient variant stock for ${name}: available ${inv}, required ${line.quantity}`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
chunkUSNT2KNT_cjs.__name(validateInventoryForOrderBecomingConfirmed, "validateInventoryForOrderBecomingConfirmed");
|
|
175
|
+
async function applyInventoryDelta(ds, entityMap, lines, sign, validateDeduct) {
|
|
176
|
+
const merged = mergeInventoryLines(lines);
|
|
177
|
+
const pRepo = productRepo(ds, entityMap);
|
|
178
|
+
const vRepo = variantRepo(ds, entityMap);
|
|
179
|
+
for (const line of merged.values()) {
|
|
180
|
+
const row = await pRepo.findOne({
|
|
181
|
+
where: sign === 1 ? {
|
|
182
|
+
id: line.productId
|
|
183
|
+
} : {
|
|
184
|
+
id: line.productId,
|
|
185
|
+
deleted: false
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (!row) return `Product #${line.productId} not found`;
|
|
189
|
+
if (row.type === "service") continue;
|
|
190
|
+
const name = row.name ?? String(line.productId);
|
|
191
|
+
const cur = Number(row.quantity ?? 0);
|
|
192
|
+
if (line.variantId && vRepo) {
|
|
193
|
+
const variant = await vRepo.findOne({
|
|
194
|
+
where: {
|
|
195
|
+
id: line.variantId,
|
|
196
|
+
productId: line.productId
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
if (!variant) return `Variant #${line.variantId} not found for ${name}`;
|
|
200
|
+
const inv = Number(variant.inventory ?? 0);
|
|
201
|
+
if (sign === -1) {
|
|
202
|
+
if (validateDeduct && inv < line.quantity) {
|
|
203
|
+
return `Insufficient variant stock for ${name}: available ${inv}, required ${line.quantity}`;
|
|
204
|
+
}
|
|
205
|
+
await vRepo.update(line.variantId, {
|
|
206
|
+
inventory: Math.max(0, inv - line.quantity)
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
await vRepo.update(line.variantId, {
|
|
210
|
+
inventory: inv + line.quantity
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (sign === -1) {
|
|
215
|
+
if (validateDeduct && cur < line.quantity) {
|
|
216
|
+
return `Insufficient stock for ${name}: available ${cur}, required ${line.quantity}`;
|
|
217
|
+
}
|
|
218
|
+
await pRepo.update(line.productId, {
|
|
219
|
+
quantity: Math.max(0, cur - line.quantity)
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
await pRepo.update(line.productId, {
|
|
223
|
+
quantity: cur + line.quantity
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
chunkUSNT2KNT_cjs.__name(applyInventoryDelta, "applyInventoryDelta");
|
|
230
|
+
async function reconcileOrderInventoryBetweenSnapshots(ds, entityMap, prev, next) {
|
|
231
|
+
const wasHeld = orderStatusHoldsStock(prev.status);
|
|
232
|
+
const isHeld = orderStatusHoldsStock(next.status);
|
|
233
|
+
if (wasHeld) {
|
|
234
|
+
const err = await applyInventoryDelta(ds, entityMap, prev.lines, 1, false);
|
|
235
|
+
if (err) return err;
|
|
236
|
+
}
|
|
237
|
+
if (isHeld) {
|
|
238
|
+
const err = await applyInventoryDelta(ds, entityMap, next.lines, -1, true);
|
|
239
|
+
if (err) return err;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
chunkUSNT2KNT_cjs.__name(reconcileOrderInventoryBetweenSnapshots, "reconcileOrderInventoryBetweenSnapshots");
|
|
244
|
+
async function deductInventoryForConfirmedOrder(ds, entityMap, lines, options) {
|
|
245
|
+
const validate = options?.validate !== false;
|
|
246
|
+
if (validate) {
|
|
247
|
+
const err = await validateInventoryForConfirmedOrderLines(ds, entityMap, lines);
|
|
248
|
+
if (err) return err;
|
|
249
|
+
}
|
|
250
|
+
return applyInventoryDelta(ds, entityMap, lines, -1, validate);
|
|
251
|
+
}
|
|
252
|
+
chunkUSNT2KNT_cjs.__name(deductInventoryForConfirmedOrder, "deductInventoryForConfirmedOrder");
|
|
253
|
+
async function restoreInventoryForCancelledOrder(ds, entityMap, lines) {
|
|
254
|
+
return applyInventoryDelta(ds, entityMap, lines, 1, false);
|
|
255
|
+
}
|
|
256
|
+
chunkUSNT2KNT_cjs.__name(restoreInventoryForCancelledOrder, "restoreInventoryForCancelledOrder");
|
|
257
|
+
|
|
258
|
+
exports.deductInventoryForConfirmedOrder = deductInventoryForConfirmedOrder;
|
|
259
|
+
exports.mergeInventoryLines = mergeInventoryLines;
|
|
260
|
+
exports.orderInventorySnapshotFromRow = orderInventorySnapshotFromRow;
|
|
261
|
+
exports.orderStatusHoldsStock = orderStatusHoldsStock;
|
|
262
|
+
exports.reconcileOrderInventoryBetweenSnapshots = reconcileOrderInventoryBetweenSnapshots;
|
|
263
|
+
exports.restoreInventoryForCancelledOrder = restoreInventoryForCancelledOrder;
|
|
264
|
+
exports.validateInventoryForConfirmedOrderLines = validateInventoryForConfirmedOrderLines;
|
|
265
|
+
exports.validateInventoryForOrderBecomingConfirmed = validateInventoryForOrderBecomingConfirmed;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { isOrderEligibleForInvoiceEmail, resolveOrderInvoicePdfBytes } from './chunk-2KUCQVAQ.js';
|
|
2
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
3
|
+
|
|
4
|
+
// src/plugins/email/email-queue.ts
|
|
5
|
+
var EMAIL_QUEUE_NAME = "email";
|
|
6
|
+
var registeredInvoiceOptions = {};
|
|
7
|
+
async function resolveInvoiceAttachmentForJob(cms, payload, options) {
|
|
8
|
+
if (!payload.attachInvoice || payload.orderId == null || payload.orderId < 1) {
|
|
9
|
+
return {
|
|
10
|
+
attachments: payload.attachments,
|
|
11
|
+
invoiceAttached: false
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const entityMap = options.entityMap;
|
|
15
|
+
if (!entityMap?.orders) {
|
|
16
|
+
console.warn("[email] attachInvoice skipped: entityMap not registered on email queue processor");
|
|
17
|
+
return {
|
|
18
|
+
attachments: payload.attachments,
|
|
19
|
+
invoiceAttached: false
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
let dataSource;
|
|
23
|
+
try {
|
|
24
|
+
dataSource = options.getDataSource ? await options.getDataSource() : cms.dataSource;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("[email] attachInvoice: getDataSource failed", err);
|
|
27
|
+
return {
|
|
28
|
+
attachments: payload.attachments,
|
|
29
|
+
invoiceAttached: false
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (!dataSource) {
|
|
33
|
+
console.warn("[email] attachInvoice skipped: no dataSource");
|
|
34
|
+
return {
|
|
35
|
+
attachments: payload.attachments,
|
|
36
|
+
invoiceAttached: false
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const order = await dataSource.getRepository(entityMap.orders).findOne({
|
|
41
|
+
where: {
|
|
42
|
+
id: payload.orderId,
|
|
43
|
+
deleted: false
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
if (!order) return {
|
|
47
|
+
attachments: payload.attachments,
|
|
48
|
+
invoiceAttached: false
|
|
49
|
+
};
|
|
50
|
+
const o = order;
|
|
51
|
+
if (!isOrderEligibleForInvoiceEmail(o.status, o.orderKind)) {
|
|
52
|
+
return {
|
|
53
|
+
attachments: payload.attachments,
|
|
54
|
+
invoiceAttached: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const resolved = await resolveOrderInvoicePdfBytes(cms, dataSource, entityMap, payload.orderId);
|
|
58
|
+
if ("error" in resolved) {
|
|
59
|
+
console.warn("[email] order invoice attach failed", {
|
|
60
|
+
orderId: payload.orderId,
|
|
61
|
+
error: resolved.error,
|
|
62
|
+
status: resolved.status
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
attachments: payload.attachments,
|
|
66
|
+
invoiceAttached: false
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const invoiceAtt = {
|
|
70
|
+
filename: resolved.filename,
|
|
71
|
+
content: resolved.bytes,
|
|
72
|
+
contentType: resolved.contentType
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
attachments: [
|
|
76
|
+
...payload.attachments ?? [],
|
|
77
|
+
invoiceAtt
|
|
78
|
+
],
|
|
79
|
+
invoiceAttached: true
|
|
80
|
+
};
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error("[email] order invoice attach error", {
|
|
83
|
+
orderId: payload.orderId,
|
|
84
|
+
err
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
attachments: payload.attachments,
|
|
88
|
+
invoiceAttached: false
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
__name(resolveInvoiceAttachmentForJob, "resolveInvoiceAttachmentForJob");
|
|
93
|
+
async function sendRendered(email, to, subject, html, text, attachments) {
|
|
94
|
+
await email.send({
|
|
95
|
+
to,
|
|
96
|
+
subject,
|
|
97
|
+
html,
|
|
98
|
+
text,
|
|
99
|
+
attachments
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
__name(sendRendered, "sendRendered");
|
|
103
|
+
function registerEmailQueueProcessor(cms, options = {}) {
|
|
104
|
+
registeredInvoiceOptions = options;
|
|
105
|
+
const queue = cms.getPlugin("queue");
|
|
106
|
+
const email = cms.getPlugin("email");
|
|
107
|
+
if (!queue || !email) return;
|
|
108
|
+
queue.registerProcessor(EMAIL_QUEUE_NAME, async (data) => {
|
|
109
|
+
const payload = data;
|
|
110
|
+
const { to, templateName, ctx, subject, html, text } = payload;
|
|
111
|
+
if (!to) return;
|
|
112
|
+
const attachmentsResult = await resolveInvoiceAttachmentForJob(cms, payload, registeredInvoiceOptions);
|
|
113
|
+
if (templateName && ctx) {
|
|
114
|
+
const rendered = email.renderTemplate(templateName, {
|
|
115
|
+
...ctx,
|
|
116
|
+
invoiceAttached: attachmentsResult.invoiceAttached
|
|
117
|
+
});
|
|
118
|
+
await sendRendered(email, to, rendered.subject, rendered.html, rendered.text, attachmentsResult.attachments);
|
|
119
|
+
} else if (subject != null && html != null) {
|
|
120
|
+
await sendRendered(email, to, subject, html, text, attachmentsResult.attachments);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
__name(registerEmailQueueProcessor, "registerEmailQueueProcessor");
|
|
125
|
+
async function queueEmail(cms, payload) {
|
|
126
|
+
const queue = cms.getPlugin("queue");
|
|
127
|
+
if (queue) {
|
|
128
|
+
await queue.add(EMAIL_QUEUE_NAME, payload);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const email = cms.getPlugin("email");
|
|
132
|
+
if (!email) return;
|
|
133
|
+
const attachmentsResult = await resolveInvoiceAttachmentForJob(cms, payload, registeredInvoiceOptions);
|
|
134
|
+
if (payload.templateName && payload.ctx) {
|
|
135
|
+
const rendered = email.renderTemplate(payload.templateName, {
|
|
136
|
+
...payload.ctx,
|
|
137
|
+
invoiceAttached: attachmentsResult.invoiceAttached
|
|
138
|
+
});
|
|
139
|
+
await sendRendered(email, payload.to, rendered.subject, rendered.html, rendered.text, attachmentsResult.attachments);
|
|
140
|
+
} else if (payload.subject != null && payload.html != null) {
|
|
141
|
+
await sendRendered(email, payload.to, payload.subject, payload.html, payload.text, attachmentsResult.attachments ?? payload.attachments);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
__name(queueEmail, "queueEmail");
|
|
145
|
+
async function queueOrderPlacedEmails(cms, payload) {
|
|
146
|
+
const { orderNumber, total, subtotal, tax, currency, customerName, customerEmail, salesTeamEmails, companyDetails, lineItems, billingAddress, shippingAddress, orderId, attachInvoiceToCustomer = true } = payload;
|
|
147
|
+
const base = {
|
|
148
|
+
orderNumber,
|
|
149
|
+
total: total != null ? String(total) : void 0,
|
|
150
|
+
subtotal: subtotal != null ? String(subtotal) : void 0,
|
|
151
|
+
tax: tax != null ? String(tax) : void 0,
|
|
152
|
+
currency,
|
|
153
|
+
customerName,
|
|
154
|
+
companyDetails: companyDetails ?? {},
|
|
155
|
+
lineItems: lineItems ?? [],
|
|
156
|
+
billingAddress,
|
|
157
|
+
shippingAddress
|
|
158
|
+
};
|
|
159
|
+
const customerLower = customerEmail?.trim().toLowerCase() ?? "";
|
|
160
|
+
const jobs = [];
|
|
161
|
+
const shouldAttach = attachInvoiceToCustomer && orderId != null && Number.isFinite(orderId) && orderId > 0;
|
|
162
|
+
if (customerEmail?.trim()) {
|
|
163
|
+
jobs.push(queueEmail(cms, {
|
|
164
|
+
to: customerEmail.trim(),
|
|
165
|
+
templateName: "orderPlaced",
|
|
166
|
+
ctx: {
|
|
167
|
+
...base,
|
|
168
|
+
audience: "customer"
|
|
169
|
+
},
|
|
170
|
+
...shouldAttach ? {
|
|
171
|
+
orderId,
|
|
172
|
+
attachInvoice: true
|
|
173
|
+
} : {}
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
const seen = /* @__PURE__ */ new Set();
|
|
177
|
+
for (const raw of salesTeamEmails) {
|
|
178
|
+
const to = raw.trim();
|
|
179
|
+
if (!to) continue;
|
|
180
|
+
const key = to.toLowerCase();
|
|
181
|
+
if (seen.has(key)) continue;
|
|
182
|
+
seen.add(key);
|
|
183
|
+
if (customerLower && key === customerLower) continue;
|
|
184
|
+
jobs.push(queueEmail(cms, {
|
|
185
|
+
to,
|
|
186
|
+
templateName: "orderPlaced",
|
|
187
|
+
ctx: {
|
|
188
|
+
...base,
|
|
189
|
+
audience: "sales",
|
|
190
|
+
internalCustomerEmail: customerEmail?.trim() || void 0
|
|
191
|
+
}
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
await Promise.all(jobs);
|
|
195
|
+
}
|
|
196
|
+
__name(queueOrderPlacedEmails, "queueOrderPlacedEmails");
|
|
197
|
+
async function queueVendorOnboardEmails(cms, payload) {
|
|
198
|
+
const { vendorName, vendorSlug, ownerName, ownerEmail, activation, inviteLink, signInLink, kind = "vendor_onboard", notifyEmails, companyDetails, sendToOwner = true } = payload;
|
|
199
|
+
const base = {
|
|
200
|
+
vendorName,
|
|
201
|
+
vendorSlug,
|
|
202
|
+
ownerName,
|
|
203
|
+
ownerEmail,
|
|
204
|
+
activation,
|
|
205
|
+
inviteLink,
|
|
206
|
+
signInLink,
|
|
207
|
+
kind,
|
|
208
|
+
companyDetails: companyDetails ?? {}
|
|
209
|
+
};
|
|
210
|
+
const ownerLower = ownerEmail?.trim().toLowerCase() ?? "";
|
|
211
|
+
const jobs = [];
|
|
212
|
+
if (sendToOwner && ownerEmail?.trim()) {
|
|
213
|
+
jobs.push(queueEmail(cms, {
|
|
214
|
+
to: ownerEmail.trim(),
|
|
215
|
+
templateName: "vendorOnboarded",
|
|
216
|
+
ctx: {
|
|
217
|
+
...base,
|
|
218
|
+
audience: "owner"
|
|
219
|
+
}
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
223
|
+
for (const raw of notifyEmails) {
|
|
224
|
+
const to = raw.trim();
|
|
225
|
+
if (!to) continue;
|
|
226
|
+
const key = to.toLowerCase();
|
|
227
|
+
if (seen.has(key)) continue;
|
|
228
|
+
seen.add(key);
|
|
229
|
+
if (ownerLower && key === ownerLower) continue;
|
|
230
|
+
jobs.push(queueEmail(cms, {
|
|
231
|
+
to,
|
|
232
|
+
templateName: "vendorOnboarded",
|
|
233
|
+
ctx: {
|
|
234
|
+
...base,
|
|
235
|
+
audience: "internal"
|
|
236
|
+
}
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
await Promise.all(jobs);
|
|
240
|
+
}
|
|
241
|
+
__name(queueVendorOnboardEmails, "queueVendorOnboardEmails");
|
|
242
|
+
|
|
243
|
+
export { queueEmail, queueOrderPlacedEmails, queueVendorOnboardEmails, registerEmailQueueProcessor };
|
|
@@ -68,7 +68,7 @@ function createCmsAuthBundle(getSession, NextResponse) {
|
|
|
68
68
|
chunkUSNT2KNT_cjs.__name(createCmsAuthBundle, "createCmsAuthBundle");
|
|
69
69
|
function createAuthHelpersFromGetSession(getSession, NextResponse) {
|
|
70
70
|
return {
|
|
71
|
-
async requireAuth() {
|
|
71
|
+
async requireAuth(_req) {
|
|
72
72
|
const session = await getSession();
|
|
73
73
|
if (!session?.user?.email) {
|
|
74
74
|
return NextResponse.json({
|
|
@@ -79,7 +79,7 @@ function createAuthHelpersFromGetSession(getSession, NextResponse) {
|
|
|
79
79
|
}
|
|
80
80
|
return null;
|
|
81
81
|
},
|
|
82
|
-
async requirePermission() {
|
|
82
|
+
async requirePermission(_req, _permission) {
|
|
83
83
|
const session = await getSession();
|
|
84
84
|
if (!session?.user?.email) {
|
|
85
85
|
return NextResponse.json({
|
|
@@ -123,7 +123,7 @@ function createAuthHelpersFromGetSession(getSession, NextResponse) {
|
|
|
123
123
|
status: 403
|
|
124
124
|
});
|
|
125
125
|
},
|
|
126
|
-
async requireAdminAccess() {
|
|
126
|
+
async requireAdminAccess(_req) {
|
|
127
127
|
const session = await getSession();
|
|
128
128
|
if (!session?.user?.email) {
|
|
129
129
|
return NextResponse.json({
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
|
|
4
|
+
|
|
5
|
+
// src/lib/retire-soft-deleted-unique.ts
|
|
6
|
+
async function retireSoftDeletedUniqueValue(repo, column, value) {
|
|
7
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
8
|
+
if (!trimmed) return;
|
|
9
|
+
const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
10
|
+
if (!cols.has(column) || !cols.has("deleted")) return;
|
|
11
|
+
const rows = await repo.find({
|
|
12
|
+
where: {
|
|
13
|
+
[column]: trimmed,
|
|
14
|
+
deleted: true
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const id = row.id;
|
|
19
|
+
if (!Number.isFinite(id)) continue;
|
|
20
|
+
const current = String(row[column] ?? "");
|
|
21
|
+
if (current.includes("__deleted_")) continue;
|
|
22
|
+
await repo.update(id, {
|
|
23
|
+
[column]: `${trimmed}__deleted_${id}`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
chunkUSNT2KNT_cjs.__name(retireSoftDeletedUniqueValue, "retireSoftDeletedUniqueValue");
|
|
28
|
+
async function activeUniqueValueExists(repo, column, value, excludeId) {
|
|
29
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
30
|
+
if (!trimmed) return false;
|
|
31
|
+
const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
32
|
+
if (!cols.has(column)) return false;
|
|
33
|
+
const qb = repo.createQueryBuilder("row").where(`row.${column} = :value`, {
|
|
34
|
+
value: trimmed
|
|
35
|
+
});
|
|
36
|
+
if (cols.has("deleted")) {
|
|
37
|
+
qb.andWhere("row.deleted = false");
|
|
38
|
+
}
|
|
39
|
+
if (excludeId != null && Number.isFinite(excludeId) && excludeId > 0) {
|
|
40
|
+
qb.andWhere("row.id != :excludeId", {
|
|
41
|
+
excludeId
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const hit = await qb.getOne();
|
|
45
|
+
return !!hit;
|
|
46
|
+
}
|
|
47
|
+
chunkUSNT2KNT_cjs.__name(activeUniqueValueExists, "activeUniqueValueExists");
|
|
48
|
+
|
|
49
|
+
exports.activeUniqueValueExists = activeUniqueValueExists;
|
|
50
|
+
exports.retireSoftDeletedUniqueValue = retireSoftDeletedUniqueValue;
|
|
@@ -66,7 +66,7 @@ function createCmsAuthBundle(getSession, NextResponse) {
|
|
|
66
66
|
__name(createCmsAuthBundle, "createCmsAuthBundle");
|
|
67
67
|
function createAuthHelpersFromGetSession(getSession, NextResponse) {
|
|
68
68
|
return {
|
|
69
|
-
async requireAuth() {
|
|
69
|
+
async requireAuth(_req) {
|
|
70
70
|
const session = await getSession();
|
|
71
71
|
if (!session?.user?.email) {
|
|
72
72
|
return NextResponse.json({
|
|
@@ -77,7 +77,7 @@ function createAuthHelpersFromGetSession(getSession, NextResponse) {
|
|
|
77
77
|
}
|
|
78
78
|
return null;
|
|
79
79
|
},
|
|
80
|
-
async requirePermission() {
|
|
80
|
+
async requirePermission(_req, _permission) {
|
|
81
81
|
const session = await getSession();
|
|
82
82
|
if (!session?.user?.email) {
|
|
83
83
|
return NextResponse.json({
|
|
@@ -121,7 +121,7 @@ function createAuthHelpersFromGetSession(getSession, NextResponse) {
|
|
|
121
121
|
status: 403
|
|
122
122
|
});
|
|
123
123
|
},
|
|
124
|
-
async requireAdminAccess() {
|
|
124
|
+
async requireAdminAccess(_req) {
|
|
125
125
|
const session = await getSession();
|
|
126
126
|
if (!session?.user?.email) {
|
|
127
127
|
return NextResponse.json({
|