@feedmepos/order-plugin-gallery 0.0.9 → 0.0.10-beta.0
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.
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const formatQty = (qty) => {
|
|
2
|
+
if (Number.isInteger(qty))
|
|
3
|
+
return `${qty}`;
|
|
4
|
+
return qty.toFixed(2).replace(/\.?0+$/, "");
|
|
5
|
+
};
|
|
6
|
+
const addQuantity = (quantityMap, itemId, quantity) => {
|
|
7
|
+
if (!itemId || typeof itemId !== "string")
|
|
8
|
+
return;
|
|
9
|
+
if (typeof quantity !== "number" || !Number.isFinite(quantity))
|
|
10
|
+
return;
|
|
11
|
+
if (quantity <= 0)
|
|
12
|
+
return;
|
|
13
|
+
quantityMap.set(itemId, (quantityMap.get(itemId) || 0) + quantity);
|
|
14
|
+
};
|
|
15
|
+
const getQuantityMap = (items) => {
|
|
16
|
+
const quantityMap = new Map();
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
addQuantity(quantityMap, item.productId, item.quantity);
|
|
19
|
+
}
|
|
20
|
+
return quantityMap;
|
|
21
|
+
};
|
|
22
|
+
const getNormalizedRules = (rawRules) => {
|
|
23
|
+
if (!Array.isArray(rawRules))
|
|
24
|
+
return [];
|
|
25
|
+
const normalizedRules = [];
|
|
26
|
+
for (const rule of rawRules) {
|
|
27
|
+
const itemId = typeof rule?.itemId === "string"
|
|
28
|
+
? rule.itemId.trim()
|
|
29
|
+
: "";
|
|
30
|
+
const minQty = rule?.minQty;
|
|
31
|
+
const maxQty = rule?.maxQty;
|
|
32
|
+
if (!itemId || !Number.isFinite(minQty) || !Number.isFinite(maxQty)) {
|
|
33
|
+
console.warn("[compulsory-items] Skip invalid rule. Expect { itemId, minQty, maxQty } with finite numbers.", rule);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (minQty < 0 || maxQty < minQty) {
|
|
37
|
+
console.warn("[compulsory-items] Skip invalid rule. Expect minQty >= 0 and maxQty >= minQty.", rule);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
normalizedRules.push({ itemId, minQty, maxQty });
|
|
41
|
+
}
|
|
42
|
+
return normalizedRules;
|
|
43
|
+
};
|
|
44
|
+
const getItemTitleMap = (sdk) => {
|
|
45
|
+
const titleMap = new Map();
|
|
46
|
+
const menuItems = (sdk.menuManager.state.value.menu?.items ||
|
|
47
|
+
[]);
|
|
48
|
+
for (const menuItem of menuItems) {
|
|
49
|
+
const itemId = menuItem.id?.trim();
|
|
50
|
+
const title = menuItem.title?.trim();
|
|
51
|
+
if (!itemId || !title)
|
|
52
|
+
continue;
|
|
53
|
+
titleMap.set(itemId, title);
|
|
54
|
+
}
|
|
55
|
+
const rawItems = (sdk.menuManager.state.value.overridedMenu?.modules?.item ||
|
|
56
|
+
[]);
|
|
57
|
+
for (const rawItem of rawItems) {
|
|
58
|
+
const itemId = rawItem._id?.trim();
|
|
59
|
+
const title = rawItem.name?.trim();
|
|
60
|
+
if (!itemId || !title || titleMap.has(itemId))
|
|
61
|
+
continue;
|
|
62
|
+
titleMap.set(itemId, title);
|
|
63
|
+
}
|
|
64
|
+
return titleMap;
|
|
65
|
+
};
|
|
66
|
+
const compulsoryItemsPlugin = ({ sdk, onBeforeSubmitOrder, pluginParams, }) => {
|
|
67
|
+
onBeforeSubmitOrder(() => {
|
|
68
|
+
const params = (pluginParams || {});
|
|
69
|
+
const rules = getNormalizedRules(params.itemLimitConfig);
|
|
70
|
+
if (rules.length === 0)
|
|
71
|
+
return true;
|
|
72
|
+
const submittedItems = (sdk.orderManager.state.value.slotActiveBills?.[0]?.items ||
|
|
73
|
+
[]);
|
|
74
|
+
const draftItems = (sdk.orderManager.state.value.order?.draft || []);
|
|
75
|
+
const submittedQtyByItemId = getQuantityMap(submittedItems);
|
|
76
|
+
const draftQtyByItemId = getQuantityMap(draftItems);
|
|
77
|
+
const itemTitleMap = getItemTitleMap(sdk);
|
|
78
|
+
const violationMessages = [];
|
|
79
|
+
for (const rule of rules) {
|
|
80
|
+
const submittedQty = submittedQtyByItemId.get(rule.itemId) || 0;
|
|
81
|
+
const draftQty = draftQtyByItemId.get(rule.itemId) || 0;
|
|
82
|
+
const totalQty = submittedQty + draftQty;
|
|
83
|
+
const itemTitle = itemTitleMap.get(rule.itemId) || rule.itemId;
|
|
84
|
+
if (totalQty < rule.minQty) {
|
|
85
|
+
const delta = rule.minQty - totalQty;
|
|
86
|
+
violationMessages.push(`Add ${formatQty(delta)} more "${itemTitle}" (minimum ${formatQty(rule.minQty)}, current total ${formatQty(totalQty)}).`);
|
|
87
|
+
}
|
|
88
|
+
if (submittedQty > rule.maxQty) {
|
|
89
|
+
if (draftQty > 0) {
|
|
90
|
+
violationMessages.push(`"${itemTitle}" is already above the limit in submitted orders. Remove ${formatQty(draftQty)} from your current cart to continue.`);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (totalQty > rule.maxQty) {
|
|
95
|
+
const delta = totalQty - rule.maxQty;
|
|
96
|
+
violationMessages.push(`Remove ${formatQty(delta)} "${itemTitle}" from your current cart (maximum ${formatQty(rule.maxQty)}, current total ${formatQty(totalQty)}).`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (violationMessages.length === 0) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
sdk.ui.toast.show({
|
|
103
|
+
title: [
|
|
104
|
+
"Please update your cart before submitting:",
|
|
105
|
+
...violationMessages.map((message) => `- ${message}`),
|
|
106
|
+
].join("\n"),
|
|
107
|
+
type: "error",
|
|
108
|
+
duration: 6000,
|
|
109
|
+
});
|
|
110
|
+
return false;
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
export default compulsoryItemsPlugin;
|
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
const setMealValidationPlugin = ({ sdk, onBeforeAddToCart, onBeforeSubmitOrder, pluginParams, }) => {
|
|
2
|
-
console.log('[ItemRestriction Plugin] Loaded');
|
|
2
|
+
console.log('[ItemRestriction Plugin] Loaded (Simplified Version)');
|
|
3
3
|
const REQUIRED_CATEGORY_IDS = pluginParams?.requiredCategoryIds || [];
|
|
4
4
|
const RESTRICTED_CATEGORY_IDS = pluginParams?.restrictedCategoryIds || [];
|
|
5
5
|
const errorMessage = pluginParams?.message || 'Please order items from required category first';
|
|
6
6
|
const getMenu = () => sdk?.menuManager?.state?.value?.overridedMenu;
|
|
7
7
|
const getCategoryIdForItem = (item, menu) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return
|
|
8
|
+
// Trust the enriched item from plugin-loader, or check the normalized menu modules
|
|
9
|
+
if (item.categoryId || item.category) {
|
|
10
|
+
return item.categoryId || item.category;
|
|
11
|
+
}
|
|
12
|
+
// Fallback: Check if the item exists in the normalized modules.item list
|
|
13
|
+
// This handles cases where 'item' is a fresh cart object that hasn't been enriched yet,
|
|
14
|
+
// but the menu has the enriched version.
|
|
15
|
+
if (menu?.modules?.item) {
|
|
16
|
+
const itemProductId = item.productId || item.id || item._id;
|
|
17
|
+
const menuItem = menu.modules.item.find((i) => i._id === itemProductId || i.id === itemProductId);
|
|
18
|
+
if (menuItem) {
|
|
19
|
+
return menuItem.categoryId || menuItem.category || null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
23
|
};
|
|
24
24
|
onBeforeAddToCart(async (item) => {
|
|
25
25
|
console.log('[ItemRestriction Plugin] Checking item:', item.title || item.name);
|
|
26
|
-
console.log('[ItemRestriction Plugin] Item ID:', item.id || item._id);
|
|
27
26
|
const menu = getMenu();
|
|
28
27
|
if (!menu) {
|
|
29
28
|
console.log('[ItemRestriction Plugin] No menu found');
|
|
@@ -31,33 +30,24 @@ const setMealValidationPlugin = ({ sdk, onBeforeAddToCart, onBeforeSubmitOrder,
|
|
|
31
30
|
}
|
|
32
31
|
const itemCategoryId = getCategoryIdForItem(item, menu);
|
|
33
32
|
const isRestricted = itemCategoryId && RESTRICTED_CATEGORY_IDS.includes(itemCategoryId);
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
console.log('[ItemRestriction Plugin] Category check:', {
|
|
34
|
+
itemName: item.title || item.name,
|
|
35
|
+
itemId: item.id || item._id,
|
|
36
|
+
resolvedCategoryId: itemCategoryId,
|
|
37
|
+
isRestricted
|
|
38
|
+
});
|
|
39
|
+
if (!isRestricted)
|
|
36
40
|
return true;
|
|
37
|
-
}
|
|
38
41
|
const slotActiveBills = sdk?.orderManager?.state?.value?.slotActiveBills || [];
|
|
39
|
-
// Use flatMap if available, or fallback to reduce/concat
|
|
40
42
|
const orderedItems = slotActiveBills.flatMap
|
|
41
43
|
? slotActiveBills.flatMap((bill) => bill?.items || [])
|
|
42
44
|
: slotActiveBills.reduce((acc, bill) => acc.concat(bill?.items || []), []);
|
|
43
45
|
const draft = sdk?.orderManager?.state?.value?.order?.draft || [];
|
|
44
|
-
console.log('[ItemRestriction Plugin] Checking order state:', {
|
|
45
|
-
draftCount: draft.length,
|
|
46
|
-
billsCount: slotActiveBills.length,
|
|
47
|
-
orderedItemsCount: orderedItems.length,
|
|
48
|
-
totalItems: draft.length + orderedItems.length
|
|
49
|
-
});
|
|
50
46
|
const allItems = [...orderedItems, ...draft];
|
|
51
47
|
const requiredCategoryItemsFound = allItems.some((orderItem) => {
|
|
52
48
|
const catId = getCategoryIdForItem(orderItem, menu);
|
|
53
|
-
console.log('[ItemRestriction Plugin] Item check:', {
|
|
54
|
-
productId: orderItem.productId || orderItem.id || orderItem._id,
|
|
55
|
-
foundCategoryId: catId,
|
|
56
|
-
isRequired: catId && REQUIRED_CATEGORY_IDS.includes(catId)
|
|
57
|
-
});
|
|
58
49
|
return catId && REQUIRED_CATEGORY_IDS.includes(catId);
|
|
59
50
|
});
|
|
60
|
-
console.log('[ItemRestriction Plugin] Required category items found?', requiredCategoryItemsFound);
|
|
61
51
|
if (requiredCategoryItemsFound) {
|
|
62
52
|
console.log('[ItemRestriction Plugin] Required category items already ordered, allowing item');
|
|
63
53
|
return true;
|
|
@@ -67,13 +57,11 @@ const setMealValidationPlugin = ({ sdk, onBeforeAddToCart, onBeforeSubmitOrder,
|
|
|
67
57
|
return false;
|
|
68
58
|
});
|
|
69
59
|
onBeforeSubmitOrder(async () => {
|
|
70
|
-
console.log('[ItemRestriction Plugin] onBeforeSubmitOrder called');
|
|
71
60
|
const slotActiveBills = sdk?.orderManager?.state?.value?.slotActiveBills || [];
|
|
72
61
|
const orderedItems = slotActiveBills.flatMap
|
|
73
62
|
? slotActiveBills.flatMap((bill) => bill?.items || [])
|
|
74
63
|
: slotActiveBills.reduce((acc, bill) => acc.concat(bill?.items || []), []);
|
|
75
64
|
const draft = sdk?.orderManager?.state?.value?.order?.draft || [];
|
|
76
|
-
console.log('[ItemRestriction Plugin] Checking order with', orderedItems.length + draft.length, 'items');
|
|
77
65
|
if (orderedItems.length === 0 && draft.length === 0)
|
|
78
66
|
return true;
|
|
79
67
|
const menu = getMenu();
|
|
@@ -84,16 +72,13 @@ const setMealValidationPlugin = ({ sdk, onBeforeAddToCart, onBeforeSubmitOrder,
|
|
|
84
72
|
const catId = getCategoryIdForItem(item, menu);
|
|
85
73
|
return catId && RESTRICTED_CATEGORY_IDS.includes(catId);
|
|
86
74
|
});
|
|
87
|
-
console.log('[ItemRestriction Plugin] Has restricted category items in order?', hasRestrictedCategoryItems);
|
|
88
75
|
if (!hasRestrictedCategoryItems)
|
|
89
76
|
return true;
|
|
90
77
|
const requiredCategoryItemsFound = allItems.some((item) => {
|
|
91
78
|
const catId = getCategoryIdForItem(item, menu);
|
|
92
79
|
return catId && REQUIRED_CATEGORY_IDS.includes(catId);
|
|
93
80
|
});
|
|
94
|
-
console.log('[ItemRestriction Plugin] Required category items found?', requiredCategoryItemsFound);
|
|
95
81
|
if (!requiredCategoryItemsFound) {
|
|
96
|
-
console.log('[ItemRestriction Plugin] No required category items in order, blocking checkout');
|
|
97
82
|
sdk?.ui?.toast?.show?.({ title: errorMessage, type: "error", duration: 5000 });
|
|
98
83
|
return false;
|
|
99
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feedmepos/order-plugin-gallery",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"./menu-debug-logger": {
|
|
25
25
|
"import": "./dist/plugins/menu-debug-logger.js",
|
|
26
26
|
"types": "./dist/plugins/menu-debug-logger.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./compulsory-items": {
|
|
29
|
+
"import": "./dist/plugins/compulsory-items.js",
|
|
30
|
+
"types": "./dist/plugins/compulsory-items.d.ts"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"peerDependencies": {
|