bugfix
This commit is contained in:
parent
a25c9e56dc
commit
b41c89e9dd
58
README.md
58
README.md
@ -1,7 +1,61 @@
|
||||
# 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.uniqueID import getID
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.dictObject import DictObject
|
||||
from sqlor.dbpools import get_sor_context
|
||||
from sqlor.dbpools import DBPools
|
||||
from ahserver.serverenv import ServerEnv
|
||||
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 = {
|
||||
'id': 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 discount_qrcode(request, params_kw):
|
||||
"""
|
||||
Generate a promotional QR code for discount
|
||||
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 = 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):
|
||||
env = request._run_ns
|
||||
id = params_kw.id
|
||||
customerid = await env.get_userorgid()
|
||||
async with get_sor_context(env, 'discount') as sor:
|
||||
recs = await sor.R('discount_qr', {'id': id})
|
||||
if not recs:
|
||||
raise Exception(f'promote id({id}) not exists')
|
||||
biz_date = await env.get_business_date(sor)
|
||||
if recs[0].expired_date <= biz_date:
|
||||
raise Exception('Promote QRCODE is out of time')
|
||||
cnt = int(recs[0].valid_term[:-1])
|
||||
unit = recs[0].valid_term[-1]
|
||||
enabled_date = biz_date
|
||||
expired_date = ''
|
||||
if unit == 'D':
|
||||
expired_date = env.strdate_add(enabled_date, days=cnt)
|
||||
elif unit == 'M':
|
||||
expired_date = env.strdate_add(enabled_date, months=cnt)
|
||||
elif unit == 'Y':
|
||||
expired_date = env.strdate_add(enabled_date, years=cnt)
|
||||
else:
|
||||
raise Exception(f'Invalid valid_term({recs[0].valid_term})')
|
||||
need_new_discount = await disable_old_discount(sor, recs[0].resellerid,
|
||||
customerid, biz_date, recs[0].discount)
|
||||
if not need_new_discount:
|
||||
return await sor_get_customer_discount(sor, recs[0].resellerid, customerid)
|
||||
ret = {
|
||||
'id': getID(),
|
||||
env = ServerEnv()
|
||||
dbname = env.get_module_dbname('discount')
|
||||
config = getConfig()
|
||||
db = DBPools()
|
||||
db.databases = config.databases
|
||||
id = params_kw.id
|
||||
customerid = await env.get_userorgid()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R('discount_qr', {'id': id})
|
||||
if not recs:
|
||||
raise Exception(f'promote id({id}) not exists')
|
||||
biz_date = await env.get_business_date(sor)
|
||||
if recs[0].expired_date <= biz_date:
|
||||
raise Exception('Promote QRCODE is out of time')
|
||||
cnt = int(recs[0].valid_term[:-1])
|
||||
unit = recs[0].valid_term[-1]
|
||||
enabled_date = biz_date
|
||||
expired_date = ''
|
||||
if unit == 'D':
|
||||
expired_date = env.strdate_add(enabled_date, days=cnt)
|
||||
elif unit == 'M':
|
||||
expired_date = env.strdate_add(enabled_date, months=cnt)
|
||||
elif unit == 'Y':
|
||||
expired_date = env.strdate_add(enabled_date, years=cnt)
|
||||
else:
|
||||
raise Exception(f'Invalid valid_term({recs[0].valid_term})')
|
||||
|
||||
# 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,
|
||||
'customerid': customerid,
|
||||
'discount': recs[0].discount,
|
||||
'enabled_date': enabled_date,
|
||||
'expired_date': expired_date
|
||||
}
|
||||
await sor.C('discount', ret.copy())
|
||||
return recs[0].discount
|
||||
return None
|
||||
await sor.C('discount', ret.copy())
|
||||
|
||||
# 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):
|
||||
env = ServerEnv()
|
||||
sql = """select * from discount
|
||||
where resellerid = ${resellerid}$
|
||||
and enabled_date <= ${biz_date}$
|
||||
and expired_date > ${biz_date}$"""
|
||||
ns = {
|
||||
"resellerid": resellerid,
|
||||
"biz_date": biz_date
|
||||
}
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
if not recs:
|
||||
return 1
|
||||
"""Get default discount for a reseller (no specific customer)."""
|
||||
sql = """select d.id from discount d
|
||||
where d.resellerid = ${resellerid}$
|
||||
and d.customerid is NULL
|
||||
and d.enabled_date <= ${biz_date}$
|
||||
and d.expired_date > ${biz_date}$"""
|
||||
ns = {
|
||||
"resellerid": resellerid,
|
||||
"biz_date": biz_date
|
||||
}
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
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):
|
||||
env = ServerEnv()
|
||||
biz_date = await env.get_business_date(sor)
|
||||
sql = """select * from discount
|
||||
"""Get discount record for a customer (legacy, returns record not value).
|
||||
Use sor_get_product_discount for product-specific discount."""
|
||||
env = ServerEnv()
|
||||
biz_date = await env.get_business_date(sor)
|
||||
sql = """select * 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:
|
||||
return await sor_get_star_discount(sor, resellerid, biz_date)
|
||||
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:
|
||||
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):
|
||||
env = ServerEnv()
|
||||
async with get_sor_context(env, 'discount') as sor:
|
||||
return await sor_get_customer_discount(sor, resellerid, customerid)
|
||||
return 1
|
||||
"""Legacy: get default discount for a customer (all products)."""
|
||||
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_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():
|
||||
env = ServerEnv()
|
||||
env.get_customer_discount = get_customer_discount
|
||||
env.sor_get_customer_discount = sor_get_customer_discount
|
||||
env.discount_qrcode = discount_qrcode
|
||||
env.set_promote_discount = set_promote_discount
|
||||
env = ServerEnv()
|
||||
env.get_customer_discount = get_customer_discount
|
||||
env.sor_get_customer_discount = sor_get_customer_discount
|
||||
env.get_product_discount = get_product_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"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"apppublic",
|
||||
"sqlor",
|
||||
"ahserver"
|
||||
"sqlor",
|
||||
"bricks_for_python"
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@ -1,58 +1,63 @@
|
||||
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')
|
||||
exception(f'{e}')
|
||||
raise e
|
||||
e = Exception(f'discount({params_kw.discount}) invalid')
|
||||
exception(f'{e}')
|
||||
raise e
|
||||
|
||||
if params_kw.valid_term[-1] not in ['D', 'M', 'Y']:
|
||||
e = Exception(f'valid_term must ends with "D", "M" or "Y"')
|
||||
exception(f'{e}')
|
||||
raise e
|
||||
e = Exception(f'valid_term must ends with "D", "M" or "Y"')
|
||||
exception(f'{e}')
|
||||
raise e
|
||||
cnt = int(params_kw.valid_term[:-1])
|
||||
if cnt < 0:
|
||||
e = Exception(f'valid_term({params_kw.valid_term}) invalid')
|
||||
exception(f'{e}')
|
||||
raise e
|
||||
e = Exception(f'valid_term({params_kw.valid_term}) invalid')
|
||||
exception(f'{e}')
|
||||
raise e
|
||||
|
||||
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)
|
||||
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",
|
||||
"cwidth":10,
|
||||
"items":[
|
||||
{
|
||||
"name":"promotecode",
|
||||
"label": "生成促销码",
|
||||
"url": "{{entire_url('promote.ui')}}",
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"name":"discount_list",
|
||||
"label": "折扣管理",
|
||||
"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": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"url": "{{entire_url('generate_qr.dspy')}}",
|
||||
"method": "POST",
|
||||
"fields": [
|
||||
{
|
||||
"name": "valid_term",
|
||||
@ -16,7 +18,7 @@
|
||||
"tip": "促销二维码需在此日期前使用"
|
||||
},{
|
||||
"name": "discount",
|
||||
"lable": "折扣",
|
||||
"label": "折扣",
|
||||
"uitype": "float",
|
||||
"tip": "折扣>0, < 1, 最终价格=产品价格*折扣, 折扣越小,折扣力度越大"
|
||||
}
|
||||
@ -25,13 +27,10 @@
|
||||
"binds":[
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "submit",
|
||||
"actiontype": "urlwidget",
|
||||
"event": "submited",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"options": {
|
||||
"url": "{{entire_url('generate_qr.dspy')}}",
|
||||
"params": {}
|
||||
}
|
||||
"script": "await bricks.show_resp_message_or_error(event.params)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user