聚焦场景(空串/纯空白 → None) 归化处理
This commit is contained in:
@@ -10,6 +10,19 @@ from datetime import datetime, timedelta
|
||||
from mcp.server import MCPServer
|
||||
from db import get_pool
|
||||
|
||||
|
||||
def _s(value):
|
||||
"""查询语句处的 str 归一化:None / 空串 / 纯空白 → 统一为 None,其他原值返回。
|
||||
|
||||
只处理 str 类型,不碰 int/float/list/bool。
|
||||
用法:在每个工具函数开头对所有 str 参数执行 `x = _s(x)`,
|
||||
确保后续 `if x:` / `if x is not None:` / LIKE 拼接 行为一致。
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return value if value.strip() else None
|
||||
return value
|
||||
|
||||
|
||||
app = MCPServer(
|
||||
name="mcp-for-crm-auto",
|
||||
description="汽车零部件智能报价 CRM 数据服务",
|
||||
@@ -28,6 +41,10 @@ async def get_customer_info(
|
||||
oem_tier: str = None
|
||||
) -> str:
|
||||
"""查询 CRM 客户信息。支持按客户编码精确查询或按名称、OEM 层级模糊搜索。返回客户信用等级、折扣率、历史订单等。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None
|
||||
customer_code = _s(customer_code)
|
||||
name = _s(name)
|
||||
oem_tier = _s(oem_tier)
|
||||
pool = await get_pool()
|
||||
query = "SELECT * FROM customer WHERE 1=1"
|
||||
params = []
|
||||
@@ -62,6 +79,11 @@ async def create_inquiry(
|
||||
notes: str = None
|
||||
) -> str:
|
||||
"""在 CRM 中创建新的询价记录。关联客户、图纸号、零件号、年用量。客户提交询价后建档使用。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None
|
||||
customer_code = _s(customer_code)
|
||||
drawing_number = _s(drawing_number)
|
||||
part_number = _s(part_number)
|
||||
notes = _s(notes)
|
||||
pool = await get_pool()
|
||||
|
||||
customer = await pool.fetchrow("SELECT name FROM customer WHERE customer_code = $1", customer_code)
|
||||
@@ -110,6 +132,12 @@ async def save_quotation(
|
||||
remarks: str = None
|
||||
) -> str:
|
||||
"""保存报价单到 CRM。包含模具费、单价、年用量、成本明细。报价生成后存档使用。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None
|
||||
inquiry_id = _s(inquiry_id)
|
||||
customer_code = _s(customer_code)
|
||||
payment_terms = _s(payment_terms)
|
||||
delivery_terms = _s(delivery_terms)
|
||||
remarks = _s(remarks)
|
||||
pool = await get_pool()
|
||||
|
||||
customer = await pool.fetchrow("SELECT name FROM customer WHERE customer_code = $1", customer_code)
|
||||
@@ -213,6 +241,12 @@ async def get_quotation_history(
|
||||
latest_only: bool = False
|
||||
) -> str:
|
||||
"""查询历史报价记录。用于核价参考和价格一致性校验。支持按客户、询价编号、日期范围筛选。latest_only=true 时每个询价单仅返回最新版本。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None
|
||||
customer_code = _s(customer_code)
|
||||
inquiry_id = _s(inquiry_id)
|
||||
start_date = _s(start_date)
|
||||
end_date = _s(end_date)
|
||||
status = _s(status)
|
||||
pool = await get_pool()
|
||||
select_clause = (
|
||||
"SELECT DISTINCT ON (q.inquiry_id) q.*, c.name as customer_name"
|
||||
@@ -280,6 +314,15 @@ async def update_opportunity(
|
||||
notes: str = None
|
||||
) -> str:
|
||||
"""更新 CRM 中的商机状态。支持创建新商机或更新已有商机的阶段、金额、概率。报价后跟进使用。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None。str 变为 None 后,
|
||||
# 后面 if value is not None 会自然跳过,避免空串被当作有效值写入。
|
||||
opportunity_id = _s(opportunity_id)
|
||||
customer_code = _s(customer_code)
|
||||
title = _s(title)
|
||||
stage = _s(stage)
|
||||
oem_program = _s(oem_program)
|
||||
expected_close_date = _s(expected_close_date)
|
||||
notes = _s(notes)
|
||||
pool = await get_pool()
|
||||
|
||||
close_date = None
|
||||
@@ -350,6 +393,8 @@ async def update_opportunity(
|
||||
@app.tool()
|
||||
async def get_inquiry_info(inquiry_id: str) -> str:
|
||||
"""按询价单号回读询价单整行(只读)。用于存档后验证:状态、图号、零件号、年用量、目标价、创建时间。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None
|
||||
inquiry_id = _s(inquiry_id)
|
||||
pool = await get_pool()
|
||||
row = await pool.fetchrow("SELECT * FROM inquiry WHERE inquiry_id = $1", inquiry_id)
|
||||
if not row:
|
||||
@@ -364,6 +409,9 @@ async def get_inquiry_info(inquiry_id: str) -> str:
|
||||
@app.tool()
|
||||
async def get_opportunity_info(opportunity_id: str = None, customer_code: str = None) -> str:
|
||||
"""按商机号或客户编码回读商机(只读)。用于存档后验证:stage/probability/expected_amount/更新时间。"""
|
||||
# 查询语句处 str 归一化:空串/纯空白同 None
|
||||
opportunity_id = _s(opportunity_id)
|
||||
customer_code = _s(customer_code)
|
||||
pool = await get_pool()
|
||||
if opportunity_id:
|
||||
row = await pool.fetchrow("SELECT * FROM opportunity WHERE opportunity_id = $1", opportunity_id)
|
||||
@@ -400,8 +448,12 @@ async def purge_test_records(
|
||||
"""清理 TC-03 回归测试数据(受限管理工具):仅删除 remarks/notes 含「TC-03回归测试」或 title 以「TC-03」开头的记录,其余一律拒绝,防止误删演示/生产数据。"""
|
||||
pool = await get_pool()
|
||||
deleted = {"quotations": 0, "inquiries": 0, "opportunities": 0, "rejected": []}
|
||||
# 查询语句处 str 归一化:过滤掉 list 里的空串/纯空白 ID(同 None,跳过)
|
||||
qids = [qid for qid in (quotation_ids or []) if _s(qid)]
|
||||
iids = [iid for iid in (inquiry_ids or []) if _s(iid)]
|
||||
oids = [oid for oid in (opportunity_ids or []) if _s(oid)]
|
||||
|
||||
for qid in quotation_ids or []:
|
||||
for qid in qids:
|
||||
row = await pool.fetchrow("SELECT remarks FROM quotation WHERE quotation_id = $1", qid)
|
||||
if row and "TC-03回归测试" in (row["remarks"] or ""):
|
||||
await pool.execute("DELETE FROM quotation_cost_detail WHERE quotation_id = $1", qid)
|
||||
@@ -410,7 +462,7 @@ async def purge_test_records(
|
||||
else:
|
||||
deleted["rejected"].append(qid)
|
||||
|
||||
for iid in inquiry_ids or []:
|
||||
for iid in iids:
|
||||
row = await pool.fetchrow("SELECT notes FROM inquiry WHERE inquiry_id = $1", iid)
|
||||
if row and "TC-03回归测试" in (row["notes"] or ""):
|
||||
await pool.execute("DELETE FROM inquiry_item WHERE inquiry_id = $1", iid)
|
||||
@@ -419,7 +471,7 @@ async def purge_test_records(
|
||||
else:
|
||||
deleted["rejected"].append(iid)
|
||||
|
||||
for oid in opportunity_ids or []:
|
||||
for oid in oids:
|
||||
row = await pool.fetchrow("SELECT title FROM opportunity WHERE opportunity_id = $1", oid)
|
||||
if row and (row["title"] or "").startswith("TC-03"):
|
||||
await pool.execute("DELETE FROM opportunity WHERE opportunity_id = $1", oid)
|
||||
|
||||
Reference in New Issue
Block a user