@infuro/cms-core 1.0.45 → 1.0.47
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 +4249 -2872
- package/dist/admin.d.cts +24 -4
- package/dist/admin.d.ts +24 -4
- package/dist/admin.js +3179 -1804
- package/dist/api.cjs +41 -39
- package/dist/api.d.cts +10 -4
- package/dist/api.d.ts +10 -4
- package/dist/api.js +9 -7
- package/dist/auth.cjs +55 -51
- package/dist/auth.d.cts +6 -3
- package/dist/auth.d.ts +6 -3
- package/dist/auth.js +3 -3
- package/dist/{chunk-KFFB6EMZ.js → chunk-2HU5R2JE.js} +2 -0
- package/dist/chunk-2IAXTKQN.cjs +19 -0
- package/dist/chunk-2KUCQVAQ.js +936 -0
- package/dist/chunk-3K5AIEIZ.js +256 -0
- package/dist/{chunk-SPQFNIYJ.cjs → chunk-4PMK3RNA.cjs} +2 -0
- package/dist/{chunk-X6UQFV4X.cjs → chunk-77NUXO6A.cjs} +14 -12
- package/dist/chunk-BXYZDMTZ.cjs +953 -0
- package/dist/chunk-DBPSJYLZ.js +47 -0
- package/dist/chunk-EIZKW2PF.js +13 -0
- package/dist/chunk-HY3AXLOI.cjs +265 -0
- package/dist/{chunk-X47PBV4I.js → chunk-JE22VP6S.js} +1 -0
- package/dist/{chunk-DN65KDIA.js → chunk-JIWUVQ6B.js} +14 -13
- package/dist/{chunk-HT5DI3XW.cjs → chunk-KOTXDSQB.cjs} +1 -0
- package/dist/chunk-L3525VXI.js +243 -0
- package/dist/{chunk-22FFHLTO.cjs → chunk-LMJ7RKPF.cjs} +421 -150
- package/dist/chunk-OY2YTBMP.cjs +50 -0
- package/dist/{chunk-TAHX46EI.cjs → chunk-Q3HQEM4R.cjs} +15 -7
- package/dist/chunk-V25RDUOU.cjs +248 -0
- package/dist/{chunk-ZMRQ72F6.cjs → chunk-VUQFARRT.cjs} +10 -10
- package/dist/{chunk-MVXLRANV.js → chunk-W42UZLQO.js} +1977 -891
- package/dist/{chunk-F5XFHYTG.cjs → chunk-XUCKZPML.cjs} +2088 -991
- package/dist/{chunk-I6NH72MO.js → chunk-YC4NUZCS.js} +10 -2
- package/dist/{chunk-NNQBWDCI.js → chunk-YXH2UUEZ.js} +418 -146
- package/dist/{chunk-BLR6H5GL.js → chunk-ZF2RQWXB.js} +4 -4
- 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/{emit-order-notification-trigger-IVLWKKJL.js → emit-order-notification-trigger-6XX7LSC4.js} +1 -1
- package/dist/{emit-order-notification-trigger-D2N3KFHL.cjs → emit-order-notification-trigger-NASG7ZEO.cjs} +3 -3
- package/dist/erp-order-invoice-3GXDE443.js +3 -0
- package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
- package/dist/{event-order-defaults-EMWBIGL3.cjs → event-order-defaults-7Z3UZOEH.cjs} +9 -9
- package/dist/{event-order-defaults-SDRNAZHC.js → event-order-defaults-XQ3IDPD7.js} +1 -1
- package/dist/hooks.cjs +43 -0
- package/dist/hooks.d.cts +10 -1
- package/dist/hooks.d.ts +10 -1
- package/dist/hooks.js +39 -1
- package/dist/index.cjs +429 -298
- package/dist/index.d.cts +317 -30
- package/dist/index.d.ts +317 -30
- package/dist/index.js +17 -13
- 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/migrations/1782200000000-VendorTeamProfiles.ts +233 -0
- package/dist/migrations/1782300000000-AddTypeToOrderNotificationBindings.ts +75 -0
- package/dist/order-inventory-2MYRKEPC.cjs +39 -0
- package/dist/order-inventory-76V4XIGW.js +2 -0
- package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-6WNG24NY.js} +4 -2
- package/dist/{order-notification-dispatcher-RU7BTGGX.cjs → order-notification-dispatcher-6XSU3AR7.cjs} +6 -4
- package/dist/{rbac-debug-db-2SGNLANF.js → rbac-debug-db-6EMIVMGF.js} +1 -1
- package/dist/{rbac-debug-db-4Z6A425H.cjs → rbac-debug-db-6PWTLBEL.cjs} +2 -2
- package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
- package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
- package/dist/theme.cjs +6 -1
- package/dist/theme.d.cts +4 -1
- package/dist/theme.d.ts +4 -1
- package/dist/theme.js +3 -1
- package/package.json +5 -4
- package/src/admin/admin.css +34 -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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
2
|
+
|
|
3
|
+
// src/lib/retire-soft-deleted-unique.ts
|
|
4
|
+
async function retireSoftDeletedUniqueValue(repo, column, value) {
|
|
5
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
6
|
+
if (!trimmed) return;
|
|
7
|
+
const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
8
|
+
if (!cols.has(column) || !cols.has("deleted")) return;
|
|
9
|
+
const rows = await repo.find({
|
|
10
|
+
where: {
|
|
11
|
+
[column]: trimmed,
|
|
12
|
+
deleted: true
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
for (const row of rows) {
|
|
16
|
+
const id = row.id;
|
|
17
|
+
if (!Number.isFinite(id)) continue;
|
|
18
|
+
const current = String(row[column] ?? "");
|
|
19
|
+
if (current.includes("__deleted_")) continue;
|
|
20
|
+
await repo.update(id, {
|
|
21
|
+
[column]: `${trimmed}__deleted_${id}`
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
__name(retireSoftDeletedUniqueValue, "retireSoftDeletedUniqueValue");
|
|
26
|
+
async function activeUniqueValueExists(repo, column, value, excludeId) {
|
|
27
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
28
|
+
if (!trimmed) return false;
|
|
29
|
+
const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
30
|
+
if (!cols.has(column)) return false;
|
|
31
|
+
const qb = repo.createQueryBuilder("row").where(`row.${column} = :value`, {
|
|
32
|
+
value: trimmed
|
|
33
|
+
});
|
|
34
|
+
if (cols.has("deleted")) {
|
|
35
|
+
qb.andWhere("row.deleted = false");
|
|
36
|
+
}
|
|
37
|
+
if (excludeId != null && Number.isFinite(excludeId) && excludeId > 0) {
|
|
38
|
+
qb.andWhere("row.id != :excludeId", {
|
|
39
|
+
excludeId
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const hit = await qb.getOne();
|
|
43
|
+
return !!hit;
|
|
44
|
+
}
|
|
45
|
+
__name(activeUniqueValueExists, "activeUniqueValueExists");
|
|
46
|
+
|
|
47
|
+
export { activeUniqueValueExists, retireSoftDeletedUniqueValue };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
function ForceLightMode() {
|
|
5
|
+
return React.createElement("script", {
|
|
6
|
+
dangerouslySetInnerHTML: {
|
|
7
|
+
__html: `(function(){try{var r=document.documentElement,b=document.body;if(r){r.classList.remove('dark');r.style.colorScheme='light';}if(b){b.classList.remove('dark');b.style.colorScheme='light';}}catch(e){}})();`
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
__name(ForceLightMode, "ForceLightMode");
|
|
12
|
+
|
|
13
|
+
export { ForceLightMode };
|
|
@@ -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;
|
|
@@ -59,6 +59,7 @@ async function emitOrderNotificationTrigger(triggerKey, orderId, deps, extra) {
|
|
|
59
59
|
total: fo.total ?? 0,
|
|
60
60
|
currency: String(fo.currency ?? "INR"),
|
|
61
61
|
vendorId: fo.vendorId ?? null,
|
|
62
|
+
...extra?.extraVariables ?? {},
|
|
62
63
|
...extra?.refundAmount != null ? {
|
|
63
64
|
refundAmount: extra.refundAmount
|
|
64
65
|
} : {}
|
|
@@ -48,12 +48,13 @@ __name(getPermissionableEntityKeys, "getPermissionableEntityKeys");
|
|
|
48
48
|
function permissionRowsToRecord(rows) {
|
|
49
49
|
const out = {};
|
|
50
50
|
if (!rows?.length) return out;
|
|
51
|
+
const flag = /* @__PURE__ */ __name((v) => v === true || v === 1 || v === "1" || v === "t" || v === "true", "flag");
|
|
51
52
|
for (const p of rows) {
|
|
52
53
|
out[p.entity] = {
|
|
53
|
-
c:
|
|
54
|
-
r:
|
|
55
|
-
u:
|
|
56
|
-
d:
|
|
54
|
+
c: flag(p.canCreate),
|
|
55
|
+
r: flag(p.canRead),
|
|
56
|
+
u: flag(p.canUpdate),
|
|
57
|
+
d: flag(p.canDelete)
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
return out;
|
|
@@ -136,11 +137,12 @@ var VENDOR_STORE_RBAC_ENTITIES = /* @__PURE__ */ new Set([
|
|
|
136
137
|
"order_addresses",
|
|
137
138
|
"event_products"
|
|
138
139
|
]);
|
|
139
|
-
var
|
|
140
|
+
var VENDOR_GROUP_NAME = "Vendor";
|
|
141
|
+
var VENDOR_OWNER_GROUP_NAME = VENDOR_GROUP_NAME;
|
|
140
142
|
function isVendorGroupName(name) {
|
|
141
143
|
if (!name?.trim()) return false;
|
|
142
144
|
const n = name.trim().toLowerCase();
|
|
143
|
-
return n ===
|
|
145
|
+
return n === "vendor" || n === "vendor owner" || n.includes("vendor");
|
|
144
146
|
}
|
|
145
147
|
__name(isVendorGroupName, "isVendorGroupName");
|
|
146
148
|
function isPlatformAdministrator(user) {
|
|
@@ -163,10 +165,8 @@ function isVendorOwner(user) {
|
|
|
163
165
|
if (user?.isVendorOwner === true) return true;
|
|
164
166
|
if (!user?.email || isPlatformAdministrator(user)) return false;
|
|
165
167
|
if (user.isVendorRoleOwner === true) return true;
|
|
166
|
-
if (user.vendorRole === "staff") return false;
|
|
167
168
|
if (user.vendorRole === "owner") return true;
|
|
168
|
-
|
|
169
|
-
return user.groupName?.trim().toLowerCase() === VENDOR_OWNER_GROUP_NAME.toLowerCase();
|
|
169
|
+
return false;
|
|
170
170
|
}
|
|
171
171
|
__name(isVendorOwner, "isVendorOwner");
|
|
172
172
|
function canManageVendorTeam(user) {
|
|
@@ -204,7 +204,7 @@ __name(resolveVendorScopeFromSessionUser, "resolveVendorScopeFromSessionUser");
|
|
|
204
204
|
function vendorPortalFlagsFromUser(input) {
|
|
205
205
|
const vendorIds = input.vendorIds ?? [];
|
|
206
206
|
const isVendorPortal = !input.isRBACAdmin && (vendorIds.length > 0 || isVendorGroupName(input.groupName));
|
|
207
|
-
const isVendorOwner2 = isVendorPortal && (input.isVendorRoleOwner === true || input.vendorRole
|
|
207
|
+
const isVendorOwner2 = isVendorPortal && (input.isVendorRoleOwner === true || input.vendorRole === "owner");
|
|
208
208
|
return {
|
|
209
209
|
isVendorPortal,
|
|
210
210
|
isVendorOwner: isVendorOwner2
|
|
@@ -461,7 +461,8 @@ function explainSessionEntityAccess(user, entity, action) {
|
|
|
461
461
|
const isStaff = user.vendorRole === "staff";
|
|
462
462
|
const vendorPerms = user.vendorEntityPerms;
|
|
463
463
|
const hasVendorRolePerms = vendorPerms != null && Object.keys(vendorPerms).length > 0;
|
|
464
|
-
|
|
464
|
+
const enforceVendorRoleMatrix = vendorIds.length > 0 && (hasVendorRolePerms || user.vendorRoleId != null);
|
|
465
|
+
if (enforceVendorRoleMatrix) {
|
|
465
466
|
const vendorRbacSet = new Set(VENDOR_RBAC_ENTITIES);
|
|
466
467
|
if (vendorRbacSet.has(entity) || entity === "analytics") {
|
|
467
468
|
if (entity === "analytics" && action === "read") {
|
|
@@ -603,7 +604,7 @@ async function logEntityAccessDecision(dataSource, context, user, entity, action
|
|
|
603
604
|
const id = Number(user.id);
|
|
604
605
|
if (Number.isFinite(id)) {
|
|
605
606
|
try {
|
|
606
|
-
const { loadDbPermissionsForUser } = await import('./rbac-debug-db-
|
|
607
|
+
const { loadDbPermissionsForUser } = await import('./rbac-debug-db-6EMIVMGF.js');
|
|
607
608
|
dbPermissions = await loadDbPermissionsForUser(dataSource, id);
|
|
608
609
|
} catch (e) {
|
|
609
610
|
dbPermissions = {
|
|
@@ -624,4 +625,4 @@ async function logEntityAccessDecision(dataSource, context, user, entity, action
|
|
|
624
625
|
}
|
|
625
626
|
__name(logEntityAccessDecision, "logEntityAccessDecision");
|
|
626
627
|
|
|
627
|
-
export { ADMIN_GROUP_NAME, DEFAULT_VENDOR_ROLE_DEFS, SUPER_ADMIN_GROUP_ID, VENDOR_ADMIN_GROUP_ID, VENDOR_OWNER_GROUP_NAME, VENDOR_RBAC_ENTITIES, VENDOR_SCOPED_STORE_ENTITIES, VENDOR_STORE_RBAC_ENTITIES, VENDOR_VIEWER_ROLE_NAME, canManageVendorRoles, canManageVendorTeam, canOnboardVendors, explainSessionEntityAccess, getPermissionableEntityKeys, hasEntityPermission, isPlatformAdministrator, isRbacDebugEnabled, isSuperAdmin, isSuperAdminGroupName, isVendorAdmin, isVendorGroupName, isVendorOwner, isVendorPortalUser, isVendorStaff, logEntityAccessDecision, logRbac, permissionRowsToRecord, resolveDisabledMultiVendorScope, resolveVendorScopeFromSessionUser, summarizeEntityPerms, vendorPortalFlagsFromUser };
|
|
628
|
+
export { ADMIN_GROUP_NAME, DEFAULT_VENDOR_ROLE_DEFS, SUPER_ADMIN_GROUP_ID, VENDOR_ADMIN_GROUP_ID, VENDOR_GROUP_NAME, VENDOR_OWNER_GROUP_NAME, VENDOR_RBAC_ENTITIES, VENDOR_SCOPED_STORE_ENTITIES, VENDOR_STORE_RBAC_ENTITIES, VENDOR_VIEWER_ROLE_NAME, canManageVendorRoles, canManageVendorTeam, canOnboardVendors, explainSessionEntityAccess, getPermissionableEntityKeys, hasEntityPermission, isPlatformAdministrator, isRbacDebugEnabled, isSuperAdmin, isSuperAdminGroupName, isVendorAdmin, isVendorGroupName, isVendorOwner, isVendorPortalUser, isVendorStaff, logEntityAccessDecision, logRbac, permissionRowsToRecord, resolveDisabledMultiVendorScope, resolveVendorScopeFromSessionUser, summarizeEntityPerms, vendorPortalFlagsFromUser };
|
|
@@ -61,6 +61,7 @@ async function emitOrderNotificationTrigger(triggerKey, orderId, deps, extra) {
|
|
|
61
61
|
total: fo.total ?? 0,
|
|
62
62
|
currency: String(fo.currency ?? "INR"),
|
|
63
63
|
vendorId: fo.vendorId ?? null,
|
|
64
|
+
...extra?.extraVariables ?? {},
|
|
64
65
|
...extra?.refundAmount != null ? {
|
|
65
66
|
refundAmount: extra.refundAmount
|
|
66
67
|
} : {}
|
|
@@ -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 };
|