package com.xly.web;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.RequestHeader;
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.List;
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;
private final ObjectMapper mapper;
/** true=确认后委托 ERP 侧暂存执行器(/ai/execStaging,§10 生产路径);false=xlyAi 直连 ERP 通用写接口执行。 */
@org.springframework.beans.factory.annotation.Value("${erp.exec-staging.enabled:false}")
private boolean execStagingEnabled;
public OpController(OpService ops, ErpClient erp, AuditService audit, ObjectMapper mapper) {
this.ops = ops;
this.erp = erp;
this.audit = audit;
this.mapper = mapper;
}
/** 会话里最近一条待确认操作(前端每轮结束后轮询,用于渲染确认卡片)。 */
@GetMapping("/pending")
public Map pending(@RequestParam("conversationId") String conversationId) {
Map op = ops.pending(conversationId);
return op == null ? Map.of() : op;
}
/**
* 确认执行:调 ERP 执行暂存的写操作,回写状态。
* {@code Authorization} 头(可空)= 用户浏览器里的 ERP 登录 token,透传给 ERP 使执行以用户身份
* 进行;为空则回退 dev-login。绝不因用户 token 缺失而静默提权(见 ErpClient.canRelogin)。
*/
@PostMapping("/{id}/confirm")
public Map confirm(@PathVariable("id") String id,
@RequestHeader(value = "Authorization", required = false) String authToken) {
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"));
// 生产路径:委托 ERP 侧暂存执行器(它以用户身份执行、事务化并自行回写 ai_op_queue 状态)。
if (execStagingEnabled) {
return confirmViaExecutor(id, op, authToken, uid, conv, target, detail);
}
try {
String opType = str(op.get("sOpType"));
JsonNode r;
if ("create".equals(opType)) {
JsonNode payload = mapper.readTree(str(op.get("sPayload")));
if (payload.has("__tables__")) { // 报价等多表主-从创建
@SuppressWarnings("unchecked")
List