@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,256 @@
|
|
|
1
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
2
|
+
|
|
3
|
+
// src/lib/order-inventory.ts
|
|
4
|
+
var STOCK_HELD = /* @__PURE__ */ new Set([
|
|
5
|
+
"confirmed",
|
|
6
|
+
"completed"
|
|
7
|
+
]);
|
|
8
|
+
function orderStatusHoldsStock(status) {
|
|
9
|
+
return STOCK_HELD.has(String(status ?? "").trim().toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
__name(orderStatusHoldsStock, "orderStatusHoldsStock");
|
|
12
|
+
function orderInventorySnapshotFromRow(order) {
|
|
13
|
+
return {
|
|
14
|
+
status: order.status ?? "pending",
|
|
15
|
+
lines: (order.items ?? []).map((i) => {
|
|
16
|
+
const meta = i.metadata && typeof i.metadata === "object" ? i.metadata : null;
|
|
17
|
+
const metaVid = meta?.variantId ?? meta?.variant_id;
|
|
18
|
+
const fromCol = i.variantId != null ? Number(i.variantId) : NaN;
|
|
19
|
+
const fromMeta = metaVid != null && String(metaVid).trim() !== "" ? Number(metaVid) : NaN;
|
|
20
|
+
const variantId = Number.isFinite(fromCol) && fromCol > 0 ? fromCol : Number.isFinite(fromMeta) && fromMeta > 0 ? fromMeta : null;
|
|
21
|
+
return {
|
|
22
|
+
productId: Number(i.productId),
|
|
23
|
+
quantity: Math.max(0, Math.floor(Number(i.quantity) || 0)),
|
|
24
|
+
variantId
|
|
25
|
+
};
|
|
26
|
+
}).filter((l) => l.productId > 0 && l.quantity > 0)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
__name(orderInventorySnapshotFromRow, "orderInventorySnapshotFromRow");
|
|
30
|
+
function lineKey(productId, variantId) {
|
|
31
|
+
const vid = variantId != null && Number.isFinite(Number(variantId)) && Number(variantId) > 0 ? Number(variantId) : 0;
|
|
32
|
+
return `${productId}:${vid}`;
|
|
33
|
+
}
|
|
34
|
+
__name(lineKey, "lineKey");
|
|
35
|
+
function mergeInventoryLines(lines) {
|
|
36
|
+
const m = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const productId = Number(line.productId);
|
|
39
|
+
const quantity = Math.max(0, Math.floor(Number(line.quantity) || 0));
|
|
40
|
+
if (!Number.isFinite(productId) || productId <= 0 || quantity <= 0) continue;
|
|
41
|
+
const variantId = line.variantId != null && Number.isFinite(Number(line.variantId)) && Number(line.variantId) > 0 ? Number(line.variantId) : null;
|
|
42
|
+
const key = lineKey(productId, variantId);
|
|
43
|
+
const prev = m.get(key);
|
|
44
|
+
if (prev) {
|
|
45
|
+
prev.quantity += quantity;
|
|
46
|
+
} else {
|
|
47
|
+
m.set(key, {
|
|
48
|
+
productId,
|
|
49
|
+
quantity,
|
|
50
|
+
variantId
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return m;
|
|
55
|
+
}
|
|
56
|
+
__name(mergeInventoryLines, "mergeInventoryLines");
|
|
57
|
+
function productRepo(ds, entityMap) {
|
|
58
|
+
const target = entityMap.products;
|
|
59
|
+
if (!target) throw new Error("products entity not configured");
|
|
60
|
+
return ds.getRepository(target);
|
|
61
|
+
}
|
|
62
|
+
__name(productRepo, "productRepo");
|
|
63
|
+
function variantRepo(ds, entityMap) {
|
|
64
|
+
const target = entityMap.product_variants;
|
|
65
|
+
if (!target) return null;
|
|
66
|
+
return ds.getRepository(target);
|
|
67
|
+
}
|
|
68
|
+
__name(variantRepo, "variantRepo");
|
|
69
|
+
async function validateInventoryForConfirmedOrderLines(ds, entityMap, lines) {
|
|
70
|
+
const merged = mergeInventoryLines(lines);
|
|
71
|
+
const byProduct = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const line of merged.values()) {
|
|
73
|
+
byProduct.set(line.productId, (byProduct.get(line.productId) ?? 0) + line.quantity);
|
|
74
|
+
}
|
|
75
|
+
const pRepo = productRepo(ds, entityMap);
|
|
76
|
+
const vRepo = variantRepo(ds, entityMap);
|
|
77
|
+
for (const [productId, qty] of byProduct) {
|
|
78
|
+
const row = await pRepo.findOne({
|
|
79
|
+
where: {
|
|
80
|
+
id: productId,
|
|
81
|
+
deleted: false
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
if (!row) return `Product #${productId} not found`;
|
|
85
|
+
if (row.type === "service") continue;
|
|
86
|
+
const name = row.name ?? String(productId);
|
|
87
|
+
const productQty = Number(row.quantity ?? 0);
|
|
88
|
+
if (productQty < qty) {
|
|
89
|
+
return `Insufficient stock for ${name}: available ${productQty}, required ${qty}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const line of merged.values()) {
|
|
93
|
+
if (!line.variantId || !vRepo) continue;
|
|
94
|
+
const row = await pRepo.findOne({
|
|
95
|
+
where: {
|
|
96
|
+
id: line.productId,
|
|
97
|
+
deleted: false
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
if (!row) continue;
|
|
101
|
+
if (row.type === "service") continue;
|
|
102
|
+
const name = row.name ?? String(line.productId);
|
|
103
|
+
const variant = await vRepo.findOne({
|
|
104
|
+
where: {
|
|
105
|
+
id: line.variantId,
|
|
106
|
+
productId: line.productId
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
if (!variant) return `Variant #${line.variantId} not found for ${name}`;
|
|
110
|
+
const inv = Number(variant.inventory ?? 0);
|
|
111
|
+
if (inv < line.quantity) {
|
|
112
|
+
return `Insufficient variant stock for ${name}: available ${inv}, required ${line.quantity}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
__name(validateInventoryForConfirmedOrderLines, "validateInventoryForConfirmedOrderLines");
|
|
118
|
+
async function validateInventoryForOrderBecomingConfirmed(ds, entityMap, prevSnap, nextLines) {
|
|
119
|
+
const mergedNext = mergeInventoryLines(nextLines);
|
|
120
|
+
const prevByKey = prevSnap && orderStatusHoldsStock(prevSnap.status) ? mergeInventoryLines(prevSnap.lines) : /* @__PURE__ */ new Map();
|
|
121
|
+
const nextByProduct = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const line of mergedNext.values()) {
|
|
123
|
+
nextByProduct.set(line.productId, (nextByProduct.get(line.productId) ?? 0) + line.quantity);
|
|
124
|
+
}
|
|
125
|
+
const prevByProduct = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const line of prevByKey.values()) {
|
|
127
|
+
prevByProduct.set(line.productId, (prevByProduct.get(line.productId) ?? 0) + line.quantity);
|
|
128
|
+
}
|
|
129
|
+
const pRepo = productRepo(ds, entityMap);
|
|
130
|
+
const vRepo = variantRepo(ds, entityMap);
|
|
131
|
+
for (const [productId, need] of nextByProduct) {
|
|
132
|
+
const row = await pRepo.findOne({
|
|
133
|
+
where: {
|
|
134
|
+
id: productId,
|
|
135
|
+
deleted: false
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
if (!row) return `Product #${productId} not found`;
|
|
139
|
+
if (row.type === "service") continue;
|
|
140
|
+
const name = row.name ?? String(productId);
|
|
141
|
+
const available = Number(row.quantity ?? 0) + (prevByProduct.get(productId) ?? 0);
|
|
142
|
+
if (available < need) {
|
|
143
|
+
return `Insufficient stock for ${name}: available ${available}, required ${need}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const line of mergedNext.values()) {
|
|
147
|
+
if (!line.variantId || !vRepo) continue;
|
|
148
|
+
const row = await pRepo.findOne({
|
|
149
|
+
where: {
|
|
150
|
+
id: line.productId,
|
|
151
|
+
deleted: false
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
if (!row || row.type === "service") continue;
|
|
155
|
+
const name = row.name ?? String(line.productId);
|
|
156
|
+
const key = lineKey(line.productId, line.variantId);
|
|
157
|
+
const variant = await vRepo.findOne({
|
|
158
|
+
where: {
|
|
159
|
+
id: line.variantId,
|
|
160
|
+
productId: line.productId
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (!variant) return `Variant #${line.variantId} not found for ${name}`;
|
|
164
|
+
let inv = Number(variant.inventory ?? 0);
|
|
165
|
+
inv += prevByKey.get(key)?.quantity ?? 0;
|
|
166
|
+
if (inv < line.quantity) {
|
|
167
|
+
return `Insufficient variant stock for ${name}: available ${inv}, required ${line.quantity}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
__name(validateInventoryForOrderBecomingConfirmed, "validateInventoryForOrderBecomingConfirmed");
|
|
173
|
+
async function applyInventoryDelta(ds, entityMap, lines, sign, validateDeduct) {
|
|
174
|
+
const merged = mergeInventoryLines(lines);
|
|
175
|
+
const pRepo = productRepo(ds, entityMap);
|
|
176
|
+
const vRepo = variantRepo(ds, entityMap);
|
|
177
|
+
for (const line of merged.values()) {
|
|
178
|
+
const row = await pRepo.findOne({
|
|
179
|
+
where: sign === 1 ? {
|
|
180
|
+
id: line.productId
|
|
181
|
+
} : {
|
|
182
|
+
id: line.productId,
|
|
183
|
+
deleted: false
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
if (!row) return `Product #${line.productId} not found`;
|
|
187
|
+
if (row.type === "service") continue;
|
|
188
|
+
const name = row.name ?? String(line.productId);
|
|
189
|
+
const cur = Number(row.quantity ?? 0);
|
|
190
|
+
if (line.variantId && vRepo) {
|
|
191
|
+
const variant = await vRepo.findOne({
|
|
192
|
+
where: {
|
|
193
|
+
id: line.variantId,
|
|
194
|
+
productId: line.productId
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
if (!variant) return `Variant #${line.variantId} not found for ${name}`;
|
|
198
|
+
const inv = Number(variant.inventory ?? 0);
|
|
199
|
+
if (sign === -1) {
|
|
200
|
+
if (validateDeduct && inv < line.quantity) {
|
|
201
|
+
return `Insufficient variant stock for ${name}: available ${inv}, required ${line.quantity}`;
|
|
202
|
+
}
|
|
203
|
+
await vRepo.update(line.variantId, {
|
|
204
|
+
inventory: Math.max(0, inv - line.quantity)
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
await vRepo.update(line.variantId, {
|
|
208
|
+
inventory: inv + line.quantity
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (sign === -1) {
|
|
213
|
+
if (validateDeduct && cur < line.quantity) {
|
|
214
|
+
return `Insufficient stock for ${name}: available ${cur}, required ${line.quantity}`;
|
|
215
|
+
}
|
|
216
|
+
await pRepo.update(line.productId, {
|
|
217
|
+
quantity: Math.max(0, cur - line.quantity)
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
await pRepo.update(line.productId, {
|
|
221
|
+
quantity: cur + line.quantity
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
__name(applyInventoryDelta, "applyInventoryDelta");
|
|
228
|
+
async function reconcileOrderInventoryBetweenSnapshots(ds, entityMap, prev, next) {
|
|
229
|
+
const wasHeld = orderStatusHoldsStock(prev.status);
|
|
230
|
+
const isHeld = orderStatusHoldsStock(next.status);
|
|
231
|
+
if (wasHeld) {
|
|
232
|
+
const err = await applyInventoryDelta(ds, entityMap, prev.lines, 1, false);
|
|
233
|
+
if (err) return err;
|
|
234
|
+
}
|
|
235
|
+
if (isHeld) {
|
|
236
|
+
const err = await applyInventoryDelta(ds, entityMap, next.lines, -1, true);
|
|
237
|
+
if (err) return err;
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
__name(reconcileOrderInventoryBetweenSnapshots, "reconcileOrderInventoryBetweenSnapshots");
|
|
242
|
+
async function deductInventoryForConfirmedOrder(ds, entityMap, lines, options) {
|
|
243
|
+
const validate = options?.validate !== false;
|
|
244
|
+
if (validate) {
|
|
245
|
+
const err = await validateInventoryForConfirmedOrderLines(ds, entityMap, lines);
|
|
246
|
+
if (err) return err;
|
|
247
|
+
}
|
|
248
|
+
return applyInventoryDelta(ds, entityMap, lines, -1, validate);
|
|
249
|
+
}
|
|
250
|
+
__name(deductInventoryForConfirmedOrder, "deductInventoryForConfirmedOrder");
|
|
251
|
+
async function restoreInventoryForCancelledOrder(ds, entityMap, lines) {
|
|
252
|
+
return applyInventoryDelta(ds, entityMap, lines, 1, false);
|
|
253
|
+
}
|
|
254
|
+
__name(restoreInventoryForCancelledOrder, "restoreInventoryForCancelledOrder");
|
|
255
|
+
|
|
256
|
+
export { deductInventoryForConfirmedOrder, mergeInventoryLines, orderInventorySnapshotFromRow, orderStatusHoldsStock, reconcileOrderInventoryBetweenSnapshots, restoreInventoryForCancelledOrder, validateInventoryForConfirmedOrderLines, validateInventoryForOrderBecomingConfirmed };
|
|
@@ -399,6 +399,14 @@ function getNextAuthOptions(config) {
|
|
|
399
399
|
});
|
|
400
400
|
return null;
|
|
401
401
|
}
|
|
402
|
+
if (user.inviteStatus === "pending") {
|
|
403
|
+
logAuth("authorize(credentials) rejected", {
|
|
404
|
+
reason: "invite_pending",
|
|
405
|
+
email,
|
|
406
|
+
userId: user.id
|
|
407
|
+
});
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
402
410
|
if (user.deleted) {
|
|
403
411
|
logAuth("authorize(credentials) rejected", {
|
|
404
412
|
reason: "deleted",
|
|
@@ -488,7 +496,7 @@ function getNextAuthOptions(config) {
|
|
|
488
496
|
channel: ch,
|
|
489
497
|
code
|
|
490
498
|
});
|
|
491
|
-
if (!user || user.blocked || user.deleted) return null;
|
|
499
|
+
if (!user || user.blocked || user.inviteStatus === "pending" || user.deleted) return null;
|
|
492
500
|
if (isCustomerGroupUser(user) && !permitsCustomerLogin(allowCustomerLogin, creds)) {
|
|
493
501
|
logAuth("authorize(otp) rejected", {
|
|
494
502
|
reason: "customer_group",
|