discount/wwwroot/discount_setting/save_discount.dspy
yumoqing 55b4943170 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
2026-09-06 15:43:01 +08:00

95 lines
4.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Upsert折扣明细:对比old_discount和discount,不同才更新
# 客户折扣不得高于全部用户折扣
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid', 'type': 'error', 'timeout': 3}}
try:
discountid = params_kw.get('discountid')
productid = params_kw.get('productid')
discount_val = params_kw.get('discount')
old_discount = params_kw.get('old_discount')
detail_id = params_kw.get('detail_id')
if not discountid or not productid:
raise Exception('缺少必要参数: discountid, productid')
# 过滤 NaN 和空值
if discount_val is not None:
s = str(discount_val).strip().lower()
if s in ('', 'nan', 'none', 'null'):
discount_val = None
if old_discount is not None:
s = str(old_discount).strip().lower()
if s in ('', 'nan', 'none', 'null'):
old_discount = None
# 对比新旧值,相同则跳过
if discount_val is not None and old_discount is not None:
try:
if float(discount_val) == float(old_discount):
result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '折扣未变化', 'type': 'success', 'timeout': 3}}
return result
except:
pass
user_orgid = await get_userorgid()
dbname = get_module_dbname('discount')
# discount为0或空时,如果已有记录则删除
if not discount_val or float(discount_val) == 0:
if detail_id:
async with DBPools().sqlorContext(dbname) as sor:
await sor.D('discount_detail', {'id': detail_id})
result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '折扣已清除', 'type': 'success', 'timeout': 3}}
else:
result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': 'ok', 'type': 'success', 'timeout': 3}}
return result
async with DBPools().sqlorContext(dbname) as sor:
# 检查是否客户专属折扣方案:如果是,验证折扣不高于全部用户折扣
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 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})
if default_recs and default_recs[0].discount is not None:
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}}
return result
if detail_id:
await sor.U('discount_detail', {
'id': detail_id,
'discount': float(discount_val)
})
else:
await sor.C('discount_detail', {
'id': getID(),
'discountid': discountid,
'resellerid': user_orgid,
'prodtypeid': None,
'productid': productid,
'discount': float(discount_val)
})
result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '折扣保存成功', 'type': 'success', 'timeout': 3}}
except Exception as e:
debug(f'save_discount error: {format_exc()}')
result['options'] = {'title': 'Error', 'message': f'保存失败: {str(e)}', 'type': 'error', 'timeout': 5}
return result