bugfix
This commit is contained in:
parent
a25c9e56dc
commit
b41c89e9dd
58
README.md
58
README.md
@ -1,7 +1,61 @@
|
|||||||
# discount
|
# discount
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
支持商户为客户设置折扣,一旦客户设置折扣,商户的所有商品此客户均享受商户为其设置的折扣
|
支持商户为客户设置折扣,可以为不同产品设置不同的折扣值。
|
||||||
|
|
||||||
##
|
## 数据模型
|
||||||
|
|
||||||
|
### discount (折扣表)
|
||||||
|
主折扣记录表,定义商户-客户关系的折扣。
|
||||||
|
- id: 折扣id
|
||||||
|
- name: 折扣名称
|
||||||
|
- resellerid: 商户id
|
||||||
|
- customerid: 客户id (可为NULL表示商户级默认折扣)
|
||||||
|
- enabled_date: 启用日期
|
||||||
|
- expired_date: 失效日期
|
||||||
|
|
||||||
|
### discount_detail (折扣产品明细表)
|
||||||
|
存储每个产品的具体折扣值。
|
||||||
|
- id: 明细id
|
||||||
|
- discountid: 关联折扣id
|
||||||
|
- resellerid: 商户id
|
||||||
|
- prodtypeid: 产品类型id (可为NULL表示所有类型)
|
||||||
|
- productid: 产品id (可为NULL表示该类型下所有产品)
|
||||||
|
- discount: 折扣值 (0 < discount < 1)
|
||||||
|
|
||||||
|
### discount_qr (折扣二维码表)
|
||||||
|
促销二维码记录。
|
||||||
|
- id: 二维码id
|
||||||
|
- resellerid: 商户id
|
||||||
|
- discount: 折扣值
|
||||||
|
- valid_term: 有效期 (如 30D, 3M, 1Y)
|
||||||
|
- expired_date: 促销失效日期
|
||||||
|
- qr_webpath: 二维码路径
|
||||||
|
|
||||||
|
## 折扣查询
|
||||||
|
|
||||||
|
### 获取产品折扣
|
||||||
|
```python
|
||||||
|
from discount.init import get_product_discount
|
||||||
|
|
||||||
|
discount = await get_product_discount(resellerid, customerid, prodtypeid, productid)
|
||||||
|
# 返回折扣值 (float),1.0表示无折扣
|
||||||
|
```
|
||||||
|
|
||||||
|
查询优先级:
|
||||||
|
1. 精确匹配:产品类型 + 产品id
|
||||||
|
2. 类型级别匹配:产品类型 (productid为NULL)
|
||||||
|
3. 默认匹配:所有产品 (prodtypeid和productid均为NULL)
|
||||||
|
|
||||||
|
### 获取客户默认折扣 (兼容旧接口)
|
||||||
|
```python
|
||||||
|
from discount.init import get_customer_discount
|
||||||
|
|
||||||
|
discount = await get_customer_discount(resellerid, customerid)
|
||||||
|
# 返回客户默认折扣值
|
||||||
|
```
|
||||||
|
|
||||||
|
## 维护界面
|
||||||
|
- 折扣管理: discount_list.ui - 创建/编辑/删除折扣记录
|
||||||
|
- 折扣产品明细: discount_detail_list.ui?discountid=xxx - 为指定折扣添加产品明细,设置各产品折扣值
|
||||||
|
- 生成促销码: promote.ui - 生成促销二维码
|
||||||
|
|||||||
1
discount/__init__.py
Normal file
1
discount/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
428
discount/init.py
428
discount/init.py
@ -1,144 +1,340 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
from appPublic.qr import gen_qr_withlogo
|
from appPublic.qr import gen_qr_withlogo
|
||||||
from appPublic.uniqueID import getID
|
from appPublic.uniqueID import getID
|
||||||
from appPublic.jsonConfig import getConfig
|
from appPublic.jsonConfig import getConfig
|
||||||
from appPublic.dictObject import DictObject
|
from appPublic.dictObject import DictObject
|
||||||
from sqlor.dbpools import get_sor_context
|
from sqlor.dbpools import DBPools
|
||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
from ahserver.filestorage import FileStorage
|
from ahserver.filestorage import FileStorage
|
||||||
|
|
||||||
async def discount_qrcode(request, params_kw):
|
|
||||||
"""
|
|
||||||
discount less then 1 and greate then 0
|
|
||||||
valid_term is digit + one of ["D", "M", "Y"]
|
|
||||||
expired_date is a date after it the promote qrcode invalidable
|
|
||||||
"""
|
|
||||||
discount = params_kw.discount
|
|
||||||
valid_term = params_kw.valid_term
|
|
||||||
expired_date = params_kw.expired_date
|
|
||||||
env = request._run_ns
|
|
||||||
resellerid = await env.get_userorgid()
|
|
||||||
id = getID()
|
|
||||||
url = env.entire_url('./promote') + f'?id={id}'
|
|
||||||
fs = FileStorage()
|
|
||||||
p = fs._name2path(f'{getID()}.png')
|
|
||||||
config = getConfig()
|
|
||||||
gen_qr_withlogo(url, p, logopath=config.logopath, logoloc='cc')
|
|
||||||
webp = fs.webpath(p)
|
|
||||||
async with get_sor_context(env, 'discount') as sor:
|
|
||||||
biz_date = await env.get_business_date(sor)
|
|
||||||
if biz_date >= expired_date:
|
|
||||||
raise Exception('Promote QRCODE is out of time')
|
|
||||||
|
|
||||||
ret = {
|
async def discount_qrcode(request, params_kw):
|
||||||
'id': id,
|
"""
|
||||||
'resellerid': resellerid,
|
Generate a promotional QR code for discount
|
||||||
'discount': discount,
|
discount less then 1 and greate then 0
|
||||||
'valid_term': valid_term,
|
valid_term is digit + one of ["D", "M", "Y"]
|
||||||
'expired_date': expired_date,
|
expired_date is a date after it the promote qrcode invalidable
|
||||||
'qr_webpath': webp
|
"""
|
||||||
}
|
discount = params_kw.discount
|
||||||
await sor.C('discount_qr', ret.copy())
|
valid_term = params_kw.valid_term
|
||||||
return DictObject(**ret)
|
expired_date = params_kw.expired_date
|
||||||
return None
|
env = ServerEnv()
|
||||||
|
dbname = env.get_module_dbname('discount')
|
||||||
|
config = getConfig()
|
||||||
|
db = DBPools()
|
||||||
|
db.databases = config.databases
|
||||||
|
resellerid = await env.get_userorgid()
|
||||||
|
qr_id = getID()
|
||||||
|
url = env.entire_url('./promote') + f'?id={qr_id}'
|
||||||
|
fs = FileStorage()
|
||||||
|
p = fs._name2path(f'{getID()}.png')
|
||||||
|
gen_qr_withlogo(url, p, logopath=config.logopath, logoloc='cc')
|
||||||
|
webp = fs.webpath(p)
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
biz_date = await env.get_business_date(sor)
|
||||||
|
if biz_date >= expired_date:
|
||||||
|
raise Exception('Promote QRCODE is out of time')
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
'id': qr_id,
|
||||||
|
'resellerid': resellerid,
|
||||||
|
'discount': discount,
|
||||||
|
'valid_term': valid_term,
|
||||||
|
'expired_date': expired_date,
|
||||||
|
'qr_webpath': webp
|
||||||
|
}
|
||||||
|
await sor.C('discount_qr', ret.copy())
|
||||||
|
return DictObject(**ret)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def set_promote_discount(request, params_kw):
|
async def set_promote_discount(request, params_kw):
|
||||||
env = request._run_ns
|
env = ServerEnv()
|
||||||
id = params_kw.id
|
dbname = env.get_module_dbname('discount')
|
||||||
customerid = await env.get_userorgid()
|
config = getConfig()
|
||||||
async with get_sor_context(env, 'discount') as sor:
|
db = DBPools()
|
||||||
recs = await sor.R('discount_qr', {'id': id})
|
db.databases = config.databases
|
||||||
if not recs:
|
id = params_kw.id
|
||||||
raise Exception(f'promote id({id}) not exists')
|
customerid = await env.get_userorgid()
|
||||||
biz_date = await env.get_business_date(sor)
|
async with db.sqlorContext(dbname) as sor:
|
||||||
if recs[0].expired_date <= biz_date:
|
recs = await sor.R('discount_qr', {'id': id})
|
||||||
raise Exception('Promote QRCODE is out of time')
|
if not recs:
|
||||||
cnt = int(recs[0].valid_term[:-1])
|
raise Exception(f'promote id({id}) not exists')
|
||||||
unit = recs[0].valid_term[-1]
|
biz_date = await env.get_business_date(sor)
|
||||||
enabled_date = biz_date
|
if recs[0].expired_date <= biz_date:
|
||||||
expired_date = ''
|
raise Exception('Promote QRCODE is out of time')
|
||||||
if unit == 'D':
|
cnt = int(recs[0].valid_term[:-1])
|
||||||
expired_date = env.strdate_add(enabled_date, days=cnt)
|
unit = recs[0].valid_term[-1]
|
||||||
elif unit == 'M':
|
enabled_date = biz_date
|
||||||
expired_date = env.strdate_add(enabled_date, months=cnt)
|
expired_date = ''
|
||||||
elif unit == 'Y':
|
if unit == 'D':
|
||||||
expired_date = env.strdate_add(enabled_date, years=cnt)
|
expired_date = env.strdate_add(enabled_date, days=cnt)
|
||||||
else:
|
elif unit == 'M':
|
||||||
raise Exception(f'Invalid valid_term({recs[0].valid_term})')
|
expired_date = env.strdate_add(enabled_date, months=cnt)
|
||||||
need_new_discount = await disable_old_discount(sor, recs[0].resellerid,
|
elif unit == 'Y':
|
||||||
customerid, biz_date, recs[0].discount)
|
expired_date = env.strdate_add(enabled_date, years=cnt)
|
||||||
if not need_new_discount:
|
else:
|
||||||
return await sor_get_customer_discount(sor, recs[0].resellerid, customerid)
|
raise Exception(f'Invalid valid_term({recs[0].valid_term})')
|
||||||
ret = {
|
|
||||||
'id': getID(),
|
# Disable old active discount for this customer
|
||||||
|
await disable_old_discount(sor, recs[0].resellerid, customerid, biz_date)
|
||||||
|
|
||||||
|
# Create new discount record (no longer stores discount value directly)
|
||||||
|
discountid = getID()
|
||||||
|
ret = {
|
||||||
|
'id': discountid,
|
||||||
|
'name': f'促销折扣-{biz_date}',
|
||||||
'resellerid': recs[0].resellerid,
|
'resellerid': recs[0].resellerid,
|
||||||
'customerid': customerid,
|
'customerid': customerid,
|
||||||
'discount': recs[0].discount,
|
|
||||||
'enabled_date': enabled_date,
|
'enabled_date': enabled_date,
|
||||||
'expired_date': expired_date
|
'expired_date': expired_date
|
||||||
}
|
}
|
||||||
await sor.C('discount', ret.copy())
|
await sor.C('discount', ret.copy())
|
||||||
return recs[0].discount
|
|
||||||
return None
|
# Create discount_detail record with the discount value from QR code
|
||||||
|
# prodtypeid=None, productid=None means applies to all products
|
||||||
|
detail_ret = {
|
||||||
|
'id': getID(),
|
||||||
|
'discountid': discountid,
|
||||||
|
'resellerid': recs[0].resellerid,
|
||||||
|
'prodtypeid': None,
|
||||||
|
'productid': None,
|
||||||
|
'discount': recs[0].discount,
|
||||||
|
}
|
||||||
|
await sor.C('discount_detail', detail_ret.copy())
|
||||||
|
|
||||||
|
return recs[0].discount
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def disable_old_discount(sor, resellerid, customerid, biz_date):
|
||||||
|
"""Disable any active discount record for the given reseller+customer pair."""
|
||||||
|
# Use sor.R with sort instead of raw SQL + FOR UPDATE (DB-agnostic)
|
||||||
|
recs = await sor.R('discount', {
|
||||||
|
'resellerid': resellerid,
|
||||||
|
'customerid': customerid,
|
||||||
|
'sort': 'enabled_date desc'
|
||||||
|
})
|
||||||
|
if not recs:
|
||||||
|
return
|
||||||
|
# Find the active one in Python (DB-agnostic date comparison)
|
||||||
|
for rec in recs:
|
||||||
|
if rec.get('enabled_date', '') <= biz_date and rec.get('expired_date', '') > biz_date:
|
||||||
|
await sor.U('discount', {'id': rec['id'], 'expired_date': biz_date})
|
||||||
|
return
|
||||||
|
|
||||||
async def disable_old_discount(sor, resellerid, customerid, biz_date, new_discount):
|
|
||||||
sql = """select * from discount
|
|
||||||
where resellerid = ${resellerid}$
|
|
||||||
and customerid=${customerid}$
|
|
||||||
and enabled_date <= ${biz_date}$
|
|
||||||
and expired_date > ${biz_date}$ for update"""
|
|
||||||
recs = await sor.sqlExe(sql, {'resellerid': resellerid, 'customerid': customerid, 'biz_date': biz_date})
|
|
||||||
if not recs:
|
|
||||||
return True
|
|
||||||
if new_discount > recs[0].discount:
|
|
||||||
return False
|
|
||||||
await sor.U('discount', {'id': recs[0].id, 'expired_date': biz_date})
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def sor_get_star_discount(sor, resellerid, biz_date):
|
async def sor_get_star_discount(sor, resellerid, biz_date):
|
||||||
env = ServerEnv()
|
"""Get default discount for a reseller (no specific customer)."""
|
||||||
sql = """select * from discount
|
sql = """select d.id from discount d
|
||||||
where resellerid = ${resellerid}$
|
where d.resellerid = ${resellerid}$
|
||||||
and enabled_date <= ${biz_date}$
|
and d.customerid is NULL
|
||||||
and expired_date > ${biz_date}$"""
|
and d.enabled_date <= ${biz_date}$
|
||||||
ns = {
|
and d.expired_date > ${biz_date}$"""
|
||||||
"resellerid": resellerid,
|
ns = {
|
||||||
"biz_date": biz_date
|
"resellerid": resellerid,
|
||||||
}
|
"biz_date": biz_date
|
||||||
recs = await sor.sqlExe(sql, ns)
|
}
|
||||||
if not recs:
|
recs = await sor.sqlExe(sql, ns)
|
||||||
return 1
|
if not recs:
|
||||||
|
return 1
|
||||||
|
|
||||||
return recs[0].discount
|
# Look up default discount detail (prodtypeid=None, productid=None)
|
||||||
|
sql2 = """select discount from discount_detail
|
||||||
|
where discountid = ${discountid}$
|
||||||
|
and prodtypeid is NULL
|
||||||
|
and productid is NULL"""
|
||||||
|
recs2 = await sor.sqlExe(sql2, {'discountid': recs[0].id})
|
||||||
|
if not recs2:
|
||||||
|
return 1
|
||||||
|
return recs2[0].discount
|
||||||
|
|
||||||
|
|
||||||
async def sor_get_customer_discount(sor, resellerid, customerid):
|
async def sor_get_customer_discount(sor, resellerid, customerid):
|
||||||
env = ServerEnv()
|
"""Get discount record for a customer (legacy, returns record not value).
|
||||||
biz_date = await env.get_business_date(sor)
|
Use sor_get_product_discount for product-specific discount."""
|
||||||
sql = """select * from discount
|
env = ServerEnv()
|
||||||
|
biz_date = await env.get_business_date(sor)
|
||||||
|
sql = """select * from discount
|
||||||
where resellerid = ${resellerid}$
|
where resellerid = ${resellerid}$
|
||||||
and customerid = ${customerid}$
|
and customerid = ${customerid}$
|
||||||
and enabled_date <= ${biz_date}$
|
and enabled_date <= ${biz_date}$
|
||||||
and expired_date > ${biz_date}$"""
|
and expired_date > ${biz_date}$"""
|
||||||
ns = {
|
ns = {
|
||||||
"resellerid": resellerid,
|
"resellerid": resellerid,
|
||||||
"customerid": customerid,
|
"customerid": customerid,
|
||||||
"biz_date": biz_date
|
"biz_date": biz_date
|
||||||
}
|
}
|
||||||
recs = await sor.sqlExe(sql, ns)
|
recs = await sor.sqlExe(sql, ns)
|
||||||
if not recs:
|
if not recs:
|
||||||
return await sor_get_star_discount(sor, resellerid, biz_date)
|
return await sor_get_star_discount(sor, resellerid, biz_date)
|
||||||
|
|
||||||
|
# Return default discount for this customer (prodtypeid=None, productid=None)
|
||||||
|
sql2 = """select discount from discount_detail
|
||||||
|
where discountid = ${discountid}$
|
||||||
|
and prodtypeid is NULL
|
||||||
|
and productid is NULL"""
|
||||||
|
recs2 = await sor.sqlExe(sql2, {'discountid': recs[0].id})
|
||||||
|
if not recs2:
|
||||||
|
return 1
|
||||||
|
return recs2[0].discount
|
||||||
|
|
||||||
|
|
||||||
|
async def sor_get_product_discount(sor, resellerid, customerid, prodtypeid, productid):
|
||||||
|
"""
|
||||||
|
Get product-specific discount.
|
||||||
|
Lookup priority:
|
||||||
|
1. Exact match: discountid -> (prodtypeid, productid)
|
||||||
|
2. Type-level match: discountid -> (prodtypeid, NULL)
|
||||||
|
3. Default match: discountid -> (NULL, NULL)
|
||||||
|
|
||||||
|
Returns discount value (float), default 1.0 (no discount).
|
||||||
|
"""
|
||||||
|
env = ServerEnv()
|
||||||
|
biz_date = await env.get_business_date(sor)
|
||||||
|
|
||||||
|
# Step 1: Find active discount record for reseller+customer
|
||||||
|
sql = """select id from discount
|
||||||
|
where resellerid = ${resellerid}$
|
||||||
|
and customerid = ${customerid}$
|
||||||
|
and enabled_date <= ${biz_date}$
|
||||||
|
and expired_date > ${biz_date}$"""
|
||||||
|
ns = {
|
||||||
|
"resellerid": resellerid,
|
||||||
|
"customerid": customerid,
|
||||||
|
"biz_date": biz_date
|
||||||
|
}
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
if not recs:
|
||||||
|
# Try reseller-level default (customerid is NULL)
|
||||||
|
sql = """select id from discount
|
||||||
|
where resellerid = ${resellerid}$
|
||||||
|
and customerid is NULL
|
||||||
|
and enabled_date <= ${biz_date}$
|
||||||
|
and expired_date > ${biz_date}$"""
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
if not recs:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
discountid = recs[0].id
|
||||||
|
|
||||||
|
# Step 2: Try exact product match
|
||||||
|
sql2 = """select discount from discount_detail
|
||||||
|
where discountid = ${discountid}$
|
||||||
|
and prodtypeid = ${prodtypeid}$
|
||||||
|
and productid = ${productid}$"""
|
||||||
|
recs2 = await sor.sqlExe(sql2, {
|
||||||
|
'discountid': discountid,
|
||||||
|
'prodtypeid': prodtypeid,
|
||||||
|
'productid': productid
|
||||||
|
})
|
||||||
|
if recs2:
|
||||||
|
return recs2[0].discount
|
||||||
|
|
||||||
|
# Step 3: Try product type level match (productid is NULL)
|
||||||
|
sql3 = """select discount from discount_detail
|
||||||
|
where discountid = ${discountid}$
|
||||||
|
and prodtypeid = ${prodtypeid}$
|
||||||
|
and productid is NULL"""
|
||||||
|
recs3 = await sor.sqlExe(sql3, {
|
||||||
|
'discountid': discountid,
|
||||||
|
'prodtypeid': prodtypeid,
|
||||||
|
})
|
||||||
|
if recs3:
|
||||||
|
return recs3[0].discount
|
||||||
|
|
||||||
|
# Step 4: Try default match (both NULL)
|
||||||
|
sql4 = """select discount from discount_detail
|
||||||
|
where discountid = ${discountid}$
|
||||||
|
and prodtypeid is NULL
|
||||||
|
and productid is NULL"""
|
||||||
|
recs4 = await sor.sqlExe(sql4, {'discountid': discountid})
|
||||||
|
if recs4:
|
||||||
|
return recs4[0].discount
|
||||||
|
|
||||||
|
return 1.0
|
||||||
|
|
||||||
return recs[0].discount
|
|
||||||
|
|
||||||
async def get_customer_discount(resellerid, customerid):
|
async def get_customer_discount(resellerid, customerid):
|
||||||
env = ServerEnv()
|
"""Legacy: get default discount for a customer (all products)."""
|
||||||
async with get_sor_context(env, 'discount') as sor:
|
env = ServerEnv()
|
||||||
return await sor_get_customer_discount(sor, resellerid, customerid)
|
dbname = env.get_module_dbname('discount')
|
||||||
return 1
|
config = getConfig()
|
||||||
|
db = DBPools()
|
||||||
|
db.databases = config.databases
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
return await sor_get_customer_discount(sor, resellerid, customerid)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
async def get_product_discount(resellerid, customerid, prodtypeid, productid):
|
||||||
|
"""
|
||||||
|
Get product-specific discount.
|
||||||
|
Parameters:
|
||||||
|
resellerid: merchant ID
|
||||||
|
customerid: customer ID
|
||||||
|
prodtypeid: product type ID
|
||||||
|
productid: product ID
|
||||||
|
Returns: discount value (float), 1.0 means no discount.
|
||||||
|
"""
|
||||||
|
env = ServerEnv()
|
||||||
|
dbname = env.get_module_dbname('discount')
|
||||||
|
config = getConfig()
|
||||||
|
db = DBPools()
|
||||||
|
db.databases = config.databases
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
return await sor_get_product_discount(sor, resellerid, customerid, prodtypeid, productid)
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
|
async def get_discount_details(sor, discountid):
|
||||||
|
"""Get all product detail records for a given discount."""
|
||||||
|
sql = """select * from discount_detail
|
||||||
|
where discountid = ${discountid}$
|
||||||
|
order by prodtypeid, productid"""
|
||||||
|
recs = await sor.sqlExe(sql, {'discountid': discountid})
|
||||||
|
return recs
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
'id': getID(),
|
||||||
|
'discountid': discountid,
|
||||||
|
'resellerid': resellerid,
|
||||||
|
'prodtypeid': prodtypeid if prodtypeid else None,
|
||||||
|
'productid': productid if productid else None,
|
||||||
|
'discount': discount,
|
||||||
|
}
|
||||||
|
await sor.C('discount_detail', ret.copy())
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
await sor.U('discount_detail', {'id': detail_id, 'discount': discount})
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_discount_detail(sor, detail_id):
|
||||||
|
"""Delete a discount detail record."""
|
||||||
|
await sor.D('discount_detail', {'id': detail_id})
|
||||||
|
|
||||||
|
|
||||||
def load_discount():
|
def load_discount():
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
env.get_customer_discount = get_customer_discount
|
env.get_customer_discount = get_customer_discount
|
||||||
env.sor_get_customer_discount = sor_get_customer_discount
|
env.sor_get_customer_discount = sor_get_customer_discount
|
||||||
env.discount_qrcode = discount_qrcode
|
env.get_product_discount = get_product_discount
|
||||||
env.set_promote_discount = set_promote_discount
|
env.sor_get_product_discount = sor_get_product_discount
|
||||||
|
env.discount_qrcode = discount_qrcode
|
||||||
|
env.set_promote_discount = set_promote_discount
|
||||||
|
env.get_discount_details = get_discount_details
|
||||||
|
env.add_discount_detail = add_discount_detail
|
||||||
|
env.update_discount_detail = update_discount_detail
|
||||||
|
env.delete_discount_detail = delete_discount_detail
|
||||||
|
|||||||
52
json/discount_detail_list.json
Normal file
52
json/discount_detail_list.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"tblname": "discount_detail",
|
||||||
|
"alias": "discount_detail_list",
|
||||||
|
"title": "折扣产品明细",
|
||||||
|
"params": {
|
||||||
|
"sortby": ["prodtypeid", "productid"],
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id", "resellerid"],
|
||||||
|
"alters": {
|
||||||
|
"discountid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "/get_code.dspy",
|
||||||
|
"datamethod": "GET",
|
||||||
|
"dataparams": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "discount",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prodtypeid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "/get_code.dspy",
|
||||||
|
"datamethod": "GET",
|
||||||
|
"dataparams": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "prodtype",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"productid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "/get_code.dspy",
|
||||||
|
"datamethod": "GET",
|
||||||
|
"dataparams": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "product",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editexclouded": ["id", "discountid", "resellerid"],
|
||||||
|
"editable": {
|
||||||
|
"new_data_url": "{{entire_url('../api/discount_detail_create.dspy')}}",
|
||||||
|
"update_data_url": "{{entire_url('../api/discount_detail_update.dspy')}}",
|
||||||
|
"delete_data_url": "{{entire_url('../api/discount_detail_delete.dspy')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
json/discount_list.json
Normal file
41
json/discount_list.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"tblname": "discount",
|
||||||
|
"alias": "discount_list",
|
||||||
|
"title": "折扣管理",
|
||||||
|
"params": {
|
||||||
|
"sortby": ["enabled_date desc"],
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"alters": {
|
||||||
|
"resellerid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "/get_code.dspy",
|
||||||
|
"datamethod": "GET",
|
||||||
|
"dataparams": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "organization",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "orgname"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customerid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "/get_code.dspy",
|
||||||
|
"datamethod": "GET",
|
||||||
|
"dataparams": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "organization",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "orgname"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editexclouded": ["id"],
|
||||||
|
"editable": {
|
||||||
|
"new_data_url": "{{entire_url('../api/discount_create.dspy')}}",
|
||||||
|
"update_data_url": "{{entire_url('../api/discount_update.dspy')}}",
|
||||||
|
"delete_data_url": "{{entire_url('../api/discount_delete.dspy')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
models/discount_detail.xlsx
Normal file
BIN
models/discount_detail.xlsx
Normal file
Binary file not shown.
@ -12,9 +12,8 @@ authors = [
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apppublic",
|
"sqlor",
|
||||||
"sqlor",
|
"bricks_for_python"
|
||||||
"ahserver"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
|
|||||||
@ -1,58 +1,63 @@
|
|||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
|
||||||
params_kw.discount = float(params_kw.discount)
|
params_kw.discount = float(params_kw.discount)
|
||||||
if params_kw.discount <= 0 or params_kw.discount >= 1:
|
if params_kw.discount <= 0 or params_kw.discount >= 1:
|
||||||
e = Exception(f'discount({params_kw.discount}) invalid')
|
e = Exception(f'discount({params_kw.discount}) invalid')
|
||||||
exception(f'{e}')
|
exception(f'{e}')
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if params_kw.valid_term[-1] not in ['D', 'M', 'Y']:
|
if params_kw.valid_term[-1] not in ['D', 'M', 'Y']:
|
||||||
e = Exception(f'valid_term must ends with "D", "M" or "Y"')
|
e = Exception(f'valid_term must ends with "D", "M" or "Y"')
|
||||||
exception(f'{e}')
|
exception(f'{e}')
|
||||||
raise e
|
raise e
|
||||||
cnt = int(params_kw.valid_term[:-1])
|
cnt = int(params_kw.valid_term[:-1])
|
||||||
if cnt < 0:
|
if cnt < 0:
|
||||||
e = Exception(f'valid_term({params_kw.valid_term}) invalid')
|
e = Exception(f'valid_term({params_kw.valid_term}) invalid')
|
||||||
exception(f'{e}')
|
exception(f'{e}')
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if not params_kw.expired_date:
|
if not params_kw.expired_date:
|
||||||
params_kw.expired_date = '9999-12-31'
|
params_kw.expired_date = '9999-12-31'
|
||||||
x = await discount_qrcode(request, params_kw)
|
x = await discount_qrcode(request, params_kw)
|
||||||
return {
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"height": "100%"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Image",
|
|
||||||
"options": {
|
|
||||||
"width": "340px",
|
|
||||||
"height": "340px",
|
|
||||||
"url": entire_url('/idfile') + f'?path={x.qr_webpath}'
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"cheight": 2
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"otext": "折扣:",
|
|
||||||
"i18n": True,
|
|
||||||
"cwidth": 3
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options":{
|
|
||||||
"text": x.discount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
env = ServerEnv()
|
||||||
|
qr_url = env.entire_url('/idfile') + f'?path={x.qr_webpath}'
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Image",
|
||||||
|
"options": {
|
||||||
|
"width": "340px",
|
||||||
|
"height": "340px",
|
||||||
|
"url": qr_url
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"cheight": 2
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"otext": "折扣:",
|
||||||
|
"i18n": True,
|
||||||
|
"cwidth": 3
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options":{
|
||||||
|
"text": x.discount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@ -5,11 +5,21 @@
|
|||||||
"target":"root.page_center",
|
"target":"root.page_center",
|
||||||
"cwidth":10,
|
"cwidth":10,
|
||||||
"items":[
|
"items":[
|
||||||
{
|
{
|
||||||
"name":"promotecode",
|
"name":"discount_list",
|
||||||
"label": "生成促销码",
|
"label": "折扣管理",
|
||||||
"url": "{{entire_url('promote.ui')}}",
|
"url": "{{entire_url('discount_list.ui')}}"
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
}
|
"name":"discount_detail_list",
|
||||||
|
"label": "折扣产品明细",
|
||||||
|
"url": "{{entire_url('discount_detail_list.ui')}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"promotecode",
|
||||||
|
"label": "生成促销码",
|
||||||
|
"url": "{{entire_url('promote.ui')}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
|
"url": "{{entire_url('generate_qr.dspy')}}",
|
||||||
|
"method": "POST",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"name": "valid_term",
|
"name": "valid_term",
|
||||||
@ -16,7 +18,7 @@
|
|||||||
"tip": "促销二维码需在此日期前使用"
|
"tip": "促销二维码需在此日期前使用"
|
||||||
},{
|
},{
|
||||||
"name": "discount",
|
"name": "discount",
|
||||||
"lable": "折扣",
|
"label": "折扣",
|
||||||
"uitype": "float",
|
"uitype": "float",
|
||||||
"tip": "折扣>0, < 1, 最终价格=产品价格*折扣, 折扣越小,折扣力度越大"
|
"tip": "折扣>0, < 1, 最终价格=产品价格*折扣, 折扣越小,折扣力度越大"
|
||||||
}
|
}
|
||||||
@ -25,13 +27,10 @@
|
|||||||
"binds":[
|
"binds":[
|
||||||
{
|
{
|
||||||
"wid": "self",
|
"wid": "self",
|
||||||
"event": "submit",
|
"event": "submited",
|
||||||
"actiontype": "urlwidget",
|
"actiontype": "script",
|
||||||
"target": "self",
|
"target": "self",
|
||||||
"options": {
|
"script": "await bricks.show_resp_message_or_error(event.params)"
|
||||||
"url": "{{entire_url('generate_qr.dspy')}}",
|
|
||||||
"params": {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user