diff --git a/discount/init.py b/discount/init.py index bee6f5b..d102854 100644 --- a/discount/init.py +++ b/discount/init.py @@ -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}) diff --git a/wwwroot/discount_setting/index.ui b/wwwroot/discount_setting/index.ui index 7e83b50..8153946 100644 --- a/wwwroot/discount_setting/index.ui +++ b/wwwroot/discount_setting/index.ui @@ -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": [ diff --git a/wwwroot/discount_setting/save_discount.dspy b/wwwroot/discount_setting/save_discount.dspy index ecee3bc..2bf4844 100644 --- a/wwwroot/discount_setting/save_discount.dspy +++ b/wwwroot/discount_setting/save_discount.dspy @@ -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: diff --git a/wwwroot/generate_qr.dspy b/wwwroot/generate_qr.dspy index e64760d..f2995a8 100644 --- a/wwwroot/generate_qr.dspy +++ b/wwwroot/generate_qr.dspy @@ -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 diff --git a/wwwroot/marketing_discount_setting/save_discount.dspy b/wwwroot/marketing_discount_setting/save_discount.dspy index 07eb0ea..d802ff0 100644 --- a/wwwroot/marketing_discount_setting/save_discount.dspy +++ b/wwwroot/marketing_discount_setting/save_discount.dspy @@ -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: