fix(agent): 规范分销商详情展示,明确合伙股东仅为会员进阶身份并杜绝误导管理员

- 修复非合伙股东会员(品酒师)被误判为自购考核已暂停的问题,明确赋值为免自购考核

- 分离基础会员信息与合伙股东进阶身份展示,对非合伙股东提供清晰的身份说明与免考核提示

- 纠正佣金规则提示文案为收益中心固定金额规则,收益现金统一规范为收益中心累计现金

- 列表页与详情页同步优化非合伙股东 30 日活跃状态呈现

- 增加 TDD 自动化验证测试套件 verify_agent_partner_identity_spec.js 并通过全部回归测试
这个提交包含在:
wikiboss
2026-09-17 15:08:46 +08:00
父节点 b67d15983e
当前提交 a67e23391e
共有 4 个文件被更改,包括 178 次插入 和 47 次删除

查看文件

@@ -58,18 +58,43 @@ class Agent extends Common
$last = intval($detail->last_self_order_time ?? 0);
$days = \addons\shopro\service\partner\PartnerConfig::activeDays();
$ago = $last ? intval(floor((time() - $last) / 86400)) : null;
$cash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount');
$identityInfo = \addons\shopro\service\partner\IdentityService::build($detail);
$isPartner = !empty($identityInfo['is_partner']);
$detail->is_partner = $isPartner ? 1 : 0;
$detail->next_push3_amount = number_format((float) $next, 2, '.', '');
$detail->next_push3_text = '第 ' . ($seq + 1) . ' 单将返 ' . $detail->next_push3_amount;
$detail->last_self_order_text = $last ? date('Y-m-d H:i:s', $last) : '从未下单';
$detail->active_status_text = !empty($detail->active_30d)
? '正常领取'
: ('已暂停' . ($ago !== null ? '(距今 ' . $ago . ' 天)' : ''));
$detail->partner_cash_income = number_format((float) $cash, 2, '.', '');
// 业务核心规范:合伙股东仅是会员专属进阶身份,普通会员与品酒师免自购考核
if (!$isPartner) {
$detail->active_status_text = '免自购考核 (基础会员/品酒师)';
} else {
$detail->active_status_text = !empty($detail->active_30d)
? '正常领取'
: ('已暂停' . ($ago !== null ? '(距今 ' . $ago . ' 天)' : ''));
}
$totalCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount');
$push3Cash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->where('status', 1)
->where('amount', '>', 0)
->where('reward_type', 'push3')
->sum('amount');
$partnerExclusiveCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->where('status', 1)
->where('amount', '>', 0)
->where('reward_type', '<>', 'push3')
->sum('amount');
$detail->income_total_cash = number_format((float) $totalCash, 2, '.', '');
$detail->push3_cash_income = number_format((float) $push3Cash, 2, '.', '');
$detail->partner_exclusive_cash = number_format((float) $partnerExclusiveCash, 2, '.', '');
$detail->partner_cash_income = $detail->income_total_cash;
$detail->partner_mode = \addons\shopro\service\partner\PartnerConfig::isPartnerMode() ? 1 : 0;
$rate = (new \addons\shopro\service\partner\RateService())->currentRate();
$detail->madow_value = \addons\shopro\service\partner\IdentityService::formatMoney((float) ($detail->madow ?? 0) * (float) $rate);

查看文件

@@ -202,7 +202,10 @@
: state.data.user_id
}}
</div>
<el-form size="small" label-position="right" label-width="76px">
<el-form size="small" label-position="right" label-width="116px" style="margin-top: 12px;">
<div style="font-weight: 600; font-size: 13px; color: var(--sa-title); margin: 8px 0 12px; padding-bottom: 4px; border-bottom: 1px solid var(--sa-border);">
基础分销信息
</div>
<el-form-item label="分销商状态:">
<el-dropdown popper-class="agent-popover" trigger="click" @command="onChangeStatus">
<span class="status" :style="{
@@ -217,8 +220,8 @@
{if $auth->check('shopro/commission/agent/edit')}
<el-dropdown-menu>
<el-dropdown-item class="status" :style="{
color: value.color,
}" v-for="(value, key) in statusStyle" :key="key" :command="key">{{ value.label }}
color: value.color,
}" v-for="(value, key) in statusStyle" :key="key" :command="key">{{ value.label }}
</el-dropdown-item>
</el-dropdown-menu>
{/if}
@@ -228,7 +231,7 @@
<el-form-item label="分销等级:">
<template v-if="state.data.level_info">
<div>{{ state.data.level_info.name }}</div>
<div>(等级{{ state.data.level_info.level }})</div>
<div class="ml-1" style="color: var(--sa-subfont);">(等级{{ state.data.level_info.level }})</div>
</template>
<template v-if="!state.data.level_info">
{{ state.data.level }}
@@ -254,8 +257,10 @@
<el-button class="ml-2" type="primary" link @click="onChangeParentUser">更换
</el-button>
</el-form-item>
<el-form-item v-if="state.data.partner_mode" label="佣金比例:">
<div class="tip">已切换为合伙股东固定金额,旧百分比不再生效。</div>
<el-form-item v-if="state.data.partner_mode" label="佣金规则:">
<div class="tip" style="color: var(--sa-subfont); line-height: 1.4;">
已切换为收益中心固定金额规则,旧百分比不再生效。
</div>
</el-form-item>
<el-form-item v-else label="佣金比例:">
<div class="commission-item">
@@ -289,45 +294,87 @@
</div>
</div>
</el-form-item>
<el-form-item label="允许升级:">
<el-form-item label="允许自动升级:">
{if $auth->check('shopro/commission/agent/edit')}
<el-switch v-model="state.data.upgrade_lock" :active-value="0" :inactive-value="1"
@change="state.data.user_id?onEdit({upgrade_lock: state.data.upgrade_lock}): ''">
</el-switch>
{/if}
</el-form-item>
<el-form-item label="酒道馆:">
{{ state.data.is_venue ? '是' : '否' }}
<el-form-item label="直推奖(推三返一):">
<div>第 {{ state.data.direct_order_seq || 0 }} 单,{{ state.data.next_push3_text || '' }}</div>
</el-form-item>
<el-form-item label="区县代理:">
{{ state.data.is_county ? '是' : '否' }}{{ state.data.county_code ? '(' + state.data.county_code + ')' : '' }}
<el-form-item label="累计直推开拓奖:">
<div>{{ state.data.push3_cash_income || '0.00' }} 元</div>
</el-form-item>
<el-form-item>
<div style="font-weight: 600; font-size: 13px; color: var(--sa-title); margin: 16px 0 10px; padding-bottom: 4px; border-bottom: 1px solid var(--sa-border);" class="sa-flex sa-row-between">
<span>合伙股东身份(会员进阶身份)</span>
{if $auth->check('shopro/commission/agent/edit')}
<el-button type="primary" @click="onOpenIdentity">开通 / 调整身份</el-button>
<el-button type="primary" size="small" link @click="onOpenIdentity">开通 / 调整身份</el-button>
{/if}
</el-form-item>
<el-form-item label="身份组合:">
{{ state.data.identity_text || '-' }}
</el-form-item>
<el-form-item label="活跃状态:">
{{ state.data.active_status_text || (state.data.active_30d ? '正常领取' : '已暂停') }}
</el-form-item>
<el-form-item label="直推序号:">
第 {{ state.data.direct_order_seq || 0 }} 单,{{ state.data.next_push3_text || '' }}
</el-form-item>
<el-form-item label="最近自购:">
{{ state.data.last_self_order_text || '从未下单' }}
</el-form-item>
<el-form-item label="马豆:">
{{ state.data.madow || 0 }}(约合 ¥{{ state.data.madow_value || '0.00' }})
</el-form-item>
<el-form-item label="兑换券:">
总额 {{ state.data.voucher_total || 0 }} / 已释放 {{ state.data.voucher_released || 0 }} /
已使用 {{ state.data.voucher_used || 0 }} / 可用 {{ state.data.voucher_available || 0 }}
</el-form-item>
<el-form-item label="累计合伙股东现金:">
{{ state.data.partner_cash_income || '0.00' }}
</div>
<template v-if="state.data.is_partner">
<el-form-item label="合伙股东身份:">
<el-tag type="warning" effect="dark" size="small">{{ state.data.identity_text }}</el-tag>
</el-form-item>
<el-form-item label="酒道馆:">
<el-tag size="small" :type="state.data.is_venue ? 'success' : 'info'">
{{ state.data.is_venue ? '已开通' : '未开通' }}
</el-tag>
</el-form-item>
<el-form-item label="区县代理:">
<el-tag size="small" :type="state.data.is_county ? 'warning' : 'info'">
{{ state.data.is_county ? '已开通' + (state.data.county_code ? ' (' + state.data.county_code + ')' : '') : '未开通' }}
</el-tag>
</el-form-item>
<el-form-item label="30天自购考核:">
<span v-if="state.data.active_30d" style="color: #52c41a; font-weight: 500;">
🟢 正常领取
</span>
<span v-else style="color: #f56c6c; font-weight: 500;">
🔴 {{ state.data.active_status_text || '已超期暂停' }}
</span>
</el-form-item>
<el-form-item label="最近自购时间:">
{{ state.data.last_self_order_text || '从未下单' }}
</el-form-item>
<el-form-item label="进阶团队分润:">
<strong>{{ state.data.partner_exclusive_cash || '0.00' }}</strong> 元
</el-form-item>
<el-form-item label="每日贡献马豆:">
{{ state.data.madow || 0 }}(约合 ¥{{ state.data.madow_value || '0.00' }})
</el-form-item>
<el-form-item label="消费补贴兑换券:">
总额 {{ state.data.voucher_total || 0 }} / 已释放 {{ state.data.voucher_released || 0 }} /
已使用 {{ state.data.voucher_used || 0 }} / 可用 {{ state.data.voucher_available || 0 }}
</el-form-item>
</template>
<template v-else>
<el-form-item label="进阶身份:">
<el-tag type="info" effect="plain" size="small">基础会员 · 品酒师(暂未开通合伙股东)</el-tag>
</el-form-item>
<div style="font-size: 12px; color: var(--sa-subfont); line-height: 1.6; background: #f8fafc; border: 1px dashed var(--sa-border); border-radius: 4px; padding: 8px 12px; margin: 4px 0 10px;">
💡 <strong>「合伙股东」是会员专属进阶身份</strong>:当会员升级创客(V1~V6)或开通酒道馆、区县代理时自动附加合伙股东身份标签,并享有团队分润与平台日结分红。当前会员为基础会员,享有推三返一,<strong>免自购考核</strong>。
</div>
<el-form-item label="自购考核要求:">
<el-tag size="small" type="info">免自购考核 (基础会员/品酒师)</el-tag>
</el-form-item>
<el-form-item label="合伙股东权益:">
<span style="color: var(--sa-subfont);">晋升创客或开通馆/县后即可享有团队奖、极差奖与马豆分红</span>
</el-form-item>
</template>
<div style="font-weight: 600; font-size: 13px; color: var(--sa-title); margin: 16px 0 10px; padding-bottom: 4px; border-bottom: 1px solid var(--sa-border);">
收益中心资产汇总
</div>
<el-form-item label="收益中心累计现金:">
<span style="font-size: 16px; font-weight: 600; color: #f56c6c;">
{{ state.data.income_total_cash || state.data.partner_cash_income || '0.00' }}
</span>
<span class="ml-1" style="font-size: 12px; color: var(--sa-subfont);">元</span>
</el-form-item>
<el-form-item>
<el-button type="primary" link

查看文件

@@ -68,10 +68,15 @@
</el-table-column>
<el-table-column label="30日活跃" min-width="100">
<template #default="scope">
<span class="sa-flex sa-col-center">
<i class="active-dot" :class="scope.row.active_30d ? 'is-on' : 'is-off'"></i>
{{ scope.row.active_30d ? '有自购' : '已暂停' }}
</span>
<template v-if="scope.row.is_partner">
<span class="sa-flex sa-col-center">
<i class="active-dot" :class="scope.row.active_30d ? 'is-on' : 'is-off'"></i>
{{ scope.row.active_30d ? '有自购' : '已暂停' }}
</span>
</template>
<template v-else>
<span class="sa-color--info" style="font-size:12px">免考核</span>
</template>
</template>
</el-table-column>
<el-table-column label="马豆" min-width="130">

查看文件

@@ -0,0 +1,54 @@
const fs = require('fs');
const path = require('path');
let passed = 0;
let failed = 0;
function assert(condition, testName, detail = '') {
if (condition) {
console.log(` [PASS] ${testName}`);
passed++;
} else {
console.error(` [FAIL] ${testName} - ${detail}`);
failed++;
}
}
console.log("========================================================");
console.log(" 合伙股东身份明确化 & 详情页避免误导管理员 规范测试");
console.log("========================================================\n");
// 1. 验证后端 Agent.php 控制器逻辑
const agentPhpPath = path.resolve(__dirname, '../application/admin/controller/shopro/commission/Agent.php');
assert(fs.existsSync(agentPhpPath), 'Agent.php 文件存在');
const agentPhpContent = fs.readFileSync(agentPhpPath, 'utf-8');
assert(agentPhpContent.includes("IdentityService::build($detail)"), 'Agent.php 中调用 IdentityService::build 识别会员身份');
assert(agentPhpContent.includes('免自购考核'), 'Agent.php 中对非合伙股东会员明确赋值免自购考核状态文案');
assert(agentPhpContent.includes('$detail->is_partner ='), 'Agent.php 中返回 is_partner 属性供前端区分展示');
assert(agentPhpContent.includes('income_total_cash') || agentPhpContent.includes('total_reward_cash'), 'Agent.php 中提供清晰规范的收益中心累计现金字段');
// 2. 验证前端 detail.html 视图模板
const agentDetailPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/agent/detail.html');
assert(fs.existsSync(agentDetailPath), 'agent/detail.html 文件存在');
const agentDetailContent = fs.readFileSync(agentDetailPath, 'utf-8');
assert(!agentDetailContent.includes('已切换为合伙股东固定金额'), 'detail.html 移除误导性文案“已切换为合伙股东固定金额”');
assert(agentDetailContent.includes('已切换为收益中心固定金额规则'), 'detail.html 规范为“已切换为收益中心固定金额规则”');
assert(agentDetailContent.includes('state.data.is_partner'), 'detail.html 包含合伙股东进阶身份的分支条件判断 (is_partner)');
assert(agentDetailContent.includes('免自购考核'), 'detail.html 包含对非合伙股东免自购考核的清晰呈现');
assert(!agentDetailContent.includes('<el-form-item label="累计合伙股东现金:">'), 'detail.html 不再将所有会员现金收益笼统称为“累计合伙股东现金”');
assert(agentDetailContent.includes('收益中心累计现金'), 'detail.html 采用规范的“收益中心累计现金”标签');
// 3. 验证列表页 index.html 视图模板
const agentIndexPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/agent/index.html');
assert(fs.existsSync(agentIndexPath), 'agent/index.html 文件存在');
const agentIndexContent = fs.readFileSync(agentIndexPath, 'utf-8');
assert(agentIndexContent.includes('is_partner') && agentIndexContent.includes('免考核'), 'agent/index.html 30日活跃列对非合伙股东显示免考核而非误报已暂停');
console.log("\n--------------------------------------------------------");
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
console.log("========================================================");
process.exit(failed === 0 ? 0 : 1);