package com.xly.web; import com.fasterxml.jackson.databind.JsonNode; import com.xly.service.AuditService; import com.xly.service.ErpClient; import com.xly.service.OpService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.LinkedHashMap; import java.util.Map; /** * 写操作确认端点(**确定性、不经过 LLM**)—— 人在环写入闭环的执行侧。 * *

用户在对话框点【确认】-> {@code /confirm} 才真正调 ERP 执行;点【取消】-> {@code /cancel}。 * v1 为同步执行(update 很快):直接调 ERP 表单更新接口,回写 ai_op_queue 状态并返回结果。 */ @RestController @RequestMapping("/api/agent/op") public class OpController { private static final Logger log = LoggerFactory.getLogger(OpController.class); private final OpService ops; private final ErpClient erp; private final AuditService audit; public OpController(OpService ops, ErpClient erp, AuditService audit) { this.ops = ops; this.erp = erp; this.audit = audit; } /** 会话里最近一条待确认操作(前端每轮结束后轮询,用于渲染确认卡片)。 */ @GetMapping("/pending") public Map pending(@RequestParam("conversationId") String conversationId) { Map op = ops.pending(conversationId); return op == null ? Map.of() : op; } /** 确认执行:调 ERP 更新,回写状态。 */ @PostMapping("/{id}/confirm") public Map confirm(@PathVariable("id") String id) { Map op = ops.get(id); if (op == null) { return result("failed", "找不到该操作", null); } if (!"draft".equals(String.valueOf(op.get("sStatus")))) { return result(String.valueOf(op.get("sStatus")), "该操作已处理过(" + op.get("sStatus") + ")", op); } String uid = str(op.get("sUserId")); String conv = str(op.get("sConversationId")); String target = str(op.get("sTargetTable")) + "#" + str(op.get("sTargetBillId")); String detail = str(op.get("sDescription")); try { String opType = str(op.get("sOpType")); JsonNode r; if ("delete".equals(opType)) { r = erp.deleteForm(str(op.get("sTargetModuleId")), str(op.get("sTargetTable")), str(op.get("sTargetBillId"))); } else { r = erp.updateForm( str(op.get("sTargetModuleId")), str(op.get("sTargetTable")), str(op.get("sTargetBillId")), str(op.get("sField")), str(op.get("sNewValue"))); } int code = r.path("code").asInt(0); if (code == 1) { ops.setStatus(id, "executed", "操作成功"); audit.log(uid, conv, "confirm", target, detail, true, "executed"); return result("executed", "已执行:" + op.get("sDescription"), op); } String msg = r.path("msg").asText("执行失败"); ops.setStatus(id, "failed", msg); audit.log(uid, conv, "confirm", target, detail, false, msg); return result("failed", msg, op); } catch (Exception e) { log.warn("confirm op {} failed", id, e); ops.setStatus(id, "failed", e.getMessage()); audit.log(uid, conv, "confirm", target, detail, false, e.getMessage()); return result("failed", "执行异常:" + e.getMessage(), op); } } /** 取消。 */ @PostMapping("/{id}/cancel") public Map cancel(@PathVariable("id") String id) { Map op = ops.get(id); if (op != null && "draft".equals(String.valueOf(op.get("sStatus")))) { ops.setStatus(id, "cancelled", "用户取消"); audit.log(str(op.get("sUserId")), str(op.get("sConversationId")), "cancel", str(op.get("sTargetTable")) + "#" + str(op.get("sTargetBillId")), str(op.get("sDescription")), true, "cancelled"); } return result("cancelled", "已取消", op); } private Map result(String status, String msg, Map op) { Map m = new LinkedHashMap<>(); m.put("status", status); m.put("msg", msg); if (op != null) { m.put("opId", op.get("sId")); m.put("description", op.get("sDescription")); } return m; } private static String str(Object o) { return o == null ? null : o.toString(); } }