支持登录后按客户范围统计运单数量
This commit is contained in:
@@ -112,42 +112,111 @@ def import_tracking_records(connection, records, replace=False):
|
||||
return {'imported': imported, 'preserved': preserved, 'missing': missing}
|
||||
|
||||
|
||||
def tenant_filter(tenant_id=None, tenant_ids=None):
|
||||
"""Share the same server-side scope intersection for exact and aggregate reads."""
|
||||
tenant = get_setting('ORDER_DATA_TENANT_ID')
|
||||
if tenant_ids is not None:
|
||||
if not isinstance(tenant_ids,(tuple,list,set)) or not 0 < len(tenant_ids) <= 256:
|
||||
return None
|
||||
allowed = {value for value in tenant_ids if isinstance(value,str) and 0 < len(value.strip()) <= 128}
|
||||
if tenant_id is not None:
|
||||
allowed.intersection_update({str(tenant_id)})
|
||||
if tenant:
|
||||
allowed.intersection_update({str(tenant)})
|
||||
return (' AND tenant_id IN (' + ','.join('?' for _ in allowed) + ')', sorted(allowed)) if allowed else None
|
||||
if tenant_id is not None:
|
||||
if not str(tenant_id).strip() or (tenant and str(tenant_id) != str(tenant)):
|
||||
return None
|
||||
tenant = str(tenant_id)
|
||||
return (' AND tenant_id=?', [str(tenant)]) if tenant else ('', [])
|
||||
|
||||
|
||||
def lookup_order(reference, tenant_id=None, tenant_ids=None):
|
||||
"""Exact, bounded lookup; scope comes from server configuration, never model SQL."""
|
||||
from .db import connect
|
||||
|
||||
connection = connect()
|
||||
connection.row_factory = sqlite3.Row
|
||||
scope = tenant_filter(tenant_id, tenant_ids)
|
||||
if scope is None:
|
||||
return []
|
||||
connection = None
|
||||
try:
|
||||
query = 'SELECT * FROM order_test_data WHERE (order_no=? OR inbound_code=? OR shipment_id=?)'
|
||||
values = [reference] * 3
|
||||
tenant = get_setting('ORDER_DATA_TENANT_ID')
|
||||
if tenant_ids is not None:
|
||||
if not isinstance(tenant_ids,(tuple,list,set)) or not 0 < len(tenant_ids) <= 256:
|
||||
return []
|
||||
allowed = {value for value in tenant_ids if isinstance(value,str) and 0 < len(value.strip()) <= 128}
|
||||
if tenant_id is not None:
|
||||
allowed.intersection_update({str(tenant_id)})
|
||||
if tenant:
|
||||
allowed.intersection_update({str(tenant)})
|
||||
if not allowed:
|
||||
return []
|
||||
query += ' AND tenant_id IN (' + ','.join('?' for _ in allowed) + ')'
|
||||
values.extend(sorted(allowed))
|
||||
elif tenant_id is not None:
|
||||
if not str(tenant_id).strip():
|
||||
return []
|
||||
if tenant and str(tenant_id) != str(tenant):
|
||||
return []
|
||||
tenant = str(tenant_id)
|
||||
if tenant and tenant_ids is None:
|
||||
query += ' AND tenant_id=?'
|
||||
values.append(str(tenant))
|
||||
connection = connect()
|
||||
connection.row_factory = sqlite3.Row
|
||||
query = 'SELECT * FROM order_test_data WHERE (order_no=? OR inbound_code=? OR shipment_id=?)' + scope[0]
|
||||
values = [reference] * 3 + scope[1]
|
||||
return [dict(row) for row in connection.execute(query+' ORDER BY id LIMIT 2', values)]
|
||||
except sqlite3.Error:
|
||||
raise OrderDataUnavailable from None
|
||||
finally:
|
||||
connection.close()
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
|
||||
|
||||
def count_orders(tenant_ids, status=None):
|
||||
"""Fresh totals require an explicit authorized scope; never fall back to all rows."""
|
||||
from .db import connect
|
||||
|
||||
empty = {'total':0, 'with_inbound':0}
|
||||
scope = tenant_filter(tenant_ids=tenant_ids if tenant_ids is not None else ())
|
||||
if scope is None or (status is not None and status not in STATUS_LABELS):
|
||||
return empty
|
||||
connection = None
|
||||
try:
|
||||
connection = connect()
|
||||
query = ("SELECT COUNT(*),COUNT(NULLIF(TRIM(inbound_code,char(9)||char(10)||char(13)||' '),'')) "
|
||||
'FROM order_test_data WHERE 1=1' + scope[0])
|
||||
values = scope[1]
|
||||
if status is not None:
|
||||
query += ' AND status=?'
|
||||
values = values + [status]
|
||||
total, with_inbound = connection.execute(query,values).fetchone()
|
||||
return {'total':total, 'with_inbound':with_inbound}
|
||||
except sqlite3.Error:
|
||||
raise OrderDataUnavailable from None
|
||||
finally:
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
|
||||
|
||||
def parse_count_query(message):
|
||||
"""Recognize bounded total/status questions without ignoring unknown filters."""
|
||||
if is_lookup_help(message):
|
||||
return None
|
||||
noun = r'订单|运单|入仓码|入仓号|\borders?\b|\bshipments?\b'
|
||||
count = r'多少|几(?:个|票|条|张|笔)|总数|数量|总量|统计|数一下|(?:订单|运单)数(?!量)|\bhow\s+many\b|\bcount\b|\bnumber\b|\btotal\b'
|
||||
if not re.search(noun,message,re.I) or not re.search(count,message,re.I):
|
||||
return None
|
||||
if re.search(r'公斤|千克|\bkg\b|重量|毛重|体积|件数|箱数|运费|费用|价格|多少钱',message,re.I):
|
||||
return None
|
||||
remaining = message
|
||||
statuses = []
|
||||
for phrase, status in (('头程订单已完成','COMPLETED'),('已完成','COMPLETED'),('履约中','PROCESSING'),
|
||||
('处理中','PROCESSING'),('已取消','CANCELLED'),('审核通过','APPROVED'),
|
||||
('审核驳回','REJECTED'),('已接单','ACCEPTED'),('已提交','SUBMITTED'),('草稿','DRAFT')):
|
||||
if phrase in remaining:
|
||||
statuses.append(status)
|
||||
remaining = remaining.replace(phrase,'')
|
||||
remaining = re.sub(count+'|'+noun,'',remaining,flags=re.I)
|
||||
remaining = re.sub(r'当前账号|当前账户|账号下|账户下|名下|可查询|能查到|可以查到|目前|现在|当前|一共|总共|合计|总计|'
|
||||
r'请问|麻烦|帮我|想知道|想查|想看|请|查询|查一下|查下|查查|看一下|看看|看下|一下|下|全部|所有|共有|我的|我|能|有|的|个|票|条|张|笔|呢|吗|呀|啊|共|'
|
||||
r'\b(?:what|is|of|do|i|have|are|there|my|in|all|the|currently)\b|[\s,,。.!!??、::]','',remaining,flags=re.I)
|
||||
return {'status':statuses[0] if statuses else None, 'unsupported':bool(remaining or len(set(statuses)) > 1)}
|
||||
|
||||
|
||||
def answer_count_query(intent, tenants):
|
||||
if not tenants:
|
||||
return order_login_response()
|
||||
if intent['unsupported']:
|
||||
return {'t':'暂不支持按这个条件统计。可以查询订单总数,或按草稿、已提交、履约中、已完成、已取消等业务状态统计。'}
|
||||
try:
|
||||
counts = count_orders(tenants,intent['status'])
|
||||
except OrderDataUnavailable:
|
||||
return {'t':'订单信息暂时无法读取,请稍后重试。'}
|
||||
if intent['status']:
|
||||
text = f"您当前有 {counts['total']} 票业务状态为“{STATUS_LABELS[intent['status']]}”的订单"
|
||||
else:
|
||||
text = f"您当前共有 {counts['total']} 票订单"
|
||||
return {'t':text + f",其中 {counts['with_inbound']} 票已登记入仓码。", '_customer_data':True}
|
||||
|
||||
|
||||
def extract_reference(message):
|
||||
@@ -263,6 +332,9 @@ def is_order_followup(message):
|
||||
def answer_query(message, conv_id):
|
||||
references = extract_reference(message)
|
||||
tenants = customer_tenant_ids()
|
||||
count_intent = parse_count_query(message) if not references else None
|
||||
if count_intent is not None:
|
||||
return answer_count_query(count_intent,tenants)
|
||||
if references and not tenants:
|
||||
return order_login_response()
|
||||
if len(references) > 1:
|
||||
|
||||
Reference in New Issue
Block a user