+45
@@ -164,6 +164,51 @@ public class InquiryAgentController {
|
||||
return Result.OK(out);
|
||||
}
|
||||
|
||||
@ApiOperation("开始事件异常等待(不改六态)")
|
||||
@PostMapping("/ticket/startEventWait")
|
||||
public Result<Map<String, Object>> startEventWait(@RequestBody Map<String, Object> body,
|
||||
HttpServletRequest http) {
|
||||
if (!authed(http)) {
|
||||
return Result.error(401, "unauthorized");
|
||||
}
|
||||
return Result.OK(workOrderService.startEventWait(
|
||||
firstStr(body, "workOrderNo", "work_order_no"),
|
||||
firstStr(body, "waitKind", "wait_kind")));
|
||||
}
|
||||
|
||||
@ApiOperation("清空事件异常等待(不改六态、不清摘要)")
|
||||
@PostMapping("/ticket/clearEventWait")
|
||||
public Result<Map<String, Object>> clearEventWait(@RequestBody Map<String, Object> body,
|
||||
HttpServletRequest http) {
|
||||
if (!authed(http)) {
|
||||
return Result.error(401, "unauthorized");
|
||||
}
|
||||
return Result.OK(workOrderService.clearEventWait(firstStr(body, "workOrderNo", "work_order_no")));
|
||||
}
|
||||
|
||||
@ApiOperation("列出未告警的事件异常等待(Worker 巡检)")
|
||||
@PostMapping("/ticket/listEventWaits")
|
||||
public Result<java.util.List<Map<String, Object>>> listEventWaits(
|
||||
@RequestBody(required = false) Map<String, Object> body,
|
||||
HttpServletRequest http) {
|
||||
if (!authed(http)) {
|
||||
return Result.error(401, "unauthorized");
|
||||
}
|
||||
return Result.OK(workOrderService.listEventWaits());
|
||||
}
|
||||
|
||||
@ApiOperation("记下事件异常摘要并标记已告警(不改六态)")
|
||||
@PostMapping("/ticket/recordEventException")
|
||||
public Result<Map<String, Object>> recordEventException(@RequestBody Map<String, Object> body,
|
||||
HttpServletRequest http) {
|
||||
if (!authed(http)) {
|
||||
return Result.error(401, "unauthorized");
|
||||
}
|
||||
return Result.OK(workOrderService.recordEventException(
|
||||
firstStr(body, "workOrderNo", "work_order_no"),
|
||||
firstStr(body, "eventException", "event_exception")));
|
||||
}
|
||||
|
||||
@ApiOperation("智能体续办读单(只读,校验发起人)")
|
||||
@PostMapping("/ticket/get")
|
||||
public Result<Map<String, Object>> get(@RequestBody Map<String, Object> body,
|
||||
|
||||
+4
-1
@@ -24,7 +24,7 @@ import java.util.Map;
|
||||
* <p>
|
||||
* 只提供全量列表与改小时数;无 add/delete,维度由 SQL 种子固定。
|
||||
* 每条有唯一 timeoutCode(如 sea_sales),智能体单码调用;roleCode 仅为角色维度可重复。
|
||||
* 本接口供管理后台使用;工单超时判定尚未接入。
|
||||
* 本接口供管理后台使用;智能体按 timeoutCode 读小时数做超时判定。
|
||||
* </p>
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -81,6 +81,9 @@ public class InquiryResponseTimeoutController
|
||||
if (body.getTimeoutHours() == null) {
|
||||
return Result.error("超时小时不能为空");
|
||||
}
|
||||
if (body.getTimeoutHours().compareTo(java.math.BigDecimal.ZERO) < 0) {
|
||||
return Result.error("超时小时不能为负数");
|
||||
}
|
||||
try {
|
||||
responseTimeoutService.updateTimeoutHours(body.getId(), body.getTimeoutHours(), currentUsername());
|
||||
return Result.OK("已保存");
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ import java.util.Date;
|
||||
* <p>
|
||||
* 维度行由种子固定(运输方式/角色/线路类别/运输类型),后台只允许改 timeoutHours。
|
||||
* timeoutCode 每条唯一,供智能体单码查询超时小时;roleCode 仅为角色维度,可重复。
|
||||
* 本表是配置真相;工单超时判定后续再读本表,本期仅 CRUD 小时数。
|
||||
* 本表是配置真相;工单超时判定读 timeoutHours(允许小数,0=不监测)。
|
||||
* </p>
|
||||
*/
|
||||
@Data
|
||||
@@ -49,8 +49,8 @@ public class InquiryResponseTimeout implements Serializable {
|
||||
@ApiModelProperty("运输类型(海运/陆运产品有值,其余空串)")
|
||||
private String transportType;
|
||||
|
||||
@ApiModelProperty("超时小时,整数≥0")
|
||||
private Integer timeoutHours;
|
||||
@ApiModelProperty("超时小时,允许小数,0=不监测")
|
||||
private java.math.BigDecimal timeoutHours;
|
||||
|
||||
@ApiModelProperty("展示排序")
|
||||
private Integer sortNo;
|
||||
|
||||
+11
@@ -116,6 +116,17 @@ public class InquiryTicket implements Serializable {
|
||||
@ApiModelProperty("事件异常摘要")
|
||||
private String eventException;
|
||||
|
||||
@ApiModelProperty("事件异常当前等待种类")
|
||||
private String eventWaitKind;
|
||||
|
||||
@ApiModelProperty("事件异常当前等待起点")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date eventWaitStartedAt;
|
||||
|
||||
@ApiModelProperty("当前等待是否已告警 0/1")
|
||||
private Integer eventWaitAlerted;
|
||||
|
||||
@ApiModelProperty("系统异常摘要")
|
||||
private String systemException;
|
||||
|
||||
|
||||
+2
-2
@@ -15,10 +15,10 @@ public interface IInquiryResponseTimeoutService extends IService<InquiryResponse
|
||||
* 仅更新超时小时数;维度字段不可改。
|
||||
*
|
||||
* @param id 配置行 id
|
||||
* @param timeoutHours 新小时数(≥0)
|
||||
* @param timeoutHours 新小时数(≥0,允许小数,0=不监测)
|
||||
* @param updateBy 操作人登录名
|
||||
*/
|
||||
void updateTimeoutHours(String id, int timeoutHours, String updateBy);
|
||||
void updateTimeoutHours(String id, java.math.BigDecimal timeoutHours, String updateBy);
|
||||
|
||||
/**
|
||||
* 把陆运产品「普件,…」运输类型对齐产品确认的括号文案。
|
||||
|
||||
+20
@@ -136,4 +136,24 @@ public interface IInquiryWorkOrderService extends IService<InquiryTicket> {
|
||||
* 只改到期行,不加全局锁。
|
||||
*/
|
||||
java.util.List<java.util.Map<String, Object>> claimDueNegotiate();
|
||||
|
||||
/**
|
||||
* 开始盯事件异常等待。种类相同不重置起点;种类不同重置并清已告警。不改六态。
|
||||
*/
|
||||
Map<String, Object> startEventWait(String workOrderNo, String waitKind);
|
||||
|
||||
/**
|
||||
* 清空当前事件异常等待三列,不改 eventException 摘要、不改六态。
|
||||
*/
|
||||
Map<String, Object> clearEventWait(String workOrderNo);
|
||||
|
||||
/**
|
||||
* 列出未告警且可盯的等待中工单,供 Worker 巡检。
|
||||
*/
|
||||
java.util.List<java.util.Map<String, Object>> listEventWaits();
|
||||
|
||||
/**
|
||||
* 追加事件异常摘要并标记当前等待已告警。不改六态。
|
||||
*/
|
||||
Map<String, Object> recordEventException(String workOrderNo, String eventException);
|
||||
}
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ public class InquiryResponseTimeoutServiceImpl
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateTimeoutHours(String id, int timeoutHours, String updateBy) {
|
||||
if (timeoutHours < 0) {
|
||||
public void updateTimeoutHours(String id, java.math.BigDecimal timeoutHours, String updateBy) {
|
||||
if (timeoutHours == null || timeoutHours.compareTo(java.math.BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalArgumentException("超时小时不能为负数");
|
||||
}
|
||||
InquiryResponseTimeout row = getById(id);
|
||||
|
||||
+200
-1
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.inquiry.domain.AirGroupActivateRule;
|
||||
import org.jeecg.modules.inquiry.domain.EventExceptionRule;
|
||||
import org.jeecg.modules.inquiry.domain.TicketStatuses;
|
||||
import org.jeecg.modules.inquiry.dto.AgentTicketCreateRequest;
|
||||
import org.jeecg.modules.inquiry.dto.TicketAbnormalRequest;
|
||||
@@ -101,7 +102,7 @@ public class InquiryWorkOrderServiceImpl extends ServiceImpl<InquiryTicketMapper
|
||||
if ("-".equals(filters.get("eventException"))) {
|
||||
qw.and(w -> w.isNull(InquiryTicket::getEventException).or().eq(InquiryTicket::getEventException, "").or().eq(InquiryTicket::getEventException, "-"));
|
||||
} else {
|
||||
qw.eq(InquiryTicket::getEventException, filters.get("eventException"));
|
||||
qw.like(InquiryTicket::getEventException, filters.get("eventException"));
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(filters.get("systemException"))) {
|
||||
@@ -1405,6 +1406,204 @@ public class InquiryWorkOrderServiceImpl extends ServiceImpl<InquiryTicketMapper
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始盯当前等待。可盯状态才写;同种类不重置起点。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> startEventWait(String workOrderNo, String waitKind) {
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
String no = StringUtils.trimToEmpty(workOrderNo);
|
||||
String kind = StringUtils.trimToEmpty(waitKind);
|
||||
InquiryTicket ticket = findByWorkOrderNo(no);
|
||||
if (ticket == null) {
|
||||
out.put("ok", false);
|
||||
out.put("error", "not_found");
|
||||
return out;
|
||||
}
|
||||
if (!EventExceptionRule.isWatchable(ticket.getStatus()) || kind.isEmpty()) {
|
||||
out.put("ok", false);
|
||||
out.put("error", "not_watchable");
|
||||
out.put("status", ticket.getStatus());
|
||||
return out;
|
||||
}
|
||||
Date now = new Date();
|
||||
EventExceptionRule.EventWaitState next = EventExceptionRule.applyStart(
|
||||
ticket.getEventWaitKind(), ticket.getEventWaitStartedAt(),
|
||||
ticket.getEventWaitAlerted(), kind, now);
|
||||
ticket.setEventWaitKind(next.kind);
|
||||
ticket.setEventWaitStartedAt(next.startedAt);
|
||||
ticket.setEventWaitAlerted(next.alerted);
|
||||
ticket.setUpdateTime(now);
|
||||
updateById(ticket);
|
||||
out.put("ok", true);
|
||||
out.put("work_order_no", no);
|
||||
out.put("eventWaitKind", next.kind);
|
||||
out.put("eventWaitAlerted", next.alerted);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> clearEventWait(String workOrderNo) {
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
InquiryTicket ticket = findByWorkOrderNo(StringUtils.trimToEmpty(workOrderNo));
|
||||
if (ticket == null) {
|
||||
out.put("ok", false);
|
||||
out.put("error", "not_found");
|
||||
return out;
|
||||
}
|
||||
ticket.setEventWaitKind(null);
|
||||
ticket.setEventWaitStartedAt(null);
|
||||
ticket.setEventWaitAlerted(0);
|
||||
ticket.setUpdateTime(new Date());
|
||||
updateById(ticket);
|
||||
out.put("ok", true);
|
||||
out.put("work_order_no", ticket.getWorkOrderNo());
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> listEventWaits() {
|
||||
List<InquiryTicket> rows = list(new LambdaQueryWrapper<InquiryTicket>()
|
||||
.isNotNull(InquiryTicket::getEventWaitKind)
|
||||
.ne(InquiryTicket::getEventWaitKind, "")
|
||||
.and(w -> w.isNull(InquiryTicket::getEventWaitAlerted)
|
||||
.or().eq(InquiryTicket::getEventWaitAlerted, 0))
|
||||
.notIn(InquiryTicket::getStatus,
|
||||
TicketStatuses.DEAL, TicketStatuses.CLOSED, TicketStatuses.HANDOFF));
|
||||
List<Map<String, Object>> out = new ArrayList<>();
|
||||
if (rows == null) {
|
||||
return out;
|
||||
}
|
||||
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
for (InquiryTicket ticket : rows) {
|
||||
if (ticket == null || !EventExceptionRule.isWatchable(ticket.getStatus())) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> one = new LinkedHashMap<>();
|
||||
one.put("workOrderNo", ticket.getWorkOrderNo());
|
||||
one.put("work_order_no", ticket.getWorkOrderNo());
|
||||
one.put("status", ticket.getStatus());
|
||||
one.put("businessLine", StringUtils.defaultString(ticket.getBusinessLine()));
|
||||
one.put("business_line", StringUtils.defaultString(ticket.getBusinessLine()));
|
||||
one.put("eventWaitKind", ticket.getEventWaitKind());
|
||||
one.put("event_wait_kind", ticket.getEventWaitKind());
|
||||
if (ticket.getEventWaitStartedAt() != null) {
|
||||
String started = fmt.format(ticket.getEventWaitStartedAt());
|
||||
one.put("eventWaitStartedAt", started);
|
||||
one.put("event_wait_started_at", started);
|
||||
}
|
||||
one.put("salesWecomId", StringUtils.defaultString(ticket.getSalesWecomId()));
|
||||
one.put("sales_wecom_id", StringUtils.defaultString(ticket.getSalesWecomId()));
|
||||
one.put("salesName", StringUtils.defaultString(ticket.getSalesName()));
|
||||
one.put("sales_name", StringUtils.defaultString(ticket.getSalesName()));
|
||||
one.put("collabChatId", StringUtils.defaultString(ticket.getCollabChatId()));
|
||||
one.put("collab_chat_id", StringUtils.defaultString(ticket.getCollabChatId()));
|
||||
List<String> members = parseMemberIds(ticket.getCollabMemberIds());
|
||||
one.put("collabMemberIds", members);
|
||||
one.put("collab_member_ids", members);
|
||||
one.put("eventException", StringUtils.defaultString(ticket.getEventException()));
|
||||
Map<String, String> facts = new LinkedHashMap<>();
|
||||
if (StringUtils.isNotBlank(ticket.getFactsJson())) {
|
||||
JSONObject parsed = JSON.parseObject(ticket.getFactsJson());
|
||||
if (parsed != null) {
|
||||
for (String key : parsed.keySet()) {
|
||||
if (parsed.get(key) != null) {
|
||||
facts.put(key, String.valueOf(parsed.get(key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
one.put("facts", facts);
|
||||
if (ticket.getNextFollowupAt() != null) {
|
||||
one.put("nextFollowupAt", fmt.format(ticket.getNextFollowupAt()));
|
||||
one.put("next_followup_at", fmt.format(ticket.getNextFollowupAt()));
|
||||
}
|
||||
one.put("watchers", buildEventWatchers(ticket, members));
|
||||
out.add(one);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> recordEventException(String workOrderNo, String eventException) {
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
InquiryTicket ticket = findByWorkOrderNo(StringUtils.trimToEmpty(workOrderNo));
|
||||
if (ticket == null) {
|
||||
out.put("ok", false);
|
||||
out.put("error", "not_found");
|
||||
return out;
|
||||
}
|
||||
String type = StringUtils.trimToEmpty(eventException);
|
||||
ticket.setEventException(EventExceptionRule.appendSummary(ticket.getEventException(), type));
|
||||
ticket.setEventWaitAlerted(1);
|
||||
ticket.setUpdateTime(new Date());
|
||||
updateById(ticket);
|
||||
appendLog(ticket, ticket.getStatus(), ticket.getStatus(), "event_exception", "agent", type);
|
||||
out.put("ok", true);
|
||||
out.put("work_order_no", ticket.getWorkOrderNo());
|
||||
out.put("eventException", ticket.getEventException());
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildEventWatchers(InquiryTicket ticket, List<String> memberIds) {
|
||||
List<Map<String, Object>> out = new ArrayList<>();
|
||||
Set<String> seen = new HashSet<>();
|
||||
List<String> ids = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(ticket.getSalesWecomId())) {
|
||||
ids.add(ticket.getSalesWecomId().trim());
|
||||
}
|
||||
if (memberIds != null) {
|
||||
ids.addAll(memberIds);
|
||||
}
|
||||
if (ids.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
List<InquiryStaffRole> rows = staffRoleMapper.selectList(new LambdaQueryWrapper<InquiryStaffRole>()
|
||||
.in(InquiryStaffRole::getWecomId, ids)
|
||||
.eq(InquiryStaffRole::getStatus, "active"));
|
||||
Map<String, InquiryStaffRole> byId = new HashMap<>();
|
||||
if (rows != null) {
|
||||
for (InquiryStaffRole row : rows) {
|
||||
if (row != null && StringUtils.isNotBlank(row.getWecomId())) {
|
||||
byId.put(row.getWecomId().trim(), row);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(ticket.getSalesWecomId())) {
|
||||
String sid = ticket.getSalesWecomId().trim();
|
||||
InquiryStaffRole sales = byId.get(sid);
|
||||
Map<String, Object> one = new LinkedHashMap<>();
|
||||
one.put("wecomId", sid);
|
||||
one.put("name", sales != null ? StringUtils.defaultString(sales.getName())
|
||||
: StringUtils.defaultString(ticket.getSalesName()));
|
||||
one.put("roleCode", sales != null ? StringUtils.defaultString(sales.getRoleCode()) : "sales");
|
||||
one.put("notifyEventException", sales != null && sales.getNotifyEventException() != null
|
||||
? sales.getNotifyEventException() : 0);
|
||||
out.add(one);
|
||||
seen.add(sid);
|
||||
}
|
||||
for (String uid : memberIds == null ? Collections.<String>emptyList() : memberIds) {
|
||||
if (StringUtils.isBlank(uid) || seen.contains(uid)) {
|
||||
continue;
|
||||
}
|
||||
InquiryStaffRole row = byId.get(uid);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> one = new LinkedHashMap<>();
|
||||
one.put("wecomId", uid);
|
||||
one.put("name", StringUtils.defaultString(row.getName()));
|
||||
one.put("roleCode", StringUtils.defaultString(row.getRoleCode()));
|
||||
one.put("notifyEventException", row.getNotifyEventException() == null ? 0 : row.getNotifyEventException());
|
||||
out.add(one);
|
||||
seen.add(uid);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void appendLog(InquiryTicket ticket, String from, String to, String action, String operator, String remark) {
|
||||
InquiryTicketStatusLog log = new InquiryTicketStatusLog();
|
||||
log.setTicketId(ticket.getId());
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
-- 超时小时改为小数,0 表示该规则不监测。
|
||||
-- 仅改 inquiry_robot;幂等说明:重复执行若已是 decimal 会报 1060/1064,可忽略。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `inq_response_timeout`
|
||||
MODIFY COLUMN `timeout_hours` decimal(10,4) NOT NULL DEFAULT 0 COMMENT '超时小时,允许小数,0=不监测';
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
-- 工单事件异常当前等待:种类、起点、是否已告警。
|
||||
-- 仅改 inquiry_robot。列已存在则跳过(手工执行前请 DESCRIBE inq_ticket)。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `inq_ticket`
|
||||
ADD COLUMN `event_wait_kind` varchar(32) NULL COMMENT '事件异常当前等待' AFTER `event_exception`,
|
||||
ADD COLUMN `event_wait_started_at` datetime NULL COMMENT '当前等待起点' AFTER `event_wait_kind`,
|
||||
ADD COLUMN `event_wait_alerted` tinyint NULL DEFAULT 0 COMMENT '当前等待是否已告警' AFTER `event_wait_started_at`;
|
||||
@@ -82,14 +82,14 @@ async function loadList() {
|
||||
onMounted(() => loadList());
|
||||
|
||||
function onHoursInput(id, raw) {
|
||||
const n = Math.floor(Number(raw));
|
||||
const n = Number(raw);
|
||||
draftHours.value[id] = Number.isFinite(n) && n >= 0 ? n : 0;
|
||||
}
|
||||
|
||||
async function commitHours(row) {
|
||||
const hours = draftHours.value[row.id];
|
||||
if (hours == null || !Number.isFinite(hours) || hours < 0) {
|
||||
message.warning('超时小时须为 ≥0 的整数');
|
||||
message.warning('超时小时须为 ≥0 的数字,支持小数');
|
||||
draftHours.value[row.id] = row.timeoutHours;
|
||||
return;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ async function commitHours(row) {
|
||||
<template>
|
||||
<div class="sys-setting-page">
|
||||
<div class="toolbar-bar">
|
||||
<span class="hint">固定规则,仅可改超时小时;编码唯一只读,供智能体调用;失焦或回车保存</span>
|
||||
<span class="hint">固定规则,仅可改超时小时(支持小数,0=不监测);编码唯一只读,供智能体调用;失焦或回车保存</span>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" style="padding: 24px; color: #94a3b8">加载中…</div>
|
||||
@@ -151,7 +151,7 @@ async function commitHours(row) {
|
||||
class="field hours-input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
step="0.01"
|
||||
:value="draftHours[row.id]"
|
||||
:disabled="savingId === row.id"
|
||||
@input="onHoursInput(row.id, $event.target.value)"
|
||||
|
||||
Reference in New Issue
Block a user