194 行
8.3 KiB
JavaScript
194 行
8.3 KiB
JavaScript
/**
|
||
* 验证酒道馆与区县代理双重身份及全拓扑场景下的团队统筹拆分奖专项测试
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
let passed = 0;
|
||
let failed = 0;
|
||
|
||
function assert(condition, message) {
|
||
if (condition) {
|
||
passed++;
|
||
console.log(` [PASS] ${message}`);
|
||
} else {
|
||
failed++;
|
||
console.error(` [FAIL] ${message}`);
|
||
}
|
||
}
|
||
|
||
console.log('========================================================');
|
||
console.log(' 酒道馆与区县代理双重身份及全拓扑场景团队奖专项测试');
|
||
console.log('========================================================\n');
|
||
|
||
// 1. 静态代码分析
|
||
const teamSplitFile = path.resolve(__dirname, '../../addons/shopro/service/partner/TeamSplitReward.php');
|
||
assert(fs.existsSync(teamSplitFile), 'TeamSplitReward.php 文件存在');
|
||
const code = fs.readFileSync(teamSplitFile, 'utf8');
|
||
|
||
assert(code.includes('$start = $venue ? $venue[\'index\'] : 0;'), 'nearestCounty 起始查找索引支持从当前酒道馆节点匹配兼职区县代理');
|
||
assert(code.includes('馆兼县'), '支持「馆兼县」专属流水明细公式标识');
|
||
|
||
// 2. 模拟 TeamSplitReward 核心算法逻辑
|
||
class MockActiveService {
|
||
isActive(node) {
|
||
return node.is_active !== false;
|
||
}
|
||
}
|
||
|
||
class MockTeamSplitReward {
|
||
run(order, buyerId, chain, active, rules = null) {
|
||
const venueSplit = (rules && typeof rules.venue_split === 'number') ? rules.venue_split : 50;
|
||
const countyFull = (rules && typeof rules.county_full === 'number') ? rules.county_full : 100;
|
||
const result = { venue: null, county: null, skips: [] };
|
||
|
||
const venue = this.nearest(chain, 'is_venue', active);
|
||
const county = this.nearestCounty(chain, venue, active);
|
||
|
||
if (!venue && !county) {
|
||
result.skips.push({ type: 'team_venue', reason: 'not_venue', formula: '链上无酒道馆且无区县' });
|
||
return result;
|
||
}
|
||
|
||
if (venue) {
|
||
if (venue.inactive) {
|
||
result.skips.push({ type: 'team_venue', user_id: venue.user_id, reason: 'inactive_30d', formula: '酒道馆 30 日无自购' });
|
||
} else {
|
||
result.venue = { user_id: venue.user_id, amount: venueSplit, index: venue.index };
|
||
}
|
||
}
|
||
|
||
if (county) {
|
||
const amount = venue ? venueSplit : countyFull;
|
||
if (county.inactive) {
|
||
result.skips.push({ type: 'team_county', user_id: county.user_id, reason: 'inactive_30d', formula: '区县 30 日无自购' });
|
||
} else {
|
||
const formulaText = venue
|
||
? (venue.user_id === county.user_id ? `区县管理奖 ${amount}(馆兼县)` : `区县管理奖 ${amount}(馆上)`)
|
||
: `区县管理奖 ${amount}(直达)`;
|
||
result.county = { user_id: county.user_id, amount, index: county.index, formula: formulaText };
|
||
}
|
||
} else if (venue && !county) {
|
||
result.skips.push({ type: 'team_county', user_id: 0, reason: 'not_county', formula: '有馆无区县' });
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
nearest(chain, flag, active) {
|
||
for (let i = 0; i < chain.length; i++) {
|
||
if (!chain[i][flag]) continue;
|
||
return {
|
||
user_id: chain[i].user_id,
|
||
index: i,
|
||
inactive: !active.isActive(chain[i]),
|
||
node: chain[i]
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
nearestCounty(chain, venue, active) {
|
||
const start = venue ? venue.index : 0;
|
||
for (let i = start; i < chain.length; i++) {
|
||
if (!chain[i].is_county) continue;
|
||
return {
|
||
user_id: chain[i].user_id,
|
||
index: i,
|
||
inactive: !active.isActive(chain[i]),
|
||
node: chain[i]
|
||
};
|
||
}
|
||
if (!venue) {
|
||
return this.nearest(chain, 'is_county', active);
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
const runner = new MockTeamSplitReward();
|
||
const active = new MockActiveService();
|
||
|
||
// 场景 1:同一人兼任酒道馆与区县代理(买家25 -> 上级20兼具馆+县,即本Bug场景)
|
||
{
|
||
const chain = [
|
||
{ user_id: 25, is_venue: 0, is_county: 0 },
|
||
{ user_id: 20, is_venue: 1, is_county: 1, is_active: true }
|
||
];
|
||
const res = runner.run({ id: 12 }, 25, chain, active);
|
||
assert(res.venue !== null && res.venue.user_id === 20 && res.venue.amount === 50, '场景1: 上级20号成功获得酒道馆奖 50 元');
|
||
assert(res.county !== null && res.county.user_id === 20 && res.county.amount === 50, '场景1: 上级20号兼任区县代理,成功获得区县管理奖 50 元');
|
||
assert(res.county.formula.includes('馆兼县'), '场景1: 公式正确标识为「区县管理奖 50(馆兼县)」');
|
||
assert(res.venue.amount + res.county.amount === 100, '场景1: 团队拆分总额严格守恒为 100 元 (50 + 50)');
|
||
assert(res.skips.length === 0, '场景1: 无错误跳过记录');
|
||
}
|
||
|
||
// 场景 2:酒道馆与区县代理为不同上级(买家25 -> 馆20 -> 县30)
|
||
{
|
||
const chain = [
|
||
{ user_id: 25, is_venue: 0, is_county: 0 },
|
||
{ user_id: 20, is_venue: 1, is_county: 0, is_active: true },
|
||
{ user_id: 30, is_venue: 0, is_county: 1, is_active: true }
|
||
];
|
||
const res = runner.run({ id: 13 }, 25, chain, active);
|
||
assert(res.venue !== null && res.venue.user_id === 20 && res.venue.amount === 50, '场景2: 独立酒道馆20号获得 50 元');
|
||
assert(res.county !== null && res.county.user_id === 30 && res.county.amount === 50, '场景2: 上层区县代理30号获得 50 元');
|
||
assert(res.county.formula.includes('馆上'), '场景2: 公式正确标识为「区县管理奖 50(馆上)」');
|
||
assert(res.venue.amount + res.county.amount === 100, '场景2: 团队拆分总额严格守恒为 100 元');
|
||
}
|
||
|
||
// 场景 3:无馆有县(买家25 -> 县20)
|
||
{
|
||
const chain = [
|
||
{ user_id: 25, is_venue: 0, is_county: 0 },
|
||
{ user_id: 20, is_venue: 0, is_county: 1, is_active: true }
|
||
];
|
||
const res = runner.run({ id: 14 }, 25, chain, active);
|
||
assert(res.venue === null, '场景3: 无酒道馆');
|
||
assert(res.county !== null && res.county.user_id === 20 && res.county.amount === 100, '场景3: 无馆区县独享 100 元全额');
|
||
assert(res.county.formula.includes('直达'), '场景3: 公式正确标识为「区县管理奖 100(直达)」');
|
||
}
|
||
|
||
// 场景 4:有馆无县(买家25 -> 馆20,上方无区县)
|
||
{
|
||
const chain = [
|
||
{ user_id: 25, is_venue: 0, is_county: 0 },
|
||
{ user_id: 20, is_venue: 1, is_county: 0, is_active: true }
|
||
];
|
||
const res = runner.run({ id: 15 }, 25, chain, active);
|
||
assert(res.venue !== null && res.venue.user_id === 20 && res.venue.amount === 50, '场景4: 馆20号获得 50 元');
|
||
assert(res.county === null, '场景4: 无区县,跳过发奖');
|
||
assert(res.skips.length === 1 && res.skips[0].reason === 'not_county', '场景4: 产生「有馆无区县」跳过记录');
|
||
}
|
||
|
||
// 场景 5:馆与县均不存在
|
||
{
|
||
const chain = [
|
||
{ user_id: 25, is_venue: 0, is_county: 0 },
|
||
{ user_id: 20, is_venue: 0, is_county: 0, is_active: true }
|
||
];
|
||
const res = runner.run({ id: 16 }, 25, chain, active);
|
||
assert(res.venue === null && res.county === null, '场景5: 均不存在');
|
||
assert(res.skips.length === 1 && res.skips[0].reason === 'not_venue', '场景5: 记录链上无酒道馆且无区县');
|
||
}
|
||
|
||
// 场景 6:买家本人自购节点兼任馆或县(自购保活与自购分成合法性)
|
||
{
|
||
const chain = [
|
||
{ user_id: 20, is_venue: 1, is_county: 1, is_active: true }
|
||
];
|
||
const res = runner.run({ id: 17 }, 20, chain, active);
|
||
assert(res.venue !== null && res.venue.user_id === 20 && res.venue.amount === 50, '场景6: 自购节点获得酒道馆运营奖 50 元');
|
||
assert(res.county !== null && res.county.user_id === 20 && res.county.amount === 50, '场景6: 自购节点获得区县管理奖 50 元');
|
||
assert(res.venue.amount + res.county.amount === 100, '场景6: 自购节点拆分总额 100 元');
|
||
}
|
||
|
||
console.log('\n--------------------------------------------------------');
|
||
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
|
||
console.log('========================================================\n');
|
||
|
||
if (failed > 0) {
|
||
process.exit(1);
|
||
}
|