Merge branch 'main' of http://39.104.65.241:3000/wangheng/xiaoqu_mcp_service into codex/crm-contract-sync-review
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
||||
package me.iiv.agent.entity.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/** 调拨/采购/划转流程视图 warehouse_allocation_process(m2,只读):一行 = 单据 × 处理人 × 待办环节。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("warehouse_allocation_process")
|
||||
public class WarehouseAllocationProcess implements Serializable {
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Integer softDelete;
|
||||
private Long warehouseAllocationId;
|
||||
private String allocationOrderNo;
|
||||
private Integer allocationCount;
|
||||
private Date applyDate;
|
||||
private String applyUserName;
|
||||
private Long applyUserId;
|
||||
/** 视图列名即 currend_audit_level(老系统拼写保留) */
|
||||
@TableField("currend_audit_level")
|
||||
private Integer currendAuditLevel;
|
||||
private Integer goodsCount;
|
||||
private LocalDate allocationDate;
|
||||
private String userName;
|
||||
private Long userId;
|
||||
/** 操作主题(0:审核通过;1:审核驳回;2:入库确认驳回;3:转出确认;4:出库确认驳回;5:驳回后修改提交;6:入库确认;7:出库确认;8:撤销) */
|
||||
private Integer operationTheme;
|
||||
/** 待办状态(0:无需待办;1:待办;2:已办) */
|
||||
private Integer scheduleState;
|
||||
/** 审核状态(0:审核驳回/驳回;1:审核通过/出库确认) */
|
||||
private Integer auditState;
|
||||
/** 流程行审核级别 */
|
||||
@TableField("process_audit_level")
|
||||
private Integer processAuditLevel;
|
||||
/** 操作类型(0:审批流操作;1:确认入库操作;2:确认出库操作;3:本人修改;4:撤销,纯日志) */
|
||||
private Integer operationType;
|
||||
private String warehouseOutName;
|
||||
private String warehouseInName;
|
||||
private Long warehouseInId;
|
||||
private Long warehouseOutId;
|
||||
/** 单据审核级别 */
|
||||
private Integer auditLevel;
|
||||
/** 调拨单状态(0:待审核;1:被驳回;2:待出库;3:待入库;4:已完成;5:已撤销;6:作废) */
|
||||
private Integer orderState;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private Date createTime;
|
||||
/** 办理类型(0:调拨;1:采购;2:划转) */
|
||||
private Integer opType;
|
||||
private String memo;
|
||||
private String inUserName;
|
||||
private Date inTime;
|
||||
private String nextUserName;
|
||||
private String outUserName;
|
||||
private Date outTime;
|
||||
private String pickupOrderNo;
|
||||
private String installOrderNo;
|
||||
private String deliveryOrderNo;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.iiv.agent.entity.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/** 入库单流程视图 warehouse_in_process(m2,只读):一行 = 单据 × 处理人 × 待办环节。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("warehouse_in_process")
|
||||
public class WarehouseInProcess implements Serializable {
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Integer softDelete;
|
||||
private Long warehouseInId;
|
||||
private Long warehouseId;
|
||||
private String warehouseName;
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private Date operationTime;
|
||||
/** 待办状态(0:无需待办;1:待办;2:已办) */
|
||||
private Integer scheduleState;
|
||||
/** 审核状态(0:审核驳回;1:审核通过),仅与流程审批有关 */
|
||||
private Integer auditState;
|
||||
/** 操作主题(0:审核通过;1:审核驳回;2:撤销入库;3:入库确认;4:驳回修改提交;5:撤销;6:入库驳回) */
|
||||
private Integer operationTheme;
|
||||
/** 操作类型(0:审批流操作;1:确认入库操作;2:本人修改;3:驳回或修改操作,纯日志) */
|
||||
private Integer operationType;
|
||||
private String inOrderNo;
|
||||
private String allocationOrderNo;
|
||||
private String outOrderNo;
|
||||
private String mallOrderNo;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private Long warehouseTypeId;
|
||||
private String warehouseTypeName;
|
||||
private LocalDate inDate;
|
||||
private Integer goodsCount;
|
||||
private Integer inCount;
|
||||
/** 入库单状态(0:待审核;1:被驳回;2:待入库;3:已完成;4:已撤销;5:作废) */
|
||||
private Integer orderState;
|
||||
private Long applyUserId;
|
||||
private String applyUserName;
|
||||
private Date applyDate;
|
||||
private Integer auditLevel;
|
||||
/** 视图列名即 currend_audit_level(老系统拼写保留) */
|
||||
@TableField("currend_audit_level")
|
||||
private Integer currendAuditLevel;
|
||||
private String memo;
|
||||
private Date createTime;
|
||||
private String pickupOrderNo;
|
||||
private String installOrderNo;
|
||||
private String deliveryOrderNo;
|
||||
private String withdrawOrderNo;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package me.iiv.agent.entity.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/** 出库单流程视图 warehouse_out_process(m2,只读):一行 = 单据 × 处理人 × 待办环节。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("warehouse_out_process")
|
||||
public class WarehouseOutProcess implements Serializable {
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Integer softDelete;
|
||||
private Long warehouseOutId;
|
||||
private Long warehouseId;
|
||||
private String warehouseName;
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private Date operationTime;
|
||||
/** 待办状态(0:无需待办;1:待办;2:已办) */
|
||||
private Integer scheduleState;
|
||||
/** 审核状态(0:审核驳回;1:审核通过),仅与流程审批有关 */
|
||||
private Integer auditState;
|
||||
/** 操作主题(0:审核通过;1:审核驳回;2:撤销入库;3:出库确认;4:驳回修改提交;5:撤销) */
|
||||
private Integer operationTheme;
|
||||
/** 操作类型(0:审批流操作;1:确认出库操作;2:本人修改;3:驳回或修改操作,纯日志) */
|
||||
private Integer operationType;
|
||||
private String outOrderNo;
|
||||
private String allocationOrderNo;
|
||||
private String mallOrderNo;
|
||||
private Long agentId;
|
||||
private String agentName;
|
||||
private Long warehouseTypeId;
|
||||
private String warehouseTypeName;
|
||||
private LocalDate outDate;
|
||||
private Integer goodsCount;
|
||||
private Integer outCount;
|
||||
/** 出库单状态(0:待审核;1:被驳回;2:待出库;3:已完成;4:已撤销;5:作废) */
|
||||
private Integer orderState;
|
||||
private Long applyUserId;
|
||||
private String applyUserName;
|
||||
private Date applyDate;
|
||||
private Integer auditLevel;
|
||||
/** 视图列名即 currend_audit_level(老系统拼写保留) */
|
||||
@TableField("currend_audit_level")
|
||||
private Integer currendAuditLevel;
|
||||
private String memo;
|
||||
private Date createTime;
|
||||
private String pickupOrderNo;
|
||||
private String installOrderNo;
|
||||
private String deliveryOrderNo;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package me.iiv.agent.mapper.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m2.WarehouseAllocationProcess;
|
||||
|
||||
public interface WarehouseAllocationProcessMapper extends BaseMapper<WarehouseAllocationProcess> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package me.iiv.agent.mapper.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m2.WarehouseInProcess;
|
||||
|
||||
public interface WarehouseInProcessMapper extends BaseMapper<WarehouseInProcess> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package me.iiv.agent.mapper.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m2.WarehouseOutProcess;
|
||||
|
||||
public interface WarehouseOutProcessMapper extends BaseMapper<WarehouseOutProcess> {
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
package me.iiv.agent.mcp.tool;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m2.WarehouseAllocationProcess;
|
||||
import me.iiv.agent.entity.m2.WarehouseInProcess;
|
||||
import me.iiv.agent.entity.m2.WarehouseOutProcess;
|
||||
import me.iiv.agent.mapper.m2.WarehouseAllocationProcessMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseInProcessMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseOutProcessMapper;
|
||||
import me.iiv.agent.mcp.ToolObject;
|
||||
import me.iiv.agent.mcp.XqTool;
|
||||
import me.iiv.agent.security.AuthContextHolder;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 仓库待办查询工具,参考老系统 /myWarehouseOut/needToBeDealtWith、/myWarehouseIn/needToBeDealtWith、
|
||||
* /warehouseAllocation/warehouseAllocationProcessListByPage,统一查询三张流程视图。
|
||||
*/
|
||||
@Component
|
||||
public class WarehouseTodoTools implements ToolObject {
|
||||
|
||||
private static final int DEFAULT_ROWS = 20;
|
||||
private static final int MAX_ROWS = 100;
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final DateTimeFormatter TIME_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(BUSINESS_ZONE);
|
||||
|
||||
/** 待办环节只保留可办理的操作类型,3(驳回或修改记录)为纯日志,排除。 */
|
||||
private static final List<Integer> TODO_OPERATION_TYPES = List.of(0, 1, 2);
|
||||
|
||||
private static final Map<Integer, String> SCHEDULE_STATE_TEXT = Map.of(
|
||||
0, "无需待办", 1, "待办", 2, "已办");
|
||||
private static final Map<Integer, String> OUT_ORDER_STATE_TEXT = Map.of(
|
||||
0, "待审核", 1, "被驳回", 2, "待出库", 3, "已完成", 4, "已撤销", 5, "作废");
|
||||
private static final Map<Integer, String> IN_ORDER_STATE_TEXT = Map.of(
|
||||
0, "待审核", 1, "被驳回", 2, "待入库", 3, "已完成", 4, "已撤销", 5, "作废");
|
||||
private static final Map<Integer, String> ALLOCATION_ORDER_STATE_TEXT = Map.of(
|
||||
0, "待审核", 1, "被驳回", 2, "待出库", 3, "待入库", 4, "已完成", 5, "已撤销", 6, "作废");
|
||||
private static final Map<Integer, String> OUT_OPERATION_TYPE_TEXT = Map.of(
|
||||
0, "审批流操作", 1, "确认出库", 2, "本人修改");
|
||||
private static final Map<Integer, String> IN_OPERATION_TYPE_TEXT = Map.of(
|
||||
0, "审批流操作", 1, "确认入库", 2, "本人修改");
|
||||
private static final Map<Integer, String> ALLOCATION_OPERATION_TYPE_TEXT = Map.of(
|
||||
0, "审批流操作", 1, "确认入库", 2, "确认出库", 3, "本人修改", 4, "撤销");
|
||||
private static final Map<Integer, String> OP_TYPE_TEXT = Map.of(
|
||||
0, "调拨", 1, "采购", 2, "划转");
|
||||
|
||||
final WarehouseOutProcessMapper outProcessMapper;
|
||||
final WarehouseInProcessMapper inProcessMapper;
|
||||
final WarehouseAllocationProcessMapper allocationProcessMapper;
|
||||
|
||||
public WarehouseTodoTools(WarehouseOutProcessMapper outProcessMapper,
|
||||
WarehouseInProcessMapper inProcessMapper,
|
||||
WarehouseAllocationProcessMapper allocationProcessMapper) {
|
||||
this.outProcessMapper = outProcessMapper;
|
||||
this.inProcessMapper = inProcessMapper;
|
||||
this.allocationProcessMapper = allocationProcessMapper;
|
||||
}
|
||||
|
||||
@XqTool(category = "warehouse", kind = "read")
|
||||
@Tool(name = "warehouse.todo.list.query", description = "分页查询当前用户的仓库待办,统一老系统 /myWarehouseOut/needToBeDealtWith、"
|
||||
+ "/myWarehouseIn/needToBeDealtWith、/warehouseAllocation/warehouseAllocationProcessListByPage 三个待办接口。"
|
||||
+ "数据来自 m2 流程视图 warehouse_out_process / warehouse_in_process / warehouse_allocation_process:"
|
||||
+ "待办语义 = 视图里存在当前用户的任务行(user_id = 当前登录人 AND soft_delete = 0 AND operation_type IN (0,1,2),"
|
||||
+ "3=纯日志排除),不按代理商过滤。同一笔调拨最多以调拨审批/确认、出库执行、入库执行 3 个视角出现,不去重;"
|
||||
+ "派生自调拨的真实出入库单带 derivedFrom=\"allocation\" 标记。"
|
||||
+ "docType:out=出库单 in=入库单 allocation=调拨/采购/划转单(按 op_type 区分) all=全部,默认 all。"
|
||||
+ "todoOnly 默认 true(只返回 schedule_state=1 真待办),false 返回当前用户参与过的全部单据。"
|
||||
+ "orderState 按单据状态过滤:出库/入库单 0待审核 1被驳回 2待出库/待入库 3已完成 4已撤销 5作废;"
|
||||
+ "调拨类 0待审核 1被驳回 2待出库 3待入库 4已完成 5已撤销 6作废。"
|
||||
+ "分页 page 默认1,rows 默认20、上限100,超出截断为 100 并在 notice 提示。")
|
||||
public Map<String, Object> warehouseTodoListQuery(
|
||||
@ToolParam(required = false, description = "单据类型:out/in/allocation/all,默认 all。allocation 涵盖调拨/采购/划转") String docType,
|
||||
@ToolParam(required = false, description = "true=只返回 schedule_state=1 真待办(默认);false=当前用户参与过的全部单据") Boolean todoOnly,
|
||||
@ToolParam(required = false, description = "按单据状态过滤:出库/入库单 0待审核 1被驳回 2待出库或待入库 3已完成 4已撤销 5作废;调拨类 0待审核 1被驳回 2待出库 3待入库 4已完成 5已撤销 6作废") Integer orderState,
|
||||
@ToolParam(required = false, description = "单号模糊匹配(出库/入库/调拨单号)") String orderNo,
|
||||
@ToolParam(required = false, description = "页码,从1开始,默认1") Integer page,
|
||||
@ToolParam(required = false, description = "每页条数,默认20,上限100") Integer rows) {
|
||||
String type = StringUtils.hasText(docType) ? docType.trim().toLowerCase() : "all";
|
||||
if (!type.equals("out") && !type.equals("in") && !type.equals("allocation") && !type.equals("all")) {
|
||||
throw new IllegalArgumentException("docType 必须是 out / in / allocation / all");
|
||||
}
|
||||
boolean onlyTodo = todoOnly == null || todoOnly;
|
||||
int pageNo = page == null || page < 1 ? 1 : page;
|
||||
boolean truncated = rows != null && rows > MAX_ROWS;
|
||||
int pageSize = rows == null || rows < 1 ? DEFAULT_ROWS : Math.min(rows, MAX_ROWS);
|
||||
|
||||
User user = AuthContextHolder.get().getUser();
|
||||
Long userId = user.getId();
|
||||
|
||||
List<TodoStub> stubs = new ArrayList<>();
|
||||
if (type.equals("out") || type.equals("all")) {
|
||||
stubs.addAll(outStubs(userId, onlyTodo, orderState, orderNo));
|
||||
}
|
||||
if (type.equals("in") || type.equals("all")) {
|
||||
stubs.addAll(inStubs(userId, onlyTodo, orderState, orderNo));
|
||||
}
|
||||
if (type.equals("allocation") || type.equals("all")) {
|
||||
stubs.addAll(allocationStubs(userId, onlyTodo, orderState, orderNo));
|
||||
}
|
||||
stubs.sort(Comparator.comparing(TodoStub::applyDate,
|
||||
Comparator.nullsLast(Comparator.reverseOrder()))
|
||||
.thenComparing(TodoStub::docId, Comparator.reverseOrder()));
|
||||
|
||||
int total = stubs.size();
|
||||
int from = Math.min((pageNo - 1) * pageSize, total);
|
||||
int to = Math.min(from + pageSize, total);
|
||||
List<TodoStub> pageStubs = stubs.subList(from, to);
|
||||
|
||||
List<Map<String, Object>> records = assemble(pageStubs, userId, onlyTodo);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("total", total);
|
||||
result.put("page", pageNo);
|
||||
result.put("rows", pageSize);
|
||||
if (truncated) {
|
||||
result.put("notice", "rows 超过上限 100,已截断为 100");
|
||||
}
|
||||
result.put("records", records);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 第一阶段:全量查符合条件(含过滤)的流程行,内存按单据 id 去重。 */
|
||||
private List<TodoStub> outStubs(Long userId, boolean onlyTodo, Integer orderState, String orderNo) {
|
||||
LambdaQueryWrapper<WarehouseOutProcess> wrapper = new LambdaQueryWrapper<WarehouseOutProcess>()
|
||||
.select(WarehouseOutProcess::getWarehouseOutId, WarehouseOutProcess::getApplyDate)
|
||||
.eq(WarehouseOutProcess::getSoftDelete, 0)
|
||||
.eq(WarehouseOutProcess::getUserId, userId)
|
||||
.in(WarehouseOutProcess::getOperationType, TODO_OPERATION_TYPES)
|
||||
.eq(onlyTodo, WarehouseOutProcess::getScheduleState, 1)
|
||||
.eq(orderState != null, WarehouseOutProcess::getOrderState, orderState)
|
||||
.like(StringUtils.hasText(orderNo), WarehouseOutProcess::getOutOrderNo,
|
||||
StringUtils.hasText(orderNo) ? orderNo.trim() : orderNo);
|
||||
Map<Long, Date> byDocId = new LinkedHashMap<>();
|
||||
for (WarehouseOutProcess row : outProcessMapper.selectList(wrapper)) {
|
||||
byDocId.putIfAbsent(row.getWarehouseOutId(), row.getApplyDate());
|
||||
}
|
||||
return byDocId.entrySet().stream()
|
||||
.map(entry -> new TodoStub("out", entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<TodoStub> inStubs(Long userId, boolean onlyTodo, Integer orderState, String orderNo) {
|
||||
LambdaQueryWrapper<WarehouseInProcess> wrapper = new LambdaQueryWrapper<WarehouseInProcess>()
|
||||
.select(WarehouseInProcess::getWarehouseInId, WarehouseInProcess::getApplyDate)
|
||||
.eq(WarehouseInProcess::getSoftDelete, 0)
|
||||
.eq(WarehouseInProcess::getUserId, userId)
|
||||
.in(WarehouseInProcess::getOperationType, TODO_OPERATION_TYPES)
|
||||
.eq(onlyTodo, WarehouseInProcess::getScheduleState, 1)
|
||||
.eq(orderState != null, WarehouseInProcess::getOrderState, orderState)
|
||||
.like(StringUtils.hasText(orderNo), WarehouseInProcess::getInOrderNo,
|
||||
StringUtils.hasText(orderNo) ? orderNo.trim() : orderNo);
|
||||
Map<Long, Date> byDocId = new LinkedHashMap<>();
|
||||
for (WarehouseInProcess row : inProcessMapper.selectList(wrapper)) {
|
||||
byDocId.putIfAbsent(row.getWarehouseInId(), row.getApplyDate());
|
||||
}
|
||||
return byDocId.entrySet().stream()
|
||||
.map(entry -> new TodoStub("in", entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<TodoStub> allocationStubs(Long userId, boolean onlyTodo, Integer orderState, String orderNo) {
|
||||
LambdaQueryWrapper<WarehouseAllocationProcess> wrapper = new LambdaQueryWrapper<WarehouseAllocationProcess>()
|
||||
.select(WarehouseAllocationProcess::getWarehouseAllocationId, WarehouseAllocationProcess::getApplyDate)
|
||||
.eq(WarehouseAllocationProcess::getSoftDelete, 0)
|
||||
.eq(WarehouseAllocationProcess::getUserId, userId)
|
||||
.in(WarehouseAllocationProcess::getOperationType, TODO_OPERATION_TYPES)
|
||||
.eq(onlyTodo, WarehouseAllocationProcess::getScheduleState, 1)
|
||||
.eq(orderState != null, WarehouseAllocationProcess::getOrderState, orderState)
|
||||
.like(StringUtils.hasText(orderNo), WarehouseAllocationProcess::getAllocationOrderNo,
|
||||
StringUtils.hasText(orderNo) ? orderNo.trim() : orderNo);
|
||||
Map<Long, Date> byDocId = new LinkedHashMap<>();
|
||||
for (WarehouseAllocationProcess row : allocationProcessMapper.selectList(wrapper)) {
|
||||
byDocId.putIfAbsent(row.getWarehouseAllocationId(), row.getApplyDate());
|
||||
}
|
||||
return byDocId.entrySet().stream()
|
||||
.map(entry -> new TodoStub("allocation", entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** 第二阶段:按页内单据 id 批量查视图行组装记录,避免 N+1;输出顺序与分页排序一致。 */
|
||||
private List<Map<String, Object>> assemble(List<TodoStub> stubs, Long userId, boolean onlyTodo) {
|
||||
Map<String, List<TodoStub>> byType = stubs.stream()
|
||||
.collect(Collectors.groupingBy(TodoStub::docType, LinkedHashMap::new, Collectors.toList()));
|
||||
|
||||
Map<Long, List<WarehouseOutProcess>> outRowsByDoc = Map.of();
|
||||
List<TodoStub> outStubs = byType.getOrDefault("out", List.of());
|
||||
if (!outStubs.isEmpty()) {
|
||||
List<Long> ids = outStubs.stream().map(TodoStub::docId).collect(Collectors.toList());
|
||||
outRowsByDoc = outProcessMapper.selectList(
|
||||
new LambdaQueryWrapper<WarehouseOutProcess>()
|
||||
.eq(WarehouseOutProcess::getSoftDelete, 0)
|
||||
.eq(WarehouseOutProcess::getUserId, userId)
|
||||
.in(WarehouseOutProcess::getOperationType, TODO_OPERATION_TYPES)
|
||||
.in(WarehouseOutProcess::getWarehouseOutId, ids)
|
||||
.orderByAsc(WarehouseOutProcess::getId)).stream()
|
||||
.collect(Collectors.groupingBy(WarehouseOutProcess::getWarehouseOutId,
|
||||
LinkedHashMap::new, Collectors.toList()));
|
||||
}
|
||||
|
||||
Map<Long, List<WarehouseInProcess>> inRowsByDoc = Map.of();
|
||||
List<TodoStub> inStubs = byType.getOrDefault("in", List.of());
|
||||
if (!inStubs.isEmpty()) {
|
||||
List<Long> ids = inStubs.stream().map(TodoStub::docId).collect(Collectors.toList());
|
||||
inRowsByDoc = inProcessMapper.selectList(
|
||||
new LambdaQueryWrapper<WarehouseInProcess>()
|
||||
.eq(WarehouseInProcess::getSoftDelete, 0)
|
||||
.eq(WarehouseInProcess::getUserId, userId)
|
||||
.in(WarehouseInProcess::getOperationType, TODO_OPERATION_TYPES)
|
||||
.in(WarehouseInProcess::getWarehouseInId, ids)
|
||||
.orderByAsc(WarehouseInProcess::getId)).stream()
|
||||
.collect(Collectors.groupingBy(WarehouseInProcess::getWarehouseInId,
|
||||
LinkedHashMap::new, Collectors.toList()));
|
||||
}
|
||||
|
||||
Map<Long, List<WarehouseAllocationProcess>> allocationRowsByDoc = Map.of();
|
||||
List<TodoStub> allocationStubs = byType.getOrDefault("allocation", List.of());
|
||||
if (!allocationStubs.isEmpty()) {
|
||||
List<Long> ids = allocationStubs.stream().map(TodoStub::docId).collect(Collectors.toList());
|
||||
allocationRowsByDoc = allocationProcessMapper.selectList(
|
||||
new LambdaQueryWrapper<WarehouseAllocationProcess>()
|
||||
.eq(WarehouseAllocationProcess::getSoftDelete, 0)
|
||||
.eq(WarehouseAllocationProcess::getUserId, userId)
|
||||
.in(WarehouseAllocationProcess::getOperationType, TODO_OPERATION_TYPES)
|
||||
.in(WarehouseAllocationProcess::getWarehouseAllocationId, ids)
|
||||
.orderByAsc(WarehouseAllocationProcess::getId)).stream()
|
||||
.collect(Collectors.groupingBy(WarehouseAllocationProcess::getWarehouseAllocationId,
|
||||
LinkedHashMap::new, Collectors.toList()));
|
||||
}
|
||||
|
||||
List<Map<String, Object>> records = new ArrayList<>();
|
||||
for (TodoStub stub : stubs) {
|
||||
switch (stub.docType()) {
|
||||
case "out" -> addIfPresent(records, outRowsByDoc.get(stub.docId()),
|
||||
rows -> outRecord(pickPending(rows, onlyTodo)));
|
||||
case "in" -> addIfPresent(records, inRowsByDoc.get(stub.docId()),
|
||||
rows -> inRecord(pickPending(rows, onlyTodo)));
|
||||
default -> addIfPresent(records, allocationRowsByDoc.get(stub.docId()),
|
||||
rows -> allocationRecord(pickPending(rows, onlyTodo)));
|
||||
}
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
private static <T> void addIfPresent(List<Map<String, Object>> records, List<T> rows,
|
||||
java.util.function.Function<List<T>, Map<String, Object>> mapper) {
|
||||
if (rows != null && !rows.isEmpty()) {
|
||||
records.add(mapper.apply(rows));
|
||||
}
|
||||
}
|
||||
|
||||
/** 取代表当前待办环节的行:优先 schedule_state=1 的真待办行,否则取首行(已办/无需待办)。 */
|
||||
private static <T> T pickPending(List<T> rows, boolean onlyTodo) {
|
||||
if (onlyTodo) {
|
||||
return rows.get(0);
|
||||
}
|
||||
return rows.stream()
|
||||
.filter(row -> currentScheduleState(row) == 1)
|
||||
.findFirst()
|
||||
.orElse(rows.get(0));
|
||||
}
|
||||
|
||||
private static int currentScheduleState(Object row) {
|
||||
if (row instanceof WarehouseOutProcess out) {
|
||||
return out.getScheduleState() == null ? -1 : out.getScheduleState();
|
||||
}
|
||||
if (row instanceof WarehouseInProcess in) {
|
||||
return in.getScheduleState() == null ? -1 : in.getScheduleState();
|
||||
}
|
||||
WarehouseAllocationProcess allocation = (WarehouseAllocationProcess) row;
|
||||
return allocation.getScheduleState() == null ? -1 : allocation.getScheduleState();
|
||||
}
|
||||
|
||||
private Map<String, Object> outRecord(WarehouseOutProcess row) {
|
||||
Map<String, Object> record = baseRecord("out", "出库单", row.getWarehouseOutId(),
|
||||
row.getOutOrderNo(), row.getOrderState(), text(OUT_ORDER_STATE_TEXT, row.getOrderState()),
|
||||
row.getScheduleState(), row.getOperationType(),
|
||||
text(OUT_OPERATION_TYPE_TEXT, row.getOperationType()),
|
||||
row.getWarehouseName(), row.getAgentId(), row.getAgentName(),
|
||||
row.getApplyUserId(), row.getApplyUserName(), row.getApplyDate());
|
||||
record.put("allocationOrderNo", row.getAllocationOrderNo());
|
||||
record.put("mallOrderNo", row.getMallOrderNo());
|
||||
if (StringUtils.hasText(row.getAllocationOrderNo())) {
|
||||
record.put("derivedFrom", "allocation");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
private Map<String, Object> inRecord(WarehouseInProcess row) {
|
||||
Map<String, Object> record = baseRecord("in", "入库单", row.getWarehouseInId(),
|
||||
row.getInOrderNo(), row.getOrderState(), text(IN_ORDER_STATE_TEXT, row.getOrderState()),
|
||||
row.getScheduleState(), row.getOperationType(),
|
||||
text(IN_OPERATION_TYPE_TEXT, row.getOperationType()),
|
||||
row.getWarehouseName(), row.getAgentId(), row.getAgentName(),
|
||||
row.getApplyUserId(), row.getApplyUserName(), row.getApplyDate());
|
||||
record.put("allocationOrderNo", row.getAllocationOrderNo());
|
||||
record.put("outOrderNo", row.getOutOrderNo());
|
||||
record.put("mallOrderNo", row.getMallOrderNo());
|
||||
if (StringUtils.hasText(row.getAllocationOrderNo())) {
|
||||
record.put("derivedFrom", "allocation");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
private Map<String, Object> allocationRecord(WarehouseAllocationProcess row) {
|
||||
Map<String, Object> record = baseRecord("allocation",
|
||||
text(OP_TYPE_TEXT, row.getOpType()) + "单", row.getWarehouseAllocationId(),
|
||||
row.getAllocationOrderNo(), row.getOrderState(),
|
||||
text(ALLOCATION_ORDER_STATE_TEXT, row.getOrderState()),
|
||||
row.getScheduleState(), row.getOperationType(),
|
||||
text(ALLOCATION_OPERATION_TYPE_TEXT, row.getOperationType()),
|
||||
null, row.getAgentId(), row.getAgentName(),
|
||||
row.getApplyUserId(), row.getApplyUserName(), row.getApplyDate());
|
||||
record.put("opType", row.getOpType());
|
||||
record.put("opTypeText", text(OP_TYPE_TEXT, row.getOpType()));
|
||||
record.put("warehouseOutName", row.getWarehouseOutName());
|
||||
record.put("warehouseInName", row.getWarehouseInName());
|
||||
record.put("allocationCount", row.getAllocationCount());
|
||||
record.put("allocationDate", row.getAllocationDate() == null ? null : row.getAllocationDate().toString());
|
||||
record.put("nextUserName", row.getNextUserName());
|
||||
record.put("inUserName", row.getInUserName());
|
||||
record.put("inTime", formatTime(row.getInTime()));
|
||||
record.put("outUserName", row.getOutUserName());
|
||||
record.put("outTime", formatTime(row.getOutTime()));
|
||||
return record;
|
||||
}
|
||||
|
||||
private Map<String, Object> baseRecord(String docType, String docTypeText, Long docId, String docNo,
|
||||
Integer orderState, String orderStateText,
|
||||
Integer scheduleState, Integer operationType, String operationTypeText,
|
||||
String warehouseName, Long agentId, String agentName,
|
||||
Long applyUserId, String applyUserName, Date applyDate) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("docType", docType);
|
||||
record.put("docTypeText", docTypeText);
|
||||
record.put("docId", docId);
|
||||
record.put("docNo", docNo);
|
||||
record.put("orderState", orderState);
|
||||
record.put("orderStateText", orderStateText);
|
||||
record.put("scheduleState", scheduleState);
|
||||
record.put("scheduleStateText", text(SCHEDULE_STATE_TEXT, scheduleState));
|
||||
record.put("operationType", operationType);
|
||||
record.put("operationTypeText", operationTypeText);
|
||||
record.put("warehouseName", warehouseName);
|
||||
record.put("agentId", agentId);
|
||||
record.put("agentName", agentName);
|
||||
record.put("applyUserId", applyUserId);
|
||||
record.put("applyUserName", applyUserName);
|
||||
record.put("applyDate", formatTime(applyDate));
|
||||
return record;
|
||||
}
|
||||
|
||||
private static String text(Map<Integer, String> texts, Integer code) {
|
||||
return code == null ? null : texts.getOrDefault(code, "未知");
|
||||
}
|
||||
|
||||
private static String formatTime(Date date) {
|
||||
return date == null ? null : TIME_FORMATTER.format(date.toInstant());
|
||||
}
|
||||
|
||||
private record TodoStub(String docType, Long docId, Date applyDate) {
|
||||
private TodoStub {
|
||||
Objects.requireNonNull(docType);
|
||||
Objects.requireNonNull(docId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package me.iiv.agent.mcp.tool;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m2.WarehouseAllocationProcess;
|
||||
import me.iiv.agent.entity.m2.WarehouseInProcess;
|
||||
import me.iiv.agent.entity.m2.WarehouseOutProcess;
|
||||
import me.iiv.agent.mapper.m2.WarehouseAllocationProcessMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseInProcessMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseOutProcessMapper;
|
||||
import me.iiv.agent.security.AuthContext;
|
||||
import me.iiv.agent.security.AuthContextHolder;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WarehouseTodoToolsTest {
|
||||
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
|
||||
@Mock WarehouseOutProcessMapper outProcessMapper;
|
||||
@Mock WarehouseInProcessMapper inProcessMapper;
|
||||
@Mock WarehouseAllocationProcessMapper allocationProcessMapper;
|
||||
|
||||
WarehouseTodoTools tools;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, WarehouseOutProcess.class);
|
||||
TableInfoHelper.initTableInfo(assistant, WarehouseInProcess.class);
|
||||
TableInfoHelper.initTableInfo(assistant, WarehouseAllocationProcess.class);
|
||||
tools = new WarehouseTodoTools(outProcessMapper, inProcessMapper, allocationProcessMapper);
|
||||
User user = new User();
|
||||
user.setId(7L);
|
||||
AuthContextHolder.set(new AuthContext(null, user));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AuthContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listMergesAllDocTypesSortsByApplyDateAndMarksDerivedDocs() {
|
||||
// 第一阶段(去重 id + applyDate)与第二阶段(整行组装)共用 mock,按调用顺序返回
|
||||
WarehouseOutProcess outStub1 = new WarehouseOutProcess();
|
||||
outStub1.setWarehouseOutId(20L);
|
||||
outStub1.setApplyDate(date(2026, 9, 10));
|
||||
WarehouseOutProcess outStub2 = new WarehouseOutProcess();
|
||||
outStub2.setWarehouseOutId(21L);
|
||||
outStub2.setApplyDate(date(2026, 9, 14));
|
||||
WarehouseInProcess inStub = new WarehouseInProcess();
|
||||
inStub.setWarehouseInId(30L);
|
||||
inStub.setApplyDate(date(2026, 9, 15));
|
||||
WarehouseAllocationProcess allocationStub = new WarehouseAllocationProcess();
|
||||
allocationStub.setWarehouseAllocationId(40L);
|
||||
allocationStub.setApplyDate(date(2026, 9, 12));
|
||||
when(outProcessMapper.selectList(any())).thenReturn(List.of(outStub1, outStub2), List.of(outRow(20L, null), outRow(21L, "DB-001")));
|
||||
when(inProcessMapper.selectList(any())).thenReturn(List.of(inStub), List.of(inRow(30L)));
|
||||
when(allocationProcessMapper.selectList(any())).thenReturn(List.of(allocationStub), List.of(allocationRow(40L)));
|
||||
|
||||
Map<String, Object> result = tools.warehouseTodoListQuery("all", null, null, null, 1, 500);
|
||||
|
||||
assertEquals(4, result.get("total"));
|
||||
assertEquals(1, result.get("page"));
|
||||
assertEquals(100, result.get("rows"));
|
||||
assertEquals("rows 超过上限 100,已截断为 100", result.get("notice"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> records = (List<Map<String, Object>>) result.get("records");
|
||||
assertEquals(4, records.size());
|
||||
// 申请时间倒序:in(9/15) → out(9/14) → allocation(9/12) → out(9/10)
|
||||
assertEquals("in", records.get(0).get("docType"));
|
||||
assertEquals(30L, records.get(0).get("docId"));
|
||||
assertEquals("待入库", records.get(0).get("orderStateText"));
|
||||
assertEquals("待办", records.get(0).get("scheduleStateText"));
|
||||
assertEquals("确认入库", records.get(0).get("operationTypeText"));
|
||||
assertNull(records.get(0).get("derivedFrom"));
|
||||
|
||||
assertEquals("out", records.get(1).get("docType"));
|
||||
assertEquals(21L, records.get(1).get("docId"));
|
||||
assertEquals("DB-001", records.get(1).get("allocationOrderNo"));
|
||||
assertEquals("allocation", records.get(1).get("derivedFrom"));
|
||||
|
||||
assertEquals("allocation", records.get(2).get("docType"));
|
||||
assertEquals("采购单", records.get(2).get("docTypeText"));
|
||||
assertEquals(1, records.get(2).get("opType"));
|
||||
assertEquals("待审核", records.get(2).get("orderStateText"));
|
||||
|
||||
assertEquals("out", records.get(3).get("docType"));
|
||||
assertEquals(20L, records.get(3).get("docId"));
|
||||
assertNull(records.get(3).get("derivedFrom"));
|
||||
verify(outProcessMapper, times(2)).selectList(any());
|
||||
verify(inProcessMapper, times(2)).selectList(any());
|
||||
verify(allocationProcessMapper, times(2)).selectList(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void todoOnlyFalsePrefersPendingRowAmongParticipatedDocs() {
|
||||
WarehouseOutProcess stub = new WarehouseOutProcess();
|
||||
stub.setWarehouseOutId(20L);
|
||||
stub.setApplyDate(date(2026, 9, 10));
|
||||
WarehouseOutProcess doneRow = outRow(20L, null);
|
||||
doneRow.setScheduleState(2);
|
||||
doneRow.setOperationType(0);
|
||||
WarehouseOutProcess pendingRow = outRow(20L, null);
|
||||
pendingRow.setScheduleState(1);
|
||||
pendingRow.setOperationType(1);
|
||||
when(outProcessMapper.selectList(any())).thenReturn(List.of(stub), List.of(doneRow, pendingRow));
|
||||
|
||||
Map<String, Object> result = tools.warehouseTodoListQuery("out", false, null, null, 1, 20);
|
||||
|
||||
assertEquals(1, result.get("total"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> records = (List<Map<String, Object>>) result.get("records");
|
||||
assertEquals(1, records.size());
|
||||
assertEquals(1, records.get(0).get("scheduleState"));
|
||||
assertEquals("待办", records.get(0).get("scheduleStateText"));
|
||||
assertEquals(1, records.get(0).get("operationType"));
|
||||
assertEquals("确认出库", records.get(0).get("operationTypeText"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRejectsInvalidDocType() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> tools.warehouseTodoListQuery("transfer", null, null, null, 1, 20));
|
||||
verify(outProcessMapper, times(0)).selectList(any());
|
||||
verify(inProcessMapper, times(0)).selectList(any());
|
||||
verify(allocationProcessMapper, times(0)).selectList(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void springAiPublishesTodoListToolSchema() {
|
||||
var callbacks = org.springframework.ai.tool.method.MethodToolCallbackProvider.builder()
|
||||
.toolObjects(tools).build().getToolCallbacks();
|
||||
Map<String, String> schemas = Arrays.stream(callbacks).collect(java.util.stream.Collectors.toMap(
|
||||
callback -> callback.getToolDefinition().name(),
|
||||
callback -> callback.getToolDefinition().inputSchema()));
|
||||
|
||||
assertEquals(1, schemas.size());
|
||||
assertTrue(schemas.get("warehouse.todo.list.query").contains("docType"));
|
||||
assertTrue(schemas.get("warehouse.todo.list.query").contains("todoOnly"));
|
||||
assertTrue(schemas.get("warehouse.todo.list.query").contains("orderNo"));
|
||||
}
|
||||
|
||||
private static WarehouseOutProcess outRow(Long docId, String allocationOrderNo) {
|
||||
WarehouseOutProcess row = new WarehouseOutProcess();
|
||||
row.setId(docId * 10 + 1);
|
||||
row.setSoftDelete(0);
|
||||
row.setWarehouseOutId(docId);
|
||||
row.setOutOrderNo("CK-" + docId);
|
||||
row.setAllocationOrderNo(allocationOrderNo);
|
||||
row.setUserId(7L);
|
||||
row.setScheduleState(1);
|
||||
row.setOperationType(1);
|
||||
row.setOrderState(2);
|
||||
row.setWarehouseName("一号仓");
|
||||
row.setAgentId(5L);
|
||||
row.setAgentName("代理商甲");
|
||||
row.setApplyUserId(7L);
|
||||
row.setApplyUserName("申请人");
|
||||
row.setApplyDate(date(2026, 9, 10));
|
||||
return row;
|
||||
}
|
||||
|
||||
private static WarehouseInProcess inRow(Long docId) {
|
||||
WarehouseInProcess row = new WarehouseInProcess();
|
||||
row.setId(docId * 10 + 1);
|
||||
row.setSoftDelete(0);
|
||||
row.setWarehouseInId(docId);
|
||||
row.setInOrderNo("RK-" + docId);
|
||||
row.setUserId(7L);
|
||||
row.setScheduleState(1);
|
||||
row.setOperationType(1);
|
||||
row.setOrderState(2);
|
||||
row.setWarehouseName("二号仓");
|
||||
row.setAgentId(5L);
|
||||
row.setAgentName("代理商甲");
|
||||
row.setApplyUserId(7L);
|
||||
row.setApplyUserName("申请人");
|
||||
row.setApplyDate(date(2026, 9, 15));
|
||||
return row;
|
||||
}
|
||||
|
||||
private static WarehouseAllocationProcess allocationRow(Long docId) {
|
||||
WarehouseAllocationProcess row = new WarehouseAllocationProcess();
|
||||
row.setId(docId * 10 + 1);
|
||||
row.setSoftDelete(0);
|
||||
row.setWarehouseAllocationId(docId);
|
||||
row.setAllocationOrderNo("DB-" + docId);
|
||||
row.setUserId(7L);
|
||||
row.setScheduleState(1);
|
||||
row.setOperationType(0);
|
||||
row.setOrderState(0);
|
||||
row.setOpType(1);
|
||||
row.setAgentId(5L);
|
||||
row.setAgentName("代理商甲");
|
||||
row.setApplyUserId(7L);
|
||||
row.setApplyUserName("申请人");
|
||||
row.setApplyDate(date(2026, 9, 12));
|
||||
row.setWarehouseOutName("一号仓");
|
||||
row.setWarehouseInName("二号仓");
|
||||
return row;
|
||||
}
|
||||
|
||||
private static Date date(int year, int month, int day) {
|
||||
return Date.from(LocalDate.of(year, month, day).atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user