@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.
Files changed (66) hide show
  1. package/README.md +19 -2
  2. package/dist/admin.cjs +1413 -620
  3. package/dist/admin.d.cts +8 -2
  4. package/dist/admin.d.ts +8 -2
  5. package/dist/admin.js +1414 -621
  6. package/dist/api.cjs +40 -37
  7. package/dist/api.d.cts +3 -1
  8. package/dist/api.d.ts +3 -1
  9. package/dist/api.js +8 -5
  10. package/dist/auth.cjs +23 -23
  11. package/dist/auth.d.cts +1 -0
  12. package/dist/auth.d.ts +1 -0
  13. package/dist/auth.js +2 -2
  14. package/dist/chunk-2KUCQVAQ.js +936 -0
  15. package/dist/chunk-3K5AIEIZ.js +256 -0
  16. package/dist/{chunk-I6NH72MO.js → chunk-57O3HZPG.js} +9 -1
  17. package/dist/chunk-7PMHZRF3.cjs +872 -0
  18. package/dist/chunk-BXYZDMTZ.cjs +953 -0
  19. package/dist/chunk-CQHXW4TR.js +107 -0
  20. package/dist/{chunk-NNQBWDCI.js → chunk-CRYKVJTO.js} +55 -959
  21. package/dist/chunk-DBPSJYLZ.js +47 -0
  22. package/dist/{chunk-TAHX46EI.cjs → chunk-FBKDMRQL.cjs} +9 -1
  23. package/dist/chunk-GUSHM5HN.js +862 -0
  24. package/dist/chunk-HY3AXLOI.cjs +265 -0
  25. package/dist/chunk-L3525VXI.js +243 -0
  26. package/dist/{chunk-ZMRQ72F6.cjs → chunk-NBY4NVTY.cjs} +3 -3
  27. package/dist/chunk-OY2YTBMP.cjs +50 -0
  28. package/dist/{chunk-BLR6H5GL.js → chunk-SF2XKMCI.js} +3 -3
  29. package/dist/{chunk-22FFHLTO.cjs → chunk-TCSGFAWB.cjs} +56 -970
  30. package/dist/{chunk-P2IWWBJV.cjs → chunk-UDVLFVEC.cjs} +1314 -601
  31. package/dist/{chunk-SQGBHYJF.js → chunk-UKC3HYXI.js} +1245 -548
  32. package/dist/chunk-V25RDUOU.cjs +248 -0
  33. package/dist/chunk-XMRMZ6NQ.cjs +109 -0
  34. package/dist/cli.cjs +9 -4
  35. package/dist/cli.js +9 -4
  36. package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
  37. package/dist/email-queue-OI2HSCGE.js +4 -0
  38. package/dist/erp-order-invoice-3GXDE443.js +3 -0
  39. package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
  40. package/dist/index.cjs +426 -359
  41. package/dist/index.d.cts +308 -20
  42. package/dist/index.d.ts +308 -20
  43. package/dist/index.js +31 -110
  44. package/dist/migrations/1781800000000-CustomerPhoneNullable.ts +53 -0
  45. package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
  46. package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
  47. package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
  48. package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
  49. package/dist/order-inventory-2MYRKEPC.cjs +39 -0
  50. package/dist/order-inventory-76V4XIGW.js +2 -0
  51. package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
  52. package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
  53. package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
  54. package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
  55. package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
  56. package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
  57. package/package.json +5 -4
  58. package/src/admin/admin.css +27 -24
  59. package/dist/chunk-5ELSW2UP.js +0 -150
  60. package/dist/chunk-PNOKNSG2.cjs +0 -222
  61. package/dist/chunk-WEMDMVHQ.js +0 -216
  62. package/dist/chunk-XW5ATPRW.cjs +0 -155
  63. package/dist/email-queue-CGZBVVAS.js +0 -2
  64. package/dist/erp-order-invoice-3YISSOWW.js +0 -3
  65. package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
  66. 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",