fix: 工单管理字段语义统一 + AI建议修复 + 租户筛选
1. 统一字段语义:title=问题标题,description=问题详细描述,resolution=解决方案 2. 修复详情页展示:问题描述取 description 而非 title 3. 修复编辑页标签:description 标注为'问题详细描述'而非'处理过程' 4. 统一分类选项:创建和编辑使用相同的分类列表(技术问题/APP功能/远程控制/车辆绑定/系统故障/OTA升级/其他) 5. 修复 AI 建议生成:用 title+description 搜索知识库,prompt 中明确区分标题和描述 6. 修复入库逻辑:question 使用 title+description 完整内容,入库时带上工单的 tenant_id 7. 工单列表新增租户筛选器,API 支持 tenant_id 过滤 8. 工单列表和详情 API 返回 tenant_id 字段 9. 租户选择器同时填充对话和工单筛选下拉框
This commit is contained in:
@@ -84,6 +84,7 @@ def get_workorders():
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
status_filter = request.args.get('status', '')
|
||||
priority_filter = request.args.get('priority', '')
|
||||
tenant_id = request.args.get('tenant_id', '')
|
||||
|
||||
# 从数据库获取分页数据
|
||||
from src.core.database import db_manager
|
||||
@@ -98,6 +99,8 @@ def get_workorders():
|
||||
query = query.filter(WorkOrder.status == status_filter)
|
||||
if priority_filter:
|
||||
query = query.filter(WorkOrder.priority == priority_filter)
|
||||
if tenant_id:
|
||||
query = query.filter(WorkOrder.tenant_id == tenant_id)
|
||||
|
||||
# 按创建时间倒序排列
|
||||
query = query.order_by(WorkOrder.created_at.desc())
|
||||
@@ -114,6 +117,7 @@ def get_workorders():
|
||||
workorders_data.append({
|
||||
'id': workorder.id,
|
||||
'order_id': workorder.order_id,
|
||||
'tenant_id': workorder.tenant_id,
|
||||
'title': workorder.title,
|
||||
'description': workorder.description,
|
||||
'category': workorder.category,
|
||||
@@ -184,6 +188,7 @@ def get_workorder_details(workorder_id):
|
||||
workorder = {
|
||||
"id": w.id,
|
||||
"order_id": w.order_id,
|
||||
"tenant_id": w.tenant_id,
|
||||
"title": w.title,
|
||||
"description": w.description,
|
||||
"category": w.category,
|
||||
@@ -315,15 +320,15 @@ def generate_workorder_ai_suggestion(workorder_id):
|
||||
if not w:
|
||||
return jsonify({"error": "工单不存在"}), 404
|
||||
# 调用知识库搜索与LLM生成
|
||||
# 使用问题描述(title)而不是处理过程(description)作为主要查询依据
|
||||
query = f"{w.title}"
|
||||
# 使用 title + description 作为查询依据,获取更精准的知识库匹配
|
||||
query = f"{w.title} {w.description or ''}"
|
||||
kb_results = service_manager.get_assistant().search_knowledge(query, top_k=3)
|
||||
kb_list = kb_results.get('results', []) if isinstance(kb_results, dict) else []
|
||||
# 组装提示词
|
||||
context = "\n".join([f"Q: {k.get('question','')}\nA: {k.get('answer','')}" for k in kb_list])
|
||||
from src.core.llm_client import QwenClient
|
||||
llm = QwenClient()
|
||||
prompt = f"请基于以下工单问题描述与知识库片段,给出简洁、可执行的处理建议。\n\n问题描述:\n{w.title}\n\n处理过程(仅供参考):\n{w.description}\n\n知识库片段:\n{context}\n\n请直接输出建议文本:"
|
||||
prompt = f"请基于以下工单问题与知识库片段,给出简洁、可执行的处理建议。\n\n问题标题:\n{w.title}\n\n问题详细描述:\n{w.description or '无'}\n\n知识库片段:\n{context}\n\n请直接输出建议文本:"
|
||||
llm_resp = llm.chat_completion(messages=[{"role":"user","content":prompt}], temperature=0.3, max_tokens=800)
|
||||
suggestion = ""
|
||||
if llm_resp and 'choices' in llm_resp:
|
||||
@@ -432,10 +437,18 @@ def approve_workorder_to_knowledge(workorder_id):
|
||||
return jsonify({"error": "未找到可入库的内容"}), 400
|
||||
|
||||
# 入库为知识条目
|
||||
# question 使用标题+描述,确保知识条目的问题完整
|
||||
question_text = w.title or ''
|
||||
if w.description:
|
||||
question_text = f"{w.title}\n{w.description}" if w.title else w.description
|
||||
if not question_text:
|
||||
question_text = '工单问题'
|
||||
|
||||
entry = KnowledgeEntry(
|
||||
question=w.title or (w.description[:20] if w.description else '工单问题'),
|
||||
question=question_text,
|
||||
answer=answer_content,
|
||||
category=w.category or '其他',
|
||||
tenant_id=w.tenant_id,
|
||||
confidence_score=confidence_score,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
|
||||
@@ -386,13 +386,23 @@ class TSPDashboard {
|
||||
const tenants = await response.json();
|
||||
if (!Array.isArray(tenants)) return;
|
||||
|
||||
const selectors = document.querySelectorAll('#chat-tenant-id');
|
||||
selectors.forEach(select => {
|
||||
const currentVal = select.value;
|
||||
select.innerHTML = tenants.map(t =>
|
||||
// 填充对话租户选择器
|
||||
const chatSelect = document.getElementById('chat-tenant-id');
|
||||
if (chatSelect) {
|
||||
const currentVal = chatSelect.value;
|
||||
chatSelect.innerHTML = tenants.map(t =>
|
||||
`<option value="${t.tenant_id}"${t.tenant_id === currentVal ? ' selected' : ''}>${t.name} (${t.tenant_id})</option>`
|
||||
).join('');
|
||||
});
|
||||
}
|
||||
|
||||
// 填充工单租户筛选器
|
||||
const woFilter = document.getElementById('workorder-tenant-filter');
|
||||
if (woFilter) {
|
||||
const currentVal = woFilter.value;
|
||||
woFilter.innerHTML = '<option value="all">全部租户</option>' + tenants.map(t =>
|
||||
`<option value="${t.tenant_id}"${t.tenant_id === currentVal ? ' selected' : ''}>${t.name}</option>`
|
||||
).join('');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('加载租户列表失败:', e);
|
||||
}
|
||||
@@ -2800,6 +2810,7 @@ class TSPDashboard {
|
||||
try {
|
||||
const statusFilter = document.getElementById('workorder-status-filter')?.value || 'all';
|
||||
const priorityFilter = document.getElementById('workorder-priority-filter')?.value || 'all';
|
||||
const tenantFilter = document.getElementById('workorder-tenant-filter')?.value || 'all';
|
||||
|
||||
let url = '/api/workorders';
|
||||
const params = new URLSearchParams();
|
||||
@@ -2808,6 +2819,7 @@ class TSPDashboard {
|
||||
params.append('per_page', pageSize.toString());
|
||||
if (statusFilter !== 'all') params.append('status', statusFilter);
|
||||
if (priorityFilter !== 'all') params.append('priority', priorityFilter);
|
||||
if (tenantFilter !== 'all') params.append('tenant_id', tenantFilter);
|
||||
|
||||
// 添加强制刷新参数
|
||||
if (forceRefresh) {
|
||||
@@ -2872,7 +2884,7 @@ class TSPDashboard {
|
||||
<input type="checkbox" class="form-check-input me-2 workorder-checkbox" value="${workorder.id}" onchange="dashboard.updateBatchDeleteButton()">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">${workorder.title}</h6>
|
||||
<p class="text-muted mb-2">${workorder.description ? workorder.description.substring(0, 100) + (workorder.description.length > 100 ? '...' : '') : '无处理过程'}</p>
|
||||
<p class="text-muted mb-2">${workorder.description ? workorder.description.substring(0, 100) + (workorder.description.length > 100 ? '...' : '') : '无问题描述'}</p>
|
||||
<div class="d-flex gap-3">
|
||||
<span class="badge bg-${this.getPriorityColor(workorder.priority)}">${this.getPriorityText(workorder.priority)}</span>
|
||||
<span class="badge bg-${this.getStatusColor(workorder.status)}">${this.getStatusText(workorder.status)}</span>
|
||||
@@ -3133,14 +3145,8 @@ class TSPDashboard {
|
||||
<div class="col-md-6">
|
||||
<h6>问题描述</h6>
|
||||
<div class="border p-3 rounded">
|
||||
${workorder.title || '无问题描述'}
|
||||
${workorder.description || '无问题描述'}
|
||||
</div>
|
||||
${workorder.description ? `
|
||||
<h6 class="mt-3">处理过程</h6>
|
||||
<div class="border p-3 rounded bg-light">
|
||||
${workorder.description}
|
||||
</div>
|
||||
` : ''}
|
||||
${workorder.resolution ? `
|
||||
<h6 class="mt-3">解决方案</h6>
|
||||
<div class="border p-3 rounded bg-light">
|
||||
@@ -3310,7 +3316,7 @@ class TSPDashboard {
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="mb-3">
|
||||
<label for="editTitle" class="form-label">标题 *</label>
|
||||
<label for="editTitle" class="form-label">问题标题 *</label>
|
||||
<input type="text" class="form-control" id="editTitle" value="${workorder.title}" required>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3333,9 +3339,11 @@ class TSPDashboard {
|
||||
<label for="editCategory" class="form-label">分类</label>
|
||||
<select class="form-select" id="editCategory">
|
||||
<option value="技术问题" ${workorder.category === '技术问题' ? 'selected' : ''}>技术问题</option>
|
||||
<option value="业务问题" ${workorder.category === '业务问题' ? 'selected' : ''}>业务问题</option>
|
||||
<option value="APP功能" ${workorder.category === 'APP功能' ? 'selected' : ''}>APP功能</option>
|
||||
<option value="远程控制" ${workorder.category === '远程控制' ? 'selected' : ''}>远程控制</option>
|
||||
<option value="车辆绑定" ${workorder.category === '车辆绑定' ? 'selected' : ''}>车辆绑定</option>
|
||||
<option value="系统故障" ${workorder.category === '系统故障' ? 'selected' : ''}>系统故障</option>
|
||||
<option value="功能需求" ${workorder.category === '功能需求' ? 'selected' : ''}>功能需求</option>
|
||||
<option value="OTA升级" ${workorder.category === 'OTA升级' ? 'selected' : ''}>OTA升级</option>
|
||||
<option value="其他" ${workorder.category === '其他' ? 'selected' : ''}>其他</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -3354,7 +3362,7 @@ class TSPDashboard {
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">处理过程 *</label>
|
||||
<label for="editDescription" class="form-label">问题详细描述 *</label>
|
||||
<textarea class="form-control" id="editDescription" rows="4" required>${workorder.description}</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -989,7 +989,12 @@
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<select class="form-select" id="workorder-tenant-filter" onchange="dashboard.loadWorkOrders()">
|
||||
<option value="all">全部租户</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select" id="workorder-status-filter">
|
||||
<option value="all">全部状态</option>
|
||||
<option value="open">待处理</option>
|
||||
@@ -998,7 +1003,7 @@
|
||||
<option value="closed">已关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<select class="form-select" id="workorder-priority-filter">
|
||||
<option value="all">全部优先级</option>
|
||||
<option value="low">低</option>
|
||||
@@ -1007,7 +1012,7 @@
|
||||
<option value="urgent">紧急</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-outline-secondary w-100" id="refresh-workorders">
|
||||
<i class="fas fa-sync-alt me-1"></i>刷新
|
||||
</button>
|
||||
@@ -2294,12 +2299,12 @@
|
||||
<div class="modal-body">
|
||||
<form id="work-order-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">工单标题</label>
|
||||
<input type="text" class="form-control" id="wo-title" required>
|
||||
<label class="form-label">问题标题</label>
|
||||
<input type="text" class="form-control" id="wo-title" placeholder="简要描述问题,如:远程启动失败" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">问题描述</label>
|
||||
<textarea class="form-control" id="wo-description" rows="3" required></textarea>
|
||||
<label class="form-label">问题详细描述</label>
|
||||
<textarea class="form-control" id="wo-description" rows="3" placeholder="详细描述问题现象、复现步骤等" required></textarea>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@@ -2310,6 +2315,8 @@
|
||||
<option value="APP功能">APP功能</option>
|
||||
<option value="远程控制">远程控制</option>
|
||||
<option value="车辆绑定">车辆绑定</option>
|
||||
<option value="系统故障">系统故障</option>
|
||||
<option value="OTA升级">OTA升级</option>
|
||||
<option value="其他">其他</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user