文件
Henglianshutong/tests/verify_voucher_unit_ge_spec.js
chen b598d56483 feat(voucher): 兑换券业务严格隔离、前台全面去¥化、不足强拦截与售后闭环返还加固
1. 普通商品购买流程中严格禁止兑换券抵扣,仅限积分商城支持同等兑换
2. 积分商城卡片、详情、规格及结算页全面去除「¥」符号,强化「个兑换券 / 券」纯资产心智
3. 兑换券与积分逻辑完全对齐,兑换券不足时禁止掏现金补差,前端置灰禁用+后端抛出「兑换券不足」异常
4. 修复售后退款状态机识别偏差,并在订单失效/取消中增加已核销兑换券原路返还兜底
5. 新增及更新 6 套专项自动化测试(累计 200 项断言全部通过)
2026-09-21 11:49:28 +08:00

133 行
8.3 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

const fs = require('fs');
const path = require('path');
let passed = 0;
let failed = 0;
function assert(condition, message, detail = '') {
if (condition) {
console.log(` [PASS] ${message}`);
passed++;
} else {
console.error(` [FAIL] ${message} ${detail ? '-> ' + detail : ''}`);
failed++;
}
}
console.log("========================================================");
console.log(" 兑换券单位全面更名为「个」(UniApp + 后台系统)测试");
console.log("========================================================\n");
// 1. UniApp - s-wallet-card.vue
const walletCardPath = path.resolve(__dirname, '../uniapp/sheep/components/s-wallet-card/s-wallet-card.vue');
assert(fs.existsSync(walletCardPath), 's-wallet-card.vue 存在');
if (fs.existsSync(walletCardPath)) {
const content = fs.readFileSync(walletCardPath, 'utf-8');
assert(content.includes("voucher_available") && content.includes('<view class="unit-text ss-m-l-6">个</view>'), 's-wallet-card.vue 兑换券单位为「个」');
assert(!content.includes("userInfo?.voucher_available || '0.00' }}\n <view class=\"unit-text ss-m-l-6\">元</view>"), 's-wallet-card.vue 兑换券不再展示「元」');
}
// 2. UniApp - account-info.vue
const accountInfoPath = path.resolve(__dirname, '../uniapp/pages/commission/components/account-info.vue');
assert(fs.existsSync(accountInfoPath), 'account-info.vue 存在');
if (fs.existsSync(accountInfoPath)) {
const content = fs.readFileSync(accountInfoPath, 'utf-8');
assert(content.includes('可用兑换券(个)'), 'account-info.vue 收益中心卡片展示「可用兑换券(个)」');
assert(!content.includes('可用兑换券(元)'), 'account-info.vue 不再包含「可用兑换券(元)」');
}
// 3. UniApp - voucher.vue
const voucherVuePath = path.resolve(__dirname, '../uniapp/pages/commission/voucher.vue');
assert(fs.existsSync(voucherVuePath), 'voucher.vue 存在');
if (fs.existsSync(voucherVuePath)) {
const content = fs.readFileSync(voucherVuePath, 'utf-8');
assert(content.includes('<text class="hero-unit">个</text>'), 'voucher.vue 核心大字数单位展示为「个」');
assert(!content.includes('<text class="hero-symbol">¥</text>'), 'voucher.vue 核心大字数移除「¥」符号');
assert(content.includes('<text class="metric-unit">个</text>'), 'voucher.vue 底部统计数据栏使用「个」单位');
assert(content.includes("formatVoucher(item.total_amount || item.total || item.voucher_total) }} 个") || content.includes("{{ item.total_amount || item.total || item.voucher_total || '0.00' }} 个") || content.includes('<text class="metric-unit-text">个</text>'), 'voucher.vue 计划列表卡片使用「个」单位');
}
// 4. UniApp - confirm.vue
const confirmVuePath = path.resolve(__dirname, '../uniapp/pages/order/confirm.vue');
assert(fs.existsSync(confirmVuePath), 'confirm.vue 存在');
if (fs.existsSync(confirmVuePath)) {
const content = fs.readFileSync(confirmVuePath, 'utf-8');
assert(content.includes('formatVoucher(state.orderInfo.voucher_available) }} 个') || content.includes('{{ state.orderInfo.voucher_available || \'0.00\' }} 个'), 'confirm.vue 抵扣弹窗展示可用数量「个」');
assert(content.includes('确定使用 ${voucherFee} 个兑换券'), 'confirm.vue 兑换确认弹窗提示为「个兑换券」');
assert(!content.includes('元兑换券'), 'confirm.vue 不再出现「元兑换券」字样');
}
// 5. UniApp - score-shop.vue
const scoreShopVuePath = path.resolve(__dirname, '../uniapp/pages/app/score-shop.vue');
assert(fs.existsSync(scoreShopVuePath), 'score-shop.vue 存在');
if (fs.existsSync(scoreShopVuePath)) {
const content = fs.readFileSync(scoreShopVuePath, 'utf-8');
assert(content.includes('{{ userVoucherAvailable }} 个'), 'score-shop.vue 资产看板兑换券单位为「个」');
assert(!content.includes('¥ {{ userVoucherAvailable }}'), 'score-shop.vue 资产看板不带「¥」');
}
// 6. 后台 - dashboard/index.html
const dashboardPath = path.resolve(__dirname, '../application/admin/view/shopro/dashboard/index.html');
assert(fs.existsSync(dashboardPath), 'dashboard/index.html 存在');
if (fs.existsSync(dashboardPath)) {
const content = fs.readFileSync(dashboardPath, 'utf-8');
assert(content.includes('{{ state.voucher.total_voucher }} <span class="sa-m-l-4" style="font-size: 14px; font-weight: normal;">个</span>'), 'dashboard/index.html 总补贴券单位为「个」');
assert(content.includes('{{ state.voucher.released_voucher }} 个'), 'dashboard/index.html 已释放补贴券单位为「个」');
assert(content.includes('{{ state.voucher.pending_voucher }} 个'), 'dashboard/index.html 待释放补贴券单位为「个」');
assert(!content.includes('<span class="currency">¥</span>{{ state.voucher.total_voucher }}'), 'dashboard/index.html 补贴券不再展示「¥」');
}
// 7. 后台 - user/recharge.html
const rechargePath = path.resolve(__dirname, '../application/admin/view/shopro/user/user/recharge.html');
assert(fs.existsSync(rechargePath), 'recharge.html 存在');
if (fs.existsSync(rechargePath)) {
const content = fs.readFileSync(rechargePath, 'utf-8');
assert(content.includes('兑换券 (个)'), 'recharge.html 单选类型为「兑换券 (个)」');
assert(!content.includes('兑换券 (元)'), 'recharge.html 不再包含「兑换券 (元)」');
assert(content.includes('{{ state.user.voucher_available || \'0.00\' }} 个'), 'recharge.html 可用兑换券为「个」');
}
// 8. 后台 - user/detail.html
const userDetailPath = path.resolve(__dirname, '../application/admin/view/shopro/user/user/detail.html');
assert(fs.existsSync(userDetailPath), 'user/detail.html 存在');
if (fs.existsSync(userDetailPath)) {
const content = fs.readFileSync(userDetailPath, 'utf-8');
assert(content.includes('充值兑换券 (个)'), 'user/detail.html 充值下拉菜单为「充值兑换券 (个)」');
assert(!content.includes('充值兑换券 (元)'), 'user/detail.html 不再包含「充值兑换券 (元)」');
assert(content.includes('{{ state.detail.voucher_available || \'0.00\' }} 个'), 'user/detail.html 商城可用兑换券卡片单位为「个」');
assert(content.includes('{{ scope.row.total_fee }} 个'), 'user/detail.html 计划总额列使用「个」');
}
// 9. 后台 - partner/voucher/index.html
const partnerVoucherPath = path.resolve(__dirname, '../application/admin/view/shopro/partner/voucher/index.html');
assert(fs.existsSync(partnerVoucherPath), 'partner/voucher/index.html 存在');
if (fs.existsSync(partnerVoucherPath)) {
const content = fs.readFileSync(partnerVoucherPath, 'utf-8');
assert(content.includes('label="总额(个)"'), 'partner/voucher/index.html 包含「总额(个)」表头');
assert(content.includes('label="每天释放(个)"'), 'partner/voucher/index.html 包含「每天释放(个)」表头');
assert(content.includes('label="释放数量(个)"'), 'partner/voucher/index.html 包含「释放数量(个)」明细表头');
}
// 10. 后台 - app/score_shop/add.html & ScoreSkuPrice.php
const scoreShopAddPath = path.resolve(__dirname, '../application/admin/view/shopro/app/score_shop/add.html');
assert(fs.existsSync(scoreShopAddPath), 'score_shop/add.html 存在');
if (fs.existsSync(scoreShopAddPath)) {
const content = fs.readFileSync(scoreShopAddPath, 'utf-8');
assert(content.includes('所需兑换券(个)'), 'score_shop/add.html 包含「所需兑换券(个)」表头');
assert(!content.includes('所需兑换券(元)'), 'score_shop/add.html 不再包含「所需兑换券(元)」');
}
const scoreSkuPricePath = path.resolve(__dirname, '../application/admin/model/shopro/app/ScoreSkuPrice.php');
assert(fs.existsSync(scoreSkuPricePath), 'ScoreSkuPrice.php 存在');
if (fs.existsSync(scoreSkuPricePath)) {
const content = fs.readFileSync(scoreSkuPricePath, 'utf-8');
assert(content.includes("return $price . ' 个兑换券'"), 'ScoreSkuPrice.php 输出格式为「个兑换券」');
assert(!content.includes("return '¥' . $price . ' 兑换券'"), 'ScoreSkuPrice.php 不再输出「¥」');
}
console.log("\n--------------------------------------------------------");
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
console.log("========================================================");
process.exit(failed === 0 ? 0 : 1);