feat(ui): 面向客户的文案中文化
系统由财务与营销人员日常使用,不是给开发者看的。本批把会渲染到页面上的 文本统一改成业务语言: - param_registry 的 22 条参数说明重写为「管什么、钱怎么变、何时该调它」, 去掉英文枚举名、需求编号、内部阶段号与精度约束 - 前端补 formatByCode:变更记录、版本历史、快照对比只给裸 value_text, 需回目录按编码补 value_type 与 unit 才能翻译枚举,否则这些位置仍显示 INCLUDE/FULL/1;编辑弹窗 BOOL 改为开启/关闭下拉 - 后端错误文案:模板报错用中文模板名并给出下一步,产品类别不再甩 municipal_water / rail_transit - 台账与预览落库的计提说明口语化(rate_note / strategy_note / 聚合行 note), 去掉「基数开关=1」「#12 参数化」「9.8 方案公式」 分数原文 11/1125 与「聚合:20%+5%」不动——那是金额口径的可复核原文, 不是说明文案。init-dev.sql 种子 description 与 registry 同源。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
111ac5425a
commit
61965e0b60
@ -60,7 +60,7 @@ from app.services.param_service import parse_fraction, resolve_snapshot
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# snapshot_json 里的策略点注记(口径检索锚点;#12 已参数化,开关 0 时留此注记)
|
||||
_NOTE_MANAGER_PENDING = "经理个人户单笔不计提(基数开关=0):聚合口径在月结时由团队聚合计算"
|
||||
_NOTE_MANAGER_PENDING = "经理个人户单笔不计提(未计入分成基数):绩效在月结时按团队与全公司分成计算"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@ -544,7 +544,7 @@ def _personal_rate(
|
||||
if customer_type == "enterprise":
|
||||
# 存储口径 ‰ → 纯比例(漏换算放大 1000 倍,与预览同防线)
|
||||
rate = parse_fraction(snapshot.get("enterprise.rate")) / 1000
|
||||
return _RateDecision(rate, "企业户固定计提比例", None, False)
|
||||
return _RateDecision(rate, "企业户:不按回款天数,统一按固定比例计提", None, False)
|
||||
|
||||
template = role.calc_template if role else "TPL_SALESPERSON_DECAY"
|
||||
if template == "TPL_MANAGER_SHARE":
|
||||
@ -554,7 +554,7 @@ def _personal_rate(
|
||||
if snapshot.get("manager.personal.included") != "1":
|
||||
return _RateDecision(
|
||||
Decimal("0"),
|
||||
"经理个人户:12.5%+5% 聚合计提(月结时计算),单笔落 0",
|
||||
"经理个人户:绩效按团队与全公司分成在月结时计算,单笔不计提",
|
||||
None,
|
||||
True,
|
||||
)
|
||||
@ -563,12 +563,12 @@ def _personal_rate(
|
||||
floor = parse_fraction(snapshot.get("salesperson.rate.floor"))
|
||||
boundary = int(parse_fraction(snapshot.get("salesperson.days.boundary")))
|
||||
rate = salesperson_rate(days, start=start, slope=slope, floor=floor, boundary_days=boundary)
|
||||
note = f"经理个人户(基数开关=1):按业务员递减 {start}‰ 起,{days} 天"
|
||||
note = f"经理个人户(已开启计入分成基数):按业务员递减,回款 {days} 天,起始 {start}‰"
|
||||
return _RateDecision(rate, note, None, False)
|
||||
if template == "TPL_INFO_SALES_FIXED":
|
||||
# 信息员基数=本人销售额(覆盖折算与阈值),‰ 换算同防线
|
||||
rate = parse_fraction(snapshot.get("info.rate")) / 1000
|
||||
return _RateDecision(rate, "信息员:本人销售额 × 计提比例", None, False)
|
||||
return _RateDecision(rate, "信息员:按本人销售额乘计提比例,不随回款天数递减", None, False)
|
||||
if template == "TPL_SALESPERSON_DECAY":
|
||||
start = parse_fraction(snapshot.get("salesperson.rate.start"))
|
||||
slope = parse_fraction(snapshot.get("salesperson.rate.slope"))
|
||||
@ -577,9 +577,16 @@ def _personal_rate(
|
||||
# 经 Fraction 转换再取整(INT 型参数入库时已校验分母为 1,取整无损)
|
||||
boundary = int(parse_fraction(snapshot.get("salesperson.days.boundary")))
|
||||
rate = salesperson_rate(days, start=start, slope=slope, floor=floor, boundary_days=boundary)
|
||||
note = f"业务员递减:{start}‰ 起,{days} 天(分界 {boundary} 天,超界取 {floor}‰)"
|
||||
note = (
|
||||
f"回款 {days} 天,从起始 {start}‰ 按日递减"
|
||||
f"(满 {boundary} 天后固定按 {floor}‰ 计算)"
|
||||
)
|
||||
return _RateDecision(rate, note, None, False)
|
||||
raise BadRequestError(f"公式模板 {template} 暂不支持核算")
|
||||
# 提示给模板中文名而非编码:业务人员在岗位角色页看到的是「营销经理·团队与全公司分成」
|
||||
from app.services.calc_templates import TEMPLATES
|
||||
|
||||
tpl_name = TEMPLATES[template].name if template in TEMPLATES else template
|
||||
raise BadRequestError(f"「{tpl_name}」不支持单笔核算,请检查该人员的岗位设置")
|
||||
|
||||
|
||||
def _role_of(session: Session, person):
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
注册表是**迁移种子与运行期校验的唯一来源**:编码、默认值、取值域在此定义
|
||||
一次;新增参数 = 改这里 + 生成迁移种子(两者一致性由测试锁定)。
|
||||
值一律以 Fraction 可解析字符串表示(如 "4/360",禁 0.011111 —— 见算法红线)。
|
||||
|
||||
`description` 会直接显示在「计提参数」页的参数名下方,**面向业务人员撰写**:
|
||||
说清这个参数管什么、钱怎么变、什么时候该调它。不写英文枚举名、需求编号、
|
||||
内部阶段号,也不写实现层面的精度约束(那些留在代码注释与测试里)。
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
@ -43,7 +47,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="10",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="线性递减段起始值:比例 = 起始 − 回款天数 × 斜率(≤分界天数)",
|
||||
description="回款当天能拿到的最高计提比例。回款每晚一天,按日递减斜率往下走,直到分界天数为止。",
|
||||
sort_order=1,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -57,7 +61,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="4/360",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="精确分数 4/360‰/天(=1/90),禁止按 0.011111 截断存储",
|
||||
description="回款每延后一天,计提比例下降多少。按分数填写(如 4/360)可保持精确,填小数会引入四舍五入误差。",
|
||||
sort_order=2,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -71,7 +75,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="4",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="回款天数超过分界天数后固定取此比例(≥361 天 = 4‰)",
|
||||
description="回款天数超过分界天数后,计提比例不再下降,固定按这个值计算,即最低计提比例。",
|
||||
sort_order=3,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -85,7 +89,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="360",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="≤分界天数按公式线性递减(360 天=6‰);超过则取下限",
|
||||
description="回款天数在这个范围内按日递减;超过这个天数就固定按下限计提。默认 360 天。",
|
||||
sort_order=4,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -99,7 +103,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="12.5",
|
||||
value_min="0",
|
||||
value_max="100",
|
||||
description="∑本人带领业务员绩效 × 此比例(限本团队)",
|
||||
description="经理按本团队业务员的绩效总额提取的分成比例,只统计自己带的业务员。",
|
||||
sort_order=5,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -113,7 +117,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="5",
|
||||
value_min="0",
|
||||
value_max="100",
|
||||
description="∑全部业务员绩效 × 此比例(全公司)",
|
||||
description="经理按全公司业务员的绩效总额提取的分成比例,与本团队分成叠加计算。",
|
||||
sort_order=6,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -127,7 +131,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="3",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="所有企业户适用;2026-08-13 起由 2‰ 提高至 3‰",
|
||||
description="企业户客户的回款统一按这个比例计提,不看回款天数。2026-08-13 起由 2‰ 调整为 3‰。",
|
||||
sort_order=7,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -141,7 +145,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="1",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="信息员绩效 = 本人销售额 × 此比例",
|
||||
description="信息员的绩效按本人销售额乘这个比例计算,不随回款天数递减。",
|
||||
sort_order=8,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -155,7 +159,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="10",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="预付款统一按此比例计提",
|
||||
description="预付款到账即按这个比例计提绩效,不必等到全额回款。",
|
||||
sort_order=9,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -169,7 +173,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="80",
|
||||
value_min="0",
|
||||
value_max="100",
|
||||
description="以物抵顶回款绩效按应发绩效的此比例折算",
|
||||
description="以物抵顶的回款,按应发绩效的这个比例折算,即打折后的金额参与绩效。",
|
||||
sort_order=10,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -183,7 +187,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="60",
|
||||
value_min="0",
|
||||
value_max="100",
|
||||
description="单项业务回款额达销售额(不含运费)此比例后起付;达标前累计",
|
||||
description="单项业务的回款累计达到销售额(不含运费)的这个比例后才开始发放绩效;达标之前只累计、不发放。",
|
||||
sort_order=11,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -197,7 +201,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="FULL",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description="FULL=按 R×R 乘当月全额应发绩效;BALANCE=乘余额(语义待 M3 前确认)",
|
||||
description="选择支付权重乘在哪个基数上:当月全额 = 乘本月全部应发绩效;余额 = 只乘尚未发放的部分。",
|
||||
sort_order=12,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -211,7 +215,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="30",
|
||||
value_min="0",
|
||||
value_max="100",
|
||||
description="折算毛利润额 = 销售额 ×(毛利润率 − 阈值);市政水利 30%、轨交 20%",
|
||||
description="折算毛利润额 = 销售额 ×(毛利润率 − 阈值),低于阈值的部分不参与绩效。市政水利默认 30%,轨道交通默认 20%。",
|
||||
sort_order=13,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -225,7 +229,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="INCLUDE",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description="INCLUDE含基本工资 / DEDUCT扣除基本工资 / EXCLUDE不计",
|
||||
description="算月工资时怎么处理基本工资:含基本工资 = 与绩效一起发;扣除基本工资 = 从绩效里扣掉已发的基本工资;不计 = 月工资只发绩效。",
|
||||
sort_order=14,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -239,7 +243,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="18000",
|
||||
value_min="0",
|
||||
value_max=None,
|
||||
description="试用期年累计绩效超过此值部分年底一次性结算",
|
||||
description="试用期绩效先不发放;年内累计超过这个金额的部分,年底一次性结算。",
|
||||
sort_order=15,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -253,11 +257,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="1",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description=(
|
||||
"0=二次分配方案提交即生效(系统自动通过全节点留痕,留痕不断链);"
|
||||
"1=提交后进入财务复核→领导审核审批流。"
|
||||
"默认开启(澄清 #5 业务答复 2026-10-19),业务可关闭"
|
||||
),
|
||||
description="二次分配方案提交后是否要审批:开启 = 先经财务复核、再经领导审核,通过后才生效;关闭 = 提交即生效,系统自动记录各节点留痕。",
|
||||
sort_order=17,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -271,11 +271,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="1",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description=(
|
||||
"月度结算审批链开关(④ 月结提交时消费):"
|
||||
"0=结算生成即生效,1=走财务复核→领导审核审批流。"
|
||||
"默认开启(澄清 #7 业务答复 2026-10-19,月结涉及工资发放从严),业务可关闭"
|
||||
),
|
||||
description="月度结算提交后是否要审批:开启 = 先经财务复核、再经领导审核;关闭 = 生成即生效。月结涉及工资发放,默认开启。",
|
||||
sort_order=18,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -289,11 +285,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="0",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description=(
|
||||
"0=调整单提交即生效(系统自动通过留痕);1=走财务复核→领导审核审批流。"
|
||||
"调整单是已锁定月份补发/扣减的唯一通道(澄清 #9:走同一审批链,"
|
||||
"默认值业务未明确,暂按直接生效,业务可开启)"
|
||||
),
|
||||
description="调整单提交后是否要审批:开启 = 先经财务复核、再经领导审核;关闭 = 提交即生效。调整单是已锁定月份补发或扣减的唯一通道。",
|
||||
sort_order=19,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -307,11 +299,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="0",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description=(
|
||||
"经理 12.5%+5% 聚合时是否计入经理本人名下经理个人户业绩(澄清 #12 "
|
||||
"业务答复 2026-10-19:作为参数配置、手动调整是否纳入):"
|
||||
"0=不计入(默认,暂口径) 1=计入"
|
||||
),
|
||||
description="经理提取团队分成时,是否把经理本人名下的业绩(经理个人户)也算进分成基数。默认不计入。",
|
||||
sort_order=20,
|
||||
),
|
||||
# ===== 预警规则(M4 ⑧ 工作台,澄清 #10「做成参数化规则便于调整」)=====
|
||||
@ -327,7 +315,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="1",
|
||||
value_min=None,
|
||||
value_max=None,
|
||||
description="工作台风险预警区块总开关:0=关闭(不扫描不展示) 1=开启(默认)",
|
||||
description="工作台风险预警区块的总开关:关闭后不再扫描、也不展示预警条目。默认开启。",
|
||||
sort_order=21,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -341,7 +329,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="90",
|
||||
value_min="1",
|
||||
value_max=None,
|
||||
description=("发货后超过该天数仍未回款的发货批次触发预警(澄清 #10 建议默认 90 天)"),
|
||||
description="发货后超过这么多天仍未收到回款的发货批次,会出现在工作台的预警列表里。默认 90 天。",
|
||||
sort_order=22,
|
||||
),
|
||||
ParamSpec(
|
||||
@ -355,7 +343,7 @@ PARAM_REGISTRY: dict[str, ParamSpec] = {
|
||||
default_value="3",
|
||||
value_min="1",
|
||||
value_max=None,
|
||||
description=("待审批单提交后超过该天数未处理触发预警(澄清 #10 建议默认 3 天)"),
|
||||
description="审批单提交后超过这么多天还没处理,会出现在工作台的预警列表里。默认 3 天。",
|
||||
sort_order=23,
|
||||
),
|
||||
]
|
||||
|
||||
@ -44,7 +44,7 @@ from app.schemas.param import (
|
||||
ParamPreviewOut,
|
||||
ParamUsedOut,
|
||||
)
|
||||
from app.services import audit_service, formula
|
||||
from app.services import audit_service, calc_templates, formula
|
||||
from app.services.audit_service import AuditRecord, RequestMeta
|
||||
from app.services.formula import to_decimal
|
||||
from app.services.no_generator import next_no
|
||||
@ -557,25 +557,25 @@ def _preview_rate(ctx: _PreviewContext, days: int) -> tuple[Fraction, str, Decim
|
||||
# 存储口径为 ‰ 数(如 "3"),除以 1000 转纯比例 —— performance_amount
|
||||
# 按纯比例相乘,漏换算会把绩效金额放大 1000 倍(与业务员分支同防线)
|
||||
rate = take("enterprise.rate") / 1000
|
||||
return rate, "企业户固定计提比例", None
|
||||
return rate, "企业户:不按回款天数,统一按固定比例计提", None
|
||||
|
||||
role_code = payload.business_role
|
||||
template = "TPL_SALESPERSON_DECAY"
|
||||
if role_code:
|
||||
role = business_role_repository.get_by_code(ctx.session, role_code)
|
||||
if role is None:
|
||||
raise BadRequestError(f"岗位 {role_code!r} 不存在")
|
||||
raise BadRequestError(f"岗位「{role_code}」不存在,请到岗位角色里确认")
|
||||
template = role.calc_template
|
||||
|
||||
if template == "TPL_MANAGER_SHARE":
|
||||
raise BadRequestError(
|
||||
"营销经理绩效为团队聚合口径(本团队 12.5% + 全公司 5%),不支持单笔预览"
|
||||
"营销经理的绩效按团队与全公司分成计算,不单笔试算;请到月度结算里查看"
|
||||
)
|
||||
if template == "TPL_INFO_SALES_FIXED":
|
||||
# ‰ 存储口径 → 纯比例,同企业户分支(不换算则绩效放大 1000 倍)
|
||||
rate = take("info.rate") / 1000
|
||||
# 信息员的基数是本人销售额(无折算与阈值),覆盖默认基数
|
||||
return rate, "信息员:本人销售额 × 计提比例", Decimal(payload.revenue)
|
||||
return rate, "信息员:按本人销售额乘计提比例,不随回款天数递减", Decimal(payload.revenue)
|
||||
if template == "TPL_SALESPERSON_DECAY":
|
||||
start = take("salesperson.rate.start")
|
||||
slope = take("salesperson.rate.slope")
|
||||
@ -584,20 +584,27 @@ def _preview_rate(ctx: _PreviewContext, days: int) -> tuple[Fraction, str, Decim
|
||||
rate = formula.salesperson_rate(
|
||||
days, start=start, slope=slope, floor=floor, boundary_days=boundary
|
||||
)
|
||||
# 说明写成业务话:说清这笔为什么是这个比例、什么时候不再降
|
||||
return (
|
||||
rate,
|
||||
f"业务员递减:{to_decimal(start)}‰ 起,{days} 天(分界 {boundary} 天,超界取 {to_decimal(floor)}‰)",
|
||||
f"回款 {days} 天,从起始 {to_decimal(start)}‰ 按日递减"
|
||||
f"(满 {boundary} 天后固定按 {to_decimal(floor)}‰ 计算)",
|
||||
None,
|
||||
)
|
||||
raise BadRequestError(f"公式模板 {template} 暂不支持预览")
|
||||
# 提示里给模板中文名而不是编码:编码是内部标识,业务人员在岗位角色页看不到它
|
||||
tpl_name = calc_templates.TEMPLATES[template].name if template in calc_templates.TEMPLATES else template
|
||||
raise BadRequestError(
|
||||
f"「{tpl_name}」不支持单笔试算,请在岗位角色里改用业务员递减或信息员固定计提"
|
||||
)
|
||||
|
||||
|
||||
def _preview_rate_decision(ctx: _PreviewContext) -> _PreviewRateDecision:
|
||||
"""产品类别校验 + 回款天数 + 计提比例判定。"""
|
||||
payload = ctx.payload
|
||||
if payload.product_category and payload.product_category not in PRODUCT_CATEGORIES:
|
||||
# 提示里用中文类别名:前端下拉显示的是「市政水利」,报错却回英文编码会让人对不上
|
||||
raise BadRequestError(
|
||||
f"产品类别 {payload.product_category!r} 不存在,可选:municipal_water / rail_transit"
|
||||
f"产品类别「{payload.product_category}」不存在,可选:市政水利、轨道交通"
|
||||
)
|
||||
days = formula.payment_days(payload.ship_date, payload.receipt_date)
|
||||
rate, rate_text, base_override = _preview_rate(ctx, days)
|
||||
@ -651,7 +658,10 @@ def _preview_probation_note(ctx: _PreviewContext) -> str | None:
|
||||
if not ctx.payload.probation:
|
||||
return None
|
||||
annual = ctx.take("probation.annual.threshold")
|
||||
return f"试用期绩效照算不发放;年累计超 {to_decimal(annual)} 元部分年底一次性结算"
|
||||
return (
|
||||
f"试用期人员:绩效照常计算但不发放;年内累计超过 {to_decimal(annual)} 元的部分,"
|
||||
"年底一次性结清。"
|
||||
)
|
||||
|
||||
|
||||
def preview(session: Session, payload: ParamPreviewIn) -> ParamPreviewOut:
|
||||
|
||||
@ -423,9 +423,10 @@ def _aggregate_row(
|
||||
"members": member_names,
|
||||
"params_as_of": str(share.param_date),
|
||||
"manager_personal_included": 1 if share.include_manager_personal else 0,
|
||||
# 说明写给业务人员看:说清这一笔的基数是怎么凑出来的
|
||||
"note": (
|
||||
f"#12 参数化:基数{'含' if share.include_manager_personal else '不含'}"
|
||||
"经理个人户业绩;9.8 方案公式"
|
||||
f"团队分成:基数{'含' if share.include_manager_personal else '不含'}"
|
||||
"经理本人名下业绩,按本团队与全公司两级比例合计计提"
|
||||
),
|
||||
},
|
||||
),
|
||||
|
||||
@ -371,7 +371,11 @@ class TestPreview:
|
||||
}
|
||||
resp = client.post(f"{_PARAMS}/preview", json=payload, headers=admin["headers"])
|
||||
assert resp.status_code == 400
|
||||
assert "聚合" in resp.json()["message"]
|
||||
# 守两件事:① 聚合口径不允许单笔试算(拒绝而不是算出个错数);
|
||||
# ② 提示要能指导下一步(点名是谁的口径 + 去哪儿看),不是一句「不支持」。
|
||||
message = resp.json()["message"]
|
||||
assert "营销经理" in message
|
||||
assert "月度结算" in message
|
||||
|
||||
def test_回款日早于发货日_400(self, client: TestClient, admin: dict) -> None:
|
||||
payload = {
|
||||
|
||||
@ -439,29 +439,29 @@ INSERT INTO param_change_batches (id, version_no, effective_date, change_type, r
|
||||
(1, 'P20260101-00', '2026-01-01', 1, '初始版本(系统初始化种子,口径依据 2026-10-12 业务裁决与 2026-10-19 澄清答复)', 'system', 19);
|
||||
|
||||
INSERT INTO param_definitions (id, param_code, param_name, param_group, value_type, unit, dimension_type, options_text, default_value, value_min, value_max, description, sort_order) VALUES
|
||||
(1, 'salesperson.rate.start', '业务员起始计提比例', '业务员个人户', 'DECIMAL', '‰', 'BUSINESS_ROLE', NULL, '10', '0', NULL, '线性递减段起始值:比例 = 起始 − 回款天数 × 斜率(≤分界天数)', 1),
|
||||
(2, 'salesperson.rate.slope', '业务员日递减斜率', '业务员个人户', 'DECIMAL', '‰/天', 'BUSINESS_ROLE', NULL, '4/360', '0', NULL, '精确分数 4/360‰/天(=1/90),禁止按 0.011111 截断存储', 2),
|
||||
(3, 'salesperson.rate.floor', '业务员计提下限', '业务员个人户', 'DECIMAL', '‰', 'BUSINESS_ROLE', NULL, '4', '0', NULL, '回款天数超过分界天数后固定取此比例(≥361 天 = 4‰)', 3),
|
||||
(4, 'salesperson.days.boundary', '线性递减分界天数', '业务员个人户', 'INT', '天', 'BUSINESS_ROLE', NULL, '360', '0', NULL, '≤分界天数按公式线性递减(360 天=6‰);超过则取下限', 4),
|
||||
(5, 'manager.share.team', '经理本团队分成比例', '经理分成', 'DECIMAL', '%', 'BUSINESS_ROLE', NULL, '12.5', '0', '100', '∑本人带领业务员绩效 × 此比例(限本团队)', 5),
|
||||
(6, 'manager.share.company', '经理全公司分成比例', '经理分成', 'DECIMAL', '%', 'BUSINESS_ROLE', NULL, '5', '0', '100', '∑全部业务员绩效 × 此比例(全公司)', 6),
|
||||
(7, 'enterprise.rate', '企业户计提比例', '企业户', 'DECIMAL', '‰', 'PRODUCT_CATEGORY', NULL, '3', '0', NULL, '所有企业户适用;2026-08-13 起由 2‰ 提高至 3‰', 7),
|
||||
(8, 'info.rate', '信息员计提比例', '信息员', 'DECIMAL', '‰', 'BUSINESS_ROLE', NULL, '1', '0', NULL, '信息员绩效 = 本人销售额 × 此比例', 8),
|
||||
(9, 'prepay.rate', '预付款计提比例', '支付环节', 'DECIMAL', '‰', 'NONE', NULL, '10', '0', NULL, '预付款统一按此比例计提', 9),
|
||||
(10, 'barter.discount', '以物抵顶折算比例', '支付环节', 'DECIMAL', '%', 'NONE', NULL, '80', '0', '100', '以物抵顶回款绩效按应发绩效的此比例折算', 10),
|
||||
(11, 'payment.start.ratio', '支付起付比例', '支付环节', 'DECIMAL', '%', 'NONE', NULL, '60', '0', '100', '单项业务回款额达销售额(不含运费)此比例后起付;达标前累计', 11),
|
||||
(12, 'payment.weight.mode', '支付权重应用方式', '支付环节', 'ENUM', NULL, 'NONE', 'FULL|BALANCE', 'FULL', NULL, NULL, 'FULL=按 R×R 乘当月全额应发绩效;BALANCE=乘余额(语义待 M3 前确认)', 12),
|
||||
(13, 'product.threshold', '折算毛利润额阈值', '产品阈值', 'DECIMAL', '%', 'PRODUCT_CATEGORY', NULL, '30', '0', '100', '折算毛利润额 = 销售额 ×(毛利润率 − 阈值);市政水利 30%、轨交 20%', 13),
|
||||
(14, 'salary.basic.mode', '月工资基本工资口径', '月工资口径', 'ENUM', NULL, 'BUSINESS_ROLE', 'INCLUDE|DEDUCT|EXCLUDE', 'INCLUDE', NULL, NULL, 'INCLUDE含基本工资 / DEDUCT扣除基本工资 / EXCLUDE不计', 14),
|
||||
(15, 'probation.annual.threshold', '试用期年累计结算阈值', '月工资口径', 'DECIMAL', '元', 'NONE', NULL, '18000', '0', NULL, '试用期年累计绩效超过此值部分年底一次性结算', 15),
|
||||
(17, 'approval.secondary_allocation.enabled', '二次分配审批开关', '审批开关', 'BOOL', NULL, 'NONE', NULL, '1', NULL, NULL, '0=二次分配方案提交即生效(系统自动通过全节点留痕,留痕不断链);1=提交后进入财务复核→领导审核审批流。默认开启(澄清 #5 业务答复 2026-10-19),业务可关闭', 17),
|
||||
(18, 'approval.monthly_settlement.enabled', '月度结算审批开关', '审批开关', 'BOOL', NULL, 'NONE', NULL, '1', NULL, NULL, '月度结算审批链开关(④ 月结提交时消费):0=结算生成即生效,1=走财务复核→领导审核审批流。默认开启(澄清 #7 业务答复 2026-10-19,月结涉及工资发放从严),业务可关闭', 18),
|
||||
(19, 'approval.adjustment.enabled', '调整单审批开关', '审批开关', 'BOOL', NULL, 'NONE', NULL, '0', NULL, NULL, '0=调整单提交即生效(系统自动通过留痕);1=走财务复核→领导审核审批流。调整单是已锁定月份补发/扣减的唯一通道(澄清 #9:走同一审批链,默认值业务未明确,暂按直接生效,业务可开启)', 19),
|
||||
(20, 'manager.personal.included', '经理个人户计入聚合基数', '经理分成', 'BOOL', NULL, 'NONE', NULL, '0', NULL, NULL, '经理 12.5%+5% 聚合时是否计入经理本人名下经理个人户业绩(澄清 #12 业务答复 2026-10-19:作为参数配置、手动调整是否纳入):0=不计入(默认,暂口径) 1=计入', 20),
|
||||
-- 预警规则参数(M4 ⑧ 工作台,澄清 #10「做成参数化规则便于调整」)
|
||||
(21, 'warning.enabled', '风险预警总开关', '预警规则', 'BOOL', NULL, 'NONE', NULL, '1', NULL, NULL, '工作台风险预警区块总开关:0=关闭(不扫描不展示) 1=开启(默认)', 21),
|
||||
(22, 'warning.receipt.overdue.days', '回款逾期天数阈值', '预警规则', 'INT', '天', 'NONE', NULL, '90', '1', NULL, '发货后超过该天数仍未回款的发货批次触发预警(澄清 #10 建议默认 90 天)', 22),
|
||||
(23, 'warning.approval.timeout.days', '审批超时天数阈值', '预警规则', 'INT', '天', 'NONE', NULL, '3', '1', NULL, '待审批单提交后超过该天数未处理触发预警(澄清 #10 建议默认 3 天)', 23);
|
||||
(1, 'salesperson.rate.start', '业务员起始计提比例', '业务员个人户', 'DECIMAL', '‰', 'BUSINESS_ROLE', NULL, '10', '0', NULL, '回款当天能拿到的最高计提比例。回款每晚一天,按日递减斜率往下走,直到分界天数为止。', 1),
|
||||
(2, 'salesperson.rate.slope', '业务员日递减斜率', '业务员个人户', 'DECIMAL', '‰/天', 'BUSINESS_ROLE', NULL, '4/360', '0', NULL, '回款每延后一天,计提比例下降多少。按分数填写(如 4/360)可保持精确,填小数会引入四舍五入误差。', 2),
|
||||
(3, 'salesperson.rate.floor', '业务员计提下限', '业务员个人户', 'DECIMAL', '‰', 'BUSINESS_ROLE', NULL, '4', '0', NULL, '回款天数超过分界天数后,计提比例不再下降,固定按这个值计算,即最低计提比例。', 3),
|
||||
(4, 'salesperson.days.boundary', '线性递减分界天数', '业务员个人户', 'INT', '天', 'BUSINESS_ROLE', NULL, '360', '0', NULL, '回款天数在这个范围内按日递减;超过这个天数就固定按下限计提。默认 360 天。', 4),
|
||||
(5, 'manager.share.team', '经理本团队分成比例', '经理分成', 'DECIMAL', '%', 'BUSINESS_ROLE', NULL, '12.5', '0', '100', '经理按本团队业务员的绩效总额提取的分成比例,只统计自己带的业务员。', 5),
|
||||
(6, 'manager.share.company', '经理全公司分成比例', '经理分成', 'DECIMAL', '%', 'BUSINESS_ROLE', NULL, '5', '0', '100', '经理按全公司业务员的绩效总额提取的分成比例,与本团队分成叠加计算。', 6),
|
||||
(7, 'enterprise.rate', '企业户计提比例', '企业户', 'DECIMAL', '‰', 'PRODUCT_CATEGORY', NULL, '3', '0', NULL, '企业户客户的回款统一按这个比例计提,不看回款天数。2026-08-13 起由 2‰ 调整为 3‰。', 7),
|
||||
(8, 'info.rate', '信息员计提比例', '信息员', 'DECIMAL', '‰', 'BUSINESS_ROLE', NULL, '1', '0', NULL, '信息员的绩效按本人销售额乘这个比例计算,不随回款天数递减。', 8),
|
||||
(9, 'prepay.rate', '预付款计提比例', '支付环节', 'DECIMAL', '‰', 'NONE', NULL, '10', '0', NULL, '预付款到账即按这个比例计提绩效,不必等到全额回款。', 9),
|
||||
(10, 'barter.discount', '以物抵顶折算比例', '支付环节', 'DECIMAL', '%', 'NONE', NULL, '80', '0', '100', '以物抵顶的回款,按应发绩效的这个比例折算,即打折后的金额参与绩效。', 10),
|
||||
(11, 'payment.start.ratio', '支付起付比例', '支付环节', 'DECIMAL', '%', 'NONE', NULL, '60', '0', '100', '单项业务的回款累计达到销售额(不含运费)的这个比例后才开始发放绩效;达标之前只累计、不发放。', 11),
|
||||
(12, 'payment.weight.mode', '支付权重应用方式', '支付环节', 'ENUM', NULL, 'NONE', 'FULL|BALANCE', 'FULL', NULL, NULL, '选择支付权重乘在哪个基数上:当月全额 = 乘本月全部应发绩效;余额 = 只乘尚未发放的部分。', 12),
|
||||
(13, 'product.threshold', '折算毛利润额阈值', '产品阈值', 'DECIMAL', '%', 'PRODUCT_CATEGORY', NULL, '30', '0', '100', '折算毛利润额 = 销售额 ×(毛利润率 − 阈值),低于阈值的部分不参与绩效。市政水利默认 30%,轨道交通默认 20%。', 13),
|
||||
(14, 'salary.basic.mode', '月工资基本工资口径', '月工资口径', 'ENUM', NULL, 'BUSINESS_ROLE', 'INCLUDE|DEDUCT|EXCLUDE', 'INCLUDE', NULL, NULL, '算月工资时怎么处理基本工资:含基本工资 = 与绩效一起发;扣除基本工资 = 从绩效里扣掉已发的基本工资;不计 = 月工资只发绩效。', 14),
|
||||
(15, 'probation.annual.threshold', '试用期年累计结算阈值', '月工资口径', 'DECIMAL', '元', 'NONE', NULL, '18000', '0', NULL, '试用期绩效先不发放;年内累计超过这个金额的部分,年底一次性结算。', 15),
|
||||
(17, 'approval.secondary_allocation.enabled', '二次分配审批开关', '审批开关', 'BOOL', NULL, 'NONE', NULL, '1', NULL, NULL, '二次分配方案提交后是否要审批:开启 = 先经财务复核、再经领导审核,通过后才生效;关闭 = 提交即生效,系统自动记录各节点留痕。', 17),
|
||||
(18, 'approval.monthly_settlement.enabled', '月度结算审批开关', '审批开关', 'BOOL', NULL, 'NONE', NULL, '1', NULL, NULL, '月度结算提交后是否要审批:开启 = 先经财务复核、再经领导审核;关闭 = 生成即生效。月结涉及工资发放,默认开启。', 18),
|
||||
(19, 'approval.adjustment.enabled', '调整单审批开关', '审批开关', 'BOOL', NULL, 'NONE', NULL, '0', NULL, NULL, '调整单提交后是否要审批:开启 = 先经财务复核、再经领导审核;关闭 = 提交即生效。调整单是已锁定月份补发或扣减的唯一通道。', 19),
|
||||
(20, 'manager.personal.included', '经理个人户计入聚合基数', '经理分成', 'BOOL', NULL, 'NONE', NULL, '0', NULL, NULL, '经理提取团队分成时,是否把经理本人名下的业绩(经理个人户)也算进分成基数。默认不计入。', 20),
|
||||
-- 预警规则参数:工作台预警阈值,可按业务需要调整
|
||||
(21, 'warning.enabled', '风险预警总开关', '预警规则', 'BOOL', NULL, 'NONE', NULL, '1', NULL, NULL, '工作台风险预警区块的总开关:关闭后不再扫描、也不展示预警条目。默认开启。', 21),
|
||||
(22, 'warning.receipt.overdue.days', '回款逾期天数阈值', '预警规则', 'INT', '天', 'NONE', NULL, '90', '1', NULL, '发货后超过这么多天仍未收到回款的发货批次,会出现在工作台的预警列表里。默认 90 天。', 22),
|
||||
(23, 'warning.approval.timeout.days', '审批超时天数阈值', '预警规则', 'INT', '天', 'NONE', NULL, '3', '1', NULL, '审批单提交后超过这么多天还没处理,会出现在工作台的预警列表里。默认 3 天。', 23);
|
||||
|
||||
INSERT INTO param_values (id, param_id, dimension_value, effective_date, value_text, batch_id) VALUES
|
||||
(1, 1, 'salesperson', '2026-02-17', '10', 1),
|
||||
|
||||
@ -171,7 +171,7 @@ const allocationHint = computed(() => {
|
||||
const total = Number(allocationTotal.value.toFixed(2))
|
||||
if (total === 100) return `二次分配合计 ${total.toFixed(2)}%`
|
||||
const rest = Number((100 - total).toFixed(2))
|
||||
return `二次分配合计 ${total.toFixed(2)}%,余额 ${rest.toFixed(2)}%(归属待业务确认)`
|
||||
return `二次分配合计 ${total.toFixed(2)}%,余额 ${rest.toFixed(2)}%(归负责人)`
|
||||
})
|
||||
|
||||
function addAllocationRow(): void {
|
||||
@ -880,7 +880,7 @@ onMounted(() => {
|
||||
<div class="items-head">
|
||||
<span class="items-title">
|
||||
二次分配方案
|
||||
<span class="alloc-note">(可选;比例不要求合计 100%,分摊计算待业务确认后另批)</span>
|
||||
<span class="alloc-note">(可选;各参与人比例不要求合计 100%,余额归负责人)</span>
|
||||
</span>
|
||||
<el-button size="small" @click="addAllocationRow">添加参与人</el-button>
|
||||
</div>
|
||||
|
||||
@ -479,7 +479,7 @@ onMounted(() => {
|
||||
v-model="form.receipt_date"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="M3 核算按此日匹配参数版本"
|
||||
placeholder="选到账当天;计提比例按这一天的参数计算"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@ -99,9 +99,24 @@ function valueLabel(value: string | null | undefined): string {
|
||||
return ENUM_VALUE_LABELS[value] ?? value
|
||||
}
|
||||
|
||||
/** 带单位的值展示 */
|
||||
/** 带单位的值展示:枚举与开关先翻成中文,其余原样加单位 */
|
||||
function formatValue(item: ParamCatalogItem, value: string): string {
|
||||
return item.unit ? `${value} ${item.unit}` : value
|
||||
// 枚举/开关的原始值是 INCLUDE、FULL、1、0 —— 直接摆给业务人员看不懂,
|
||||
// 数字型(比例、天数、金额)保持原样,只补单位
|
||||
const shown =
|
||||
item.value_type === 'ENUM' || item.value_type === 'BOOL' ? valueLabel(value) : value
|
||||
return item.unit ? `${shown} ${item.unit}` : shown
|
||||
}
|
||||
|
||||
/**
|
||||
* 按参数编码格式化取值:变更记录 / 版本历史 / 快照对比拿到的都是裸 value_text,
|
||||
* 没有 value_type 与 unit,只能回目录里按编码查定义再格式化。
|
||||
* 目录还没加载完或编码已下线时原样返回,宁可显示原文也不显示空白。
|
||||
*/
|
||||
function formatByCode(code: string, value: string | null | undefined): string {
|
||||
if (value === null || value === undefined || value === '') return '—'
|
||||
const item = catalog.value.find((entry) => entry.param_code === code)
|
||||
return item ? formatValue(item, value) : value
|
||||
}
|
||||
|
||||
// ==================== 修改(待提交) ====================
|
||||
@ -436,7 +451,7 @@ onMounted(() => {
|
||||
<div class="page-head">
|
||||
<h1 class="page-title">计提参数</h1>
|
||||
<p class="page-desc">
|
||||
计提比例、系数、阈值、分成与起付比例在此统一维护;变更逐项留痕并支持按日期生效。
|
||||
计提比例、系数、阈值、分成与起付比例都在这里统一维护;每次改动留下旧值,可以指定从哪天起生效。
|
||||
</p>
|
||||
<div class="waterline"></div>
|
||||
</div>
|
||||
@ -484,7 +499,7 @@ onMounted(() => {
|
||||
type="warning"
|
||||
disable-transitions
|
||||
>
|
||||
待改为 {{ pendingOf(item, v.dimension_value)!.new_value }}
|
||||
待改为 {{ formatValue(item, pendingOf(item, v.dimension_value)!.new_value) }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="isAdmin"
|
||||
@ -498,7 +513,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
<span v-else class="no-value">
|
||||
未设置(默认 {{ item.default_value ?? '—' }})
|
||||
未设置(默认 {{ formatByCode(item.param_code, item.default_value) }})
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -537,10 +552,14 @@ onMounted(() => {
|
||||
<template #default="{ row }">{{ dimensionLabel(row.dimension_value) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="旧值" min-width="110">
|
||||
<template #default="{ row }">{{ row.old_value || '—' }}</template>
|
||||
<template #default="{ row }">
|
||||
{{ row.old_value ? formatByCode(row.item.param_code, row.old_value) : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新值" min-width="110">
|
||||
<template #default="{ row }"><b class="num">{{ row.new_value }}</b></template>
|
||||
<template #default="{ row }">
|
||||
<b class="num">{{ formatByCode(row.item.param_code, row.new_value) }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{ row }">
|
||||
@ -590,24 +609,50 @@ onMounted(() => {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前值">
|
||||
<span class="num">{{ currentValue(editTarget.item, editTarget.dimension) ?? editTarget.item.default_value ?? '—' }}</span>
|
||||
<span v-if="editTarget.item.unit" style="color: var(--ys-ink-3)"> {{ editTarget.item.unit }}</span>
|
||||
<!-- 当前值同目录卡口径:枚举翻中文、数字补单位;编辑框里存的仍是原始值 -->
|
||||
<span class="num">
|
||||
{{ formatByCode(editTarget.item.param_code, currentValue(editTarget.item, editTarget.dimension) ?? editTarget.item.default_value) }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="新值">
|
||||
<!--
|
||||
枚举与开关都给下拉:让业务人员在「含基本工资」「开」里选,
|
||||
而不是自己敲 INCLUDE / 1 —— 提交给后端的仍是原始值(v-model 绑的是 value)
|
||||
-->
|
||||
<el-select
|
||||
v-if="editTarget.item.value_type === 'ENUM'"
|
||||
v-model="editValue"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="opt in editTarget.item.options" :key="opt" :label="opt" :value="opt" />
|
||||
<!-- label 走 valueLabel:下拉里给的是「含基本工资」这类中文,提交值仍是 INCLUDE 原值 -->
|
||||
<el-option
|
||||
v-for="opt in editTarget.item.options"
|
||||
:key="opt"
|
||||
:label="valueLabel(opt)"
|
||||
:value="opt"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="editTarget.item.value_type === 'BOOL'"
|
||||
v-model="editValue"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="开启" value="1" />
|
||||
<el-option label="关闭" value="0" />
|
||||
</el-select>
|
||||
<el-input v-else v-model="editValue" placeholder="输入新值(分数式如 4/360 原样保留)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="edit-tip">
|
||||
取值范围:{{ editTarget.item.value_min ?? '−' }} ~ {{ editTarget.item.value_max ?? '不限' }};
|
||||
精确分数(如 4/360)原样存储、不做小数截断。
|
||||
<!--
|
||||
取值提示只对数字型参数有意义:枚举与开关是选出来的,摆「取值范围」只会添乱。
|
||||
分数式(4/360)保留原文是因为截成小数会算出不一样的绩效,这条得讲给业务听。
|
||||
-->
|
||||
<div v-if="editTarget.item.value_type !== 'ENUM' && editTarget.item.value_type !== 'BOOL'" class="edit-tip">
|
||||
取值范围:{{ editTarget.item.value_min ?? '不限' }} ~ {{ editTarget.item.value_max ?? '不限' }}
|
||||
<template v-if="editTarget.item.unit">(单位:{{ editTarget.item.unit }})</template>
|
||||
;比例可写成 4/360 这样的分数,系统按精确值计算,不四舍五入。
|
||||
</div>
|
||||
<div v-else class="edit-tip">改动提交后按你选择的生效日期起作用,旧值会保留在变更记录里。</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="editVisible = false">取消</el-button>
|
||||
@ -615,7 +660,7 @@ onMounted(() => {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 版本管理抽屉 -->
|
||||
<!-- 版本管理抽屉:对比两个日期各自的生效值 + 变更记录 -->
|
||||
<el-drawer v-model="versionVisible" title="版本管理" size="760px">
|
||||
<div class="drawer-block">
|
||||
<div class="drawer-row">
|
||||
@ -637,18 +682,18 @@ onMounted(() => {
|
||||
<el-button :loading="exporting" @click="doExport">导出记录</el-button>
|
||||
</div>
|
||||
<template v-if="diffSearched">
|
||||
<div class="drawer-title">快照对比({{ compareFrom }} → {{ compareTo }})</div>
|
||||
<div class="drawer-title">两个日期的参数对比({{ compareFrom }} → {{ compareTo }})</div>
|
||||
<el-table :data="diffResult" size="small" max-height="260">
|
||||
<el-table-column prop="param_name" label="参数" min-width="130" />
|
||||
<el-table-column label="维度" width="90">
|
||||
<template #default="{ row }">{{ dimensionLabel(row.dimension_value) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="旧值" min-width="90">
|
||||
<template #default="{ row }">{{ row.old_value ?? '—' }}</template>
|
||||
<template #default="{ row }">{{ formatByCode(row.param_code, row.old_value) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新值" min-width="90">
|
||||
<template #default="{ row }">
|
||||
<span :class="{ changed: row.changed }">{{ row.new_value ?? '—' }}</span>
|
||||
<span :class="{ changed: row.changed }">{{ formatByCode(row.param_code, row.new_value) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变化" width="70" align="center">
|
||||
@ -670,7 +715,7 @@ onMounted(() => {
|
||||
<div v-for="item in detailCache[row.id].items" :key="item.param_code + item.dimension_value" class="batch-item">
|
||||
<span class="param-name">{{ item.param_name }}</span>
|
||||
<span class="dim-label">{{ dimensionLabel(item.dimension_value) }}</span>
|
||||
<span class="num">{{ item.old_value_text ?? '—' }} → <b>{{ item.value_text }}</b></span>
|
||||
<span class="num">{{ formatByCode(item.param_code, item.old_value_text) }} → <b>{{ formatByCode(item.param_code, item.value_text) }}</b></span>
|
||||
</div>
|
||||
</template>
|
||||
<el-button v-else link size="small" @click="detailOf(row).catch(() => {})">
|
||||
@ -719,7 +764,11 @@ onMounted(() => {
|
||||
</el-table-column>
|
||||
<el-table-column label="旧值 → 新值" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<span class="num">{{ row.old_value_text ?? '—' }} → {{ row.value_text }}</span>
|
||||
<!-- 历史行没有 value_type,按当前参数定义格式化(枚举翻中文、数字补单位) -->
|
||||
<span class="num">
|
||||
{{ formatByCode(historyItem?.param_code ?? '', row.old_value_text) }} →
|
||||
{{ formatByCode(historyItem?.param_code ?? '', row.value_text) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version_no" label="版本" width="115" />
|
||||
@ -809,9 +858,10 @@ onMounted(() => {
|
||||
<div v-if="previewResult.probation_note" class="pr-note">{{ previewResult.probation_note }}</div>
|
||||
</div>
|
||||
<div class="pr-used">
|
||||
<div class="drawer-title">所用参数(按回款到账日取值)</div>
|
||||
<div class="drawer-title">本次用到的参数(按回款到账日取当天生效值)</div>
|
||||
<!-- 只显示中文参数名与格式化后的值:参数编码是内部标识,业务人员对不上 -->
|
||||
<div v-for="u in previewResult.params_used" :key="u.param_code" class="pr-used-row">
|
||||
{{ paramNameByCode(u.param_code) }}({{ u.param_code }})= {{ valueLabel(u.value_text) || u.value_text }}
|
||||
{{ paramNameByCode(u.param_code) }} = {{ formatByCode(u.param_code, u.value_text) }}
|
||||
<span v-if="u.effective_date" class="eff">({{ u.effective_date }} 起)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user