@pisell/pisellos
Version:
一个可扩展的前端模块化SDK框架,支持插件系统
836 lines (834 loc) • 39.6 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/model/strategy/adapter/walletPass/utils.ts
var utils_exports = {};
__export(utils_exports, {
getApplicableProductIds: () => getApplicableProductIds,
getBundleItemDiscountDifference: () => getBundleItemDiscountDifference,
getBundleItemIsDiscountPrice: () => getBundleItemIsDiscountPrice,
getBundleItemIsMarkupOrDiscountPrice: () => getBundleItemIsMarkupOrDiscountPrice,
getBundleItemIsMarkupPrice: () => getBundleItemIsMarkupPrice,
getBundleItemIsOriginalPrice: () => getBundleItemIsOriginalPrice,
getBundleItemPrice: () => getBundleItemPrice,
getBundleItemTaxAndFeeRoundingRemainder: () => getBundleItemTaxAndFeeRoundingRemainder,
getMainProductPrice: () => getMainProductPrice,
getProductDiscountDifference: () => getProductDiscountDifference,
getProductQuantity: () => getProductQuantity,
getTaxAndFeeRoundingRemainder: () => getTaxAndFeeRoundingRemainder,
processVouchers: () => processVouchers,
recalculateVouchers: () => recalculateVouchers,
resolveWalletPassLineKey: () => resolveWalletPassLineKey
});
module.exports = __toCommonJS(utils_exports);
var import_decimal = __toESM(require("decimal.js"));
var getProductQuantity = (product) => {
return product.quantity || product.product_quantity || 1;
};
function resolveWalletPassLineKey(product, indexInOrder) {
const m = (product == null ? void 0 : product.metadata) || {};
const u1 = m.product_unique;
if (u1 != null && String(u1).length > 0)
return String(u1);
const u2 = m.unique_identification_number;
if (u2 != null && String(u2).length > 0)
return String(u2);
if ((product == null ? void 0 : product.id) != null && String(product.id).length > 0)
return `order_item:${product.id}`;
if ((product == null ? void 0 : product.product_unique_string) != null && String(product.product_unique_string).length > 0) {
return String(product.product_unique_string);
}
return `order_line_${indexInOrder}`;
}
function getExpandedOrderLineQuantity(p) {
if ((p == null ? void 0 : p._orderLineQuantity) != null && p._orderLineQuantity > 0)
return p._orderLineQuantity;
return getProductQuantity(p);
}
function getMaxPassSlotsForExpandedLine(maxPassesPerItem, p) {
if (maxPassesPerItem <= 0)
return Infinity;
return maxPassesPerItem * getExpandedOrderLineQuantity(p);
}
function computePassSlotsIncrement(deductAmount, deductQty, maxPassesPerItem, orderLineQuantity, currentUsage) {
if (maxPassesPerItem <= 0 || !deductAmount.greaterThan(0))
return 0;
const cap = maxPassesPerItem * orderLineQuantity;
const room = Math.max(0, cap - currentUsage);
if (room <= 0)
return 0;
const raw = Math.max(1, Math.ceil(Number(deductQty)) || 1);
return Math.min(raw, room);
}
function applyMaxPassUsageIncrements(usageMap, walletPassProductId, maxPassesPerItem, deductionDetails) {
deductionDetails.forEach((detail) => {
var _a;
const lineKey = detail.lineKey;
if (!lineKey || maxPassesPerItem <= 0)
return;
const orderLineQty = detail.orderLineQuantity != null && detail.orderLineQuantity > 0 ? detail.orderLineQuantity : 1;
const cur = ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
const inc = computePassSlotsIncrement(
new import_decimal.default(detail.deductAmount),
detail.deductQuantity,
maxPassesPerItem,
orderLineQty,
cur
);
if (inc <= 0)
return;
if (!usageMap.has(walletPassProductId))
usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
const inner = usageMap.get(walletPassProductId);
inner.set(lineKey, cur + inc);
});
}
var getRecommendedAmount = (voucher) => {
console.log("voucher312", voucher);
const { config, recommended_usage_amount, recommended_pure_product_usage_amount } = voucher;
const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
return deductTaxAndFee ? recommended_usage_amount : recommended_pure_product_usage_amount ?? recommended_usage_amount;
};
var getApplicableProductIds = (voucher) => {
const { available_product_type, available_product_ids } = voucher;
const productType = available_product_type || "product_all";
if (productType === "product_all") {
return null;
}
if (productType === "product_collection" || productType === "products") {
return available_product_ids || [];
}
return [];
};
var getApplicableProductsAmount = (voucher, productsData) => {
const applicableProductIds = getApplicableProductIds(voucher);
const { config } = voucher;
const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
if (applicableProductIds === null) {
return productsData.filter((p) => p[amountField].greaterThan(0)).reduce((sum, p) => sum.plus(p[amountField]), new import_decimal.default(0));
}
if (applicableProductIds.length === 0) {
return new import_decimal.default(0);
}
return productsData.filter((p) => applicableProductIds.includes(p.product_id) && p[amountField].greaterThan(0)).reduce((sum, p) => sum.plus(p[amountField]), new import_decimal.default(0));
};
var getApplicableProducts = (voucher, productsData) => {
const applicableProductIds = getApplicableProductIds(voucher);
if (applicableProductIds === null) {
return productsData;
}
if (applicableProductIds.length === 0) {
return [];
}
return productsData.filter((p) => applicableProductIds.includes(p.product_id));
};
function processVouchers(applicableVouchers, orderTotalAmount, products) {
console.log(products, "products123");
const productsCopy = expandProductsWithBundleItems(products, true);
let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
const getItemPassUsage = (usageMap, walletPassProductId, lineKey) => {
var _a;
return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
};
const filterByMaxPassesPerItem = (products2, usageMap, walletPassProductId, maxPassesPerItem) => {
if (maxPassesPerItem <= 0)
return products2;
return products2.filter((p) => {
const lineKey = p._walletPassLineKey;
if (!lineKey)
return true;
return getItemPassUsage(usageMap, walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
});
};
const calculateAvailableMaxAmount = (voucher, productsData, itemPassUsage) => {
const { config } = voucher;
const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
const recommendedAmount = getRecommendedAmount(voucher);
const baseAmount = import_decimal.default.min(
new import_decimal.default(recommendedAmount),
new import_decimal.default(maxDeductionAmount)
);
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
let applicableProducts = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0));
if (itemPassUsage) {
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsage, voucher.product_id, maxPassesPerItem);
}
if (applicableProducts.length === 0) {
return new import_decimal.default(0);
}
let finalApplicableAmount = new import_decimal.default(0);
if (allowCrossProduct) {
if (applicableProductLimit > 0) {
const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
let remainingLimit = applicableProductLimit;
for (const product of sortedProducts) {
if (remainingLimit <= 0)
break;
const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
const deductQty = Math.min(currentAvailableQty, remainingLimit);
const deductAmount = import_decimal.default.min(
product[unitPriceField].times(deductQty),
product[amountField]
);
finalApplicableAmount = finalApplicableAmount.plus(deductAmount);
remainingLimit -= deductQty;
}
} else {
finalApplicableAmount = applicableProducts.reduce(
(sum, p) => sum.plus(p[amountField]),
new import_decimal.default(0)
);
}
} else {
const maxProduct = applicableProducts.reduce(
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
);
if (maxPassesPerItem > 0) {
finalApplicableAmount = import_decimal.default.min(
maxProduct[unitPriceField].times(maxPassesPerItem),
maxProduct[amountField]
);
} else {
finalApplicableAmount = maxProduct[amountField];
}
}
return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
};
const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2, itemPassUsage) => {
const { config, id, product_id } = voucher;
const recommendedAmount = getRecommendedAmount(voucher);
if (recommendedAmount <= 0) {
return { isAvailable: false, reasonCode: "not_meet_the_required_conditions" };
}
if (remainingOrderAmount.lessThanOrEqualTo(0)) {
return { isAvailable: false, reasonCode: "exceeds_the_maximum_deduction_limit" };
}
const applicableAmount = getApplicableProductsAmount(voucher, productsData);
if (applicableAmount.lessThanOrEqualTo(0)) {
return { isAvailable: false, reasonCode: "not_meet_the_required_conditions" };
}
if (((config == null ? void 0 : config.maxUsagePerOrder) || 0) > 0) {
const usedCount = usedVoucherCounts2.get(product_id) || 0;
if (usedCount >= ((config == null ? void 0 : config.maxUsagePerOrder) || 0)) {
return { isAvailable: false, reasonCode: "usage_limit_reached" };
}
}
const maxPassesPerItem = (config == null ? void 0 : config.maxPassesPerItem) || 0;
if (maxPassesPerItem > 0 && itemPassUsage) {
const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => {
const lineKey = p._walletPassLineKey;
if (!lineKey)
return true;
return getItemPassUsage(itemPassUsage, product_id, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
});
if (availableAfterPassLimit.length === 0) {
return { isAvailable: false, reasonCode: "max_passes_per_item_reached" };
}
}
return { isAvailable: true };
};
const usedVoucherCountsForAll = /* @__PURE__ */ new Map();
const allVouchersWithStatus = applicableVouchers.map((voucher) => {
const _available_max_amount = calculateAvailableMaxAmount(
voucher,
productsCopy
);
const availabilityResult = isVoucherAvailable(
voucher,
productsCopy,
usedVoucherCountsForAll
);
const _unified_available_status = availabilityResult.isAvailable ? 1 : 0;
return {
...voucher,
_available_max_amount: _available_max_amount.toNumber(),
// 转换为数字
_unified_available_status,
...availabilityResult.reasonCode && { reasonCode: availabilityResult.reasonCode }
};
});
const recommendedVouchers = [];
const usedVoucherCounts = /* @__PURE__ */ new Map();
const productsForRecommendation = expandProductsWithBundleItems(products, true);
remainingOrderAmount = new import_decimal.default(orderTotalAmount);
const itemPassUsageMap = /* @__PURE__ */ new Map();
const applyVoucher = (voucher) => {
const availabilityCheck = isVoucherAvailable(
voucher,
productsForRecommendation,
usedVoucherCounts,
itemPassUsageMap
);
if (!availabilityCheck.isAvailable) {
return false;
}
const { config, id, product_id } = voucher;
const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
let applicableProducts = getApplicableProducts(
voucher,
productsForRecommendation
).filter((p) => p[amountField].greaterThan(0));
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsageMap, product_id, maxPassesPerItem);
if (applicableProducts.length === 0)
return false;
const usageAmount = typeof voucher.edit_current_amount === "number" ? voucher.edit_current_amount : getRecommendedAmount(voucher);
const baseAmount = import_decimal.default.min(
new import_decimal.default(usageAmount),
new import_decimal.default(maxDeductionAmount)
);
let calculatedAvailableMaxAmount = new import_decimal.default(0);
if (allowCrossProduct) {
if (applicableProductLimit > 0) {
const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
let remainingLimit = applicableProductLimit;
for (const product of sortedProducts) {
if (remainingLimit <= 0)
break;
const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
const deductQty = Math.min(currentAvailableQty, remainingLimit);
const deductAmount = import_decimal.default.min(
product[unitPriceField].times(deductQty),
product[amountField]
);
calculatedAvailableMaxAmount = calculatedAvailableMaxAmount.plus(deductAmount);
remainingLimit -= deductQty;
}
} else {
calculatedAvailableMaxAmount = applicableProducts.reduce(
(sum, p) => sum.plus(p[amountField]),
new import_decimal.default(0)
);
}
} else {
const maxProduct = applicableProducts.reduce(
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
);
if (maxPassesPerItem > 0) {
calculatedAvailableMaxAmount = import_decimal.default.min(
maxProduct[unitPriceField].times(maxPassesPerItem),
maxProduct[amountField]
);
} else {
calculatedAvailableMaxAmount = maxProduct[amountField];
}
}
const availableMaxAmount = import_decimal.default.min(
baseAmount,
calculatedAvailableMaxAmount,
remainingOrderAmount
);
const maxDeduction = import_decimal.default.min(
new import_decimal.default(usageAmount),
new import_decimal.default(maxDeductionAmount)
);
let deductionLeft = maxDeduction;
const deductionDetails = [];
if (allowCrossProduct) {
const sortedProducts = [...applicableProducts].sort(
(a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1
);
let remainingLimit = applicableProductLimit > 0 ? applicableProductLimit : Infinity;
for (const product of sortedProducts) {
if (deductionLeft.lessThanOrEqualTo(0) || remainingLimit <= 0)
break;
const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
const availableQty = Math.min(currentAvailableQty, remainingLimit);
const maxDeductForProduct = import_decimal.default.min(
product[unitPriceField].times(availableQty),
product[amountField]
);
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(product[unitPriceField]).toNumber());
product[amountField] = product[amountField].minus(actualDeductAmount);
deductionLeft = deductionLeft.minus(actualDeductAmount);
remainingLimit -= actualDeductQty;
deductionDetails.push({
product_id: product.product_id,
parent_product_id: product.parent_product_id || null,
is_bundle_item: product.is_bundle_item || false,
lineKey: product._walletPassLineKey,
orderLineQuantity: getExpandedOrderLineQuantity(product),
deductAmount: actualDeductAmount.toNumber(),
// 转换为数字
deductQuantity: actualDeductQty
// 抵扣涉及的数量(用于记录)
});
}
} else {
const targetProduct = applicableProducts.reduce(
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
);
let maxDeductForProduct = targetProduct[amountField];
if (maxPassesPerItem > 0) {
maxDeductForProduct = import_decimal.default.min(
targetProduct[unitPriceField].times(maxPassesPerItem),
targetProduct[amountField]
);
}
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
deductionLeft = deductionLeft.minus(actualDeductAmount);
deductionDetails.push({
product_id: targetProduct.product_id,
parent_product_id: targetProduct.parent_product_id || null,
is_bundle_item: targetProduct.is_bundle_item || false,
lineKey: targetProduct._walletPassLineKey,
orderLineQuantity: getExpandedOrderLineQuantity(targetProduct),
deductAmount: actualDeductAmount.toNumber(),
deductQuantity: actualDeductQty
});
}
const totalDeducted = maxDeduction.minus(deductionLeft);
if (totalDeducted.greaterThan(0)) {
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
applyMaxPassUsageIncrements(itemPassUsageMap, product_id, maxPassesPerItem, deductionDetails);
recommendedVouchers.push({
...voucher,
actualDeduction: totalDeducted.toNumber(),
// 转换为数字
deductionDetails,
_available_max_amount: availableMaxAmount.toNumber(),
// 转换为数字
_unified_available_status: 1
});
return true;
}
return false;
};
applicableVouchers.forEach((voucher) => {
applyVoucher(voucher);
});
const recommendedMap = /* @__PURE__ */ new Map();
recommendedVouchers.forEach((v) => {
recommendedMap.set(v.id, v);
});
const allWithEnhancedData = allVouchersWithStatus.map((voucher) => {
if (recommendedMap.has(voucher.id)) {
return recommendedMap.get(voucher.id);
} else {
return {
...voucher,
actualDeduction: 0,
deductionDetails: []
// 保留 reasonCode(如果不可用的话)
};
}
});
return {
recommended: recommendedVouchers,
transformList: allWithEnhancedData
};
}
function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, products) {
const productsForCalc = expandProductsWithBundleItems(products, true);
let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
const selectedWithDetails = [];
const itemPassUsageMap = /* @__PURE__ */ new Map();
const getItemPassUsageRecalc = (walletPassProductId, lineKey) => {
var _a;
return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
};
const filterByMaxPassesPerItemRecalc = (products2, walletPassProductId, maxPassesPerItem) => {
if (maxPassesPerItem <= 0)
return products2;
return products2.filter((p) => {
const lineKey = p._walletPassLineKey;
if (!lineKey)
return true;
return getItemPassUsageRecalc(walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
});
};
selectedVouchers.forEach((selectedVoucher) => {
const { config, id } = selectedVoucher;
const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
let applicableProducts = getApplicableProducts(
selectedVoucher,
productsForCalc
).filter((p) => p[amountField].greaterThan(0));
applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
if (applicableProducts.length === 0) {
selectedWithDetails.push({
...selectedVoucher,
actualDeduction: 0,
deductionDetails: [],
_available_max_amount: 0,
_unified_available_status: 0,
reasonCode: "not_meet_the_required_conditions"
});
return;
}
const usageAmount = typeof selectedVoucher.edit_current_amount === "number" ? selectedVoucher.edit_current_amount : getRecommendedAmount(selectedVoucher);
const maxDeduction = import_decimal.default.min(
new import_decimal.default(usageAmount),
new import_decimal.default(maxDeductionAmount),
remainingOrderAmount
);
let deductionLeft = maxDeduction;
const deductionDetails = [];
if (allowCrossProduct) {
const sortedProducts = [...applicableProducts].sort(
(a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1
);
let remainingLimit = applicableProductLimit > 0 ? applicableProductLimit : Infinity;
for (const product of sortedProducts) {
if (deductionLeft.lessThanOrEqualTo(0) || remainingLimit <= 0)
break;
const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
const availableQty = Math.min(currentAvailableQty, remainingLimit);
const maxDeductForProduct = import_decimal.default.min(
product[unitPriceField].times(availableQty),
product[amountField]
);
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(product[unitPriceField]).toNumber());
product[amountField] = product[amountField].minus(actualDeductAmount);
deductionLeft = deductionLeft.minus(actualDeductAmount);
remainingLimit -= actualDeductQty;
deductionDetails.push({
product_id: product.product_id,
parent_product_id: product.parent_product_id || null,
is_bundle_item: product.is_bundle_item || false,
lineKey: product._walletPassLineKey,
orderLineQuantity: getExpandedOrderLineQuantity(product),
deductAmount: actualDeductAmount.toNumber(),
// 转换为数字
deductQuantity: actualDeductQty
// 抵扣涉及的数量(用于记录)
});
}
} else {
const targetProduct = applicableProducts.reduce(
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
);
let maxDeductForProduct = targetProduct[amountField];
if (maxPassesPerItem > 0) {
maxDeductForProduct = import_decimal.default.min(
targetProduct[unitPriceField].times(maxPassesPerItem),
targetProduct[amountField]
);
}
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
deductionLeft = deductionLeft.minus(actualDeductAmount);
deductionDetails.push({
product_id: targetProduct.product_id,
parent_product_id: targetProduct.parent_product_id || null,
is_bundle_item: targetProduct.is_bundle_item || false,
lineKey: targetProduct._walletPassLineKey,
orderLineQuantity: getExpandedOrderLineQuantity(targetProduct),
deductAmount: actualDeductAmount.toNumber(),
deductQuantity: actualDeductQty
});
}
const totalDeducted = maxDeduction.minus(deductionLeft);
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
applyMaxPassUsageIncrements(itemPassUsageMap, selectedVoucher.product_id, maxPassesPerItem, deductionDetails);
selectedWithDetails.push({
...selectedVoucher,
actualDeduction: totalDeducted.toNumber(),
// 转换为数字
deductionDetails,
_available_max_amount: totalDeducted.toNumber(),
// 转换为数字
_unified_available_status: totalDeducted.greaterThan(0) ? 1 : 0
});
});
const selectedIds = new Set(selectedVouchers.map((v) => v.id));
const usedVoucherCounts = /* @__PURE__ */ new Map();
selectedVouchers.forEach((v) => {
usedVoucherCounts.set(v.product_id, (usedVoucherCounts.get(v.product_id) || 0) + 1);
});
const allWithUpdatedStatus = allVouchers.map((voucher) => {
if (selectedIds.has(voucher.id)) {
const selectedDetail = selectedWithDetails.find(
(v) => v.id === voucher.id
);
return selectedDetail || voucher;
}
const { config, id, product_id } = voucher;
const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
const recommendedAmount = getRecommendedAmount(voucher);
let isAvailable = true;
let calculatedMaxAmount = new import_decimal.default(0);
let reasonCode;
if (recommendedAmount <= 0) {
isAvailable = false;
reasonCode = "not_meet_the_required_conditions";
} else if (remainingOrderAmount.lessThanOrEqualTo(0)) {
isAvailable = false;
reasonCode = "exceeds_the_maximum_deduction_limit";
} else {
if (config.maxUsagePerOrder > 0) {
const usedCount = usedVoucherCounts.get(product_id) || 0;
if (usedCount >= config.maxUsagePerOrder) {
isAvailable = false;
reasonCode = "usage_limit_reached";
}
}
if (isAvailable) {
let applicableProducts = getApplicableProducts(
voucher,
productsForCalc
).filter((p) => p[amountField].greaterThan(0));
applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, product_id, maxPassesPerItem);
if (applicableProducts.length === 0) {
isAvailable = false;
reasonCode = "not_meet_the_required_conditions";
} else {
const baseAmount = import_decimal.default.min(
new import_decimal.default(recommendedAmount),
new import_decimal.default(maxDeductionAmount)
);
if (allowCrossProduct) {
if (applicableProductLimit > 0) {
const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
let remainingLimit = applicableProductLimit;
for (const product of sortedProducts) {
if (remainingLimit <= 0)
break;
const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
const deductQty = Math.min(currentAvailableQty, remainingLimit);
const deductAmount = import_decimal.default.min(
product[unitPriceField].times(deductQty),
product[amountField]
);
calculatedMaxAmount = calculatedMaxAmount.plus(deductAmount);
remainingLimit -= deductQty;
}
} else {
calculatedMaxAmount = applicableProducts.reduce(
(sum, p) => sum.plus(p[amountField]),
new import_decimal.default(0)
);
}
} else {
const maxProduct = applicableProducts.reduce(
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
);
if (maxPassesPerItem > 0) {
calculatedMaxAmount = import_decimal.default.min(
maxProduct[unitPriceField].times(maxPassesPerItem),
maxProduct[amountField]
);
} else {
calculatedMaxAmount = maxProduct[amountField];
}
}
calculatedMaxAmount = import_decimal.default.min(
baseAmount,
calculatedMaxAmount,
remainingOrderAmount
);
if (calculatedMaxAmount.lessThanOrEqualTo(0)) {
isAvailable = false;
reasonCode = "exceeds_the_maximum_deduction_limit";
}
}
}
}
return {
...voucher,
_available_max_amount: calculatedMaxAmount.toNumber(),
// 转换为数字
_unified_available_status: isAvailable ? 1 : 0,
...reasonCode && { reasonCode }
};
});
return {
allWithUpdatedStatus,
selectedWithDetails
};
}
var getTaxAndFeeRoundingRemainder = (product, isDeductTaxAndFee) => {
var _a, _b;
if (!isDeductTaxAndFee) {
return 0;
}
let taxFeeRoundingRemainder = new import_decimal.default(0);
if (product.is_price_include_tax !== 1) {
taxFeeRoundingRemainder = new import_decimal.default(((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.tax_fee_rounding_remainder) || 0);
}
const surchargeFeeRoundingRemainder = new import_decimal.default(((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.surcharge_rounding_remainder) || 0);
return taxFeeRoundingRemainder.add(surchargeFeeRoundingRemainder).toNumber();
};
var getProductDiscountDifference = (product) => {
var _a;
return new import_decimal.default(((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.product_discount_difference) || 0).toNumber();
};
var getMainProductPrice = (product, isDeductTaxAndFee) => {
var _a, _b, _c, _d;
let mainProductPrice = new import_decimal.default((product == null ? void 0 : product.main_product_selling_price) || ((_a = product.metadata) == null ? void 0 : _a.main_product_selling_price) || 0);
for (let bundleItem of (product == null ? void 0 : product.product_bundle) || []) {
if (getBundleItemIsMarkupOrDiscountPrice(bundleItem)) {
const bundleItemPrice = new import_decimal.default(bundleItem.bundle_selling_price ?? 0);
mainProductPrice = mainProductPrice.add(bundleItemPrice.times(bundleItem.num));
}
}
let taxFee = new import_decimal.default((product == null ? void 0 : product.tax_fee) || ((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.main_product_attached_bundle_tax_fee) || 0).add(((_c = product == null ? void 0 : product.metadata) == null ? void 0 : _c.tax_fee_rounding_remainder) || 0);
if (product.is_price_include_tax === 1) {
taxFee = new import_decimal.default(0);
}
const surchargeFee = new import_decimal.default(((_d = product == null ? void 0 : product.metadata) == null ? void 0 : _d.main_product_attached_bundle_surcharge_fee) || 0);
const taxAndFeeTotal = taxFee.add(surchargeFee);
if (isDeductTaxAndFee) {
mainProductPrice = mainProductPrice.add(taxAndFeeTotal);
}
return mainProductPrice.toNumber();
};
var getBundleItemTaxAndFeeRoundingRemainder = (bundleItem, isDeductTaxAndFee) => {
var _a, _b;
if (!isDeductTaxAndFee) {
return 0;
}
let taxFeeRoundingRemainder = new import_decimal.default(0);
if (bundleItem.is_price_include_tax !== 1) {
taxFeeRoundingRemainder = new import_decimal.default(((_a = bundleItem.metadata) == null ? void 0 : _a.tax_fee_rounding_remainder) || 0);
}
const surchargeFeeRoundingRemainder = new import_decimal.default(((_b = bundleItem.metadata) == null ? void 0 : _b.surcharge_rounding_remainder) || 0);
return taxFeeRoundingRemainder.add(surchargeFeeRoundingRemainder).toNumber();
};
var getBundleItemDiscountDifference = (bundleItem) => {
var _a;
return new import_decimal.default(((_a = bundleItem.metadata) == null ? void 0 : _a.product_discount_difference) || 0).toNumber();
};
var getBundleItemPrice = (bundleItem, parentQuantity, isDeductTaxAndFee) => {
var _a;
const totalQuantity = bundleItem.num * parentQuantity;
let bundleItemPrice = new import_decimal.default(bundleItem.bundle_selling_price ?? 0).times(totalQuantity);
if (isDeductTaxAndFee) {
let taxFee = new import_decimal.default(bundleItem.tax_fee ?? 0).times(totalQuantity);
if (bundleItem.is_price_include_tax === 1) {
taxFee = new import_decimal.default(0);
}
const surchargeFee = new import_decimal.default(((_a = bundleItem.metadata) == null ? void 0 : _a.surcharge_fee) ?? 0).times(totalQuantity);
bundleItemPrice = bundleItemPrice.add(taxFee).add(surchargeFee);
}
return bundleItemPrice.toNumber();
};
var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
const expandedProducts = [];
products.forEach((product, indexInOrder) => {
const productQuantity = getProductQuantity(product);
const parentLineKey = resolveWalletPassLineKey(product, indexInOrder);
const unitPriceWithTax = getMainProductPrice(product, true);
const unitPricePure = getMainProductPrice(product, false);
const roundingRemainderWithTax = getTaxAndFeeRoundingRemainder(product, true);
const discountDifference = getProductDiscountDifference(product);
expandedProducts.push({
...product,
is_bundle_item: false,
parent_product_id: null,
_walletPassLineKey: parentLineKey,
_orderLineQuantity: productQuantity,
// 单价(用于按 quantity 计算抵扣)
unitPriceWithTax: new import_decimal.default(unitPriceWithTax),
unitPricePure: new import_decimal.default(unitPricePure),
// 剩余可抵扣数量
remainingQuantity: productQuantity,
// 含税费的剩余金额(单价 * 数量 + 舍入余数 - 商品差额)
remainingAmountWithTax: new import_decimal.default(unitPriceWithTax).times(productQuantity).add(roundingRemainderWithTax).sub(discountDifference),
// 纯商品金额(不含税费,也需要减去商品差额)
remainingAmountPure: new import_decimal.default(unitPricePure).times(productQuantity).sub(discountDifference)
});
if (product.product_bundle && product.product_bundle.length > 0) {
product.product_bundle.forEach((bundleItem) => {
if (getBundleItemIsOriginalPrice(bundleItem)) {
const bundleQuantity = bundleItem.num * productQuantity;
const bundleLineKey = `${parentLineKey}#bundle:${bundleItem.bundle_id}:${bundleItem.bundle_product_id}`;
const bundleUnitPriceWithTax = new import_decimal.default(getBundleItemPrice(bundleItem, 1, true)).dividedBy(bundleItem.num);
const bundleUnitPricePure = new import_decimal.default(getBundleItemPrice(bundleItem, 1, false)).dividedBy(bundleItem.num);
const bundleRoundingRemainder = getBundleItemTaxAndFeeRoundingRemainder(bundleItem, true);
const bundleDiscountDifference = getBundleItemDiscountDifference(bundleItem);
expandedProducts.push({
...bundleItem,
product_id: bundleItem.bundle_product_id,
// 使用 bundle_product_id 作为 product_id
is_bundle_item: true,
parent_product_id: product.product_id,
quantity: bundleQuantity,
// 子商品数量 * 主商品数量
_walletPassLineKey: bundleLineKey,
_orderLineQuantity: bundleQuantity,
// 单价(用于按 quantity 计算抵扣)
unitPriceWithTax: bundleUnitPriceWithTax,
unitPricePure: bundleUnitPricePure,
// 剩余可抵扣数量
remainingQuantity: bundleQuantity,
// 含税费的剩余金额(总价 + 舍入余数 - 商品差额)
remainingAmountWithTax: new import_decimal.default(getBundleItemPrice(bundleItem, productQuantity, true)).add(bundleRoundingRemainder).sub(bundleDiscountDifference),
// 纯商品金额(不含税费,也需要减去商品差额)
remainingAmountPure: new import_decimal.default(getBundleItemPrice(bundleItem, productQuantity, false)).sub(bundleDiscountDifference)
});
}
});
}
});
return expandedProducts;
};
var getBundleItemIsOriginalPrice = (item) => {
return (item == null ? void 0 : item.price_type) === "markup" && (item == null ? void 0 : item.price_type_ext) === "product_price";
};
var getBundleItemIsMarkupPrice = (item) => {
return (item == null ? void 0 : item.price_type) === "markup" && ((item == null ? void 0 : item.price_type_ext) === "" || !(item == null ? void 0 : item.price_type_ext));
};
var getBundleItemIsDiscountPrice = (item) => {
return (item == null ? void 0 : item.price_type) === "markdown" && ((item == null ? void 0 : item.price_type_ext) === "" || !(item == null ? void 0 : item.price_type_ext));
};
var getBundleItemIsMarkupOrDiscountPrice = (item) => {
return getBundleItemIsMarkupPrice(item) || getBundleItemIsDiscountPrice(item);
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getApplicableProductIds,
getBundleItemDiscountDifference,
getBundleItemIsDiscountPrice,
getBundleItemIsMarkupOrDiscountPrice,
getBundleItemIsMarkupPrice,
getBundleItemIsOriginalPrice,
getBundleItemPrice,
getBundleItemTaxAndFeeRoundingRemainder,
getMainProductPrice,
getProductDiscountDifference,
getProductQuantity,
getTaxAndFeeRoundingRemainder,
processVouchers,
recalculateVouchers,
resolveWalletPassLineKey
});