feat: customer assignment - free_customer_assign + customer_reassign pages, register hook bind_type=3, sales list filter
This commit is contained in:
parent
f801507961
commit
029fd91d79
111
discount/init.py
111
discount/init.py
@ -881,6 +881,11 @@ def load_discount():
|
||||
# Marketing plan discount setting
|
||||
env.get_marketing_discount_products = get_marketing_discount_products
|
||||
env.get_marketing_info = get_marketing_info
|
||||
# Customer assignment
|
||||
env.get_free_customers = get_free_customers
|
||||
env.get_bound_customers = get_bound_customers
|
||||
env.save_customer_sale = save_customer_sale
|
||||
env.get_reseller_sales = get_reseller_sales
|
||||
|
||||
|
||||
async def get_discount_setting_products(request):
|
||||
@ -1020,3 +1025,109 @@ async def get_marketing_info(request):
|
||||
if recs:
|
||||
return {'name': recs[0].name or ''}
|
||||
return {}
|
||||
|
||||
|
||||
async def get_free_customers(request):
|
||||
"""Return unbound customers for the current reseller.
|
||||
Used in Jinja2 template: {% set customers = get_free_customers(request) %}
|
||||
"""
|
||||
env = request._run_ns
|
||||
userorgid = await env.get_userorgid()
|
||||
if not userorgid or userorgid == '0':
|
||||
return []
|
||||
dbname = env.get_module_dbname('discount')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# Find customers of this reseller that are unbound (no sale_id)
|
||||
rows = await sor.sqlExe(
|
||||
"""SELECT dcb.id as bind_id, dcb.customerid,
|
||||
o.orgname as customer_name, dcb.sale_id
|
||||
FROM discount_customer_bind dcb
|
||||
JOIN organization o ON o.id = dcb.customerid
|
||||
WHERE dcb.resellerid = ${rid}$ AND dcb.sale_id IS NULL
|
||||
ORDER BY o.orgname""",
|
||||
{'rid': userorgid}
|
||||
)
|
||||
return rows or []
|
||||
|
||||
|
||||
async def get_bound_customers(request):
|
||||
"""Return bound customers with their current sale for the current reseller.
|
||||
Used in Jinja2 template: {% set customers = get_bound_customers(request) %}
|
||||
"""
|
||||
env = request._run_ns
|
||||
userorgid = await env.get_userorgid()
|
||||
if not userorgid or userorgid == '0':
|
||||
return []
|
||||
dbname = env.get_module_dbname('discount')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
rows = await sor.sqlExe(
|
||||
"""SELECT dcb.id as bind_id, dcb.customerid,
|
||||
o.orgname as customer_name, dcb.sale_id,
|
||||
u.nick_name as sale_name
|
||||
FROM discount_customer_bind dcb
|
||||
JOIN organization o ON o.id = dcb.customerid
|
||||
LEFT JOIN users u ON u.id = dcb.sale_id
|
||||
WHERE dcb.resellerid = ${rid}$ AND dcb.sale_id IS NOT NULL
|
||||
ORDER BY o.orgname""",
|
||||
{'rid': userorgid}
|
||||
)
|
||||
return rows or []
|
||||
|
||||
|
||||
async def save_customer_sale(request, params_kw):
|
||||
"""Save/update sale assignment for a customer (upsert on discount_customer_bind).
|
||||
Called from free_customer_assign and customer_reassign pages on blur.
|
||||
"""
|
||||
env = request._run_ns
|
||||
userorgid = await env.get_userorgid()
|
||||
bind_id = params_kw.get('bind_id')
|
||||
customerid = params_kw.get('customerid')
|
||||
sale_id = params_kw.get('sale_id')
|
||||
|
||||
if not customerid:
|
||||
return {'status': 'error', 'message': 'Missing customerid'}
|
||||
|
||||
dbname = env.get_module_dbname('discount')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
if bind_id:
|
||||
# Update existing binding
|
||||
await sor.U('discount_customer_bind', {
|
||||
'id': bind_id,
|
||||
'sale_id': sale_id or None,
|
||||
})
|
||||
else:
|
||||
# Create new binding for free customer
|
||||
new_id = getID()
|
||||
await sor.C('discount_customer_bind', {
|
||||
'id': new_id,
|
||||
'customerid': customerid,
|
||||
'resellerid': userorgid,
|
||||
'sale_id': sale_id or None,
|
||||
'promo_code_id': None,
|
||||
'bind_type': '2', # manual assignment
|
||||
})
|
||||
bind_id = new_id
|
||||
|
||||
return {'status': 'success', 'bind_id': bind_id}
|
||||
|
||||
|
||||
async def get_reseller_sales(request):
|
||||
"""Return sales users list for the current reseller (for dropdown).
|
||||
Used in Jinja2 template: {% set sales = get_reseller_sales(request) %}
|
||||
"""
|
||||
env = request._run_ns
|
||||
userorgid = await env.get_userorgid()
|
||||
if not userorgid or userorgid == '0':
|
||||
return []
|
||||
async with DBPools().sqlorContext('sage') as sor:
|
||||
rows = await sor.sqlExe(
|
||||
"""SELECT DISTINCT u.id as sale_id, u.nick_name as sale_name
|
||||
FROM users u
|
||||
JOIN userrole ur ON ur.userid = u.id
|
||||
JOIN role r ON r.id = ur.roleid
|
||||
WHERE r.name = 'sale' AND u.orgid = ${oid}$
|
||||
ORDER BY u.nick_name""",
|
||||
{'oid': userorgid}
|
||||
)
|
||||
return [{'value': r.sale_id, 'text': r.sale_name} for r in rows]
|
||||
|
||||
|
||||
@ -177,9 +177,17 @@ for tblname in SALE_TABLES:
|
||||
# sale 相关 API
|
||||
for api_path in get_api_files():
|
||||
basename = os.path.basename(api_path)
|
||||
if any(k in basename for k in ["promo_code", "customer_bind"]):
|
||||
if any(k in basename for k in ["promo_code", "customer_bind", "customer_sale"]):
|
||||
PATHS_SALE.append(api_path)
|
||||
|
||||
# sale 客户分配页面
|
||||
PATHS_SALE.extend([
|
||||
f"/{MOD}/free_customer_assign",
|
||||
f"/{MOD}/free_customer_assign/index.ui",
|
||||
f"/{MOD}/customer_reassign",
|
||||
f"/{MOD}/customer_reassign/index.ui",
|
||||
])
|
||||
|
||||
# ============================================================
|
||||
# 去重
|
||||
# ============================================================
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
# 获取机构下所有客户及销售归属
|
||||
# 获取当前机构下的客户及销售归属
|
||||
userorgid = await get_userorgid()
|
||||
if not userorgid:
|
||||
return []
|
||||
|
||||
db = DBPools()
|
||||
resellerid = params_kw.get('resellerid') or '0'
|
||||
resellerid = userorgid
|
||||
|
||||
sql = """
|
||||
select o.id as customerid, o.orgname as customer_name,
|
||||
dcb.id as bind_id, dcb.sale_id,
|
||||
u.name as sale_name
|
||||
u.nick_name as sale_name
|
||||
from organization o
|
||||
left join discount_customer_bind dcb on dcb.customerid = o.id and dcb.resellerid = ${resellerid}$
|
||||
left join user u on u.id = dcb.sale_id
|
||||
where 1=1
|
||||
left join users u on u.id = dcb.sale_id
|
||||
where o.org_type = 'customer' and o.parentid = ${resellerid}$
|
||||
order by o.orgname
|
||||
"""
|
||||
|
||||
|
||||
17
wwwroot/api/save_customer_sale.dspy
Normal file
17
wwwroot/api/save_customer_sale.dspy
Normal file
@ -0,0 +1,17 @@
|
||||
# Save/update customer-sale assignment (called on blur from customer assign pages)
|
||||
from discount.init import save_customer_sale
|
||||
|
||||
ret = await save_customer_sale(request, params_kw)
|
||||
if ret.get('status') == 'success':
|
||||
return {
|
||||
'widgettype': 'Message',
|
||||
'options': {
|
||||
'title': '成功', 'message': '已更新', 'type': 'success', 'timeout': 2
|
||||
}
|
||||
}
|
||||
return {
|
||||
'widgettype': 'Error',
|
||||
'options': {
|
||||
'title': '失败', 'message': ret.get('message', '未知错误'), 'timeout': 3
|
||||
}
|
||||
}
|
||||
69
wwwroot/customer_reassign/index.ui
Normal file
69
wwwroot/customer_reassign/index.ui
Normal file
@ -0,0 +1,69 @@
|
||||
{% set customers = get_bound_customers(request) %}
|
||||
{% set sales = get_reseller_sales(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "css": "filler"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "客户再分配 - 更换销售",
|
||||
"fontSize": "16px", "fontWeight": "600", "color": "#F1F5F9", "padding": "8px 12px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"bgcolor": "#0F172A", "padding": "8px 0", "borderRadius": "6px 6px 0 0", "alignItems": "center", "width": "100%"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "客户名称", "width": "30%", "padding": "0 8px", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8"}},
|
||||
{"widgettype": "Text", "options": {"text": "当前销售", "width": "25%", "padding": "0 8px", "fontSize": "12px", "fontWeight": "600", "color": "#22C55E"}},
|
||||
{"widgettype": "Text", "options": {"text": "更换为", "width": "35%", "padding": "0 8px", "fontSize": "12px", "fontWeight": "600", "color": "#F59E0B"}},
|
||||
{"widgettype": "Text", "options": {"text": "", "width": "10%", "padding": "0 8px"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {"width": "100%", "flex": "1"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%"},
|
||||
"subwidgets": [
|
||||
{% for c in customers %}
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"padding": "6px 0", "border": "0 0 1px 0", "borderColor": "#334155", "alignItems": "center", "width": "100%"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": {{json.dumps(c.customer_name or '')}}, "width": "30%", "padding": "0 8px", "fontSize": "12px", "color": "#E2E8F0"}},
|
||||
{"widgettype": "Text", "options": {"text": {{json.dumps(c.sale_name or '-')}}, "width": "25%", "padding": "0 8px", "fontSize": "12px", "color": "#22C55E"}},
|
||||
{
|
||||
"widgettype": "UiCode",
|
||||
"options": {
|
||||
"width": "35%", "padding": "0 8px",
|
||||
"name": "sale_id",
|
||||
"value": "",
|
||||
"placeholder": "选择新销售...",
|
||||
"data": {{json.dumps(sales or [], ensure_ascii=False)}}
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "blur",
|
||||
"actiontype": "urlwidget", "datawidget": "self", "target": "self",
|
||||
"options": {
|
||||
"url": "{{entire_url('/discount/api/save_customer_sale.dspy')}}",
|
||||
"params": {
|
||||
"bind_id": {{json.dumps(c.bind_id or '')}},
|
||||
"customerid": {{json.dumps(c.customerid)}}
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
{"widgettype": "Text", "options": {"text": "", "width": "10%", "padding": "0 8px"}}
|
||||
]
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
67
wwwroot/free_customer_assign/index.ui
Normal file
67
wwwroot/free_customer_assign/index.ui
Normal file
@ -0,0 +1,67 @@
|
||||
{% set customers = get_free_customers(request) %}
|
||||
{% set sales = get_reseller_sales(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "css": "filler"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "自由客户分配 - 选择销售",
|
||||
"fontSize": "16px", "fontWeight": "600", "color": "#F1F5F9", "padding": "8px 12px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"bgcolor": "#0F172A", "padding": "8px 0", "borderRadius": "6px 6px 0 0", "alignItems": "center", "width": "100%"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "客户名称", "width": "45%", "padding": "0 8px", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8"}},
|
||||
{"widgettype": "Text", "options": {"text": "分配销售", "width": "45%", "padding": "0 8px", "fontSize": "12px", "fontWeight": "600", "color": "#F59E0B"}},
|
||||
{"widgettype": "Text", "options": {"text": "状态", "width": "10%", "padding": "0 8px", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {"width": "100%", "flex": "1"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%"},
|
||||
"subwidgets": [
|
||||
{% for c in customers %}
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"padding": "6px 0", "border": "0 0 1px 0", "borderColor": "#334155", "alignItems": "center", "width": "100%"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": {{json.dumps(c.customer_name or '')}}, "width": "45%", "padding": "0 8px", "fontSize": "12px", "color": "#E2E8F0"}},
|
||||
{
|
||||
"widgettype": "UiCode",
|
||||
"options": {
|
||||
"width": "45%", "padding": "0 8px",
|
||||
"name": "sale_id",
|
||||
"value": "",
|
||||
"placeholder": "选择销售...",
|
||||
"data": {{json.dumps(sales or [], ensure_ascii=False)}}
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "blur",
|
||||
"actiontype": "urlwidget", "datawidget": "self", "target": "self",
|
||||
"options": {
|
||||
"url": "{{entire_url('/discount/api/save_customer_sale.dspy')}}",
|
||||
"params": {
|
||||
"bind_id": {{json.dumps(c.bind_id or '')}},
|
||||
"customerid": {{json.dumps(c.customerid)}}
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
{"widgettype": "Text", "options": {"text": "未分配", "width": "10%", "padding": "0 8px", "fontSize": "11px", "color": "#475569"}}
|
||||
]
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user