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:
parent
a3d7077acc
commit
55b4943170
@ -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})
|
||||
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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.9999(DB 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
|
||||
|
||||
|
||||
@ -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.9999(DB 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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user