@pisell/private-materials 6.12.370 → 6.12.371
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/build/lowcode/assets-daily.json +11 -11
- package/build/lowcode/assets-dev.json +2 -2
- package/build/lowcode/assets-prod.json +11 -11
- package/build/lowcode/meta.js +1 -1
- package/build/lowcode/render/default/view.js +1 -1
- package/build/lowcode/view.js +1 -1
- package/es/components/vendingMachine/README.md +2 -0
- package/es/components/vendingMachine/VoucherCollectPage.js +21 -23
- package/es/components/vendingMachine/voucherRedemption.js +16 -25
- package/es/pro/skuDetailModal/flow/registry.d.ts +2 -2
- package/lib/components/vendingMachine/README.md +2 -0
- package/lib/components/vendingMachine/VoucherCollectPage.js +19 -21
- package/lib/components/vendingMachine/voucherRedemption.js +16 -24
- package/lib/pro/skuDetailModal/flow/registry.d.ts +2 -2
- package/package.json +3 -3
|
@@ -124,6 +124,8 @@ Home 的 Redeem Socks 入口打开独立 `RedeemPage`,展示扫码指引、扫
|
|
|
124
124
|
|
|
125
125
|
Sale 与 Redeem 共用同一购物车和实时券列表,券不按来源预留,也不注册 `productVoucherGroups` 或向商品附加 `productVoucherGroupKey`。兑换分组仅用于展示,已选和剩余数量按商品行实际 `good_pass` 抵扣记录统计;Apply、Unapply、登录资产变化后重新计算。Unapply 本身保留普通购物车行为;若用户随后在兑换页明确新增商品,可通过同一 `setDiscount` 操作重新启用对应券。兑换页的 Also buy something? 入口暂时隐藏;主按钮直接结算同一订单。
|
|
126
126
|
|
|
127
|
+
零售兑换页按券的 `product_id` 合并展示,同一商品 ID 的数字和字符串形式归入同组,不按持有人、扩展字段或适用规则拆组;缺少商品 ID 的券按实例单独展示。组内可兑商品取各券适用范围的并集,每个商品的新增数量按尚未实际抵扣且适用于该商品的券数限制;本机无适用商品且无关联已选商品的券不计入额度。加购不预填持有人,实际抵扣仍保留各张券的实例 ID 和原有规则。此分组逻辑仅用于 vendingMachine,不改变普通销售、优惠钱包或底层抵扣规则。
|
|
128
|
+
|
|
127
129
|
零价商品可在兑换页按普通免费商品加入购物车并正常结算、出货,不要求分配商品券。页面从实际零价且无折扣记录的订单行读取数量,支持加减和键盘修改;免费数量计入已选商品,不占用券额度,继续受实时库存限制。存在免费选择时,汇总分别展示已选商品数和剩余商品券数;多个券组匹配同一免费商品时共享数量,汇总只计一次。有价商品仍按实际 `good_pass` 分配统计,`pisell_os` 的抵扣规则保持原样。
|
|
128
130
|
|
|
129
131
|
售货机购物车通过 `saleDetailModuleConfig.cartItems.renderProductLineItemStatus` 在 A12 右侧展示兑换印章:仅商品行实际分配了 `good_pass` 时替代价格、编辑和数量区域,保留原删除按钮及确认流程;Unapply 后恢复正常展示。印章由内联 SVG 绘制圆框、勾选和多语言文字,颜色跟随卡片主题色 `--pc-a12-accent`,保留倾斜样式,不依赖图片文件;以透明背景、不降低不透明度的水印形式相对右侧区域居中绝对定位,不撑高卡片、不拦截点击,删除按钮保持在上层。文字跟随页面语言,未提供的语言回退英文。此配置仅由 VendingSale 启用,普通 Kiosk、H5、POS 及 A12 的其它调用方保持原样;不改变订单金额或券分配逻辑。
|
|
@@ -4,7 +4,7 @@ import { useSalesInventory } from "../../plus/salesSdk/inventory/context.js";
|
|
|
4
4
|
import { resolveLocalizedText } from "../../plus/salesSdk/utils/resolveLocalizedText.js";
|
|
5
5
|
import { pickIdentity } from "../../plus/salesSdk/utils/identity.js";
|
|
6
6
|
import { resolveEffectiveSaleDetailAmountDue } from "../../plus/saleDetail/utils.js";
|
|
7
|
-
import {
|
|
7
|
+
import { sanitizeVoucherAddProduct } from "../../plus/saleDetail/components/PromotionWalletPanels/voucherProductAdd.js";
|
|
8
8
|
import { useVendingLocale } from "./locales.js";
|
|
9
9
|
import { Brand, CustomerViewport, Icon as Icon$1 } from "./CustomerScreens.js";
|
|
10
10
|
import { VendingActionBar } from "./VendingBottomBar.js";
|
|
@@ -12,7 +12,7 @@ import { buildVendingCatalog, buildVendingSkuProduct } from "./catalogModel.js";
|
|
|
12
12
|
import VoucherSkuList from "./VoucherSkuList.js";
|
|
13
13
|
import useVendingCartInteraction from "./useVendingCartInteraction.js";
|
|
14
14
|
import { buildVoucherSkuChoices, isFreeVoucherProduct, isSimpleVoucherProduct, usesInlineVoucherSkus } from "./voucherSkuSelection.js";
|
|
15
|
-
import {
|
|
15
|
+
import { buildRedemptionGroups, getRemainingProductVouchers, isFreeRedemptionItem, redemptionRows } from "./voucherRedemption.js";
|
|
16
16
|
import React, { useEffect, useRef } from "react";
|
|
17
17
|
import { useToast } from "@pisell/materials";
|
|
18
18
|
import { useMemoizedFn } from "ahooks";
|
|
@@ -55,14 +55,14 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
55
55
|
var _ticket$getDiscountLi, _ticket$getTempOrder3, _context$cart$tempOrd;
|
|
56
56
|
return buildRedemptionGroups((ticket === null || ticket === void 0 || (_ticket$getDiscountLi = ticket.getDiscountList) === null || _ticket$getDiscountLi === void 0 ? void 0 : _ticket$getDiscountLi.call(ticket)) || context.cart.discountList, products, (ticket === null || ticket === void 0 || (_ticket$getTempOrder3 = ticket.getTempOrder) === null || _ticket$getTempOrder3 === void 0 || (_ticket$getTempOrder3 = _ticket$getTempOrder3.call(ticket)) === null || _ticket$getTempOrder3 === void 0 ? void 0 : _ticket$getTempOrder3.products) || ((_context$cart$tempOrd = context.cart.tempOrder) === null || _context$cart$tempOrd === void 0 ? void 0 : _context$cart$tempOrd.products) || []);
|
|
57
57
|
});
|
|
58
|
-
const remaining = useMemoizedFn((key) => {
|
|
59
|
-
|
|
60
|
-
return
|
|
58
|
+
const remaining = useMemoizedFn((key, product) => {
|
|
59
|
+
const group = getLiveGroups().find((entry) => entry.key === key);
|
|
60
|
+
return group ? getRemainingProductVouchers(group, product).length : 0;
|
|
61
61
|
});
|
|
62
62
|
const restoreUnappliedVouchers = useMemoizedFn(function() {
|
|
63
|
-
var _ref = _asyncToGenerator(function* (key, targetSelected, isCurrent) {
|
|
63
|
+
var _ref = _asyncToGenerator(function* (key, product, targetSelected, isCurrent) {
|
|
64
64
|
let group = getLiveGroups().find((item) => item.key === key);
|
|
65
|
-
const manuallyUnapplied =
|
|
65
|
+
const manuallyUnapplied = group ? getRemainingProductVouchers(group, product).filter((voucher) => voucher.source.isManualSelect) : [];
|
|
66
66
|
for (const voucher of manuallyUnapplied) {
|
|
67
67
|
if (!isCurrent() || !group || group.selected >= targetSelected) return;
|
|
68
68
|
yield context.cart.setDiscount({
|
|
@@ -72,7 +72,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
72
72
|
group = getLiveGroups().find((item) => item.key === key);
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
|
-
return function(_x, _x2, _x3) {
|
|
75
|
+
return function(_x, _x2, _x3, _x4) {
|
|
76
76
|
return _ref.apply(this, arguments);
|
|
77
77
|
};
|
|
78
78
|
}());
|
|
@@ -85,7 +85,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
85
85
|
var _variant$id;
|
|
86
86
|
const id = (_variant$id = variant.id) !== null && _variant$id !== void 0 ? _variant$id : variant.product_variant_id;
|
|
87
87
|
const stock = inventory.selectionAvailability(product, id, items);
|
|
88
|
-
const limit = freeSelection || isFreeVoucherProduct(product, id) ? Infinity : remaining(group.key);
|
|
88
|
+
const limit = freeSelection || isFreeVoucherProduct(product, id) ? Infinity : remaining(group.key, product);
|
|
89
89
|
return stock.availability.status === "ready" ? Math.min(stock.availableQuantity, limit) : 0;
|
|
90
90
|
}));
|
|
91
91
|
});
|
|
@@ -99,17 +99,17 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
99
99
|
quantity,
|
|
100
100
|
debounceMs: 0,
|
|
101
101
|
notShowToast: true,
|
|
102
|
-
getQuantityLimit: free ? void 0 : () => remaining(group.key),
|
|
103
|
-
initialHolderIds:
|
|
102
|
+
getQuantityLimit: free ? void 0 : () => remaining(group.key, product),
|
|
103
|
+
initialHolderIds: []
|
|
104
104
|
});
|
|
105
105
|
if (result.status === "added" && isCurrent() && !free) {
|
|
106
106
|
const after = getLiveGroups().find((item) => item.key === group.key);
|
|
107
107
|
const freeAdded = Math.max(0, ((after === null || after === void 0 ? void 0 : after.freeQuantity) || 0) - ((before === null || before === void 0 ? void 0 : before.freeQuantity) || 0));
|
|
108
|
-
yield restoreUnappliedVouchers(group.key, ((before === null || before === void 0 ? void 0 : before.selected) || 0) + Math.max(0, quantity - freeAdded), isCurrent);
|
|
108
|
+
yield restoreUnappliedVouchers(group.key, product, ((before === null || before === void 0 ? void 0 : before.selected) || 0) + Math.max(0, quantity - freeAdded), isCurrent);
|
|
109
109
|
}
|
|
110
110
|
return result;
|
|
111
111
|
});
|
|
112
|
-
return function(
|
|
112
|
+
return function(_x5, _x6, _x7, _x8) {
|
|
113
113
|
return _ref3.apply(this, arguments);
|
|
114
114
|
};
|
|
115
115
|
}());
|
|
@@ -118,7 +118,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
118
118
|
if (increaseLimit(group, product) < 1) throw new Error(t("collectLimit"));
|
|
119
119
|
return addVoucherProduct(group, product, 1, isCurrent);
|
|
120
120
|
});
|
|
121
|
-
return function(
|
|
121
|
+
return function(_x9) {
|
|
122
122
|
return _ref4.apply(this, arguments);
|
|
123
123
|
};
|
|
124
124
|
}());
|
|
@@ -152,16 +152,14 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
152
152
|
const row = (_buildVendingCatalog$2 = buildVendingCatalog([product], [], localized)[0]) === null || _buildVendingCatalog$2 === void 0 ? void 0 : _buildVendingCatalog$2.rows[0];
|
|
153
153
|
const currentItems = ((_ref5 = (_ticket$getTempOrder6 = ticket === null || ticket === void 0 || (_ticket$getTempOrder7 = ticket.getTempOrder) === null || _ticket$getTempOrder7 === void 0 ? void 0 : _ticket$getTempOrder7.call(ticket)) !== null && _ticket$getTempOrder6 !== void 0 ? _ticket$getTempOrder6 : context.cart.tempOrder) === null || _ref5 === void 0 ? void 0 : _ref5.products) || [];
|
|
154
154
|
if (!(row === null || row === void 0 ? void 0 : row.enabled) || inventoryFailed || inventoryRefreshing || (inventory === null || inventory === void 0 ? void 0 : inventory.selectionAvailability(product, 0, currentItems).availability.status) !== "ready") return;
|
|
155
|
-
const
|
|
156
|
-
const allocated = new Set(group.items.flatMap(appliedProductVoucherIds));
|
|
157
|
-
const unused = fresh.filter((voucher) => !voucher.source.isManualSelect && !allocated.has(String(voucher.id)));
|
|
155
|
+
const unused = getRemainingProductVouchers(group, product).filter((voucher) => freshIds.has(String(voucher.id)) && !voucher.source.isManualSelect);
|
|
158
156
|
const quantity = Math.min(unused.length, increaseLimit(group, product, 0), isFreeVoucherProduct(product, 0) ? Math.max(0, group.quota - group.freeQuantity) : Infinity);
|
|
159
157
|
if (quantity > 0) {
|
|
160
158
|
const result = yield addVoucherProduct(group, buildVendingSkuProduct(row), quantity, isCurrent);
|
|
161
159
|
if (isCurrent() && result.status !== "added" && result.status !== "cancelled") throw new Error(t("catalogUpdateFailed"));
|
|
162
160
|
}
|
|
163
161
|
});
|
|
164
|
-
return function(
|
|
162
|
+
return function(_x10) {
|
|
165
163
|
return _ref6.apply(this, arguments);
|
|
166
164
|
};
|
|
167
165
|
}());
|
|
@@ -213,10 +211,10 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
213
211
|
const result = yield addVoucherProduct(liveGroup, buildVendingSkuProduct(liveRow), delta, isCurrent);
|
|
214
212
|
if (isCurrent() && result.status !== "added" && result.status !== "cancelled") throw new Error(t("catalogUpdateFailed"));
|
|
215
213
|
} else {
|
|
216
|
-
var _getLiveGroups$
|
|
217
|
-
const selectedBefore = ((_getLiveGroups$
|
|
214
|
+
var _getLiveGroups$find;
|
|
215
|
+
const selectedBefore = ((_getLiveGroups$find = getLiveGroups().find((entry) => entry.key === group.key)) === null || _getLiveGroups$find === void 0 ? void 0 : _getLiveGroups$find.selected) || 0;
|
|
218
216
|
yield context.cart.updateItemQuantity(item, Number(item.num) + delta);
|
|
219
|
-
if (isCurrent() && !free) yield restoreUnappliedVouchers(group.key, selectedBefore + delta, isCurrent);
|
|
217
|
+
if (isCurrent() && !free) yield restoreUnappliedVouchers(group.key, product, selectedBefore + delta, isCurrent);
|
|
220
218
|
}
|
|
221
219
|
return;
|
|
222
220
|
}
|
|
@@ -233,7 +231,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
233
231
|
yield context.cart.waitForCartConsistency();
|
|
234
232
|
}
|
|
235
233
|
});
|
|
236
|
-
return function(
|
|
234
|
+
return function(_x11) {
|
|
237
235
|
return _ref9.apply(this, arguments);
|
|
238
236
|
};
|
|
239
237
|
}());
|
|
@@ -340,7 +338,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
340
338
|
product,
|
|
341
339
|
items: (order === null || order === void 0 ? void 0 : order.products) || [],
|
|
342
340
|
selectedItems: group.items,
|
|
343
|
-
remaining: group.
|
|
341
|
+
remaining: getRemainingProductVouchers(group, product).length,
|
|
344
342
|
inventory,
|
|
345
343
|
localize: localized
|
|
346
344
|
}),
|
|
@@ -2,33 +2,15 @@ import { resolveLocalizedText } from "../../plus/salesSdk/utils/resolveLocalized
|
|
|
2
2
|
import { enrichCartItem } from "../../plus/salesSdk/utils/enrich.js";
|
|
3
3
|
import { buildVoucherViews } from "../../plus/saleDetail/components/PromotionWalletPanels/adapters.js";
|
|
4
4
|
//#region src/components/vendingMachine/voucherRedemption.ts
|
|
5
|
-
/**
|
|
5
|
+
/** Normalize selection details without depending on response ordering. */
|
|
6
6
|
function canonical(value) {
|
|
7
7
|
if (Array.isArray(value)) return value.map(canonical).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
8
8
|
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
11
|
function voucherGroupKey(voucher) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const relation = {
|
|
15
|
-
type: source.type,
|
|
16
|
-
filter: source.filter,
|
|
17
|
-
product_ids: source.product_ids,
|
|
18
|
-
exclude_product_ids: source.exclude_product_ids,
|
|
19
|
-
product_collection_id: source.product_collection_id,
|
|
20
|
-
package_sub_item_usage_rules: source.package_sub_item_usage_rules
|
|
21
|
-
};
|
|
22
|
-
return JSON.stringify(canonical({
|
|
23
|
-
productId: voucher.product_id,
|
|
24
|
-
relation,
|
|
25
|
-
holder: (_voucher$metadata = voucher.metadata) === null || _voucher$metadata === void 0 ? void 0 : _voucher$metadata.holder,
|
|
26
|
-
holderId: (_voucher$holder = voucher.holder) === null || _voucher$holder === void 0 ? void 0 : _voucher$holder.holder_id,
|
|
27
|
-
rules: (voucher.extension_data || []).map((field) => ({
|
|
28
|
-
key: field.field_key,
|
|
29
|
-
value: field.value
|
|
30
|
-
}))
|
|
31
|
-
}));
|
|
12
|
+
const productId = voucher.product_id;
|
|
13
|
+
return JSON.stringify(productId == null || String(productId) === "" ? ["voucher", String(voucher.id)] : ["product", String(productId)]);
|
|
32
14
|
}
|
|
33
15
|
function isRedeemableVoucher(voucher) {
|
|
34
16
|
var _source$config;
|
|
@@ -64,13 +46,21 @@ function isFreeRedemptionItem(item) {
|
|
|
64
46
|
const price = item.selling_price;
|
|
65
47
|
return price != null && String(price).trim() !== "" && Number(price) === 0 && !((_item$discount_list = item.discount_list) === null || _item$discount_list === void 0 ? void 0 : _item$discount_list.length);
|
|
66
48
|
}
|
|
49
|
+
/** A merged group can contain instances with different eligible products. */
|
|
50
|
+
function getRemainingProductVouchers(group, product) {
|
|
51
|
+
const used = new Set(group.items.flatMap(appliedProductVoucherIds));
|
|
52
|
+
return group.vouchers.filter((voucher) => !used.has(String(voucher.id)) && voucherAppliesToProduct(voucher.source, product));
|
|
53
|
+
}
|
|
67
54
|
function buildRedemptionGroups(current, products, items = []) {
|
|
68
55
|
const groups = /* @__PURE__ */ new Map();
|
|
69
56
|
const seen = /* @__PURE__ */ new Set();
|
|
57
|
+
const allocatedIds = new Set(items.flatMap(appliedProductVoucherIds));
|
|
58
|
+
const freeItems = items.filter(isFreeRedemptionItem);
|
|
70
59
|
for (const voucher of buildVoucherViews(current).filter(isRedeemableVoucher)) {
|
|
71
60
|
if (seen.has(String(voucher.id))) continue;
|
|
72
61
|
seen.add(String(voucher.id));
|
|
73
62
|
const source = voucher.source;
|
|
63
|
+
if (!products.some((product) => voucherAppliesToProduct(source, product)) && !allocatedIds.has(String(voucher.id)) && !freeItems.some((item) => voucherAppliesToProduct(source, { id: item.product_id }))) continue;
|
|
74
64
|
const key = voucherGroupKey(source);
|
|
75
65
|
let group = groups.get(key);
|
|
76
66
|
if (!group) {
|
|
@@ -84,7 +74,7 @@ function buildRedemptionGroups(current, products, items = []) {
|
|
|
84
74
|
freeQuantity: 0,
|
|
85
75
|
remaining: 0,
|
|
86
76
|
items: [],
|
|
87
|
-
products:
|
|
77
|
+
products: []
|
|
88
78
|
};
|
|
89
79
|
groups.set(key, group);
|
|
90
80
|
}
|
|
@@ -92,14 +82,15 @@ function buildRedemptionGroups(current, products, items = []) {
|
|
|
92
82
|
group.vouchers.push(voucher);
|
|
93
83
|
group.quota += 1;
|
|
94
84
|
}
|
|
95
|
-
const
|
|
85
|
+
const freeItemSet = new Set(freeItems);
|
|
96
86
|
for (const group of groups.values()) {
|
|
87
|
+
group.products = products.filter((product) => group.vouchers.some((voucher) => voucherAppliesToProduct(voucher.source, product)));
|
|
97
88
|
const ids = new Set(group.discountIds.map(String));
|
|
98
89
|
const used = /* @__PURE__ */ new Set();
|
|
99
90
|
group.items = items.filter((item) => {
|
|
100
91
|
const allocated = appliedProductVoucherIds(item).filter((id) => ids.has(id));
|
|
101
92
|
allocated.forEach((id) => used.add(id));
|
|
102
|
-
if (
|
|
93
|
+
if (freeItemSet.has(item) && group.vouchers.some((voucher) => voucherAppliesToProduct(voucher.source, { id: item.product_id }))) {
|
|
103
94
|
group.freeQuantity += Number(item.num || 0);
|
|
104
95
|
return true;
|
|
105
96
|
}
|
|
@@ -134,4 +125,4 @@ function redemptionRows(items, displayContext) {
|
|
|
134
125
|
return [...rows.values()];
|
|
135
126
|
}
|
|
136
127
|
//#endregion
|
|
137
|
-
export { appliedProductVoucherIds, buildRedemptionGroups, isFreeRedemptionItem, mergeRedemptionVouchers, redemptionRows };
|
|
128
|
+
export { appliedProductVoucherIds, buildRedemptionGroups, getRemainingProductVouchers, isFreeRedemptionItem, mergeRedemptionVouchers, redemptionRows };
|
|
@@ -37,7 +37,7 @@ declare const flowModuleRegistry: {
|
|
|
37
37
|
Component: _$react.FC<FlowModuleProps<string>>;
|
|
38
38
|
initializeWhileInactive: true;
|
|
39
39
|
hardRequires: ("productData" | "booking.selectedCandidate")[];
|
|
40
|
-
refreshOn: ("
|
|
40
|
+
refreshOn: ("productData" | "skuValue" | "quantity" | "booking.customerId" | "booking.duration" | "booking.selectedCandidate")[];
|
|
41
41
|
isApplicable: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => boolean;
|
|
42
42
|
canInitialize: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => boolean;
|
|
43
43
|
refresh: (context: SelectionFlowContextValue<string>, module: CompiledSelectionFlowModuleConfig<string>) => Promise<void>;
|
|
@@ -55,7 +55,7 @@ declare const flowModuleRegistry: {
|
|
|
55
55
|
isSkippable: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => any;
|
|
56
56
|
provides: "booking.capacity"[];
|
|
57
57
|
hardRequires: "productData"[];
|
|
58
|
-
refreshOn: ("
|
|
58
|
+
refreshOn: ("productData" | "skuValue" | "quantity")[];
|
|
59
59
|
isApplicable: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => boolean;
|
|
60
60
|
canInitialize: () => true;
|
|
61
61
|
refresh: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>, module: _$_pisell_materials_src0.CompiledSelectionFlowModuleConfig<string>) => void;
|
|
@@ -124,6 +124,8 @@ Home 的 Redeem Socks 入口打开独立 `RedeemPage`,展示扫码指引、扫
|
|
|
124
124
|
|
|
125
125
|
Sale 与 Redeem 共用同一购物车和实时券列表,券不按来源预留,也不注册 `productVoucherGroups` 或向商品附加 `productVoucherGroupKey`。兑换分组仅用于展示,已选和剩余数量按商品行实际 `good_pass` 抵扣记录统计;Apply、Unapply、登录资产变化后重新计算。Unapply 本身保留普通购物车行为;若用户随后在兑换页明确新增商品,可通过同一 `setDiscount` 操作重新启用对应券。兑换页的 Also buy something? 入口暂时隐藏;主按钮直接结算同一订单。
|
|
126
126
|
|
|
127
|
+
零售兑换页按券的 `product_id` 合并展示,同一商品 ID 的数字和字符串形式归入同组,不按持有人、扩展字段或适用规则拆组;缺少商品 ID 的券按实例单独展示。组内可兑商品取各券适用范围的并集,每个商品的新增数量按尚未实际抵扣且适用于该商品的券数限制;本机无适用商品且无关联已选商品的券不计入额度。加购不预填持有人,实际抵扣仍保留各张券的实例 ID 和原有规则。此分组逻辑仅用于 vendingMachine,不改变普通销售、优惠钱包或底层抵扣规则。
|
|
128
|
+
|
|
127
129
|
零价商品可在兑换页按普通免费商品加入购物车并正常结算、出货,不要求分配商品券。页面从实际零价且无折扣记录的订单行读取数量,支持加减和键盘修改;免费数量计入已选商品,不占用券额度,继续受实时库存限制。存在免费选择时,汇总分别展示已选商品数和剩余商品券数;多个券组匹配同一免费商品时共享数量,汇总只计一次。有价商品仍按实际 `good_pass` 分配统计,`pisell_os` 的抵扣规则保持原样。
|
|
128
130
|
|
|
129
131
|
售货机购物车通过 `saleDetailModuleConfig.cartItems.renderProductLineItemStatus` 在 A12 右侧展示兑换印章:仅商品行实际分配了 `good_pass` 时替代价格、编辑和数量区域,保留原删除按钮及确认流程;Unapply 后恢复正常展示。印章由内联 SVG 绘制圆框、勾选和多语言文字,颜色跟随卡片主题色 `--pc-a12-accent`,保留倾斜样式,不依赖图片文件;以透明背景、不降低不透明度的水印形式相对右侧区域居中绝对定位,不撑高卡片、不拦截点击,删除按钮保持在上层。文字跟随页面语言,未提供的语言回退英文。此配置仅由 VendingSale 启用,普通 Kiosk、H5、POS 及 A12 的其它调用方保持原样;不改变订单金额或券分配逻辑。
|
|
@@ -57,14 +57,14 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
57
57
|
var _ticket$getDiscountLi, _ticket$getTempOrder3, _context$cart$tempOrd;
|
|
58
58
|
return require_voucherRedemption.buildRedemptionGroups((ticket === null || ticket === void 0 || (_ticket$getDiscountLi = ticket.getDiscountList) === null || _ticket$getDiscountLi === void 0 ? void 0 : _ticket$getDiscountLi.call(ticket)) || context.cart.discountList, products, (ticket === null || ticket === void 0 || (_ticket$getTempOrder3 = ticket.getTempOrder) === null || _ticket$getTempOrder3 === void 0 || (_ticket$getTempOrder3 = _ticket$getTempOrder3.call(ticket)) === null || _ticket$getTempOrder3 === void 0 ? void 0 : _ticket$getTempOrder3.products) || ((_context$cart$tempOrd = context.cart.tempOrder) === null || _context$cart$tempOrd === void 0 ? void 0 : _context$cart$tempOrd.products) || []);
|
|
59
59
|
});
|
|
60
|
-
const remaining = (0, ahooks.useMemoizedFn)((key) => {
|
|
61
|
-
|
|
62
|
-
return
|
|
60
|
+
const remaining = (0, ahooks.useMemoizedFn)((key, product) => {
|
|
61
|
+
const group = getLiveGroups().find((entry) => entry.key === key);
|
|
62
|
+
return group ? require_voucherRedemption.getRemainingProductVouchers(group, product).length : 0;
|
|
63
63
|
});
|
|
64
64
|
const restoreUnappliedVouchers = (0, ahooks.useMemoizedFn)(function() {
|
|
65
|
-
var _ref = require_asyncToGenerator._asyncToGenerator(function* (key, targetSelected, isCurrent) {
|
|
65
|
+
var _ref = require_asyncToGenerator._asyncToGenerator(function* (key, product, targetSelected, isCurrent) {
|
|
66
66
|
let group = getLiveGroups().find((item) => item.key === key);
|
|
67
|
-
const manuallyUnapplied =
|
|
67
|
+
const manuallyUnapplied = group ? require_voucherRedemption.getRemainingProductVouchers(group, product).filter((voucher) => voucher.source.isManualSelect) : [];
|
|
68
68
|
for (const voucher of manuallyUnapplied) {
|
|
69
69
|
if (!isCurrent() || !group || group.selected >= targetSelected) return;
|
|
70
70
|
yield context.cart.setDiscount({
|
|
@@ -74,7 +74,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
74
74
|
group = getLiveGroups().find((item) => item.key === key);
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
|
-
return function(_x, _x2, _x3) {
|
|
77
|
+
return function(_x, _x2, _x3, _x4) {
|
|
78
78
|
return _ref.apply(this, arguments);
|
|
79
79
|
};
|
|
80
80
|
}());
|
|
@@ -87,7 +87,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
87
87
|
var _variant$id;
|
|
88
88
|
const id = (_variant$id = variant.id) !== null && _variant$id !== void 0 ? _variant$id : variant.product_variant_id;
|
|
89
89
|
const stock = inventory.selectionAvailability(product, id, items);
|
|
90
|
-
const limit = freeSelection || require_voucherSkuSelection.isFreeVoucherProduct(product, id) ? Infinity : remaining(group.key);
|
|
90
|
+
const limit = freeSelection || require_voucherSkuSelection.isFreeVoucherProduct(product, id) ? Infinity : remaining(group.key, product);
|
|
91
91
|
return stock.availability.status === "ready" ? Math.min(stock.availableQuantity, limit) : 0;
|
|
92
92
|
}));
|
|
93
93
|
});
|
|
@@ -101,17 +101,17 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
101
101
|
quantity,
|
|
102
102
|
debounceMs: 0,
|
|
103
103
|
notShowToast: true,
|
|
104
|
-
getQuantityLimit: free ? void 0 : () => remaining(group.key),
|
|
105
|
-
initialHolderIds:
|
|
104
|
+
getQuantityLimit: free ? void 0 : () => remaining(group.key, product),
|
|
105
|
+
initialHolderIds: []
|
|
106
106
|
});
|
|
107
107
|
if (result.status === "added" && isCurrent() && !free) {
|
|
108
108
|
const after = getLiveGroups().find((item) => item.key === group.key);
|
|
109
109
|
const freeAdded = Math.max(0, ((after === null || after === void 0 ? void 0 : after.freeQuantity) || 0) - ((before === null || before === void 0 ? void 0 : before.freeQuantity) || 0));
|
|
110
|
-
yield restoreUnappliedVouchers(group.key, ((before === null || before === void 0 ? void 0 : before.selected) || 0) + Math.max(0, quantity - freeAdded), isCurrent);
|
|
110
|
+
yield restoreUnappliedVouchers(group.key, product, ((before === null || before === void 0 ? void 0 : before.selected) || 0) + Math.max(0, quantity - freeAdded), isCurrent);
|
|
111
111
|
}
|
|
112
112
|
return result;
|
|
113
113
|
});
|
|
114
|
-
return function(
|
|
114
|
+
return function(_x5, _x6, _x7, _x8) {
|
|
115
115
|
return _ref3.apply(this, arguments);
|
|
116
116
|
};
|
|
117
117
|
}());
|
|
@@ -120,7 +120,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
120
120
|
if (increaseLimit(group, product) < 1) throw new Error(t("collectLimit"));
|
|
121
121
|
return addVoucherProduct(group, product, 1, isCurrent);
|
|
122
122
|
});
|
|
123
|
-
return function(
|
|
123
|
+
return function(_x9) {
|
|
124
124
|
return _ref4.apply(this, arguments);
|
|
125
125
|
};
|
|
126
126
|
}());
|
|
@@ -154,16 +154,14 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
154
154
|
const row = (_buildVendingCatalog$2 = require_catalogModel.buildVendingCatalog([product], [], localized)[0]) === null || _buildVendingCatalog$2 === void 0 ? void 0 : _buildVendingCatalog$2.rows[0];
|
|
155
155
|
const currentItems = ((_ref5 = (_ticket$getTempOrder6 = ticket === null || ticket === void 0 || (_ticket$getTempOrder7 = ticket.getTempOrder) === null || _ticket$getTempOrder7 === void 0 ? void 0 : _ticket$getTempOrder7.call(ticket)) !== null && _ticket$getTempOrder6 !== void 0 ? _ticket$getTempOrder6 : context.cart.tempOrder) === null || _ref5 === void 0 ? void 0 : _ref5.products) || [];
|
|
156
156
|
if (!(row === null || row === void 0 ? void 0 : row.enabled) || inventoryFailed || inventoryRefreshing || (inventory === null || inventory === void 0 ? void 0 : inventory.selectionAvailability(product, 0, currentItems).availability.status) !== "ready") return;
|
|
157
|
-
const
|
|
158
|
-
const allocated = new Set(group.items.flatMap(require_voucherRedemption.appliedProductVoucherIds));
|
|
159
|
-
const unused = fresh.filter((voucher) => !voucher.source.isManualSelect && !allocated.has(String(voucher.id)));
|
|
157
|
+
const unused = require_voucherRedemption.getRemainingProductVouchers(group, product).filter((voucher) => freshIds.has(String(voucher.id)) && !voucher.source.isManualSelect);
|
|
160
158
|
const quantity = Math.min(unused.length, increaseLimit(group, product, 0), require_voucherSkuSelection.isFreeVoucherProduct(product, 0) ? Math.max(0, group.quota - group.freeQuantity) : Infinity);
|
|
161
159
|
if (quantity > 0) {
|
|
162
160
|
const result = yield addVoucherProduct(group, require_catalogModel.buildVendingSkuProduct(row), quantity, isCurrent);
|
|
163
161
|
if (isCurrent() && result.status !== "added" && result.status !== "cancelled") throw new Error(t("catalogUpdateFailed"));
|
|
164
162
|
}
|
|
165
163
|
});
|
|
166
|
-
return function(
|
|
164
|
+
return function(_x10) {
|
|
167
165
|
return _ref6.apply(this, arguments);
|
|
168
166
|
};
|
|
169
167
|
}());
|
|
@@ -215,10 +213,10 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
215
213
|
const result = yield addVoucherProduct(liveGroup, require_catalogModel.buildVendingSkuProduct(liveRow), delta, isCurrent);
|
|
216
214
|
if (isCurrent() && result.status !== "added" && result.status !== "cancelled") throw new Error(t("catalogUpdateFailed"));
|
|
217
215
|
} else {
|
|
218
|
-
var _getLiveGroups$
|
|
219
|
-
const selectedBefore = ((_getLiveGroups$
|
|
216
|
+
var _getLiveGroups$find;
|
|
217
|
+
const selectedBefore = ((_getLiveGroups$find = getLiveGroups().find((entry) => entry.key === group.key)) === null || _getLiveGroups$find === void 0 ? void 0 : _getLiveGroups$find.selected) || 0;
|
|
220
218
|
yield context.cart.updateItemQuantity(item, Number(item.num) + delta);
|
|
221
|
-
if (isCurrent() && !free) yield restoreUnappliedVouchers(group.key, selectedBefore + delta, isCurrent);
|
|
219
|
+
if (isCurrent() && !free) yield restoreUnappliedVouchers(group.key, product, selectedBefore + delta, isCurrent);
|
|
222
220
|
}
|
|
223
221
|
return;
|
|
224
222
|
}
|
|
@@ -235,7 +233,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
235
233
|
yield context.cart.waitForCartConsistency();
|
|
236
234
|
}
|
|
237
235
|
});
|
|
238
|
-
return function(
|
|
236
|
+
return function(_x11) {
|
|
239
237
|
return _ref9.apply(this, arguments);
|
|
240
238
|
};
|
|
241
239
|
}());
|
|
@@ -342,7 +340,7 @@ function VoucherCollectPage({ brand, context, products, loading, isActive, inven
|
|
|
342
340
|
product,
|
|
343
341
|
items: (order === null || order === void 0 ? void 0 : order.products) || [],
|
|
344
342
|
selectedItems: group.items,
|
|
345
|
-
remaining: group.
|
|
343
|
+
remaining: require_voucherRedemption.getRemainingProductVouchers(group, product).length,
|
|
346
344
|
inventory,
|
|
347
345
|
localize: localized
|
|
348
346
|
}),
|
|
@@ -2,33 +2,15 @@ const require_resolveLocalizedText = require("../../plus/salesSdk/utils/resolveL
|
|
|
2
2
|
const require_enrich = require("../../plus/salesSdk/utils/enrich.js");
|
|
3
3
|
const require_adapters = require("../../plus/saleDetail/components/PromotionWalletPanels/adapters.js");
|
|
4
4
|
//#region src/components/vendingMachine/voucherRedemption.ts
|
|
5
|
-
/**
|
|
5
|
+
/** Normalize selection details without depending on response ordering. */
|
|
6
6
|
function canonical(value) {
|
|
7
7
|
if (Array.isArray(value)) return value.map(canonical).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
8
8
|
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
11
|
function voucherGroupKey(voucher) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const relation = {
|
|
15
|
-
type: source.type,
|
|
16
|
-
filter: source.filter,
|
|
17
|
-
product_ids: source.product_ids,
|
|
18
|
-
exclude_product_ids: source.exclude_product_ids,
|
|
19
|
-
product_collection_id: source.product_collection_id,
|
|
20
|
-
package_sub_item_usage_rules: source.package_sub_item_usage_rules
|
|
21
|
-
};
|
|
22
|
-
return JSON.stringify(canonical({
|
|
23
|
-
productId: voucher.product_id,
|
|
24
|
-
relation,
|
|
25
|
-
holder: (_voucher$metadata = voucher.metadata) === null || _voucher$metadata === void 0 ? void 0 : _voucher$metadata.holder,
|
|
26
|
-
holderId: (_voucher$holder = voucher.holder) === null || _voucher$holder === void 0 ? void 0 : _voucher$holder.holder_id,
|
|
27
|
-
rules: (voucher.extension_data || []).map((field) => ({
|
|
28
|
-
key: field.field_key,
|
|
29
|
-
value: field.value
|
|
30
|
-
}))
|
|
31
|
-
}));
|
|
12
|
+
const productId = voucher.product_id;
|
|
13
|
+
return JSON.stringify(productId == null || String(productId) === "" ? ["voucher", String(voucher.id)] : ["product", String(productId)]);
|
|
32
14
|
}
|
|
33
15
|
function isRedeemableVoucher(voucher) {
|
|
34
16
|
var _source$config;
|
|
@@ -64,13 +46,21 @@ function isFreeRedemptionItem(item) {
|
|
|
64
46
|
const price = item.selling_price;
|
|
65
47
|
return price != null && String(price).trim() !== "" && Number(price) === 0 && !((_item$discount_list = item.discount_list) === null || _item$discount_list === void 0 ? void 0 : _item$discount_list.length);
|
|
66
48
|
}
|
|
49
|
+
/** A merged group can contain instances with different eligible products. */
|
|
50
|
+
function getRemainingProductVouchers(group, product) {
|
|
51
|
+
const used = new Set(group.items.flatMap(appliedProductVoucherIds));
|
|
52
|
+
return group.vouchers.filter((voucher) => !used.has(String(voucher.id)) && voucherAppliesToProduct(voucher.source, product));
|
|
53
|
+
}
|
|
67
54
|
function buildRedemptionGroups(current, products, items = []) {
|
|
68
55
|
const groups = /* @__PURE__ */ new Map();
|
|
69
56
|
const seen = /* @__PURE__ */ new Set();
|
|
57
|
+
const allocatedIds = new Set(items.flatMap(appliedProductVoucherIds));
|
|
58
|
+
const freeItems = items.filter(isFreeRedemptionItem);
|
|
70
59
|
for (const voucher of require_adapters.buildVoucherViews(current).filter(isRedeemableVoucher)) {
|
|
71
60
|
if (seen.has(String(voucher.id))) continue;
|
|
72
61
|
seen.add(String(voucher.id));
|
|
73
62
|
const source = voucher.source;
|
|
63
|
+
if (!products.some((product) => voucherAppliesToProduct(source, product)) && !allocatedIds.has(String(voucher.id)) && !freeItems.some((item) => voucherAppliesToProduct(source, { id: item.product_id }))) continue;
|
|
74
64
|
const key = voucherGroupKey(source);
|
|
75
65
|
let group = groups.get(key);
|
|
76
66
|
if (!group) {
|
|
@@ -84,7 +74,7 @@ function buildRedemptionGroups(current, products, items = []) {
|
|
|
84
74
|
freeQuantity: 0,
|
|
85
75
|
remaining: 0,
|
|
86
76
|
items: [],
|
|
87
|
-
products:
|
|
77
|
+
products: []
|
|
88
78
|
};
|
|
89
79
|
groups.set(key, group);
|
|
90
80
|
}
|
|
@@ -92,14 +82,15 @@ function buildRedemptionGroups(current, products, items = []) {
|
|
|
92
82
|
group.vouchers.push(voucher);
|
|
93
83
|
group.quota += 1;
|
|
94
84
|
}
|
|
95
|
-
const
|
|
85
|
+
const freeItemSet = new Set(freeItems);
|
|
96
86
|
for (const group of groups.values()) {
|
|
87
|
+
group.products = products.filter((product) => group.vouchers.some((voucher) => voucherAppliesToProduct(voucher.source, product)));
|
|
97
88
|
const ids = new Set(group.discountIds.map(String));
|
|
98
89
|
const used = /* @__PURE__ */ new Set();
|
|
99
90
|
group.items = items.filter((item) => {
|
|
100
91
|
const allocated = appliedProductVoucherIds(item).filter((id) => ids.has(id));
|
|
101
92
|
allocated.forEach((id) => used.add(id));
|
|
102
|
-
if (
|
|
93
|
+
if (freeItemSet.has(item) && group.vouchers.some((voucher) => voucherAppliesToProduct(voucher.source, { id: item.product_id }))) {
|
|
103
94
|
group.freeQuantity += Number(item.num || 0);
|
|
104
95
|
return true;
|
|
105
96
|
}
|
|
@@ -136,6 +127,7 @@ function redemptionRows(items, displayContext) {
|
|
|
136
127
|
//#endregion
|
|
137
128
|
exports.appliedProductVoucherIds = appliedProductVoucherIds;
|
|
138
129
|
exports.buildRedemptionGroups = buildRedemptionGroups;
|
|
130
|
+
exports.getRemainingProductVouchers = getRemainingProductVouchers;
|
|
139
131
|
exports.isFreeRedemptionItem = isFreeRedemptionItem;
|
|
140
132
|
exports.mergeRedemptionVouchers = mergeRedemptionVouchers;
|
|
141
133
|
exports.redemptionRows = redemptionRows;
|
|
@@ -37,7 +37,7 @@ declare const flowModuleRegistry: {
|
|
|
37
37
|
Component: _$react.FC<FlowModuleProps<string>>;
|
|
38
38
|
initializeWhileInactive: true;
|
|
39
39
|
hardRequires: ("productData" | "booking.selectedCandidate")[];
|
|
40
|
-
refreshOn: ("
|
|
40
|
+
refreshOn: ("productData" | "skuValue" | "quantity" | "booking.customerId" | "booking.duration" | "booking.selectedCandidate")[];
|
|
41
41
|
isApplicable: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => boolean;
|
|
42
42
|
canInitialize: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => boolean;
|
|
43
43
|
refresh: (context: SelectionFlowContextValue<string>, module: CompiledSelectionFlowModuleConfig<string>) => Promise<void>;
|
|
@@ -55,7 +55,7 @@ declare const flowModuleRegistry: {
|
|
|
55
55
|
isSkippable: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => any;
|
|
56
56
|
provides: "booking.capacity"[];
|
|
57
57
|
hardRequires: "productData"[];
|
|
58
|
-
refreshOn: ("
|
|
58
|
+
refreshOn: ("productData" | "skuValue" | "quantity")[];
|
|
59
59
|
isApplicable: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>) => boolean;
|
|
60
60
|
canInitialize: () => true;
|
|
61
61
|
refresh: (context: _$_pisell_materials_src0.SelectionFlowContextValue<SelectionFlowDraft, SelectionFlowDraftUpdate, SelectionDraftField, string, SelectionFlowServices, unknown>, module: _$_pisell_materials_src0.CompiledSelectionFlowModuleConfig<string>) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pisell/private-materials",
|
|
3
|
-
"version": "6.12.
|
|
3
|
+
"version": "6.12.371",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"module": "./es/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -74,10 +74,10 @@
|
|
|
74
74
|
"styled-components": "^6.0.0-rc.3",
|
|
75
75
|
"use-sync-external-store": "^1.6.0",
|
|
76
76
|
"@pisell/pi-payment-sdk": "0.0.4",
|
|
77
|
-
"@pisell/
|
|
77
|
+
"@pisell/utils": "3.0.7",
|
|
78
78
|
"@pisell/materials": "6.12.88",
|
|
79
79
|
"@pisell/icon": "0.0.11",
|
|
80
|
-
"@pisell/
|
|
80
|
+
"@pisell/date-picker": "3.0.12"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
83
|
"react": "^18.0.0",
|