Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aedf638534 |
@@ -89,6 +89,8 @@ xiaoqu-agent-service/
|
||||
| `delivery.order.detail.query` | workorder | read | 送货工单详情;返回物流、配发物品、发货/签收附件、流程及关联单据 |
|
||||
| `install.order.list.query` | workorder | read | 装机工单分页查询;仅保留申请日期范围,返回状态文案与安装数量汇总 |
|
||||
| `install.order.detail.query` | workorder | read | 装机工单详情;返回明细、作业人员、地址、商城订单、流程、配发 SN、出入库关联及凭证 |
|
||||
| `pickup.order.list.query` | workorder | read | 取货工单分页查询;申请/取货/完成日期独立过滤,物流按页批量富化 |
|
||||
| `pickup.order.detail.query` | workorder | read | 取货工单详情;返回物流、取货物品、SN 调整、附件、流程及关联单据 |
|
||||
|
||||
分页统一规范:page 从 1 开始,rows 默认 20、上限 100,超出截断为 100 并在返回 `notice` 字段提示。
|
||||
|
||||
|
||||
@@ -648,6 +648,38 @@
|
||||
|
||||
---
|
||||
|
||||
## 24. pickup.order.list.query
|
||||
|
||||
分页查询取货工单,对应旧系统 `POST /pickup/pickupListByPage`。
|
||||
|
||||
- 分类:workorder | 类型:读
|
||||
- 数据来源:m2 `pickup_order`,物流来自 m1 `mall_order_logistics`。
|
||||
- 入参:`pickupOrderNo`、`installOrderNo`、`withdrawOrderNo`、`agentId`、`districtName`、
|
||||
`type`、`orderState`、`sendMode`、`applyUserName`、申请/取货/完成日期范围及 `page/rows`。
|
||||
- `type`:0=装机发起,1=人工发起,2=撤机发起;`orderState`:-2=被驳回,-1=待审核,
|
||||
0=待取货,1=待入库,2=已完成,3=已取消;`sendMode`:0=快递公司,1=自行派送。
|
||||
- 申请日期按 `apply_time` 查询;三组日期范围可同时过滤,结束日期含当天。
|
||||
- 行级权限:总部看全部;代理身份可看申请代理或所属代理在自己、授权代管范围内的工单,
|
||||
以及授权代管物业和本人参与流程的工单;销售非负责人按其可见物业及本人参与流程过滤。
|
||||
- 返回 `total/page/rows/records`,固定按 ID 倒序;物流按本页单号一次批量查询。
|
||||
|
||||
## 25. pickup.order.detail.query
|
||||
|
||||
按 `id` 或 `pickupOrderNo` 查询详情,两者至少传一个;同时传入时必须匹配同一条可见记录。
|
||||
|
||||
- 行级权限与列表完全一致;不存在和无权统一返回 `NOT_FOUND_OR_FORBIDDEN`。
|
||||
- `record/logistics`:取货主单、状态/来源文案及物流信息。
|
||||
- `pickupGoods`:m2 `pickup_order_details` 中 `type IN (0,2)` 的原取货物品。
|
||||
- `serialChanges`:同表 `type=1` 的取货后录入或调整 SN 明细。
|
||||
- `shippingFiles`:m1 `mall_logistics_pic` 物流取货图片。
|
||||
- `processes`:`schedule_state=2` 的流程记录及 m2 `warehouse_in_out_pic` 取货/入库凭证。
|
||||
- `relatedDocuments`:m2 `install_to_out_warehouse` 关联的出库、入库、调拨或划转单。
|
||||
- 同时返回关联装机单、撤机单及维修工单号;附件仅返回 `id/fileName/bucket/objectKey`。
|
||||
|
||||
详细设计见 `pickup-order-mcp-tools-design.md`。
|
||||
|
||||
---
|
||||
|
||||
## 附录:在线查看工具清单
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# 取货工单 MCP 工具设计
|
||||
|
||||
需求:REQ-20260917-0003;开发任务:#21964;设计文档:doc 12684。
|
||||
|
||||
## 工具
|
||||
|
||||
- `pickup.order.list.query`:分页查询 `m2.pickup_order`,支持工单号、公司、项目、类型、状态、取货方式、
|
||||
申请人和申请/取货/完成日期过滤。固定按 ID 倒序,物流以本页工单号批量读取 m1,避免逐条查询。
|
||||
- `pickup.order.detail.query`:按 ID 或工单号查询;返回主单、物流、原取货物品、SN 调整、物流图片、
|
||||
已办流程与凭证、关联出入库/调拨/划转单。
|
||||
|
||||
## 权限
|
||||
|
||||
列表和详情统一调用 `PermissionService.applyPickupOrderRowFilter`:总部不加过滤;代理用户可见自己、
|
||||
授权代管代理/物业范围及本人参与流程的工单(不扩展到直属下级代理);销售非负责人使用其物业范围并保留本人流程参与范围。
|
||||
不存在与无权访问统一返回 `NOT_FOUND_OR_FORBIDDEN`。
|
||||
|
||||
## 数据约定
|
||||
|
||||
- 申请日期按业务字段 `apply_time`,不沿用旧列表实际使用的 `create_time`。
|
||||
- `pickup_order_details.type IN (0,2)` 为原取货物品,`type=1` 为取货后 SN 明细。
|
||||
- 流程只返回 `schedule_state=2` 的已办记录,按 `operation_time,id` 正序。
|
||||
- 流程附件限定 `file_type=3`、`in_out_allocation_type=12`。
|
||||
- 附件只返回 bucket 与 objectKey,不拼接公开 URL。
|
||||
- 不返回旧页面通过姓名字符串推断的 `isReceipt/isPickup` UI 操作标记。
|
||||
@@ -0,0 +1,50 @@
|
||||
package me.iiv.agent.entity.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
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.util.Date;
|
||||
|
||||
/** 取货工单主表(m2,只读)。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("pickup_order")
|
||||
public class PickupOrder implements Serializable {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Long createId;
|
||||
private Date createTime;
|
||||
private Long modifyId;
|
||||
private Date modifyTime;
|
||||
private Integer softDelete;
|
||||
private Long applyAgentId;
|
||||
private String applyAgentName;
|
||||
private String pickupOrderNo;
|
||||
private String installOrderNo;
|
||||
private Long agentId;
|
||||
private Long propertyId;
|
||||
private Long districtId;
|
||||
private String agentName;
|
||||
private String propertyName;
|
||||
private String districtName;
|
||||
private Integer orderState;
|
||||
private Long applyUserId;
|
||||
private String applyUserName;
|
||||
private Date applyTime;
|
||||
private Date pickupDate;
|
||||
private Date finishDate;
|
||||
private String scheduleUserName;
|
||||
private Integer sendMode;
|
||||
private String prePickupDate;
|
||||
private String pickupUserName;
|
||||
private String inWarehouseUserName;
|
||||
private String withdrawOrderNo;
|
||||
private Integer type;
|
||||
private String maintainWorkNo;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package me.iiv.agent.entity.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
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.util.Date;
|
||||
|
||||
/** 取货物品及入库 SN 明细(m2,只读)。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("pickup_order_details")
|
||||
public class PickupOrderDetail implements Serializable {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Long createId;
|
||||
private Date createTime;
|
||||
private Long modifyId;
|
||||
private Date modifyTime;
|
||||
private Integer softDelete;
|
||||
private String pickupOrderNo;
|
||||
private Long materielDefinitionId;
|
||||
private String materialNo;
|
||||
private String materielFullName;
|
||||
private String materielSort;
|
||||
private Long materielIndexId;
|
||||
private String specifications;
|
||||
private String snCode;
|
||||
private Integer inCount;
|
||||
private Long inWarehouseId;
|
||||
private String inWarehouseName;
|
||||
private Integer type;
|
||||
private String productSort;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.iiv.agent.entity.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
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.util.Date;
|
||||
|
||||
/** 取货工单流程(m2,只读)。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("pickup_order_process")
|
||||
public class PickupOrderProcess implements Serializable {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Long createId;
|
||||
private Date createTime;
|
||||
private Long modifyId;
|
||||
private Date modifyTime;
|
||||
private Integer softDelete;
|
||||
private String pickupOrderNo;
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private Date operationTime;
|
||||
private Integer scheduleState;
|
||||
private Integer auditState;
|
||||
private Integer operationTheme;
|
||||
private Integer operationType;
|
||||
private String memo;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.iiv.agent.mapper.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m2.PickupOrderDetail;
|
||||
|
||||
public interface PickupOrderDetailMapper extends BaseMapper<PickupOrderDetail> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.iiv.agent.mapper.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m2.PickupOrder;
|
||||
|
||||
public interface PickupOrderMapper extends BaseMapper<PickupOrder> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package me.iiv.agent.mapper.m2;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m2.PickupOrderProcess;
|
||||
|
||||
public interface PickupOrderProcessMapper extends BaseMapper<PickupOrderProcess> {
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
package me.iiv.agent.mcp.tool;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m1.MallLogisticsPic;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogistics;
|
||||
import me.iiv.agent.entity.m2.InstallToOutWarehouse;
|
||||
import me.iiv.agent.entity.m2.PickupOrder;
|
||||
import me.iiv.agent.entity.m2.PickupOrderDetail;
|
||||
import me.iiv.agent.entity.m2.PickupOrderProcess;
|
||||
import me.iiv.agent.entity.m2.WarehouseInOutPic;
|
||||
import me.iiv.agent.mapper.m1.MallLogisticsPicMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderLogisticsMapper;
|
||||
import me.iiv.agent.mapper.m2.InstallToOutWarehouseMapper;
|
||||
import me.iiv.agent.mapper.m2.PickupOrderDetailMapper;
|
||||
import me.iiv.agent.mapper.m2.PickupOrderMapper;
|
||||
import me.iiv.agent.mapper.m2.PickupOrderProcessMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseInOutPicMapper;
|
||||
import me.iiv.agent.mcp.McpErrorCode;
|
||||
import me.iiv.agent.mcp.ToolCallException;
|
||||
import me.iiv.agent.mcp.ToolObject;
|
||||
import me.iiv.agent.mcp.XqTool;
|
||||
import me.iiv.agent.permission.PermissionService;
|
||||
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.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.format.ResolverStyle;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** 取货工单只读查询,对齐老系统 /pickup/pickupListByPage 与 /pickup/pickupScan。 */
|
||||
@Component
|
||||
public class PickupOrderTools implements ToolObject {
|
||||
|
||||
private static final int DEFAULT_ROWS = 20;
|
||||
private static final int MAX_ROWS = 100;
|
||||
private static final int RECEIPT_FILE_TYPE = 3;
|
||||
private static final int PICKUP_AGGREGATE_TYPE = 12;
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final DateTimeFormatter DATE_FORMATTER =
|
||||
DateTimeFormatter.ISO_LOCAL_DATE.withResolverStyle(ResolverStyle.STRICT);
|
||||
private static final DateTimeFormatter TIME_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(BUSINESS_ZONE);
|
||||
|
||||
private static final Map<Integer, String> ORDER_STATE_TEXT = Map.of(
|
||||
-2, "被驳回", -1, "待审核", 0, "待取货", 1, "待入库", 2, "已完成", 3, "已取消");
|
||||
private static final Map<Integer, String> TYPE_TEXT = Map.of(
|
||||
0, "装机工单发起", 1, "人工发起", 2, "撤机工单发起");
|
||||
private static final Map<Integer, String> SEND_MODE_TEXT = Map.of(
|
||||
0, "快递公司", 1, "自行派送");
|
||||
private static final Map<Integer, String> SCHEDULE_STATE_TEXT = Map.of(
|
||||
0, "无需待办", 1, "待办", 2, "已办");
|
||||
private static final Map<Integer, String> OPERATION_THEME_TEXT = Map.of(
|
||||
0, "提交取货工单", 1, "取货", 2, "入库", 3, "取消",
|
||||
4, "审核通过", 5, "再提交", 6, "审核驳回");
|
||||
private static final Map<Integer, String> OPERATION_TYPE_TEXT = Map.of(
|
||||
0, "提交取货工单", 1, "取货", 2, "入库", 3, "取消",
|
||||
4, "部门内审核通过", 5, "部门内审核驳回", 6, "取货驳回");
|
||||
private static final Map<Integer, String> LOGISTICS_STATE_TEXT = Map.of(
|
||||
0, "已出单", 1, "已出库", 2, "已发货", 3, "已揽件",
|
||||
4, "运输中", 5, "待取件", 6, "派送中", 7, "已签收");
|
||||
private static final Map<Integer, String> DOCUMENT_TYPE_TEXT = Map.of(
|
||||
1, "出库单", 2, "入库单", 3, "调拨单", 4, "划转单");
|
||||
|
||||
final PickupOrderMapper pickupOrderMapper;
|
||||
final PickupOrderDetailMapper detailMapper;
|
||||
final PickupOrderProcessMapper processMapper;
|
||||
final MallOrderLogisticsMapper logisticsMapper;
|
||||
final MallLogisticsPicMapper shippingPicMapper;
|
||||
final WarehouseInOutPicMapper receiptPicMapper;
|
||||
final InstallToOutWarehouseMapper relationMapper;
|
||||
final PermissionService permissionService;
|
||||
|
||||
public PickupOrderTools(PickupOrderMapper pickupOrderMapper,
|
||||
PickupOrderDetailMapper detailMapper,
|
||||
PickupOrderProcessMapper processMapper,
|
||||
MallOrderLogisticsMapper logisticsMapper,
|
||||
MallLogisticsPicMapper shippingPicMapper,
|
||||
WarehouseInOutPicMapper receiptPicMapper,
|
||||
InstallToOutWarehouseMapper relationMapper,
|
||||
PermissionService permissionService) {
|
||||
this.pickupOrderMapper = pickupOrderMapper;
|
||||
this.detailMapper = detailMapper;
|
||||
this.processMapper = processMapper;
|
||||
this.logisticsMapper = logisticsMapper;
|
||||
this.shippingPicMapper = shippingPicMapper;
|
||||
this.receiptPicMapper = receiptPicMapper;
|
||||
this.relationMapper = relationMapper;
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
@XqTool(category = "workorder", kind = "read")
|
||||
@Tool(name = "pickup.order.list.query", description = "分页查询取货工单,对齐老系统 /pickup/pickupListByPage。"
|
||||
+ "总部可看全部;代理身份可看自己、直属下级、代管代理/物业及本人参与流程的工单;销售非负责人按其物业范围过滤。"
|
||||
+ "申请日期使用 apply_time,申请、取货、完成日期可同时过滤,结束日期含当天。"
|
||||
+ "type:0装机发起、1人工发起、2撤机发起;orderState:-2被驳回、-1待审核、0待取货、1待入库、2已完成、3已取消。")
|
||||
public Map<String, Object> pickupOrderListQuery(
|
||||
@ToolParam(required = false, description = "取货工单号,模糊匹配") String pickupOrderNo,
|
||||
@ToolParam(required = false, description = "装机工单号,模糊匹配") String installOrderNo,
|
||||
@ToolParam(required = false, description = "撤机工单号,模糊匹配") String withdrawOrderNo,
|
||||
@ToolParam(required = false, description = "所属代理商 id") Long agentId,
|
||||
@ToolParam(required = false, description = "站点名称,模糊匹配") String districtName,
|
||||
@ToolParam(required = false, description = "来源类型:0装机发起 1人工发起 2撤机发起") Integer type,
|
||||
@ToolParam(required = false, description = "状态:-2被驳回 -1待审核 0待取货 1待入库 2已完成 3已取消") Integer orderState,
|
||||
@ToolParam(required = false, description = "取货方式:0快递公司 1自行派送") Integer sendMode,
|
||||
@ToolParam(required = false, description = "申请人,模糊匹配") String applyUserName,
|
||||
@ToolParam(required = false, description = "申请日期起,yyyy-MM-dd") String applyDateFrom,
|
||||
@ToolParam(required = false, description = "申请日期止,yyyy-MM-dd,含当天") String applyDateTo,
|
||||
@ToolParam(required = false, description = "取货日期起,yyyy-MM-dd") String pickupDateFrom,
|
||||
@ToolParam(required = false, description = "取货日期止,yyyy-MM-dd,含当天") String pickupDateTo,
|
||||
@ToolParam(required = false, description = "完成日期起,yyyy-MM-dd") String finishDateFrom,
|
||||
@ToolParam(required = false, description = "完成日期止,yyyy-MM-dd,含当天") String finishDateTo,
|
||||
@ToolParam(required = false, description = "页码,从1开始,默认1") Integer page,
|
||||
@ToolParam(required = false, description = "每页条数,默认20,上限100") Integer rows) {
|
||||
validateCode(type, Set.of(0, 1, 2), "type");
|
||||
validateCode(orderState, Set.of(-2, -1, 0, 1, 2, 3), "orderState");
|
||||
validateCode(sendMode, Set.of(0, 1), "sendMode");
|
||||
LocalDate applyFrom = parseDate(applyDateFrom, "applyDateFrom");
|
||||
LocalDate applyTo = parseDate(applyDateTo, "applyDateTo");
|
||||
LocalDate pickupFrom = parseDate(pickupDateFrom, "pickupDateFrom");
|
||||
LocalDate pickupTo = parseDate(pickupDateTo, "pickupDateTo");
|
||||
LocalDate finishFrom = parseDate(finishDateFrom, "finishDateFrom");
|
||||
LocalDate finishTo = parseDate(finishDateTo, "finishDateTo");
|
||||
validateRange(applyFrom, applyTo, "applyDateFrom", "applyDateTo");
|
||||
validateRange(pickupFrom, pickupTo, "pickupDateFrom", "pickupDateTo");
|
||||
validateRange(finishFrom, finishTo, "finishDateFrom", "finishDateTo");
|
||||
|
||||
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);
|
||||
LambdaQueryWrapper<PickupOrder> wrapper = new LambdaQueryWrapper<PickupOrder>()
|
||||
.eq(PickupOrder::getSoftDelete, 0)
|
||||
.eq(agentId != null, PickupOrder::getAgentId, agentId)
|
||||
.eq(type != null, PickupOrder::getType, type)
|
||||
.eq(orderState != null, PickupOrder::getOrderState, orderState)
|
||||
.eq(sendMode != null, PickupOrder::getSendMode, sendMode)
|
||||
.ge(applyFrom != null, PickupOrder::getApplyTime, startOfDay(applyFrom))
|
||||
.lt(applyTo != null, PickupOrder::getApplyTime, nextDayStart(applyTo))
|
||||
.ge(pickupFrom != null, PickupOrder::getPickupDate, startOfDay(pickupFrom))
|
||||
.lt(pickupTo != null, PickupOrder::getPickupDate, nextDayStart(pickupTo))
|
||||
.ge(finishFrom != null, PickupOrder::getFinishDate, startOfDay(finishFrom))
|
||||
.lt(finishTo != null, PickupOrder::getFinishDate, nextDayStart(finishTo));
|
||||
like(wrapper, PickupOrder::getPickupOrderNo, pickupOrderNo);
|
||||
like(wrapper, PickupOrder::getInstallOrderNo, installOrderNo);
|
||||
like(wrapper, PickupOrder::getWithdrawOrderNo, withdrawOrderNo);
|
||||
like(wrapper, PickupOrder::getDistrictName, districtName);
|
||||
like(wrapper, PickupOrder::getApplyUserName, applyUserName);
|
||||
permissionService.applyPickupOrderRowFilter(wrapper, currentUser());
|
||||
|
||||
long total = pickupOrderMapper.selectCount(wrapper);
|
||||
List<PickupOrder> orders = pickupOrderMapper.selectList(wrapper
|
||||
.orderByDesc(PickupOrder::getId)
|
||||
.last("LIMIT " + (long) (pageNo - 1) * pageSize + "," + pageSize));
|
||||
Map<String, MallOrderLogistics> logisticsByOrder = logisticsByOrder(orders);
|
||||
|
||||
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", orders.stream()
|
||||
.map(order -> orderRecord(order, logisticsByOrder.get(order.getPickupOrderNo())))
|
||||
.collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@XqTool(category = "workorder", kind = "read")
|
||||
@Tool(name = "pickup.order.detail.query", description = "按 id 或取货工单号查询取货工单详情。"
|
||||
+ "行级权限与 pickup.order.list.query 完全一致;不存在与无权查看统一返回 NOT_FOUND_OR_FORBIDDEN。"
|
||||
+ "返回主单、物流、原取货物品、取货后 SN 调整、发货附件、已处理流程及凭证、关联出入库/调拨/划转单。附件仅返回 bucket 与 objectKey。")
|
||||
public Map<String, Object> pickupOrderDetailQuery(
|
||||
@ToolParam(required = false, description = "取货工单主键 id,与 pickupOrderNo 至少传一个") Long id,
|
||||
@ToolParam(required = false, description = "取货工单号,与 id 至少传一个") String pickupOrderNo) {
|
||||
if ((id == null || id <= 0) && !StringUtils.hasText(pickupOrderNo)) {
|
||||
throw new IllegalArgumentException("id、pickupOrderNo 至少传一个");
|
||||
}
|
||||
if (id != null && id <= 0) {
|
||||
throw new IllegalArgumentException("id 必须为正整数");
|
||||
}
|
||||
LambdaQueryWrapper<PickupOrder> wrapper = new LambdaQueryWrapper<PickupOrder>()
|
||||
.eq(PickupOrder::getSoftDelete, 0)
|
||||
.eq(id != null, PickupOrder::getId, id)
|
||||
.eq(StringUtils.hasText(pickupOrderNo), PickupOrder::getPickupOrderNo,
|
||||
StringUtils.hasText(pickupOrderNo) ? pickupOrderNo.trim() : pickupOrderNo);
|
||||
permissionService.applyPickupOrderRowFilter(wrapper, currentUser());
|
||||
List<PickupOrder> visible = pickupOrderMapper.selectList(
|
||||
wrapper.orderByDesc(PickupOrder::getId).last("LIMIT 1"));
|
||||
if (visible.isEmpty()) {
|
||||
throw new ToolCallException(McpErrorCode.NOT_FOUND_OR_FORBIDDEN,
|
||||
"取货工单不存在或当前身份无权查看");
|
||||
}
|
||||
PickupOrder order = visible.get(0);
|
||||
String orderNo = order.getPickupOrderNo();
|
||||
List<PickupOrderDetail> details = detailMapper.selectList(
|
||||
new LambdaQueryWrapper<PickupOrderDetail>()
|
||||
.eq(PickupOrderDetail::getSoftDelete, 0)
|
||||
.eq(PickupOrderDetail::getPickupOrderNo, orderNo)
|
||||
.orderByAsc(PickupOrderDetail::getId));
|
||||
List<PickupOrderProcess> processes = processMapper.selectList(
|
||||
new LambdaQueryWrapper<PickupOrderProcess>()
|
||||
.eq(PickupOrderProcess::getSoftDelete, 0)
|
||||
.eq(PickupOrderProcess::getPickupOrderNo, orderNo)
|
||||
.eq(PickupOrderProcess::getScheduleState, 2)
|
||||
.orderByAsc(PickupOrderProcess::getOperationTime)
|
||||
.orderByAsc(PickupOrderProcess::getId));
|
||||
Map<Long, List<WarehouseInOutPic>> receiptFiles = receiptFiles(processes);
|
||||
List<InstallToOutWarehouse> relations = relationMapper.selectList(
|
||||
new LambdaQueryWrapper<InstallToOutWarehouse>()
|
||||
.eq(InstallToOutWarehouse::getSoftDelete, 0)
|
||||
.eq(InstallToOutWarehouse::getPickupOrderNo, orderNo)
|
||||
.orderByAsc(InstallToOutWarehouse::getId));
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("record", orderRecord(order, null));
|
||||
result.put("logistics", logisticsRecord(orderNo));
|
||||
result.put("pickupGoods", details.stream().filter(d -> Integer.valueOf(0).equals(d.getType())
|
||||
|| Integer.valueOf(2).equals(d.getType())).map(this::detailRecord).collect(Collectors.toList()));
|
||||
result.put("serialChanges", details.stream().filter(d -> Integer.valueOf(1).equals(d.getType()))
|
||||
.map(this::detailRecord).collect(Collectors.toList()));
|
||||
result.put("shippingFiles", shippingFiles(orderNo));
|
||||
result.put("processes", processes.stream()
|
||||
.map(process -> processRecord(process, receiptFiles.getOrDefault(process.getId(), List.of())))
|
||||
.collect(Collectors.toList()));
|
||||
result.put("receiptFiles", receiptFiles.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(pic -> {
|
||||
Map<String, Object> file = fileRecord(pic.getId(), pic.getFileName(),
|
||||
pic.getOssbucketname(), pic.getOssfolder());
|
||||
file.put("processId", entry.getKey());
|
||||
return file;
|
||||
})).collect(Collectors.toList()));
|
||||
result.put("relatedDocuments", relations.stream().map(this::relationRecord).collect(Collectors.toList()));
|
||||
result.put("relatedInstallOrderNo", order.getInstallOrderNo());
|
||||
result.put("relatedWithdrawOrderNo", order.getWithdrawOrderNo());
|
||||
result.put("maintainWorkNo", order.getMaintainWorkNo());
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, MallOrderLogistics> logisticsByOrder(List<PickupOrder> orders) {
|
||||
if (orders.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<String> orderNos = orders.stream().map(PickupOrder::getPickupOrderNo)
|
||||
.filter(StringUtils::hasText).distinct().collect(Collectors.toList());
|
||||
if (orderNos.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
return logisticsMapper.selectList(new LambdaQueryWrapper<MallOrderLogistics>()
|
||||
.eq(MallOrderLogistics::getSoftDelete, 0)
|
||||
.in(MallOrderLogistics::getOrderNo, orderNos)
|
||||
.orderByDesc(MallOrderLogistics::getId)).stream()
|
||||
.collect(Collectors.toMap(MallOrderLogistics::getOrderNo, Function.identity(),
|
||||
(newest, ignored) -> newest, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private Map<String, Object> orderRecord(PickupOrder order, MallOrderLogistics logistics) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", order.getId());
|
||||
record.put("pickupOrderNo", order.getPickupOrderNo());
|
||||
record.put("installOrderNo", order.getInstallOrderNo());
|
||||
record.put("withdrawOrderNo", order.getWithdrawOrderNo());
|
||||
record.put("maintainWorkNo", order.getMaintainWorkNo());
|
||||
record.put("type", order.getType());
|
||||
record.put("typeText", text(TYPE_TEXT, order.getType()));
|
||||
record.put("applyAgentId", order.getApplyAgentId());
|
||||
record.put("applyAgentName", order.getApplyAgentName());
|
||||
record.put("agentId", order.getAgentId());
|
||||
record.put("agentName", order.getAgentName());
|
||||
record.put("propertyId", order.getPropertyId());
|
||||
record.put("propertyName", order.getPropertyName());
|
||||
record.put("districtId", order.getDistrictId());
|
||||
record.put("districtName", order.getDistrictName());
|
||||
record.put("orderState", order.getOrderState());
|
||||
record.put("orderStateText", text(ORDER_STATE_TEXT, order.getOrderState()));
|
||||
record.put("sendMode", order.getSendMode());
|
||||
record.put("sendModeText", text(SEND_MODE_TEXT, order.getSendMode()));
|
||||
record.put("applyUserId", order.getApplyUserId());
|
||||
record.put("applyUserName", order.getApplyUserName());
|
||||
record.put("applyTime", formatTime(order.getApplyTime()));
|
||||
record.put("prePickupDate", order.getPrePickupDate());
|
||||
record.put("pickupDate", formatTime(order.getPickupDate()));
|
||||
record.put("finishDate", formatTime(order.getFinishDate()));
|
||||
record.put("pickupUserName", order.getPickupUserName());
|
||||
record.put("inWarehouseUserName", order.getInWarehouseUserName());
|
||||
record.put("scheduleUserName", order.getScheduleUserName());
|
||||
record.put("createTime", formatTime(order.getCreateTime()));
|
||||
record.put("modifyTime", formatTime(order.getModifyTime()));
|
||||
if (logistics != null) {
|
||||
record.put("logisticsName", logistics.getLogisticsName());
|
||||
record.put("logisticsOrderNo", logistics.getLogisticsOrderNo());
|
||||
record.put("deliverName", logistics.getDeliverName());
|
||||
record.put("deliverNumber", logistics.getDeliverNumber());
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
private Map<String, Object> logisticsRecord(String orderNo) {
|
||||
List<MallOrderLogistics> rows = logisticsMapper.selectList(
|
||||
new LambdaQueryWrapper<MallOrderLogistics>()
|
||||
.eq(MallOrderLogistics::getSoftDelete, 0)
|
||||
.eq(MallOrderLogistics::getOrderNo, orderNo)
|
||||
.orderByDesc(MallOrderLogistics::getId).last("LIMIT 1"));
|
||||
if (rows.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
MallOrderLogistics logistics = rows.get(0);
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("sendMode", logistics.getSendMode());
|
||||
record.put("sendModeText", text(SEND_MODE_TEXT, logistics.getSendMode()));
|
||||
record.put("logisticsId", logistics.getLogisticsId());
|
||||
record.put("logisticsNo", logistics.getLogisticsNo());
|
||||
record.put("logisticsName", logistics.getLogisticsName());
|
||||
record.put("logisticsOrderNo", logistics.getLogisticsOrderNo());
|
||||
record.put("deliverName", logistics.getDeliverName());
|
||||
record.put("deliverNumber", logistics.getDeliverNumber());
|
||||
record.put("state", logistics.getState());
|
||||
record.put("stateText", text(LOGISTICS_STATE_TEXT, logistics.getState()));
|
||||
record.put("sendTime", formatTime(logistics.getModifyTime()));
|
||||
return record;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailRecord(PickupOrderDetail detail) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", detail.getId());
|
||||
record.put("type", detail.getType());
|
||||
record.put("materielDefinitionId", detail.getMaterielDefinitionId());
|
||||
record.put("materialNo", detail.getMaterialNo());
|
||||
record.put("materielFullName", detail.getMaterielFullName());
|
||||
record.put("materielSort", detail.getMaterielSort());
|
||||
record.put("productSort", detail.getProductSort());
|
||||
record.put("materielIndexId", detail.getMaterielIndexId());
|
||||
record.put("specifications", detail.getSpecifications());
|
||||
record.put("snCode", detail.getSnCode());
|
||||
record.put("inCount", detail.getInCount());
|
||||
record.put("inWarehouseId", detail.getInWarehouseId());
|
||||
record.put("inWarehouseName", detail.getInWarehouseName());
|
||||
return record;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> shippingFiles(String orderNo) {
|
||||
return shippingPicMapper.selectList(new LambdaQueryWrapper<MallLogisticsPic>()
|
||||
.eq(MallLogisticsPic::getSoftDelete, 0)
|
||||
.eq(MallLogisticsPic::getState, 0)
|
||||
.eq(MallLogisticsPic::getOrderNo, orderNo)
|
||||
.orderByAsc(MallLogisticsPic::getId)).stream()
|
||||
.map(pic -> fileRecord(pic.getId(), pic.getFileName(), pic.getOssbucketname(), pic.getOssfolder()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Map<Long, List<WarehouseInOutPic>> receiptFiles(List<PickupOrderProcess> processes) {
|
||||
if (processes.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<Long> ids = processes.stream().map(PickupOrderProcess::getId).collect(Collectors.toList());
|
||||
return receiptPicMapper.selectList(new LambdaQueryWrapper<WarehouseInOutPic>()
|
||||
.eq(WarehouseInOutPic::getSoftDelete, 0)
|
||||
.in(WarehouseInOutPic::getAggregateId, ids)
|
||||
.eq(WarehouseInOutPic::getFileType, RECEIPT_FILE_TYPE)
|
||||
.eq(WarehouseInOutPic::getInOutAllocationType, PICKUP_AGGREGATE_TYPE)
|
||||
.orderByAsc(WarehouseInOutPic::getId)).stream()
|
||||
.collect(Collectors.groupingBy(WarehouseInOutPic::getAggregateId,
|
||||
LinkedHashMap::new, Collectors.toList()));
|
||||
}
|
||||
|
||||
private Map<String, Object> processRecord(PickupOrderProcess process, List<WarehouseInOutPic> files) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", process.getId());
|
||||
record.put("userId", process.getUserId());
|
||||
record.put("userName", process.getUserName());
|
||||
record.put("operationTime", formatTime(process.getOperationTime()));
|
||||
record.put("scheduleState", process.getScheduleState());
|
||||
record.put("scheduleStateText", text(SCHEDULE_STATE_TEXT, process.getScheduleState()));
|
||||
record.put("auditState", process.getAuditState());
|
||||
record.put("operationTheme", process.getOperationTheme());
|
||||
record.put("operationThemeText", text(OPERATION_THEME_TEXT, process.getOperationTheme()));
|
||||
record.put("operationType", process.getOperationType());
|
||||
record.put("operationTypeText", text(OPERATION_TYPE_TEXT, process.getOperationType()));
|
||||
record.put("memo", process.getMemo());
|
||||
record.put("files", files.stream().map(pic -> fileRecord(pic.getId(), pic.getFileName(),
|
||||
pic.getOssbucketname(), pic.getOssfolder())).collect(Collectors.toList()));
|
||||
return record;
|
||||
}
|
||||
|
||||
private Map<String, Object> relationRecord(InstallToOutWarehouse relation) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", relation.getId());
|
||||
record.put("documentType", relation.getOutWarehouseType());
|
||||
record.put("documentTypeText", text(DOCUMENT_TYPE_TEXT, relation.getOutWarehouseType()));
|
||||
record.put("documentNo", relation.getOrderNo());
|
||||
return record;
|
||||
}
|
||||
|
||||
private static Map<String, Object> fileRecord(Long id, String fileName, String bucket, String folder) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", id);
|
||||
record.put("fileName", fileName);
|
||||
record.put("bucket", bucket);
|
||||
record.put("objectKey", nullToEmpty(folder) + nullToEmpty(fileName));
|
||||
return record;
|
||||
}
|
||||
|
||||
private static User currentUser() {
|
||||
return AuthContextHolder.get().getUser();
|
||||
}
|
||||
|
||||
private static void like(LambdaQueryWrapper<PickupOrder> wrapper,
|
||||
SFunction<PickupOrder, ?> column, String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
wrapper.like(column, value.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateCode(Integer value, Set<Integer> accepted, String name) {
|
||||
if (value != null && !accepted.contains(value)) {
|
||||
throw new IllegalArgumentException(name + " 取值不合法");
|
||||
}
|
||||
}
|
||||
|
||||
private static LocalDate parseDate(String value, String name) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(value.trim(), DATE_FORMATTER);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException(name + " 格式必须为 yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
private static Date startOfDay(LocalDate date) {
|
||||
return date == null ? null : Date.from(date.atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
}
|
||||
|
||||
private static Date nextDayStart(LocalDate date) {
|
||||
return date == null ? null : Date.from(date.plusDays(1).atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
}
|
||||
|
||||
private static void validateRange(LocalDate from, LocalDate to, String fromName, String toName) {
|
||||
if (from != null && to != null && from.isAfter(to)) {
|
||||
throw new IllegalArgumentException(fromName + " 不能晚于 " + toName);
|
||||
}
|
||||
}
|
||||
|
||||
private static String text(Map<Integer, String> values, Integer code) {
|
||||
return code == null ? null : values.getOrDefault(code, "未知");
|
||||
}
|
||||
|
||||
private static String formatTime(Date value) {
|
||||
return value == null ? null : TIME_FORMATTER.format(value.toInstant());
|
||||
}
|
||||
|
||||
private static String nullToEmpty(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import me.iiv.agent.entity.m1.MallGoodsOrder;
|
||||
import me.iiv.agent.entity.m2.ContractNew;
|
||||
import me.iiv.agent.entity.m2.DeliveryOrder;
|
||||
import me.iiv.agent.entity.m2.InstallOrder;
|
||||
import me.iiv.agent.entity.m2.PickupOrder;
|
||||
import me.iiv.agent.entity.m2.WarehouseAgentAuth;
|
||||
import me.iiv.agent.entity.m2.WarehouseIn;
|
||||
import me.iiv.agent.entity.m2.WarehouseOut;
|
||||
@@ -222,6 +223,58 @@ public class PermissionService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取货工单权限:总部看全部;销售非负责人按自己创建/代管物业;其余代理身份
|
||||
* 可看申请代理、所属代理、代管物业范围,以及本人参与过流程的工单。
|
||||
* 列表和详情复用本方法,防止详情接口绕过行权限。
|
||||
*/
|
||||
public void applyPickupOrderRowFilter(LambdaQueryWrapper<PickupOrder> wrapper, User user) {
|
||||
if (!isAgentAuthority(user.getAuthorityId())) {
|
||||
return;
|
||||
}
|
||||
if (user.getAgentId() == null) {
|
||||
throw new IllegalStateException("代理商用户未关联代理商,拒绝访问业务数据");
|
||||
}
|
||||
List<WarehouseAgentAuth> auths = managedAuths(user.getId());
|
||||
List<Long> agentIds = new ArrayList<>();
|
||||
agentIds.add(user.getAgentId());
|
||||
agentIds.addAll(auths.stream().map(WarehouseAgentAuth::getManagerAgentId)
|
||||
.collect(Collectors.toList()));
|
||||
String participantExists = "EXISTS (SELECT 1 FROM pickup_order_process pop "
|
||||
+ "WHERE pop.pickup_order_no = pickup_order.pickup_order_no "
|
||||
+ "AND pop.soft_delete = 0 AND pop.user_id = {0})";
|
||||
if (isSalesNonLeader(user)) {
|
||||
List<Long> propertyIds = properytMapper.selectList(new LambdaQueryWrapper<Properyt>()
|
||||
.eq(Properyt::getSoftDelete, 0)
|
||||
.eq(Properyt::getCreateId, user.getId())
|
||||
.select(Properyt::getId))
|
||||
.stream().map(Properyt::getId).collect(Collectors.toList());
|
||||
List<Long> managedAgentIds = auths.stream().map(WarehouseAgentAuth::getManagerAgentId)
|
||||
.collect(Collectors.toList());
|
||||
if (!managedAgentIds.isEmpty()) {
|
||||
propertyIds.addAll(properytMapper.selectList(new LambdaQueryWrapper<Properyt>()
|
||||
.eq(Properyt::getSoftDelete, 0)
|
||||
.in(Properyt::getAgentId, managedAgentIds)
|
||||
.select(Properyt::getId))
|
||||
.stream().map(Properyt::getId).collect(Collectors.toList()));
|
||||
}
|
||||
List<Long> visiblePropertyIds = propertyIds.isEmpty()
|
||||
? List.of(0L) : new ArrayList<>(new LinkedHashSet<>(propertyIds));
|
||||
wrapper.and(w -> w.in(PickupOrder::getPropertyId, visiblePropertyIds)
|
||||
.or().apply(participantExists, user.getId()));
|
||||
return;
|
||||
}
|
||||
List<Long> managedPropertyIds = managedPropertyIds(agentIds);
|
||||
wrapper.and(w -> {
|
||||
w.in(PickupOrder::getApplyAgentId, agentIds)
|
||||
.or().in(PickupOrder::getAgentId, agentIds);
|
||||
if (!managedPropertyIds.isEmpty()) {
|
||||
w.or().in(PickupOrder::getPropertyId, managedPropertyIds);
|
||||
}
|
||||
w.or().apply(participantExists, user.getId());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 装机工单权限,1:1 对齐老 getWorkOrderAuth(type=0) 的有效业务口径:总部看全部;
|
||||
* 销售非负责人只看自己创建物业及授权代管公司的物业;其他代理用户可看自己、
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
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.m1.MallLogisticsPic;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogistics;
|
||||
import me.iiv.agent.entity.m2.InstallToOutWarehouse;
|
||||
import me.iiv.agent.entity.m2.PickupOrder;
|
||||
import me.iiv.agent.entity.m2.PickupOrderDetail;
|
||||
import me.iiv.agent.entity.m2.PickupOrderProcess;
|
||||
import me.iiv.agent.entity.m2.WarehouseInOutPic;
|
||||
import me.iiv.agent.mapper.m1.MallLogisticsPicMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderLogisticsMapper;
|
||||
import me.iiv.agent.mapper.m2.InstallToOutWarehouseMapper;
|
||||
import me.iiv.agent.mapper.m2.PickupOrderDetailMapper;
|
||||
import me.iiv.agent.mapper.m2.PickupOrderMapper;
|
||||
import me.iiv.agent.mapper.m2.PickupOrderProcessMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseInOutPicMapper;
|
||||
import me.iiv.agent.mcp.McpErrorCode;
|
||||
import me.iiv.agent.mcp.ToolCallException;
|
||||
import me.iiv.agent.permission.PermissionService;
|
||||
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.util.Arrays;
|
||||
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.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PickupOrderToolsTest {
|
||||
|
||||
@Mock PickupOrderMapper pickupOrderMapper;
|
||||
@Mock PickupOrderDetailMapper detailMapper;
|
||||
@Mock PickupOrderProcessMapper processMapper;
|
||||
@Mock MallOrderLogisticsMapper logisticsMapper;
|
||||
@Mock MallLogisticsPicMapper shippingPicMapper;
|
||||
@Mock WarehouseInOutPicMapper receiptPicMapper;
|
||||
@Mock InstallToOutWarehouseMapper relationMapper;
|
||||
@Mock PermissionService permissionService;
|
||||
|
||||
PickupOrderTools tools;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, PickupOrder.class);
|
||||
TableInfoHelper.initTableInfo(assistant, PickupOrderDetail.class);
|
||||
TableInfoHelper.initTableInfo(assistant, PickupOrderProcess.class);
|
||||
TableInfoHelper.initTableInfo(assistant, MallOrderLogistics.class);
|
||||
TableInfoHelper.initTableInfo(assistant, MallLogisticsPic.class);
|
||||
TableInfoHelper.initTableInfo(assistant, WarehouseInOutPic.class);
|
||||
TableInfoHelper.initTableInfo(assistant, InstallToOutWarehouse.class);
|
||||
tools = new PickupOrderTools(pickupOrderMapper, detailMapper, processMapper, logisticsMapper,
|
||||
shippingPicMapper, receiptPicMapper, relationMapper, permissionService);
|
||||
User user = new User();
|
||||
user.setId(7L);
|
||||
AuthContextHolder.set(new AuthContext(null, user));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AuthContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listReturnsTextsBatchLogisticsAndClampsRows() {
|
||||
when(pickupOrderMapper.selectCount(any())).thenReturn(1L);
|
||||
when(pickupOrderMapper.selectList(any())).thenReturn(List.of(order()));
|
||||
MallOrderLogistics logistics = new MallOrderLogistics();
|
||||
logistics.setOrderNo("QH-001");
|
||||
logistics.setLogisticsName("快递公司");
|
||||
logistics.setLogisticsOrderNo("KD-001");
|
||||
when(logisticsMapper.selectList(any())).thenReturn(List.of(logistics));
|
||||
|
||||
Map<String, Object> result = tools.pickupOrderListQuery("QH", "AZ", "CX", 10L, "项目",
|
||||
0, 2, 0, "申请人", "2026-09-01", "2026-09-30",
|
||||
"2026-09-01", "2026-09-30", "2026-09-01", "2026-09-30", 0, 101);
|
||||
|
||||
assertEquals(1L, result.get("total"));
|
||||
assertEquals(1, result.get("page"));
|
||||
assertEquals(100, result.get("rows"));
|
||||
assertEquals("rows 超过上限 100,已截断为 100", result.get("notice"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> record = ((List<Map<String, Object>>) result.get("records")).get(0);
|
||||
assertEquals("已完成", record.get("orderStateText"));
|
||||
assertEquals("装机工单发起", record.get("typeText"));
|
||||
assertEquals("快递公司", record.get("sendModeText"));
|
||||
assertEquals("KD-001", record.get("logisticsOrderNo"));
|
||||
verify(permissionService).applyPickupOrderRowFilter(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRejectsInvalidCodesAndDateRangesBeforeQuery() {
|
||||
assertThrows(IllegalArgumentException.class, () -> tools.pickupOrderListQuery(null, null, null,
|
||||
null, null, 9, null, null, null, null, null, null, null, null, null, null, null));
|
||||
assertThrows(IllegalArgumentException.class, () -> tools.pickupOrderListQuery(null, null, null,
|
||||
null, null, null, null, null, null, "2026-09-30", "2026-09-01",
|
||||
null, null, null, null, null, null));
|
||||
verifyNoInteractions(pickupOrderMapper, permissionService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailUsesSamePermissionAndHidesExistence() {
|
||||
when(pickupOrderMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
ToolCallException error = assertThrows(ToolCallException.class,
|
||||
() -> tools.pickupOrderDetailQuery(20L, null));
|
||||
|
||||
assertEquals(McpErrorCode.NOT_FOUND_OR_FORBIDDEN, error.getCode());
|
||||
verify(permissionService).applyPickupOrderRowFilter(any(), any());
|
||||
verifyNoInteractions(detailMapper, processMapper, logisticsMapper, shippingPicMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailSplitsGoodsAndSerialChangesAndReturnsFilesProcessesRelations() {
|
||||
when(pickupOrderMapper.selectList(any())).thenReturn(List.of(order()));
|
||||
PickupOrderDetail goods = new PickupOrderDetail();
|
||||
goods.setId(3L);
|
||||
goods.setType(0);
|
||||
goods.setMaterialNo("MAT-1");
|
||||
PickupOrderDetail serial = new PickupOrderDetail();
|
||||
serial.setId(4L);
|
||||
serial.setType(1);
|
||||
serial.setSnCode("SN-1");
|
||||
when(detailMapper.selectList(any())).thenReturn(List.of(goods, serial));
|
||||
|
||||
PickupOrderProcess process = new PickupOrderProcess();
|
||||
process.setId(5L);
|
||||
process.setScheduleState(2);
|
||||
process.setOperationTheme(2);
|
||||
process.setOperationType(2);
|
||||
when(processMapper.selectList(any())).thenReturn(List.of(process));
|
||||
WarehouseInOutPic receipt = new WarehouseInOutPic();
|
||||
receipt.setId(6L);
|
||||
receipt.setAggregateId(5L);
|
||||
receipt.setFileName("receipt.jpg");
|
||||
receipt.setOssbucketname("bucket-a");
|
||||
receipt.setOssfolder("receipt/");
|
||||
when(receiptPicMapper.selectList(any())).thenReturn(List.of(receipt));
|
||||
|
||||
InstallToOutWarehouse relation = new InstallToOutWarehouse();
|
||||
relation.setId(7L);
|
||||
relation.setOutWarehouseType(2);
|
||||
relation.setOrderNo("RK-1");
|
||||
when(relationMapper.selectList(any())).thenReturn(List.of(relation));
|
||||
MallOrderLogistics logistics = new MallOrderLogistics();
|
||||
logistics.setSendMode(0);
|
||||
logistics.setState(7);
|
||||
when(logisticsMapper.selectList(any())).thenReturn(List.of(logistics));
|
||||
MallLogisticsPic shipping = new MallLogisticsPic();
|
||||
shipping.setId(8L);
|
||||
shipping.setFileName("ship.jpg");
|
||||
shipping.setOssbucketname("bucket-b");
|
||||
shipping.setOssfolder("shipping/");
|
||||
when(shippingPicMapper.selectList(any())).thenReturn(List.of(shipping));
|
||||
|
||||
Map<String, Object> result = tools.pickupOrderDetailQuery(20L, "QH-001");
|
||||
|
||||
assertEquals(1, ((List<?>) result.get("pickupGoods")).size());
|
||||
assertEquals(1, ((List<?>) result.get("serialChanges")).size());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> logisticsResult = (Map<String, Object>) result.get("logistics");
|
||||
assertEquals("已签收", logisticsResult.get("stateText"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> shippingFiles = (List<Map<String, Object>>) result.get("shippingFiles");
|
||||
assertEquals("shipping/ship.jpg", shippingFiles.get(0).get("objectKey"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> processes = (List<Map<String, Object>>) result.get("processes");
|
||||
assertEquals("入库", processes.get(0).get("operationThemeText"));
|
||||
assertEquals(1, ((List<?>) processes.get(0).get("files")).size());
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> receiptFiles = (List<Map<String, Object>>) result.get("receiptFiles");
|
||||
assertEquals(5L, receiptFiles.get(0).get("processId"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> documents = (List<Map<String, Object>>) result.get("relatedDocuments");
|
||||
assertEquals("入库单", documents.get(0).get("documentTypeText"));
|
||||
assertEquals("AZ-001", result.get("relatedInstallOrderNo"));
|
||||
assertEquals("CX-001", result.get("relatedWithdrawOrderNo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailReturnsNullLogisticsWhenNoRecordExists() {
|
||||
when(pickupOrderMapper.selectList(any())).thenReturn(List.of(order()));
|
||||
when(detailMapper.selectList(any())).thenReturn(List.of());
|
||||
when(processMapper.selectList(any())).thenReturn(List.of());
|
||||
when(relationMapper.selectList(any())).thenReturn(List.of());
|
||||
when(logisticsMapper.selectList(any())).thenReturn(List.of());
|
||||
when(shippingPicMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
Map<String, Object> result = tools.pickupOrderDetailQuery(20L, null);
|
||||
|
||||
assertNull(result.get("logistics"));
|
||||
assertEquals(List.of(), result.get("processes"));
|
||||
verifyNoInteractions(receiptPicMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void springAiPublishesBothSchemas() {
|
||||
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(2, schemas.size());
|
||||
assertTrue(schemas.get("pickup.order.list.query").contains("pickupDateTo"));
|
||||
assertTrue(schemas.get("pickup.order.detail.query").contains("pickupOrderNo"));
|
||||
}
|
||||
|
||||
private static PickupOrder order() {
|
||||
PickupOrder order = new PickupOrder();
|
||||
order.setId(20L);
|
||||
order.setPickupOrderNo("QH-001");
|
||||
order.setInstallOrderNo("AZ-001");
|
||||
order.setWithdrawOrderNo("CX-001");
|
||||
order.setType(0);
|
||||
order.setOrderState(2);
|
||||
order.setSendMode(0);
|
||||
return order;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package me.iiv.agent.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import me.iiv.agent.entity.m0.AgentManageDistrictAuth;
|
||||
import me.iiv.agent.entity.m0.Authority;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m2.PickupOrder;
|
||||
import me.iiv.agent.entity.m2.WarehouseAgentAuth;
|
||||
import me.iiv.agent.mapper.m0.AgentManageDistrictAuthMapper;
|
||||
import me.iiv.agent.mapper.m0.AgentMapper;
|
||||
import me.iiv.agent.mapper.m0.AuthorityMapper;
|
||||
import me.iiv.agent.mapper.m0.ProperytMapper;
|
||||
import me.iiv.agent.mapper.m2.WarehouseAgentAuthMapper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
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 org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
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.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PermissionServicePickupOrderRowFilterTest {
|
||||
|
||||
@Mock AuthorityMapper authorityMapper;
|
||||
@Mock AgentMapper agentMapper;
|
||||
@Mock WarehouseAgentAuthMapper warehouseAgentAuthMapper;
|
||||
@Mock AgentManageDistrictAuthMapper agentManageDistrictAuthMapper;
|
||||
@Mock ProperytMapper properytMapper;
|
||||
@Mock StringRedisTemplate redisTemplate;
|
||||
|
||||
PermissionService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), PickupOrder.class);
|
||||
service = new PermissionService(authorityMapper, agentMapper, warehouseAgentAuthMapper,
|
||||
agentManageDistrictAuthMapper, properytMapper, redisTemplate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void headquartersAddsNoFilter() {
|
||||
when(authorityMapper.selectById(9L)).thenReturn(authority(0));
|
||||
LambdaQueryWrapper<PickupOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
service.applyPickupOrderRowFilter(wrapper, user(7L, 9L, null));
|
||||
|
||||
assertTrue(wrapper.getSqlSegment().isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
void agentCombinesApplyAgentOwnerManagedPropertyAndParticipant() {
|
||||
when(authorityMapper.selectById(9L)).thenReturn(authority(1));
|
||||
WarehouseAgentAuth managed = new WarehouseAgentAuth();
|
||||
managed.setManagerAgentId(12L);
|
||||
when(warehouseAgentAuthMapper.selectList(any())).thenReturn(List.of(managed));
|
||||
AgentManageDistrictAuth property = new AgentManageDistrictAuth();
|
||||
property.setPropertyId(30L);
|
||||
when(agentManageDistrictAuthMapper.selectList(any())).thenReturn(List.of(property));
|
||||
LambdaQueryWrapper<PickupOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
service.applyPickupOrderRowFilter(wrapper, user(7L, 9L, 10L));
|
||||
|
||||
String sql = wrapper.getSqlSegment();
|
||||
Collection<Object> values = wrapper.getParamNameValuePairs().values();
|
||||
assertTrue(sql.contains("apply_agent_id IN"));
|
||||
assertTrue(sql.contains("agent_id IN"));
|
||||
assertTrue(sql.contains("property_id IN"));
|
||||
assertTrue(sql.contains("pickup_order_process"));
|
||||
assertTrue(values.containsAll(List.of(7L, 10L, 12L, 30L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void agentWithoutAgentFailsClosed() {
|
||||
when(authorityMapper.selectById(9L)).thenReturn(authority(1));
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> service.applyPickupOrderRowFilter(
|
||||
new LambdaQueryWrapper<>(), user(7L, 9L, null)));
|
||||
}
|
||||
|
||||
private static User user(Long id, Long authorityId, Long agentId) {
|
||||
User user = new User();
|
||||
user.setId(id);
|
||||
user.setAuthorityId(authorityId);
|
||||
user.setAgentId(agentId);
|
||||
return user;
|
||||
}
|
||||
|
||||
private static Authority authority(int isAgent) {
|
||||
Authority authority = new Authority();
|
||||
authority.setId(9L);
|
||||
authority.setIsagent(isAgent);
|
||||
authority.setSoftDelete(0);
|
||||
return authority;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user