告警-工单解耦:企微交互+Agent全面切换到工单驱动
Part A: 数据层
- 新增 WechatCardState 模型(order_id ↔ alarm_id 映射 + response_code)
- 新建 models_iot.py(IoT 工单只读 ORM:ops_order + security_ext + clean_ext)
- config.py 新增 IOT_DATABASE_URL 配置
Part B: 企微解耦(alarm_id → order_id)
- wechat_service: response_code 存储迁移到 wechat_card_state,集中 helper
- 卡片发送/更新方法改用 order_id,按钮 key: confirm_{order_id}
- wechat_callback: 按钮解析改 order_id,反查 alarm_id(可空)
- wechat_notify_api: send-card/sync-status 以 orderId 为主键
- yudao_aiot_alarm: 卡片操作改用 order_id,删重复 helper
Part C: Agent 工具全面改为工单驱动
- 新建 order_query.py(查 IoT ops_order,支持安保+保洁工单)
- 新建 order_action.py(操作工单状态 + 提交处理结果)
- 更新 prompts.py 为工单助手
- 更新工具注册(__init__.py)
Part D: 日报改为工单驱动
- daily_report_service 从查 alarm_event 改为查 IoT ops_order + 扩展表
- 支持安保+保洁工单统计
This commit is contained in:
@@ -2,18 +2,16 @@
|
||||
工具注册表:导出 all_tools 供图构建使用
|
||||
"""
|
||||
|
||||
from .alarm_query import query_alarm_stats, list_alarms, get_alarm_detail
|
||||
from .alarm_action import update_alarm_status
|
||||
from .order_tools import list_my_orders, submit_order_result
|
||||
from .order_query import query_order_stats, list_orders, get_order_detail
|
||||
from .order_action import update_order_status, submit_order_result
|
||||
from .camera_tools import query_camera
|
||||
|
||||
# 所有工具列表 — 添加新工具只需在这里追加
|
||||
all_tools = [
|
||||
query_alarm_stats,
|
||||
list_alarms,
|
||||
get_alarm_detail,
|
||||
update_alarm_status,
|
||||
list_my_orders,
|
||||
query_order_stats,
|
||||
list_orders,
|
||||
get_order_detail,
|
||||
update_order_status,
|
||||
submit_order_result,
|
||||
query_camera,
|
||||
]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import json
|
||||
from langchain_core.tools import tool
|
||||
|
||||
from .alarm_query import _get_camera_display_name
|
||||
from .order_query import _get_camera_display_name
|
||||
|
||||
|
||||
@tool
|
||||
|
||||
182
app/services/agent/tools/order_action.py
Normal file
182
app/services/agent/tools/order_action.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
工单操作工具:确认接单、完成、误报、提交处理结果
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Optional, List
|
||||
from langchain_core.tools import tool
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
async def _update_wechat_card(order_id: str, user_id: str, action: str):
|
||||
"""更新企微卡片状态(以 order_id 为 key)"""
|
||||
try:
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
wechat = get_wechat_service()
|
||||
response_code = wechat.get_response_code(order_id)
|
||||
if not response_code:
|
||||
return
|
||||
|
||||
if action == "confirm":
|
||||
await wechat.update_alarm_card_step2(
|
||||
response_code=response_code, user_ids=[user_id],
|
||||
order_id=order_id, operator_name=user_id,
|
||||
)
|
||||
else:
|
||||
await wechat.update_alarm_card_terminal(
|
||||
response_code=response_code, user_ids=[user_id],
|
||||
order_id=order_id, action=action, operator_name=user_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"更新企微卡片失败: order={order_id}, error={e}")
|
||||
|
||||
|
||||
@tool
|
||||
async def update_order_status(order_id: str, action: str, config: RunnableConfig) -> str:
|
||||
"""更新工单状态:确认接单(confirm)、处理完成(complete)、标记误报(false)、忽略(ignore)
|
||||
|
||||
Args:
|
||||
order_id: 工单ID(ops_order.id)
|
||||
action: 操作类型 confirm=确认接单 complete=处理完成 false=标记误报 ignore=忽略
|
||||
"""
|
||||
user_id = config.get("configurable", {}).get("user_id", "")
|
||||
|
||||
from app.services.work_order_client import get_work_order_client
|
||||
wo_client = get_work_order_client()
|
||||
|
||||
if not wo_client.enabled:
|
||||
return json.dumps({"error": "工单系统未启用"}, ensure_ascii=False)
|
||||
|
||||
# IoT 工单操作
|
||||
iot_success = False
|
||||
if action == "confirm":
|
||||
iot_success = await wo_client.confirm_order(order_id)
|
||||
elif action in ("ignore", "false"):
|
||||
iot_success = await wo_client.false_alarm(order_id)
|
||||
elif action == "complete":
|
||||
iot_success = await wo_client.submit_order(order_id, result=f"已处理 by {user_id}")
|
||||
|
||||
if not iot_success:
|
||||
return json.dumps({"error": f"工单操作失败: order={order_id}, action={action}"}, ensure_ascii=False)
|
||||
|
||||
# 有关联告警时同步告警状态
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
wechat = get_wechat_service()
|
||||
alarm_id = wechat.get_alarm_id_for_order(order_id)
|
||||
|
||||
if alarm_id:
|
||||
action_map = {
|
||||
"confirm": ("CONFIRMED", "HANDLING", "Agent确认接单"),
|
||||
"ignore": ("FALSE", "IGNORED", "Agent忽略"),
|
||||
"complete": ("CLOSED", "DONE", "Agent已处理"),
|
||||
"false": ("FALSE", "IGNORED", "Agent标记误报"),
|
||||
}
|
||||
if action in action_map:
|
||||
alarm_status, handle_status, remark = action_map[action]
|
||||
from app.services.alarm_event_service import get_alarm_event_service
|
||||
svc = get_alarm_event_service()
|
||||
svc.handle_alarm(
|
||||
alarm_id=alarm_id, alarm_status=alarm_status,
|
||||
handle_status=handle_status, handler=user_id, remark=remark,
|
||||
)
|
||||
|
||||
# 更新企微卡片
|
||||
await _update_wechat_card(order_id, user_id, action)
|
||||
|
||||
action_labels = {
|
||||
"confirm": "已确认接单",
|
||||
"ignore": "已忽略",
|
||||
"complete": "已处理完成",
|
||||
"false": "已标记误报",
|
||||
}
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"{action_labels.get(action, action)}: 工单{order_id}"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
|
||||
@tool
|
||||
async def submit_order_result(
|
||||
order_id: str,
|
||||
result_text: str,
|
||||
config: RunnableConfig,
|
||||
image_urls: Optional[List[str]] = None,
|
||||
) -> str:
|
||||
"""提交工单处理结果(文字描述+处理后照片URL)
|
||||
|
||||
Args:
|
||||
order_id: 工单ID(ops_order.id)
|
||||
result_text: 处理结果描述
|
||||
image_urls: 处理后照片URL列表(COS永久URL)
|
||||
"""
|
||||
user_id = config.get("configurable", {}).get("user_id", "")
|
||||
|
||||
if image_urls is None:
|
||||
image_urls = []
|
||||
|
||||
# 合并 session 中暂存的图片
|
||||
from app.services.session_manager import get_session_manager
|
||||
session = get_session_manager().get(user_id)
|
||||
if session.pending_images:
|
||||
image_urls = session.pending_images + image_urls
|
||||
session.pending_images = []
|
||||
|
||||
from app.services.work_order_client import get_work_order_client
|
||||
wo_client = get_work_order_client()
|
||||
|
||||
if wo_client.enabled:
|
||||
success = await wo_client.submit_order(
|
||||
order_id,
|
||||
result=f"{result_text} by {user_id}",
|
||||
result_img_urls=image_urls or None,
|
||||
)
|
||||
if not success:
|
||||
return json.dumps({"error": f"提交工单结果失败: order={order_id}"}, ensure_ascii=False)
|
||||
|
||||
# 有关联告警时同步告警状态
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
wechat = get_wechat_service()
|
||||
alarm_id = wechat.get_alarm_id_for_order(order_id)
|
||||
|
||||
if alarm_id:
|
||||
from app.services.alarm_event_service import get_alarm_event_service
|
||||
svc = get_alarm_event_service()
|
||||
remark = f"Agent结单: {result_text}"
|
||||
if image_urls:
|
||||
remark += f" (附{len(image_urls)}张图片)"
|
||||
svc.handle_alarm(alarm_id=alarm_id, alarm_status="CLOSED",
|
||||
handle_status="DONE", handler=user_id, remark=remark)
|
||||
|
||||
# 持久化处理结果图片到 alarm_event_ext
|
||||
if image_urls:
|
||||
try:
|
||||
from app.models import get_session as get_db_session, AlarmEventExt
|
||||
db = get_db_session()
|
||||
try:
|
||||
ext = AlarmEventExt(
|
||||
alarm_id=alarm_id,
|
||||
ext_type="HANDLER_RESULT",
|
||||
ext_data={"result_text": result_text, "image_urls": image_urls, "handler": user_id},
|
||||
)
|
||||
db.add(ext)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"持久化处理结果图片失败: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"保存处理结果失败: {e}")
|
||||
|
||||
# 更新卡片到终态
|
||||
await _update_wechat_card(order_id, user_id, "complete")
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"工单已提交: {order_id}",
|
||||
"result": result_text,
|
||||
"images_count": len(image_urls),
|
||||
}
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
334
app/services/agent/tools/order_query.py
Normal file
334
app/services/agent/tools/order_query.py
Normal file
@@ -0,0 +1,334 @@
|
||||
"""
|
||||
工单查询工具:统计、列表、详情(查 IoT ops_order + 扩展表)
|
||||
支持安保工单(SECURITY)和保洁工单(CLEAN)
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
from langchain_core.tools import tool
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from app.utils.logger import logger
|
||||
from app.utils.timezone import beijing_now
|
||||
|
||||
|
||||
# 告警类型中文映射
|
||||
ALARM_TYPE_NAMES = {
|
||||
"leave_post": "人员离岗", "intrusion": "周界入侵",
|
||||
"illegal_parking": "车辆违停", "vehicle_congestion": "车辆拥堵",
|
||||
}
|
||||
|
||||
# 工单状态映射
|
||||
ORDER_STATUS_NAMES = {
|
||||
"PENDING": "待处理", "ASSIGNED": "已派单", "ARRIVED": "已到岗",
|
||||
"PAUSED": "已暂停", "COMPLETED": "已完成", "CANCELLED": "已取消",
|
||||
}
|
||||
|
||||
# 工单优先级映射
|
||||
PRIORITY_NAMES = {0: "低", 1: "中", 2: "高"}
|
||||
|
||||
# 保洁类型映射
|
||||
CLEANING_TYPE_NAMES = {
|
||||
"ROUTINE": "日常保洁", "DEEP": "深度保洁",
|
||||
"SPOT": "点状保洁", "EMERGENCY": "应急保洁",
|
||||
}
|
||||
|
||||
|
||||
def _parse_time_range(time_range: str):
|
||||
"""解析时间范围,返回 (start_time, label)"""
|
||||
now = beijing_now()
|
||||
if time_range == "week":
|
||||
start = now - timedelta(days=now.weekday())
|
||||
start = start.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
return start, "本周"
|
||||
elif time_range == "month":
|
||||
start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
return start, "本月"
|
||||
elif time_range == "yesterday":
|
||||
start = (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
return start, "昨日"
|
||||
else:
|
||||
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
return start, "今日"
|
||||
|
||||
|
||||
def _get_camera_display_name(device_id: str) -> str:
|
||||
"""同步获取摄像头显示名称"""
|
||||
try:
|
||||
import asyncio
|
||||
from app.services.camera_name_service import get_camera_name_service
|
||||
camera_service = get_camera_name_service()
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
import concurrent.futures
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
cam_info = pool.submit(
|
||||
asyncio.run, camera_service.get_camera_info(device_id)
|
||||
).result(timeout=5)
|
||||
else:
|
||||
cam_info = asyncio.run(camera_service.get_camera_info(device_id))
|
||||
return camera_service.format_display_name(device_id, cam_info)
|
||||
except Exception:
|
||||
return device_id
|
||||
|
||||
|
||||
def _query_orders(
|
||||
order_type: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
limit: int = 100,
|
||||
assignee_name: Optional[str] = None,
|
||||
):
|
||||
"""查询 IoT 工单(跨库只读)"""
|
||||
from app.models_iot import get_iot_session, IotOpsOrder
|
||||
db = get_iot_session()
|
||||
try:
|
||||
q = db.query(IotOpsOrder).filter(IotOpsOrder.deleted == 0)
|
||||
if order_type and order_type != "ALL":
|
||||
q = q.filter(IotOpsOrder.order_type == order_type)
|
||||
if status:
|
||||
q = q.filter(IotOpsOrder.status == status)
|
||||
if start_time:
|
||||
q = q.filter(IotOpsOrder.create_time >= start_time)
|
||||
if end_time:
|
||||
q = q.filter(IotOpsOrder.create_time < end_time)
|
||||
if assignee_name:
|
||||
q = q.filter(IotOpsOrder.assignee_name.contains(assignee_name))
|
||||
|
||||
total = q.count()
|
||||
orders = q.order_by(IotOpsOrder.create_time.desc()).limit(limit).all()
|
||||
|
||||
# 提取所有 order_id 用于关联查询扩展表
|
||||
order_ids = [o.id for o in orders]
|
||||
|
||||
# 批量查安保扩展
|
||||
sec_ext_map = {}
|
||||
if order_ids:
|
||||
from app.models_iot import IotOpsOrderSecurityExt
|
||||
sec_exts = db.query(IotOpsOrderSecurityExt).filter(
|
||||
IotOpsOrderSecurityExt.ops_order_id.in_(order_ids),
|
||||
IotOpsOrderSecurityExt.deleted == 0,
|
||||
).all()
|
||||
sec_ext_map = {e.ops_order_id: e for e in sec_exts}
|
||||
|
||||
# 批量查保洁扩展
|
||||
clean_ext_map = {}
|
||||
if order_ids:
|
||||
from app.models_iot import IotOpsOrderCleanExt
|
||||
clean_exts = db.query(IotOpsOrderCleanExt).filter(
|
||||
IotOpsOrderCleanExt.ops_order_id.in_(order_ids),
|
||||
IotOpsOrderCleanExt.deleted == 0,
|
||||
).all()
|
||||
clean_ext_map = {e.ops_order_id: e for e in clean_exts}
|
||||
|
||||
return orders, total, sec_ext_map, clean_ext_map
|
||||
except Exception as e:
|
||||
logger.error(f"查询IoT工单失败: {e}", exc_info=True)
|
||||
return [], 0, {}, {}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@tool
|
||||
def query_order_stats(time_range: str = "today", order_type: str = "ALL") -> str:
|
||||
"""查询工单统计数据(总数、按状态分布、按类型分布)
|
||||
|
||||
Args:
|
||||
time_range: 时间范围 today=今日 week=本周 month=本月 yesterday=昨日
|
||||
order_type: 工单类型筛选 SECURITY=安保 CLEAN=保洁 ALL=全部
|
||||
"""
|
||||
start, range_label = _parse_time_range(time_range)
|
||||
now = beijing_now()
|
||||
end = now
|
||||
if time_range == "yesterday":
|
||||
end = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
orders, total, sec_ext_map, clean_ext_map = _query_orders(
|
||||
order_type=order_type if order_type != "ALL" else None,
|
||||
start_time=start, end_time=end, limit=10000,
|
||||
)
|
||||
|
||||
# 按状态统计
|
||||
status_count = {}
|
||||
for o in orders:
|
||||
s = o.status or "PENDING"
|
||||
status_count[s] = status_count.get(s, 0) + 1
|
||||
|
||||
# 按类型统计
|
||||
type_count = {"SECURITY": 0, "CLEAN": 0}
|
||||
alarm_type_count = {}
|
||||
for o in orders:
|
||||
ot = o.order_type or "SECURITY"
|
||||
type_count[ot] = type_count.get(ot, 0) + 1
|
||||
# 安保工单细分告警类型
|
||||
sec_ext = sec_ext_map.get(o.id)
|
||||
if sec_ext and sec_ext.alarm_type:
|
||||
alarm_type_count[sec_ext.alarm_type] = alarm_type_count.get(sec_ext.alarm_type, 0) + 1
|
||||
|
||||
# 误报统计(安保特有)
|
||||
false_alarm_count = sum(
|
||||
1 for e in sec_ext_map.values() if e.false_alarm == 1
|
||||
)
|
||||
|
||||
result = {
|
||||
"range": range_label,
|
||||
"total": total,
|
||||
"by_status": {ORDER_STATUS_NAMES.get(s, s): c for s, c in status_count.items()},
|
||||
"by_order_type": {k: v for k, v in type_count.items() if v > 0},
|
||||
"by_alarm_type": {ALARM_TYPE_NAMES.get(t, t): c for t, c in alarm_type_count.items()} if alarm_type_count else {},
|
||||
"false_alarm_count": false_alarm_count,
|
||||
}
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
|
||||
|
||||
@tool
|
||||
def list_orders(
|
||||
time_range: str = "today",
|
||||
order_type: str = "ALL",
|
||||
status: str = "",
|
||||
limit: int = 10,
|
||||
assigned_to_me: bool = False,
|
||||
config: RunnableConfig = None,
|
||||
) -> str:
|
||||
"""查询工单列表,返回最近的工单记录
|
||||
|
||||
Args:
|
||||
time_range: 时间范围 today/week/month/yesterday
|
||||
order_type: 工单类型 SECURITY=安保 CLEAN=保洁 ALL=全部
|
||||
status: 状态筛选 PENDING/ASSIGNED/ARRIVED/PAUSED/COMPLETED/CANCELLED
|
||||
limit: 返回条数,默认10,最多20
|
||||
assigned_to_me: 是否只看我的工单
|
||||
"""
|
||||
start, range_label = _parse_time_range(time_range)
|
||||
now = beijing_now()
|
||||
end = now
|
||||
if time_range == "yesterday":
|
||||
end = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
limit = min(limit, 20)
|
||||
|
||||
# 获取当前用户
|
||||
user_id = ""
|
||||
if config and assigned_to_me:
|
||||
user_id = config.get("configurable", {}).get("user_id", "")
|
||||
|
||||
orders, total, sec_ext_map, clean_ext_map = _query_orders(
|
||||
order_type=order_type if order_type != "ALL" else None,
|
||||
status=status or None,
|
||||
start_time=start, end_time=end, limit=limit,
|
||||
)
|
||||
|
||||
items = []
|
||||
for o in orders:
|
||||
create_time = ""
|
||||
if o.create_time:
|
||||
try:
|
||||
create_time = o.create_time.strftime("%m-%d %H:%M")
|
||||
except Exception:
|
||||
create_time = str(o.create_time)[:16]
|
||||
|
||||
item = {
|
||||
"order_id": str(o.id),
|
||||
"order_code": o.order_code or "",
|
||||
"type": o.order_type or "SECURITY",
|
||||
"title": o.title or "",
|
||||
"status": ORDER_STATUS_NAMES.get(o.status, o.status or "待处理"),
|
||||
"priority": PRIORITY_NAMES.get(o.priority, "中"),
|
||||
"assignee": o.assignee_name or "",
|
||||
"time": create_time,
|
||||
}
|
||||
|
||||
# 安保扩展
|
||||
sec_ext = sec_ext_map.get(o.id)
|
||||
if sec_ext:
|
||||
item["alarm_type"] = ALARM_TYPE_NAMES.get(sec_ext.alarm_type, sec_ext.alarm_type or "")
|
||||
item["camera"] = sec_ext.camera_name or _get_camera_display_name(sec_ext.camera_id) if sec_ext.camera_id else ""
|
||||
item["false_alarm"] = bool(sec_ext.false_alarm)
|
||||
|
||||
# 保洁扩展
|
||||
clean_ext = clean_ext_map.get(o.id)
|
||||
if clean_ext:
|
||||
item["cleaning_type"] = CLEANING_TYPE_NAMES.get(clean_ext.cleaning_type, clean_ext.cleaning_type or "")
|
||||
item["difficulty"] = clean_ext.difficulty_level
|
||||
|
||||
items.append(item)
|
||||
|
||||
result = {"range": range_label, "total": total, "items": items}
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
|
||||
|
||||
@tool
|
||||
def get_order_detail(order_id: str, config: RunnableConfig) -> str:
|
||||
"""查询单条工单的详细信息
|
||||
|
||||
Args:
|
||||
order_id: 工单ID(ops_order.id)
|
||||
"""
|
||||
from app.models_iot import get_iot_session, IotOpsOrder, IotOpsOrderSecurityExt, IotOpsOrderCleanExt
|
||||
db = get_iot_session()
|
||||
try:
|
||||
order = db.query(IotOpsOrder).filter(
|
||||
IotOpsOrder.id == int(order_id),
|
||||
IotOpsOrder.deleted == 0,
|
||||
).first()
|
||||
if not order:
|
||||
return json.dumps({"error": f"未找到工单: {order_id}"}, ensure_ascii=False)
|
||||
|
||||
result = {
|
||||
"order_id": str(order.id),
|
||||
"order_code": order.order_code or "",
|
||||
"order_type": order.order_type or "",
|
||||
"title": order.title or "",
|
||||
"description": order.description or "",
|
||||
"status": ORDER_STATUS_NAMES.get(order.status, order.status or ""),
|
||||
"priority": PRIORITY_NAMES.get(order.priority, "中"),
|
||||
"assignee": order.assignee_name or "",
|
||||
"location": order.location or "",
|
||||
"create_time": order.create_time.strftime("%Y-%m-%d %H:%M:%S") if order.create_time else "",
|
||||
}
|
||||
|
||||
# 安保扩展
|
||||
sec_ext = db.query(IotOpsOrderSecurityExt).filter(
|
||||
IotOpsOrderSecurityExt.ops_order_id == order.id,
|
||||
IotOpsOrderSecurityExt.deleted == 0,
|
||||
).first()
|
||||
if sec_ext:
|
||||
result["alarm_id"] = sec_ext.alarm_id or ""
|
||||
result["alarm_type"] = ALARM_TYPE_NAMES.get(sec_ext.alarm_type, sec_ext.alarm_type or "")
|
||||
result["camera_name"] = sec_ext.camera_name or (
|
||||
_get_camera_display_name(sec_ext.camera_id) if sec_ext.camera_id else ""
|
||||
)
|
||||
result["has_image"] = bool(sec_ext.image_url)
|
||||
result["false_alarm"] = bool(sec_ext.false_alarm)
|
||||
result["result"] = sec_ext.result or ""
|
||||
if sec_ext.dispatched_time:
|
||||
result["dispatched_time"] = sec_ext.dispatched_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if sec_ext.confirmed_time:
|
||||
result["confirmed_time"] = sec_ext.confirmed_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if sec_ext.completed_time:
|
||||
result["completed_time"] = sec_ext.completed_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 保洁扩展
|
||||
clean_ext = db.query(IotOpsOrderCleanExt).filter(
|
||||
IotOpsOrderCleanExt.ops_order_id == order.id,
|
||||
IotOpsOrderCleanExt.deleted == 0,
|
||||
).first()
|
||||
if clean_ext:
|
||||
result["cleaning_type"] = CLEANING_TYPE_NAMES.get(clean_ext.cleaning_type, clean_ext.cleaning_type or "")
|
||||
result["difficulty"] = clean_ext.difficulty_level
|
||||
result["expected_duration"] = clean_ext.expected_duration
|
||||
result["is_auto"] = bool(clean_ext.is_auto)
|
||||
if clean_ext.arrived_time:
|
||||
result["arrived_time"] = clean_ext.arrived_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if clean_ext.completed_time:
|
||||
result["clean_completed_time"] = clean_ext.completed_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"查询工单详情失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"查询失败: {e}"}, ensure_ascii=False)
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user