feat(discount): 折扣率语义扩展——允许>1加价卖给客户+解析改专属优先/*兜底

用户决策(2026-09-06):
1. 折扣率允许大于1:平台可加价卖产品给客户(如存储缺省1.5);
   也可小于1让利(如百炼模型0.9,供应商有折扣传导给客户)
2. 解析规则:确定客户→查客户专属折扣;查不到→'*'通配兜底;再无→1.0
   旧实现bug:专属与通配OR混查取全局min——专属0.95+通配0.8返回0.8,
   专属记录形同虚设

改动:
- init.py sor_get_min_product_discount: 两步查询(专属层min→通配层min)
- init.py add/update_discount_detail: 上限1→9.9999(DB double(5,4)约束)
- discount_setting/save_discount.dspy: 兜底记录识别'*'+NULL两种形态;
  「客户折扣率不得高于通用折扣率」校验保留(加价语义下=单客户价不贵于通用价)
- marketing_discount_setting/save_discount.dspy: >1上限放开,保留<基准约束
- generate_qr.dspy: 同上放开
- index.ui: placeholder 0~1→'>1加价 <1折扣',dec_len 2→4

本地mock测试5场景全过:专属优先/通配兜底/无记录1.0/exact_match不兜底/层内min
This commit is contained in:
yumoqing 2026-09-06 15:43:01 +08:00
parent a3d7077acc
commit 55b4943170
5 changed files with 57 additions and 26 deletions

View File

@ -277,7 +277,18 @@ where discountid = ${discountid}$
async def sor_get_min_product_discount(sor, product_id, resellerid, customerid, exact_match=False):
"""Find the minimum discount for a specific product from active discount records.
"""客户折扣率解析2026-09-06 语义修订:专属优先,通配兜底)。
解析顺序用户定义
1. 客户专属记录优先discount.customerid 精确匹配同层级多条取 min
2. 查不到且非 exact_match '*' NULL 通配兜底记录多条取 min
3. 仍查不到 1.0无折扣
旧实现的 bug把专属与通配记录混在一起 OR 查询后取全局 min
专属 0.95 + 通配 0.8 会返回 0.8专属记录形同虚设
折扣率允许 >1加价卖给客户 <1让利上限受
discount_detail.discount double(5,4) 约束 = 9.9999
Args:
sor: sqlor context
@ -290,30 +301,40 @@ async def sor_get_min_product_discount(sor, product_id, resellerid, customerid,
env = ServerEnv()
biz_date = await env.get_business_date(sor)
if exact_match:
customer_cond = "d.customerid = ${customerid}$"
else:
customer_cond = "(d.customerid = ${customerid}$ OR d.customerid IS NULL OR d.customerid = '*')"
sql = f"""SELECT dd.discount
base_sql = """SELECT dd.discount
FROM discount_detail dd
JOIN discount d ON dd.discountid = d.id
WHERE d.resellerid = ${{resellerid}}$
WHERE d.resellerid = ${resellerid}$
AND {customer_cond}
AND d.enabled_date <= ${{biz_date}}$
AND d.expired_date > ${{biz_date}}$
AND dd.productid = ${{product_id}}$"""
AND d.enabled_date <= ${biz_date}$
AND d.expired_date > ${biz_date}$
AND dd.productid = ${product_id}$"""
ns = {
'resellerid': resellerid,
'customerid': customerid,
'biz_date': biz_date,
'product_id': product_id,
}
# 1) 客户专属优先
sql = base_sql.replace('{customer_cond}', "d.customerid = ${customerid}$")
recs = await sor.sqlExe(sql, ns)
if recs:
discounts = [r.discount for r in recs if r.discount is not None]
if discounts:
return min(discounts)
# 2) '*' / NULL 通配兜底(供应商/分销商 exact_match 不兜底)
if not exact_match:
sql = base_sql.replace(
'{customer_cond}', "(d.customerid = '*' OR d.customerid IS NULL)")
recs = await sor.sqlExe(sql, ns)
if recs:
discounts = [r.discount for r in recs if r.discount is not None]
if discounts:
return min(discounts)
# 3) 无任何记录 → 原价
return 1.0
@ -402,8 +423,11 @@ order by prodtypeid, productid"""
async def add_discount_detail(sor, discountid, resellerid, prodtypeid, productid, discount):
"""Add a product-specific discount detail."""
if discount <= 0 or discount > 1:
raise Exception(f'discount({discount}) invalid, must be between 0 and 1 (inclusive)')
# 2026-09-06: 折扣率允许 >1加价卖给客户如存储缺省1.5)或 <1让利
# 上限受 discount_detail.discount double(5,4) 约束 = 9.9999
if discount <= 0 or discount > 9.9999:
raise Exception(f'discount({discount}) invalid, must be in (0, 9.9999] — '
f'>1 表示加价、<1 表示折扣')
ret = {
'id': getID(),
@ -419,8 +443,10 @@ async def add_discount_detail(sor, discountid, resellerid, prodtypeid, productid
async def update_discount_detail(sor, detail_id, discount):
"""Update a discount detail record."""
if discount <= 0 or discount > 1:
raise Exception(f'discount({discount}) invalid, must be between 0 and 1 (inclusive)')
# 2026-09-06: 同 add_discount_detail——允许 >1 加价
if discount <= 0 or discount > 9.9999:
raise Exception(f'discount({discount}) invalid, must be in (0, 9.9999] — '
f'>1 表示加价、<1 表示折扣')
await sor.U('discount_detail', {'id': detail_id, 'discount': discount})

View File

@ -69,8 +69,8 @@
"padding": "0 8px",
"name": "discount",
"value": {{json.dumps(p.discount or '1.0')}},
"placeholder": "0~1",
"dec_len": 2,
"placeholder": ">1加价 <1折扣",
"dec_len": 4,
"fontSize": "12px"
},
"binds": [

View File

@ -51,12 +51,15 @@ try:
disc_sql = "SELECT customerid, resellerid FROM discount WHERE id = ${id}$"
disc_recs = await sor.sqlExe(disc_sql, {'id': discountid})
if disc_recs and disc_recs[0].customerid:
# 查找全部用户默认方案的同一产品折扣
# 查找全部用户默认方案的同一产品折扣兜底记录两种形态customerid='*' 或 NULL
# 与 sor_get_min_product_discount 的通配兜底语义一致)
default_sql = """
SELECT dd.discount FROM discount_detail dd
JOIN discount d ON dd.discountid = d.id
WHERE d.resellerid = ${resellerid}$ AND d.customerid IS NULL
WHERE d.resellerid = ${resellerid}$
AND (d.customerid IS NULL OR d.customerid = '*')
AND dd.productid = ${productid}$
AND d.enabled_date <= NOW() AND d.expired_date > NOW()
LIMIT 1
"""
default_recs = await sor.sqlExe(default_sql, {'resellerid': disc_recs[0].resellerid, 'productid': productid})
@ -64,7 +67,7 @@ try:
default_d = float(default_recs[0].discount)
customer_d = float(discount_val)
if customer_d > default_d:
result = {'widgettype': 'Error', 'options': {'title': '折扣无效', 'message': f'客户折扣({customer_d})不能高于全部用户折扣({default_d})', 'timeout': 5}}
result = {'widgettype': 'Error', 'options': {'title': '折扣无效', 'message': f'客户折扣率({customer_d})不能高于通用折扣率({default_d})——单个客户价格不得贵于通用价', 'timeout': 5}}
return result
if detail_id:

View File

@ -1,8 +1,9 @@
from ahserver.serverenv import ServerEnv
params_kw.discount = float(params_kw.discount)
if params_kw.discount <= 0 or params_kw.discount >= 1:
e = Exception(f'discount({params_kw.discount}) invalid')
# 2026-09-06: 折扣率允许 >1加价促销场景上限 9.9999DB double(5,4)
if params_kw.discount <= 0 or params_kw.discount > 9.9999:
e = Exception(f'discount({params_kw.discount}) invalid, must be in (0, 9.9999]')
exception(f'{e}')
raise e

View File

@ -44,12 +44,13 @@ try:
if base_discount:
base_val = float(base_discount)
if new_val >= base_val:
result = {'widgettype': 'Error', 'options': {'title': '折扣无效', 'message': f'营销折扣({new_val})必须小于基准折扣({base_val})', 'timeout': 5}}
result = {'widgettype': 'Error', 'options': {'title': '折扣无效', 'message': f'营销折扣({new_val})必须小于基准折扣({base_val})', 'timeout': 5}}
return result
# 验证: 不能超过100%1.0
if new_val > 1.0:
result = {'widgettype': 'Error', 'options': {'title': '折扣无效', 'message': '折扣不能大于1.0', 'timeout': 5}}
# 2026-09-06: 放开 >1 上限——折扣率>1=加价基准若是1.5加价营销可设1.2仍低于基准)。
# 约束只剩两条:>0上面已验、< 基准折扣率上面已验、≤9.9999DB double(5,4)
if new_val > 9.9999:
result = {'widgettype': 'Error', 'options': {'title': '折扣无效', 'message': '折扣率不能大于9.9999', 'timeout': 5}}
return result
async with DBPools().sqlorContext(dbname) as sor: