Merge pull request 'feat(mcp): 新增商城订单查询工具 mall.order.list/detail/logistics.query (REQ-20260909-0004)' (#6) from feat/REQ-20260909-0004-mall-order-tools into main
This commit was merged in pull request #6.
This commit is contained in:
@@ -197,6 +197,88 @@
|
||||
|
||||
---
|
||||
|
||||
## 7. mall.order.list.query
|
||||
|
||||
查询商城订单列表(分页),对应老系统 `POST /mallGoodsOrder/listByPage`。
|
||||
|
||||
- 分类:customer | 类型:读
|
||||
- 数据来源:m1 商城库(`xiaoqu_mall_d`)`mall_goods_order` 表
|
||||
- 行级权限(`PermissionService.applyOrderRowFilter`,移植老接口 `WrapperUtils.getSaleCompany` 语义):
|
||||
- 总部(isagent=0):全部;
|
||||
- 代理商(isagent=1):`sale_company_id IN (自己 + 直属下级 + warehouse_agent_auth 代管代理)`,有代管物业授权时再 `OR property_id IN (agent_manage_district_auth 代管物业)`;
|
||||
- 销售部门非负责人:`property_id IN (自己创建的物业 + 代管代理名下物业)`,与上一条互斥。
|
||||
|
||||
**入参**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderNo | string | 否 | 订单编号,模糊匹配 |
|
||||
| agentId | number | 否 | 代理商 id,精确匹配 |
|
||||
| propertyId | number | 否 | 物业 id,精确匹配 |
|
||||
| orderType | number | 否 | 订单类型:0=代理 1=物业 |
|
||||
| orderState | number | 否 | 订单状态(单选):0=待付款 1=待出库 2=待发货 3=已发货 4=已签收 5=已收货 6=已取消 7=已删除 |
|
||||
| payState | number | 否 | 回款状态:0=待付款 1=已付款 2=付款中 |
|
||||
| startOrderTime | string | 否 | 下单时间起,`yyyy-MM-dd` |
|
||||
| endOrderTime | string | 否 | 下单时间止,`yyyy-MM-dd`(含当天) |
|
||||
| page | number | 否 | 页码,从 1 开始,默认 1 |
|
||||
| rows | number | 否 | 每页条数,默认 20,上限 100(超出截断为 100 并在 notice 提示) |
|
||||
|
||||
**出参 records 字段**(按 id 倒序):
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| id / orderNo / orderTime | 订单 id / 编号 / 下单时间(`yyyy-MM-dd HH:mm:ss`) |
|
||||
| orderType / agentName / propertyName | 订单类型 / 代理名称 / 物业名称 |
|
||||
| saleCompany | 销售公司 |
|
||||
| payMoney / paidMoney / unPayMoney | 应付 / 已付 / 未付金额 |
|
||||
| orderState / payState / warehouseState | 订单状态 / 回款状态 / 出库状态(0=未出库 1=已出库 2=部分出库) |
|
||||
| customerName | 客户名称:orderType=0 取代理金蝶名称,否则取物业金蝶名称(批量富化,同老接口) |
|
||||
|
||||
---
|
||||
|
||||
## 8. mall.order.detail.query
|
||||
|
||||
按订单号查询商城订单详情(订单 + 商品明细 + 收货地址一次返回,合并老系统三个接口)。
|
||||
|
||||
- 分类:customer | 类型:读
|
||||
- 数据来源:m1 库 `mall_goods_order` + `mall_order_details` + `mall_shipping_address`
|
||||
- 行级权限:与 `mall.order.list.query` 一致;订单不存在或无权查看时 `record=null` 并在 notice 说明(两者不区分,不泄露订单是否存在)。
|
||||
|
||||
**入参**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderNo | string | 是 | 订单编号 |
|
||||
|
||||
**出参**:
|
||||
|
||||
- `record`:订单关键字段(金额类 payMoney/paidMoney/unPayMoney/totalMoney/goodsMoney/postageMoney/discountMoney,状态类 orderState/payState/orderPayState/warehouseState/orderInvoiceState,时间类 orderTime/payTime/receiptTime/signTime/createTime,及 accountsType/paymentType/goodsType/saleCompany/payNo/memo 等)+ 收货信息(reveiveName/reveiveNumber/province/city/countyseat/address,由 `address_id` 关联 `mall_shipping_address`)。
|
||||
- `items`:商品明细列表,每行含 spuNo/spuName/skuNo/count/price/payMoney/payState/warehouseState。
|
||||
|
||||
---
|
||||
|
||||
## 9. mall.order.logistics.query
|
||||
|
||||
按订单号查询商城订单物流(发货信息 + 物流轨迹),对应老系统 `GET /mallGoodsOrder/getMallLogistics`。
|
||||
|
||||
- 分类:customer | 类型:读
|
||||
- 数据来源:m1 库 `mall_order_logistics` + `mall_order_logistics_detai`
|
||||
- 行级权限:与 `mall.order.list.query` 一致;订单不存在或无权查看时 `logistics=null` 并在 notice 说明(两者不区分)。
|
||||
- **注意**:轨迹表 `logistics_order_no` 字段老系统写入时塞的是订单号而非快递单号,故按 orderNo 查询(照搬老系统语义)。物流轨迹为系统内业务节点手工写入的记录,非实时快递查询,可能不全(菜鸟定时同步已停用)。
|
||||
|
||||
**入参**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderNo | string | 是 | 订单编号 |
|
||||
|
||||
**出参**:
|
||||
|
||||
- `logistics`(无物流记录时为 null):logisticsName(快递公司)/ logisticsOrderNo(快递单号)/ sendMode(0=快递 1=自送)/ state(0=已出单 1=已出库 2=已发货 3=已揽件 4=运输中 5=待取件 6=派送中 7=已签收)/ deliverName / deliverNumber / sendTime / receivingGoodsName / reveiveName / reveiveNumber / address。
|
||||
- `tracks`:轨迹列表,按时间正序,每行含 memo(轨迹描述)/ state / picList / createTime。
|
||||
|
||||
---
|
||||
|
||||
## 附录:在线查看工具清单
|
||||
|
||||
```bash
|
||||
|
||||
@@ -15,9 +15,10 @@ SELECT '运营', 'operation', 'dev 种子:运营作用域', 1, 0
|
||||
WHERE NOT EXISTS (SELECT 1 FROM agent_scope WHERE code = 'operation');
|
||||
|
||||
-- 作用域-工具:sys.ping / agent.count / agent.list.query / property.list.query / agent.detail.query / property.detail.query
|
||||
-- / mall.order.list.query / mall.order.detail.query / mall.order.logistics.query
|
||||
INSERT INTO scope_tool (scope_id, tool_id, create_id)
|
||||
SELECT s.id, t.id, 0 FROM agent_scope s JOIN tool t
|
||||
WHERE s.code = 'operation' AND t.name IN ('sys.ping', 'agent.count', 'agent.list.query', 'property.list.query', 'agent.detail.query', 'property.detail.query')
|
||||
WHERE s.code = 'operation' AND t.name IN ('sys.ping', 'agent.count', 'agent.list.query', 'property.list.query', 'agent.detail.query', 'property.detail.query', 'mall.order.list.query', 'mall.order.detail.query', 'mall.order.logistics.query')
|
||||
AND NOT EXISTS (SELECT 1 FROM scope_tool st WHERE st.scope_id = s.id AND st.tool_id = t.id);
|
||||
|
||||
COMMIT;
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
package me.iiv.agent.entity.m1;
|
||||
|
||||
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.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单(m1 商城库)
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("mall_goods_order")
|
||||
public class MallGoodsOrder implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 销售id */
|
||||
private Long createId;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 修改id */
|
||||
private Long modifyId;
|
||||
|
||||
/** 修改时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 数据状态(0:启用,99:停用) */
|
||||
private Integer softDelete;
|
||||
|
||||
/** 代理ID */
|
||||
private Long agentId;
|
||||
|
||||
/** 物业ID */
|
||||
private Long propertyId;
|
||||
|
||||
/** 站点ID */
|
||||
private Long districtId;
|
||||
|
||||
/** 客户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 订单编号 */
|
||||
private String orderNo;
|
||||
|
||||
/** 订单类型(0:代理;1:物业) */
|
||||
private Integer orderType;
|
||||
|
||||
/** 结账类型(0:预付;1:月结) */
|
||||
private Integer accountsType;
|
||||
|
||||
/** 支付方式 */
|
||||
private Integer paymentType;
|
||||
|
||||
/** 收货地址 */
|
||||
private Long addressId;
|
||||
|
||||
/** 订单状态(0:待付款;1:待出库;2:待发货;3:已发货;4:已签收;5:已收货;6:已取消;7:已删除) */
|
||||
private Integer orderState;
|
||||
|
||||
/** 支付金额 */
|
||||
private BigDecimal payMoney;
|
||||
|
||||
/** 已支付金额 */
|
||||
private BigDecimal paidMoney;
|
||||
|
||||
/** 未支付金额 */
|
||||
private BigDecimal unPayMoney;
|
||||
|
||||
/** 回款状态(0:待付款;1:已付款;2:付款中) */
|
||||
private Integer payState;
|
||||
|
||||
/** 付款付款确认状态(0:未确认;1:已确认;2:延迟确认) */
|
||||
private Integer orderPayState;
|
||||
|
||||
/** 退款状态(0:待退款;1:退款成功;2:退款失败;99:未知) */
|
||||
private Integer rePayState;
|
||||
|
||||
/** 发票状态(0:未开票;1:已开票;2:部分开票) */
|
||||
private Integer orderInvoiceState;
|
||||
|
||||
/** 订单有效时长(小时) */
|
||||
private Integer effectiveTime;
|
||||
|
||||
/** 退款原因 */
|
||||
private String reMemo;
|
||||
|
||||
/** 邮费 */
|
||||
private BigDecimal postageMoney;
|
||||
|
||||
/** 订单备注 */
|
||||
private String memo;
|
||||
|
||||
/** 应支付总金额 */
|
||||
private BigDecimal totalMoney;
|
||||
|
||||
/** 商品金额 */
|
||||
private BigDecimal goodsMoney;
|
||||
|
||||
/** 支付编号 */
|
||||
private String payNo;
|
||||
|
||||
/** 下单时间 */
|
||||
private Date orderTime;
|
||||
|
||||
/** 实际回款时间 */
|
||||
private Date payTime;
|
||||
|
||||
/** 收货时间 */
|
||||
private Date receiptTime;
|
||||
|
||||
/** 签收时间 */
|
||||
private Date signTime;
|
||||
|
||||
/** 商品类型(0:非月结商品;1:月结商品) */
|
||||
private Integer goodsType;
|
||||
|
||||
/** 账单编号(月结商品有) */
|
||||
private String billNo;
|
||||
|
||||
/** 付款识别号(线下付款) */
|
||||
private String payIdentifier;
|
||||
|
||||
/** 如果自行配送则有值 */
|
||||
private String mobile;
|
||||
|
||||
/** 特许状态(0:未特许;1:已特许) */
|
||||
private Integer franchiseState;
|
||||
|
||||
/** 优惠金额 */
|
||||
private BigDecimal discountMoney;
|
||||
|
||||
/** 活动名称 */
|
||||
private String activityName;
|
||||
|
||||
/** 活动编号 */
|
||||
private String activityNo;
|
||||
|
||||
/** 促销编号 */
|
||||
private String schemeNo;
|
||||
|
||||
/** 出库状态(0:未出库;1:已出库;2:部分出库) */
|
||||
private Integer warehouseState;
|
||||
|
||||
/** 预订单编号 */
|
||||
private String beforehandOrderNo;
|
||||
|
||||
/** 锁定状态(0:未锁定;1:已锁定) */
|
||||
private Integer lockState;
|
||||
|
||||
/** 预订单下单人 */
|
||||
private String placingPeople;
|
||||
|
||||
/** 预订单下单人 */
|
||||
private Long placingPeopleId;
|
||||
|
||||
/** 代理 */
|
||||
private String agentName;
|
||||
|
||||
/** 物业 */
|
||||
private String propertyName;
|
||||
|
||||
/** 站点 */
|
||||
private String districtName;
|
||||
|
||||
/** 下单人 */
|
||||
private String userName;
|
||||
|
||||
/** 销售名称 */
|
||||
private String createName;
|
||||
|
||||
/** 原始应支付金额 */
|
||||
private BigDecimal originalPayMoney;
|
||||
|
||||
/** 逾期标识0:否;1:是 */
|
||||
private Integer overdueFlag;
|
||||
|
||||
/** 逾期天数 */
|
||||
private Integer overdueDays;
|
||||
|
||||
/** 回款周期(天) */
|
||||
private Integer paymentCollection;
|
||||
|
||||
/** 最晚应回款时间 */
|
||||
private Date lastPayTime;
|
||||
|
||||
/** 出库日期 */
|
||||
private Date deliveryDate;
|
||||
|
||||
/** 金蝶系统内码 */
|
||||
private Long jindieInternalId;
|
||||
|
||||
/** 金蝶系统采购订单内码 */
|
||||
private Long jindiePurchaseInternalId;
|
||||
|
||||
/** 销售公司 */
|
||||
private Long saleCompanyId;
|
||||
|
||||
/** 销售公司 */
|
||||
private String saleCompany;
|
||||
|
||||
/** 发货公司 */
|
||||
private Long deliveryCompanyId;
|
||||
|
||||
/** 发货公司 */
|
||||
private String deliveryCompany;
|
||||
|
||||
/** 已开票金额 */
|
||||
private BigDecimal invoiceMoney;
|
||||
|
||||
/** 未开票金额 */
|
||||
private BigDecimal unInvoiceMoney;
|
||||
|
||||
/** 金蝶销售服务订单内码 */
|
||||
private Long jindieServiceOrderInternalId;
|
||||
|
||||
/** 服务订单状态(0:未处理;1:已处理;2:无需处理) */
|
||||
private Integer serviceOrderState;
|
||||
|
||||
/** 微店订单标识(0:否;1:是) */
|
||||
private Integer weiDianOrderFlag;
|
||||
|
||||
/** 微店订单编号 */
|
||||
private String weiDianOrderNo;
|
||||
|
||||
/** 装机单标识(0:否;1:是) */
|
||||
private Integer installOrderFlag;
|
||||
|
||||
/** 营销状态(0:未营销;1:部分营销;2:已营销) */
|
||||
private Integer marketingState;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package me.iiv.agent.entity.m1;
|
||||
|
||||
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.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单商品详情(m1 商城库)
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("mall_order_details")
|
||||
public class MallOrderDetails implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 创建id */
|
||||
private Long createId;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 修改id */
|
||||
private Long modifyId;
|
||||
|
||||
/** 修改时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 数据状态(0:启用,99:停用) */
|
||||
private Integer softDelete;
|
||||
|
||||
/** spu编号 */
|
||||
private String spuNo;
|
||||
|
||||
/** sku编号 */
|
||||
private String skuNo;
|
||||
|
||||
/** 购买数量 */
|
||||
private Integer count;
|
||||
|
||||
/** 单价 */
|
||||
private BigDecimal price;
|
||||
|
||||
/** 折扣 */
|
||||
private Double discount;
|
||||
|
||||
/** 订单编号 */
|
||||
private String orderNo;
|
||||
|
||||
/** 商品类型(0:非月结商品;1:月结商品) */
|
||||
private Integer goodsType;
|
||||
|
||||
/** sku标签 */
|
||||
private String skuLable;
|
||||
|
||||
/** 商品描述 */
|
||||
private String skuDesc;
|
||||
|
||||
/** 封面路径 */
|
||||
private String picUrl;
|
||||
|
||||
/** spu名称 */
|
||||
private String spuName;
|
||||
|
||||
/** 物料类型(0:设备;1:配件;2:耗材);历史遗留驼峰列名 */
|
||||
@TableField("spuType")
|
||||
private Integer spuType;
|
||||
|
||||
/** 出库状态(0:未出库;1:已出库) */
|
||||
private Integer warehouseState;
|
||||
|
||||
/** 出库单ID */
|
||||
private Long warehouseOutId;
|
||||
|
||||
/** 原始单价 */
|
||||
private BigDecimal originalPrice;
|
||||
|
||||
/** 原始购买数量 */
|
||||
private Integer originalCount;
|
||||
|
||||
/** 收款状态(0:未收款;1:全部收款;2:部分收款) */
|
||||
private Integer payState;
|
||||
|
||||
/** 发票状态(0:未开票;1:全部开票;2:部分开票) */
|
||||
private Integer orderInvoiceState;
|
||||
|
||||
/** 应支付金额 */
|
||||
private BigDecimal payMoney;
|
||||
|
||||
/** 应开票金额 */
|
||||
private BigDecimal invoiceMoney;
|
||||
|
||||
/** 未支付金额 */
|
||||
private BigDecimal notPayMoney;
|
||||
|
||||
/** 未开票金额 */
|
||||
private BigDecimal notInvoiceMoney;
|
||||
|
||||
/** 营销状态(0:未营销;1:营销中;2:已营销) */
|
||||
private Integer marketingState;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package me.iiv.agent.entity.m1;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单物流(m1 商城库)
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("mall_order_logistics")
|
||||
public class MallOrderLogistics implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 订单发货人 */
|
||||
private Long createId;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 修改id */
|
||||
private Long modifyId;
|
||||
|
||||
/** 发货时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 数据状态(0:启用,99:停用) */
|
||||
private Integer softDelete;
|
||||
|
||||
/** 发货方式(0:快递公司;1:自行派送) */
|
||||
private Integer sendMode;
|
||||
|
||||
/** 订单编号 */
|
||||
private String orderNo;
|
||||
|
||||
/** 物流公司id */
|
||||
private Long logisticsId;
|
||||
|
||||
/** 物流公司编号 */
|
||||
private String logisticsNo;
|
||||
|
||||
/** 物流公司名称 */
|
||||
private String logisticsName;
|
||||
|
||||
/** 快递单号 */
|
||||
private String logisticsOrderNo;
|
||||
|
||||
/** 自己生成的编号 */
|
||||
private String logisticsSelfNo;
|
||||
|
||||
/** 派送员姓名 */
|
||||
private String deliverName;
|
||||
|
||||
/** 派送手机号 */
|
||||
private String deliverNumber;
|
||||
|
||||
/** 配送员id */
|
||||
private Long userId;
|
||||
|
||||
/** 物流当前状态(0:已出单;1:已出库;2:已发货;3:已揽件;4:运输中;5:待取件;6:派送中;7:已签收) */
|
||||
private Integer state;
|
||||
|
||||
/** 收货方公司名称 */
|
||||
private String receivingGoodsName;
|
||||
|
||||
/** 收货姓名 */
|
||||
private String reveiveName;
|
||||
|
||||
/** 收货手机号 */
|
||||
private String reveiveNumber;
|
||||
|
||||
/** 详细地址 */
|
||||
private String address;
|
||||
|
||||
private Long agentId;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package me.iiv.agent.entity.m1;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单物流轨迹(m1 商城库;表名为老系统遗留拼写 mall_order_logistics_detai)。
|
||||
* 注意:logistics_order_no 字段在老系统写入时混用,实际存的是订单号。
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("mall_order_logistics_detai")
|
||||
public class MallOrderLogisticsDetai implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 创建id */
|
||||
private Long createId;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 修改id */
|
||||
private Long modifyId;
|
||||
|
||||
/** 修改时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 数据状态(0:启用,99:停用) */
|
||||
private Integer softDelete;
|
||||
|
||||
/** 快递单号(老系统写入时混用,实际多为订单号) */
|
||||
private String logisticsOrderNo;
|
||||
|
||||
/** 物流详情 */
|
||||
private String memo;
|
||||
|
||||
/** 物流状态(0:已出单;1:已出库;2:已发货;3:已揽件;4:运输中;5:待取件;6:派送中;7:已签收;13:上传凭证) */
|
||||
private Integer state;
|
||||
|
||||
/** 订单号 */
|
||||
private String orderNo;
|
||||
|
||||
/** 图片列表 */
|
||||
private String picList;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package me.iiv.agent.entity.m1;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 收货地址(m1 商城库)
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName("mall_shipping_address")
|
||||
public class MallShippingAddress implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 创建id */
|
||||
private Long createId;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 修改id */
|
||||
private Long modifyId;
|
||||
|
||||
/** 修改时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 数据状态(0:启用,99:停用) */
|
||||
private Integer softDelete;
|
||||
|
||||
/** 收货姓名 */
|
||||
private String reveiveName;
|
||||
|
||||
/** 收货手机号 */
|
||||
private String reveiveNumber;
|
||||
|
||||
/** 客户id */
|
||||
private Long userId;
|
||||
|
||||
/** 下单人 */
|
||||
private String userName;
|
||||
|
||||
/** 所属站点 */
|
||||
private Long districtId;
|
||||
|
||||
/** 省 */
|
||||
private String province;
|
||||
|
||||
/** 市 */
|
||||
private String city;
|
||||
|
||||
/** 县 */
|
||||
private String countyseat;
|
||||
|
||||
/** 详细地址 */
|
||||
private String address;
|
||||
|
||||
/** 备注信息 */
|
||||
private String memo;
|
||||
|
||||
/** 状态 */
|
||||
private Integer status;
|
||||
|
||||
/** 代理ID */
|
||||
private Long agentId;
|
||||
|
||||
/** 默认标识0:否;1:是 */
|
||||
private Integer defaultFlag;
|
||||
|
||||
/** 使用客户类型(0:内部人员;1:渠道商;2:直营客户) */
|
||||
private Integer useCustomerType;
|
||||
|
||||
/** 使用客户ID */
|
||||
private Long useCustomerId;
|
||||
|
||||
/** 使用客户名称 */
|
||||
private String useCustomerName;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.iiv.agent.mapper.m1;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m1.MallGoodsOrder;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* MallGoodsOrder Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
public interface MallGoodsOrderMapper extends BaseMapper<MallGoodsOrder> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.iiv.agent.mapper.m1;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m1.MallOrderDetails;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* MallOrderDetails Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
public interface MallOrderDetailsMapper extends BaseMapper<MallOrderDetails> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package me.iiv.agent.mapper.m1;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogisticsDetai;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* MallOrderLogisticsDetai Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
public interface MallOrderLogisticsDetaiMapper extends BaseMapper<MallOrderLogisticsDetai> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package me.iiv.agent.mapper.m1;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogistics;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* MallOrderLogistics Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
public interface MallOrderLogisticsMapper extends BaseMapper<MallOrderLogistics> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package me.iiv.agent.mapper.m1;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import me.iiv.agent.entity.m1.MallShippingAddress;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* MallShippingAddress Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author codegen
|
||||
* @since 2026-09-09
|
||||
*/
|
||||
public interface MallShippingAddressMapper extends BaseMapper<MallShippingAddress> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
package me.iiv.agent.mcp.tool;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import me.iiv.agent.entity.m0.Agent;
|
||||
import me.iiv.agent.entity.m0.Properyt;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m1.MallGoodsOrder;
|
||||
import me.iiv.agent.entity.m1.MallOrderDetails;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogistics;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogisticsDetai;
|
||||
import me.iiv.agent.entity.m1.MallShippingAddress;
|
||||
import me.iiv.agent.mapper.m0.AgentMapper;
|
||||
import me.iiv.agent.mapper.m0.ProperytMapper;
|
||||
import me.iiv.agent.mapper.m1.MallGoodsOrderMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderDetailsMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderLogisticsDetaiMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderLogisticsMapper;
|
||||
import me.iiv.agent.mapper.m1.MallShippingAddressMapper;
|
||||
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.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 商城订单域工具(m1 库 xiaoqu_mall_d):仿老系统 MallGoodsOrderController。
|
||||
* 三个工具共用 PermissionService.applyOrderRowFilter(移植 WrapperUtils.getSaleCompany 语义)。
|
||||
*/
|
||||
@Component
|
||||
public class OrderTools implements ToolObject {
|
||||
|
||||
private static final int DEFAULT_ROWS = 20;
|
||||
private static final int MAX_ROWS = 100;
|
||||
/** 商城业务时区固定 Asia/Shanghai,日期解析与时间格式化统一走该时区。 */
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final Pattern DATE_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$");
|
||||
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);
|
||||
|
||||
final MallGoodsOrderMapper orderMapper;
|
||||
final MallOrderDetailsMapper orderDetailsMapper;
|
||||
final MallShippingAddressMapper shippingAddressMapper;
|
||||
final MallOrderLogisticsMapper logisticsMapper;
|
||||
final MallOrderLogisticsDetaiMapper logisticsDetaiMapper;
|
||||
final AgentMapper agentMapper;
|
||||
final ProperytMapper properytMapper;
|
||||
final PermissionService permissionService;
|
||||
|
||||
public OrderTools(MallGoodsOrderMapper orderMapper, MallOrderDetailsMapper orderDetailsMapper,
|
||||
MallShippingAddressMapper shippingAddressMapper,
|
||||
MallOrderLogisticsMapper logisticsMapper,
|
||||
MallOrderLogisticsDetaiMapper logisticsDetaiMapper,
|
||||
AgentMapper agentMapper, ProperytMapper properytMapper,
|
||||
PermissionService permissionService) {
|
||||
this.orderMapper = orderMapper;
|
||||
this.orderDetailsMapper = orderDetailsMapper;
|
||||
this.shippingAddressMapper = shippingAddressMapper;
|
||||
this.logisticsMapper = logisticsMapper;
|
||||
this.logisticsDetaiMapper = logisticsDetaiMapper;
|
||||
this.agentMapper = agentMapper;
|
||||
this.properytMapper = properytMapper;
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单列表,对应老接口 POST /mallGoodsOrder/listByPage。
|
||||
* customerName 照搬老接口:orderType=0 取代理金蝶名称,否则取物业金蝶名称(批量富化,无 N+1)。
|
||||
*/
|
||||
@XqTool(category = "customer", kind = "read")
|
||||
@Tool(name = "mall.order.list.query", description = "查询商城订单列表(分页),数据来源 m1 商城库 mall_goods_order 表。"
|
||||
+ "行级权限:代理商身份(权限组 isagent=1)只能看自己+直属下级+授权代管代理的订单(有代管物业授权时可看对应物业订单);"
|
||||
+ "销售部门非负责人只能看自己创建及代管代理名下物业的订单;总部身份(isagent=0)可看全部。"
|
||||
+ "orderType 含义:0=代理 1=物业;orderState 含义:0=待付款 1=待出库 2=待发货 3=已发货 4=已签收 5=已收货 6=已取消 7=已删除;"
|
||||
+ "payState 含义:0=待付款 1=已付款 2=付款中;warehouseState 含义:0=未出库 1=已出库 2=部分出库。"
|
||||
+ "分页:page 从 1 开始,rows 默认 20、上限 100,超出自动截断为 100 并在返回的 notice 字段提示。"
|
||||
+ "返回 total(符合条件的总数)、records(本页记录,按 id 倒序)。")
|
||||
public Map<String, Object> mallOrderListQuery(
|
||||
@ToolParam(required = false, description = "订单编号,模糊匹配") String orderNo,
|
||||
@ToolParam(required = false, description = "代理商 id,精确匹配") Long agentId,
|
||||
@ToolParam(required = false, description = "物业 id,精确匹配") Long propertyId,
|
||||
@ToolParam(required = false, description = "订单类型:0=代理 1=物业") Integer orderType,
|
||||
@ToolParam(required = false, description = "订单状态:0=待付款 1=待出库 2=待发货 3=已发货 4=已签收 5=已收货 6=已取消 7=已删除") Integer orderState,
|
||||
@ToolParam(required = false, description = "回款状态:0=待付款 1=已付款 2=付款中") Integer payState,
|
||||
@ToolParam(required = false, description = "下单时间起,格式 yyyy-MM-dd") String startOrderTime,
|
||||
@ToolParam(required = false, description = "下单时间止,格式 yyyy-MM-dd(含当天)") String endOrderTime,
|
||||
@ToolParam(required = false, description = "页码,从 1 开始,默认 1") Integer page,
|
||||
@ToolParam(required = false, description = "每页条数,默认 20,上限 100(超出截断为 100)") Integer rows) {
|
||||
Date startTime = parseDate(startOrderTime, "startOrderTime");
|
||||
Date endTimeExclusive = parseNextDayStart(endOrderTime, "endOrderTime");
|
||||
User user = AuthContextHolder.get().getUser();
|
||||
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<MallGoodsOrder> wrapper = new LambdaQueryWrapper<MallGoodsOrder>()
|
||||
.eq(MallGoodsOrder::getSoftDelete, 0);
|
||||
if (StringUtils.hasText(orderNo)) {
|
||||
wrapper.like(MallGoodsOrder::getOrderNo, orderNo);
|
||||
}
|
||||
if (agentId != null) {
|
||||
wrapper.eq(MallGoodsOrder::getAgentId, agentId);
|
||||
}
|
||||
if (propertyId != null) {
|
||||
wrapper.eq(MallGoodsOrder::getPropertyId, propertyId);
|
||||
}
|
||||
if (orderType != null) {
|
||||
wrapper.eq(MallGoodsOrder::getOrderType, orderType);
|
||||
}
|
||||
if (orderState != null) {
|
||||
wrapper.eq(MallGoodsOrder::getOrderState, orderState);
|
||||
}
|
||||
if (payState != null) {
|
||||
wrapper.eq(MallGoodsOrder::getPayState, payState);
|
||||
}
|
||||
if (startTime != null) {
|
||||
wrapper.ge(MallGoodsOrder::getOrderTime, startTime);
|
||||
}
|
||||
if (endTimeExclusive != null) {
|
||||
wrapper.lt(MallGoodsOrder::getOrderTime, endTimeExclusive);
|
||||
}
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
|
||||
Long total = orderMapper.selectCount(wrapper);
|
||||
wrapper.select(MallGoodsOrder::getId, MallGoodsOrder::getOrderNo, MallGoodsOrder::getOrderTime,
|
||||
MallGoodsOrder::getOrderType, MallGoodsOrder::getAgentId, MallGoodsOrder::getAgentName,
|
||||
MallGoodsOrder::getPropertyId, MallGoodsOrder::getPropertyName,
|
||||
MallGoodsOrder::getSaleCompany, MallGoodsOrder::getPayMoney, MallGoodsOrder::getPaidMoney,
|
||||
MallGoodsOrder::getUnPayMoney, MallGoodsOrder::getOrderState, MallGoodsOrder::getPayState,
|
||||
MallGoodsOrder::getWarehouseState)
|
||||
.orderByDesc(MallGoodsOrder::getId)
|
||||
.last("LIMIT " + (long) (pageNo - 1) * pageSize + "," + pageSize);
|
||||
List<MallGoodsOrder> orders = orderMapper.selectList(wrapper);
|
||||
|
||||
Map<Long, String> agentJinideNames = agentJinideNames(orders);
|
||||
Map<Long, String> propertyJinideNames = propertyJinideNames(orders);
|
||||
List<Map<String, Object>> records = new ArrayList<>();
|
||||
for (MallGoodsOrder order : orders) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", order.getId());
|
||||
record.put("orderNo", order.getOrderNo());
|
||||
record.put("orderTime", order.getOrderTime() == null ? null : formatTime(order.getOrderTime()));
|
||||
record.put("orderType", order.getOrderType());
|
||||
record.put("agentName", order.getAgentName());
|
||||
record.put("propertyName", order.getPropertyName());
|
||||
record.put("saleCompany", order.getSaleCompany());
|
||||
record.put("payMoney", order.getPayMoney());
|
||||
record.put("paidMoney", order.getPaidMoney());
|
||||
record.put("unPayMoney", order.getUnPayMoney());
|
||||
record.put("orderState", order.getOrderState());
|
||||
record.put("payState", order.getPayState());
|
||||
record.put("warehouseState", order.getWarehouseState());
|
||||
record.put("customerName", customerName(order, agentJinideNames, propertyJinideNames));
|
||||
records.add(record);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单详情,合并老系统三个接口:订单主表 + orderDetailListByPage 明细 + getAddress 收货地址。
|
||||
* 先按 orderNo + 行权限查订单主表,查不到即 record=null + notice(不区分不存在与无权限)。
|
||||
*/
|
||||
@XqTool(category = "customer", kind = "read")
|
||||
@Tool(name = "mall.order.detail.query", description = "按订单号查询商城订单详情(含商品明细和收货地址),数据来源 m1 商城库。"
|
||||
+ "行级权限与 mall.order.list.query 一致;订单不存在或当前身份无权查看时 record 为 null 并在 notice 字段说明(两者不区分)。"
|
||||
+ "返回 record(订单关键字段 + 收货人姓名/电话/省市区/详细地址)和 items(商品明细列表)。")
|
||||
public Map<String, Object> mallOrderDetailQuery(
|
||||
@ToolParam(required = true, description = "订单编号(orderNo)") String orderNo) {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw new IllegalArgumentException("orderNo 必填");
|
||||
}
|
||||
User user = AuthContextHolder.get().getUser();
|
||||
MallGoodsOrder order = findVisibleOrder(orderNo, user);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
if (order == null) {
|
||||
result.put("record", null);
|
||||
result.put("items", List.of());
|
||||
result.put("notice", "orderNo=" + orderNo + " 不存在或当前身份无权查看");
|
||||
return result;
|
||||
}
|
||||
|
||||
MallShippingAddress address = order.getAddressId() == null
|
||||
? null : shippingAddressMapper.selectById(order.getAddressId());
|
||||
List<MallOrderDetails> details = orderDetailsMapper.selectList(
|
||||
new LambdaQueryWrapper<MallOrderDetails>()
|
||||
.eq(MallOrderDetails::getSoftDelete, 0)
|
||||
.eq(MallOrderDetails::getOrderNo, orderNo)
|
||||
.orderByAsc(MallOrderDetails::getId));
|
||||
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("id", order.getId());
|
||||
record.put("orderNo", order.getOrderNo());
|
||||
record.put("orderTime", order.getOrderTime() == null ? null : formatTime(order.getOrderTime()));
|
||||
record.put("orderType", order.getOrderType());
|
||||
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("userName", order.getUserName());
|
||||
record.put("accountsType", order.getAccountsType());
|
||||
record.put("paymentType", order.getPaymentType());
|
||||
record.put("goodsType", order.getGoodsType());
|
||||
record.put("saleCompanyId", order.getSaleCompanyId());
|
||||
record.put("saleCompany", order.getSaleCompany());
|
||||
record.put("payMoney", order.getPayMoney());
|
||||
record.put("paidMoney", order.getPaidMoney());
|
||||
record.put("unPayMoney", order.getUnPayMoney());
|
||||
record.put("totalMoney", order.getTotalMoney());
|
||||
record.put("goodsMoney", order.getGoodsMoney());
|
||||
record.put("postageMoney", order.getPostageMoney());
|
||||
record.put("discountMoney", order.getDiscountMoney());
|
||||
record.put("orderState", order.getOrderState());
|
||||
record.put("payState", order.getPayState());
|
||||
record.put("orderPayState", order.getOrderPayState());
|
||||
record.put("warehouseState", order.getWarehouseState());
|
||||
record.put("orderInvoiceState", order.getOrderInvoiceState());
|
||||
record.put("payNo", order.getPayNo());
|
||||
record.put("payTime", order.getPayTime() == null ? null : formatTime(order.getPayTime()));
|
||||
record.put("receiptTime", order.getReceiptTime() == null ? null : formatTime(order.getReceiptTime()));
|
||||
record.put("signTime", order.getSignTime() == null ? null : formatTime(order.getSignTime()));
|
||||
record.put("memo", order.getMemo());
|
||||
record.put("createTime", order.getCreateTime() == null ? null : formatTime(order.getCreateTime()));
|
||||
record.put("reveiveName", address == null ? null : address.getReveiveName());
|
||||
record.put("reveiveNumber", address == null ? null : address.getReveiveNumber());
|
||||
record.put("province", address == null ? null : address.getProvince());
|
||||
record.put("city", address == null ? null : address.getCity());
|
||||
record.put("countyseat", address == null ? null : address.getCountyseat());
|
||||
record.put("address", address == null ? null : address.getAddress());
|
||||
result.put("record", record);
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>();
|
||||
for (MallOrderDetails detail : details) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("spuNo", detail.getSpuNo());
|
||||
item.put("spuName", detail.getSpuName());
|
||||
item.put("skuNo", detail.getSkuNo());
|
||||
item.put("count", detail.getCount());
|
||||
item.put("price", detail.getPrice());
|
||||
item.put("payMoney", detail.getPayMoney());
|
||||
item.put("payState", detail.getPayState());
|
||||
item.put("warehouseState", detail.getWarehouseState());
|
||||
items.add(item);
|
||||
}
|
||||
result.put("items", items);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单物流。轨迹表 mall_order_logistics_detai 的 logistics_order_no 字段
|
||||
* 老系统写入时塞的是订单号(见 MallGoodsOrderController.confirmReceipt/mallGoodOrderSubmitCertificate),
|
||||
* 故按 orderNo 查询,照搬老系统语义。
|
||||
*/
|
||||
@XqTool(category = "customer", kind = "read")
|
||||
@Tool(name = "mall.order.logistics.query", description = "按订单号查询商城订单物流(发货信息 + 物流轨迹),数据来源 m1 商城库。"
|
||||
+ "行级权限与 mall.order.list.query 一致;订单不存在或当前身份无权查看时 logistics 为 null 并在 notice 字段说明(两者不区分)。"
|
||||
+ "返回 logistics(快递公司/快递单号/发货方式/当前状态/派送员,无物流记录时为 null)和 tracks(轨迹列表,按时间正序)。"
|
||||
+ "sendMode 含义:0=快递 1=自送;state 含义:0=已出单 1=已出库 2=已发货 3=已揽件 4=运输中 5=待取件 6=派送中 7=已签收。"
|
||||
+ "注意:物流轨迹为系统内记录,非实时快递查询,可能不全(菜鸟定时同步已停用)。")
|
||||
public Map<String, Object> mallOrderLogisticsQuery(
|
||||
@ToolParam(required = true, description = "订单编号(orderNo)") String orderNo) {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw new IllegalArgumentException("orderNo 必填");
|
||||
}
|
||||
User user = AuthContextHolder.get().getUser();
|
||||
MallGoodsOrder order = findVisibleOrder(orderNo, user);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
if (order == null) {
|
||||
result.put("logistics", null);
|
||||
result.put("tracks", List.of());
|
||||
result.put("notice", "orderNo=" + orderNo + " 不存在或当前身份无权查看");
|
||||
return result;
|
||||
}
|
||||
|
||||
List<MallOrderLogistics> logisticsList = logisticsMapper.selectList(
|
||||
new LambdaQueryWrapper<MallOrderLogistics>()
|
||||
.eq(MallOrderLogistics::getSoftDelete, 0)
|
||||
.eq(MallOrderLogistics::getOrderNo, orderNo)
|
||||
.orderByDesc(MallOrderLogistics::getId)
|
||||
.last("LIMIT 1"));
|
||||
MallOrderLogistics logistics = logisticsList.isEmpty() ? null : logisticsList.get(0);
|
||||
|
||||
if (logistics == null) {
|
||||
result.put("logistics", null);
|
||||
} else {
|
||||
Map<String, Object> logisticsMap = new LinkedHashMap<>();
|
||||
logisticsMap.put("logisticsName", logistics.getLogisticsName());
|
||||
logisticsMap.put("logisticsOrderNo", logistics.getLogisticsOrderNo());
|
||||
logisticsMap.put("sendMode", logistics.getSendMode());
|
||||
logisticsMap.put("state", logistics.getState());
|
||||
logisticsMap.put("deliverName", logistics.getDeliverName());
|
||||
logisticsMap.put("deliverNumber", logistics.getDeliverNumber());
|
||||
logisticsMap.put("sendTime", logistics.getModifyTime() == null ? null : formatTime(logistics.getModifyTime()));
|
||||
logisticsMap.put("receivingGoodsName", logistics.getReceivingGoodsName());
|
||||
logisticsMap.put("reveiveName", logistics.getReveiveName());
|
||||
logisticsMap.put("reveiveNumber", logistics.getReveiveNumber());
|
||||
logisticsMap.put("address", logistics.getAddress());
|
||||
result.put("logistics", logisticsMap);
|
||||
}
|
||||
|
||||
List<MallOrderLogisticsDetai> detaiList = logisticsDetaiMapper.selectList(
|
||||
new LambdaQueryWrapper<MallOrderLogisticsDetai>()
|
||||
.eq(MallOrderLogisticsDetai::getSoftDelete, 0)
|
||||
.eq(MallOrderLogisticsDetai::getLogisticsOrderNo, orderNo)
|
||||
.orderByAsc(MallOrderLogisticsDetai::getCreateTime)
|
||||
.orderByAsc(MallOrderLogisticsDetai::getId));
|
||||
List<Map<String, Object>> tracks = new ArrayList<>();
|
||||
for (MallOrderLogisticsDetai detai : detaiList) {
|
||||
Map<String, Object> track = new LinkedHashMap<>();
|
||||
track.put("memo", detai.getMemo());
|
||||
track.put("state", detai.getState());
|
||||
track.put("picList", detai.getPicList());
|
||||
track.put("createTime", detai.getCreateTime() == null ? null : formatTime(detai.getCreateTime()));
|
||||
tracks.add(track);
|
||||
}
|
||||
result.put("tracks", tracks);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 按 orderNo + 行权限查订单主表;查不到返回 null(不区分不存在与无权限)。 */
|
||||
private MallGoodsOrder findVisibleOrder(String orderNo, User user) {
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = new LambdaQueryWrapper<MallGoodsOrder>()
|
||||
.eq(MallGoodsOrder::getSoftDelete, 0)
|
||||
.eq(MallGoodsOrder::getOrderNo, orderNo);
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
List<MallGoodsOrder> orders = orderMapper.selectList(
|
||||
wrapper.orderByDesc(MallGoodsOrder::getId).last("LIMIT 1"));
|
||||
return orders.isEmpty() ? null : orders.get(0);
|
||||
}
|
||||
|
||||
/** customerName 照搬老接口 listByPage:orderType=0 取代理金蝶名称,否则取物业金蝶名称。 */
|
||||
private static String customerName(MallGoodsOrder order, Map<Long, String> agentJinideNames,
|
||||
Map<Long, String> propertyJinideNames) {
|
||||
if (order.getOrderType() != null && order.getOrderType() == 0) {
|
||||
return order.getAgentId() == null ? "" : agentJinideNames.getOrDefault(order.getAgentId(), "");
|
||||
}
|
||||
return order.getPropertyId() == null ? "" : propertyJinideNames.getOrDefault(order.getPropertyId(), "");
|
||||
}
|
||||
|
||||
private Map<Long, String> agentJinideNames(List<MallGoodsOrder> orders) {
|
||||
List<Long> ids = orders.stream()
|
||||
.filter(order -> order.getOrderType() != null && order.getOrderType() == 0)
|
||||
.map(MallGoodsOrder::getAgentId)
|
||||
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (ids.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
return agentMapper.selectBatchIds(ids).stream()
|
||||
.collect(Collectors.toMap(Agent::getId,
|
||||
agent -> agent.getJinideName() == null ? "" : agent.getJinideName(), (a, b) -> a));
|
||||
}
|
||||
|
||||
private Map<Long, String> propertyJinideNames(List<MallGoodsOrder> orders) {
|
||||
List<Long> ids = orders.stream()
|
||||
.filter(order -> order.getOrderType() == null || order.getOrderType() != 0)
|
||||
.map(MallGoodsOrder::getPropertyId)
|
||||
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (ids.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
return properytMapper.selectBatchIds(ids).stream()
|
||||
.collect(Collectors.toMap(Properyt::getId,
|
||||
property -> property.getJinideName() == null ? "" : property.getJinideName(), (a, b) -> a));
|
||||
}
|
||||
|
||||
/** 严格校验 yyyy-MM-dd:先正则挡掉尾部垃圾字符,再 STRICT 解析(拒绝 2026-02-30 之类)。 */
|
||||
private static Date parseDate(String value, String name) {
|
||||
LocalDate date = parseLocalDate(value, name);
|
||||
return date == null ? null : Date.from(date.atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
}
|
||||
|
||||
/** 结束边界取「< 次日零点」,避免 23:59:59 写法漏掉当天末秒内的记录。 */
|
||||
private static Date parseNextDayStart(String value, String name) {
|
||||
LocalDate date = parseLocalDate(value, name);
|
||||
return date == null ? null : Date.from(date.plusDays(1).atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
}
|
||||
|
||||
private static LocalDate parseLocalDate(String value, String name) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
if (!DATE_PATTERN.matcher(trimmed).matches()) {
|
||||
throw new IllegalArgumentException(name + " 格式必须为 yyyy-MM-dd");
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(trimmed, DATE_FORMATTER);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException(name + " 格式必须为 yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatTime(Date date) {
|
||||
return TIME_FORMATTER.format(date.toInstant());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import me.iiv.agent.entity.m0.Authority;
|
||||
import me.iiv.agent.entity.m0.DistrictDistrictStrategy;
|
||||
import me.iiv.agent.entity.m0.Properyt;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m1.MallGoodsOrder;
|
||||
import me.iiv.agent.entity.m2.WarehouseAgentAuth;
|
||||
import me.iiv.agent.mapper.m0.AgentManageDistrictAuthMapper;
|
||||
import me.iiv.agent.mapper.m0.AgentMapper;
|
||||
@@ -245,6 +246,60 @@ public class PermissionService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 商城订单(m1 mall_goods_order 表)行级过滤,移植老系统 WrapperUtils.getSaleCompany
|
||||
* (xiaoqu-franchisee utils/WrapperUtils.java:541-616,propertyFlag=true, className="property_id"):
|
||||
* <ol>
|
||||
* <li>isagent!=1(总部):不加任何过滤;</li>
|
||||
* <li>销售部门非负责人(saleFlag 分支,:573-595,互斥 return):
|
||||
* property_id IN (create_id=自己 的物业 ∪ [代管授权非空时] agent_id IN (下级+代管,移除自己) 的物业),
|
||||
* 集合为空时 property_id IN (0);</li>
|
||||
* <li>其余 isagent=1:sale_company_id IN (自己+下级+代管),
|
||||
* 若存在 agent_manage_district_auth 代管物业则 AND (sale_company_id IN ids OR property_id IN 代管物业ids)。</li>
|
||||
* </ol>
|
||||
*/
|
||||
public void applyOrderRowFilter(LambdaQueryWrapper<MallGoodsOrder> wrapper, User user) {
|
||||
if (!isAgentAuthority(user.getAuthorityId())) {
|
||||
return;
|
||||
}
|
||||
if (user.getAgentId() == null) {
|
||||
throw new IllegalStateException("代理商用户未关联代理商,拒绝访问业务数据");
|
||||
}
|
||||
|
||||
List<Long> ids = new ArrayList<>(subordinateAndSelfIds(user));
|
||||
List<WarehouseAgentAuth> agentlist = managedAuths(user.getId());
|
||||
ids.addAll(agentlist.stream().map(WarehouseAgentAuth::getManagerAgentId).collect(Collectors.toList()));
|
||||
|
||||
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());
|
||||
if (!agentlist.isEmpty()) {
|
||||
ids.remove(user.getAgentId());
|
||||
if (!ids.isEmpty()) {
|
||||
propertyIds.addAll(properytMapper.selectList(new LambdaQueryWrapper<Properyt>()
|
||||
.eq(Properyt::getSoftDelete, 0)
|
||||
.in(Properyt::getAgentId, ids)
|
||||
.select(Properyt::getId))
|
||||
.stream().map(Properyt::getId).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
wrapper.in(MallGoodsOrder::getPropertyId,
|
||||
propertyIds.isEmpty() ? List.of(0L) : propertyIds);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> managedPropertyIds = managedPropertyIds(ids);
|
||||
if (!managedPropertyIds.isEmpty()) {
|
||||
wrapper.and(w -> w.in(MallGoodsOrder::getSaleCompanyId, ids)
|
||||
.or().in(MallGoodsOrder::getPropertyId, managedPropertyIds));
|
||||
} else {
|
||||
wrapper.in(MallGoodsOrder::getSaleCompanyId, ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点行权限:1:1 对齐老接口 districtlistByPage 使用的
|
||||
* getByIsAgent(..., propertyFlag=true, className="property_id")。
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
package me.iiv.agent.mcp.tool;
|
||||
|
||||
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.Properyt;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m1.MallGoodsOrder;
|
||||
import me.iiv.agent.entity.m1.MallOrderDetails;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogistics;
|
||||
import me.iiv.agent.entity.m1.MallOrderLogisticsDetai;
|
||||
import me.iiv.agent.entity.m1.MallShippingAddress;
|
||||
import me.iiv.agent.mapper.m0.AgentMapper;
|
||||
import me.iiv.agent.mapper.m0.ProperytMapper;
|
||||
import me.iiv.agent.mapper.m1.MallGoodsOrderMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderDetailsMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderLogisticsDetaiMapper;
|
||||
import me.iiv.agent.mapper.m1.MallOrderLogisticsMapper;
|
||||
import me.iiv.agent.mapper.m1.MallShippingAddressMapper;
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
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.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OrderToolsTest {
|
||||
|
||||
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);
|
||||
|
||||
@Mock
|
||||
MallGoodsOrderMapper orderMapper;
|
||||
@Mock
|
||||
MallOrderDetailsMapper orderDetailsMapper;
|
||||
@Mock
|
||||
MallShippingAddressMapper shippingAddressMapper;
|
||||
@Mock
|
||||
MallOrderLogisticsMapper logisticsMapper;
|
||||
@Mock
|
||||
MallOrderLogisticsDetaiMapper logisticsDetaiMapper;
|
||||
@Mock
|
||||
AgentMapper agentMapper;
|
||||
@Mock
|
||||
ProperytMapper properytMapper;
|
||||
@Mock
|
||||
PermissionService permissionService;
|
||||
|
||||
OrderTools tools;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, MallGoodsOrder.class);
|
||||
TableInfoHelper.initTableInfo(assistant, MallOrderDetails.class);
|
||||
TableInfoHelper.initTableInfo(assistant, MallOrderLogistics.class);
|
||||
TableInfoHelper.initTableInfo(assistant, MallOrderLogisticsDetai.class);
|
||||
tools = new OrderTools(orderMapper, orderDetailsMapper, shippingAddressMapper,
|
||||
logisticsMapper, logisticsDetaiMapper, agentMapper, properytMapper, permissionService);
|
||||
User user = new User();
|
||||
user.setId(7L);
|
||||
AuthContextHolder.set(new AuthContext(null, user));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AuthContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listTruncatesRowsAndAppliesOrderStateFilter() {
|
||||
MallGoodsOrder order = new MallGoodsOrder();
|
||||
order.setId(1L);
|
||||
order.setOrderNo("SO-001");
|
||||
order.setOrderType(1);
|
||||
order.setPropertyId(11L);
|
||||
order.setOrderState(3);
|
||||
order.setPayState(1);
|
||||
order.setWarehouseState(0);
|
||||
order.setPayMoney(new BigDecimal("99.50"));
|
||||
when(orderMapper.selectCount(any())).thenReturn(1L);
|
||||
when(orderMapper.selectList(any())).thenReturn(List.of(order));
|
||||
Properyt property = new Properyt();
|
||||
property.setId(11L);
|
||||
property.setJinideName("测试物业金蝶");
|
||||
when(properytMapper.selectBatchIds(any())).thenReturn(List.of(property));
|
||||
|
||||
Map<String, Object> result = tools.mallOrderListQuery(
|
||||
null, null, null, null, 3, null, null, null, 0, 500);
|
||||
|
||||
assertEquals(1, result.get("page"));
|
||||
assertEquals(100, result.get("rows"));
|
||||
assertEquals("rows 超过上限 100,已截断为 100", result.get("notice"));
|
||||
assertEquals(1L, result.get("total"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> record = ((List<Map<String, Object>>) result.get("records")).get(0);
|
||||
assertEquals("SO-001", record.get("orderNo"));
|
||||
assertEquals(3, record.get("orderState"));
|
||||
assertEquals("测试物业金蝶", record.get("customerName"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<MallGoodsOrder>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(orderMapper).selectList(captor.capture());
|
||||
String where = captor.getValue().getSqlSegment();
|
||||
assertTrue(where.contains("soft_delete"));
|
||||
assertTrue(where.contains("order_state"));
|
||||
verify(permissionService).applyOrderRowFilter(any(), any());
|
||||
verifyNoInteractions(agentMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailReturnsNullRecordWithNoticeWhenNotVisible() {
|
||||
when(orderMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
Map<String, Object> result = tools.mallOrderDetailQuery("SO-404");
|
||||
|
||||
assertNull(result.get("record"));
|
||||
assertEquals(List.of(), result.get("items"));
|
||||
assertEquals("orderNo=SO-404 不存在或当前身份无权查看", result.get("notice"));
|
||||
verify(permissionService).applyOrderRowFilter(any(), any());
|
||||
verifyNoInteractions(orderDetailsMapper, shippingAddressMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailMergesShippingAddressAndItems() {
|
||||
MallGoodsOrder order = new MallGoodsOrder();
|
||||
order.setId(1L);
|
||||
order.setOrderNo("SO-100");
|
||||
order.setOrderType(1);
|
||||
order.setAddressId(5L);
|
||||
order.setOrderTime(new Date(1700000000000L));
|
||||
when(orderMapper.selectList(any())).thenReturn(List.of(order));
|
||||
MallShippingAddress address = new MallShippingAddress();
|
||||
address.setId(5L);
|
||||
address.setReveiveName("张三");
|
||||
address.setReveiveNumber("13800000000");
|
||||
address.setProvince("广东省");
|
||||
address.setCity("深圳市");
|
||||
address.setCountyseat("南山区");
|
||||
address.setAddress("科技园 1 栋");
|
||||
when(shippingAddressMapper.selectById(5L)).thenReturn(address);
|
||||
MallOrderDetails detail = new MallOrderDetails();
|
||||
detail.setId(9L);
|
||||
detail.setSpuNo("SPU-1");
|
||||
detail.setSpuName("大卷纸");
|
||||
detail.setSkuNo("SKU-1");
|
||||
detail.setCount(2);
|
||||
detail.setPrice(new BigDecimal("10.00"));
|
||||
detail.setPayMoney(new BigDecimal("20.00"));
|
||||
detail.setPayState(1);
|
||||
detail.setWarehouseState(0);
|
||||
when(orderDetailsMapper.selectList(any())).thenReturn(List.of(detail));
|
||||
|
||||
Map<String, Object> result = tools.mallOrderDetailQuery("SO-100");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> record = (Map<String, Object>) result.get("record");
|
||||
assertEquals("SO-100", record.get("orderNo"));
|
||||
assertEquals("张三", record.get("reveiveName"));
|
||||
assertEquals("13800000000", record.get("reveiveNumber"));
|
||||
assertEquals("南山区", record.get("countyseat"));
|
||||
assertEquals("科技园 1 栋", record.get("address"));
|
||||
assertEquals(TIME_FORMATTER.format(new Date(1700000000000L).toInstant()),
|
||||
record.get("orderTime"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> items = (List<Map<String, Object>>) result.get("items");
|
||||
assertEquals(1, items.size());
|
||||
assertEquals("SPU-1", items.get(0).get("spuNo"));
|
||||
assertEquals(2, items.get(0).get("count"));
|
||||
verify(permissionService).applyOrderRowFilter(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void logisticsReturnsNullLogisticsWhenNoRecord() {
|
||||
MallGoodsOrder order = new MallGoodsOrder();
|
||||
order.setId(1L);
|
||||
order.setOrderNo("SO-200");
|
||||
when(orderMapper.selectList(any())).thenReturn(List.of(order));
|
||||
when(logisticsMapper.selectList(any())).thenReturn(List.of());
|
||||
when(logisticsDetaiMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
Map<String, Object> result = tools.mallOrderLogisticsQuery("SO-200");
|
||||
|
||||
assertNull(result.get("logistics"));
|
||||
assertEquals(List.of(), result.get("tracks"));
|
||||
verify(permissionService).applyOrderRowFilter(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void logisticsTracksQueriedByOrderNoAsc() {
|
||||
MallGoodsOrder order = new MallGoodsOrder();
|
||||
order.setId(1L);
|
||||
order.setOrderNo("SO-300");
|
||||
when(orderMapper.selectList(any())).thenReturn(List.of(order));
|
||||
MallOrderLogistics logistics = new MallOrderLogistics();
|
||||
logistics.setId(3L);
|
||||
logistics.setOrderNo("SO-300");
|
||||
logistics.setLogisticsName("顺丰速运");
|
||||
logistics.setLogisticsOrderNo("SF-123");
|
||||
logistics.setSendMode(0);
|
||||
logistics.setState(4);
|
||||
logistics.setModifyTime(new Date(1700000100000L));
|
||||
when(logisticsMapper.selectList(any())).thenReturn(List.of(logistics));
|
||||
// 老系统语义:轨迹表 logistics_order_no 存的是订单号而非快递单号
|
||||
MallOrderLogisticsDetai track = new MallOrderLogisticsDetai();
|
||||
track.setId(8L);
|
||||
track.setLogisticsOrderNo("SO-300");
|
||||
track.setMemo("包裹已到达深圳转运中心");
|
||||
track.setState(4);
|
||||
track.setCreateTime(new Date(1700000200000L));
|
||||
when(logisticsDetaiMapper.selectList(any())).thenReturn(List.of(track));
|
||||
|
||||
Map<String, Object> result = tools.mallOrderLogisticsQuery("SO-300");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> logisticsMap = (Map<String, Object>) result.get("logistics");
|
||||
assertEquals("顺丰速运", logisticsMap.get("logisticsName"));
|
||||
assertEquals("SF-123", logisticsMap.get("logisticsOrderNo"));
|
||||
assertEquals(4, logisticsMap.get("state"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> tracks = (List<Map<String, Object>>) result.get("tracks");
|
||||
assertEquals(1, tracks.size());
|
||||
assertEquals("包裹已到达深圳转运中心", tracks.get(0).get("memo"));
|
||||
assertEquals(TIME_FORMATTER.format(new Date(1700000200000L).toInstant()),
|
||||
tracks.get(0).get("createTime"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<MallOrderLogisticsDetai>> captor =
|
||||
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(logisticsDetaiMapper).selectList(captor.capture());
|
||||
LambdaQueryWrapper<MallOrderLogisticsDetai> wrapper = captor.getValue();
|
||||
assertTrue(wrapper.getSqlSegment().contains("logistics_order_no"));
|
||||
String orderBy = wrapper.getExpression().getOrderBy().getSqlSegment();
|
||||
assertTrue(orderBy.contains("create_time ASC"));
|
||||
assertTrue(orderBy.contains("id ASC"));
|
||||
verify(permissionService).applyOrderRowFilter(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAppliesDateRangeInShanghaiZoneWithExclusiveEnd() {
|
||||
when(orderMapper.selectCount(any())).thenReturn(0L);
|
||||
when(orderMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
Map<String, Object> result = tools.mallOrderListQuery(
|
||||
null, null, null, null, null, null, "2026-09-01", "2026-09-09", null, null);
|
||||
|
||||
assertEquals(0L, result.get("total"));
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<MallGoodsOrder>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(orderMapper).selectList(captor.capture());
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = captor.getValue();
|
||||
String where = wrapper.getSqlSegment();
|
||||
assertTrue(where.contains("order_time >="));
|
||||
assertTrue(where.contains("order_time <"));
|
||||
Date expectedStart = Date.from(LocalDate.of(2026, 9, 1).atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
// 结束边界为「< 次日零点」(Asia/Shanghai),而非 <= 当天 23:59:59
|
||||
Date expectedEndExclusive = Date.from(LocalDate.of(2026, 9, 10).atStartOfDay(BUSINESS_ZONE).toInstant());
|
||||
assertTrue(wrapper.getParamNameValuePairs().values().contains(expectedStart));
|
||||
assertTrue(wrapper.getParamNameValuePairs().values().contains(expectedEndExclusive));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRejectsDateParamWithTrailingJunk() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
|
||||
tools.mallOrderListQuery(null, null, null, null, null, null, "2026-09-09junk", null, null, null));
|
||||
assertEquals("startOrderTime 格式必须为 yyyy-MM-dd", ex.getMessage());
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
tools.mallOrderListQuery(null, null, null, null, null, null, null, "2026-9-9", null, null));
|
||||
verifyNoInteractions(orderMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listRejectsNonexistentCalendarDate() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
|
||||
tools.mallOrderListQuery(null, null, null, null, null, null, null, "2026-02-30", null, null));
|
||||
assertEquals("endOrderTime 格式必须为 yyyy-MM-dd", ex.getMessage());
|
||||
verifyNoInteractions(orderMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailAndLogisticsRequireOrderNo() {
|
||||
assertThrows(IllegalArgumentException.class, () -> tools.mallOrderDetailQuery(null));
|
||||
assertThrows(IllegalArgumentException.class, () -> tools.mallOrderDetailQuery(" "));
|
||||
assertThrows(IllegalArgumentException.class, () -> tools.mallOrderLogisticsQuery(null));
|
||||
assertThrows(IllegalArgumentException.class, () -> tools.mallOrderLogisticsQuery(" "));
|
||||
verifyNoInteractions(orderMapper);
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
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.Agent;
|
||||
import me.iiv.agent.entity.m0.AgentManageDistrictAuth;
|
||||
import me.iiv.agent.entity.m0.Authority;
|
||||
import me.iiv.agent.entity.m0.Properyt;
|
||||
import me.iiv.agent.entity.m0.User;
|
||||
import me.iiv.agent.entity.m1.MallGoodsOrder;
|
||||
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.HashOperations;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
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.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/** PermissionService.applyOrderRowFilter 直接单测:商城订单(mall_goods_order)行级权限各分支。 */
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PermissionServiceOrderRowFilterTest {
|
||||
|
||||
@Mock
|
||||
AuthorityMapper authorityMapper;
|
||||
@Mock
|
||||
AgentMapper agentMapper;
|
||||
@Mock
|
||||
WarehouseAgentAuthMapper warehouseAgentAuthMapper;
|
||||
@Mock
|
||||
AgentManageDistrictAuthMapper agentManageDistrictAuthMapper;
|
||||
@Mock
|
||||
ProperytMapper properytMapper;
|
||||
@Mock
|
||||
StringRedisTemplate redisTemplate;
|
||||
@Mock
|
||||
HashOperations<String, Object, Object> hashOperations;
|
||||
|
||||
PermissionService permissionService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
MallGoodsOrder.class);
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
Properyt.class);
|
||||
permissionService = new PermissionService(authorityMapper, agentMapper,
|
||||
warehouseAgentAuthMapper, agentManageDistrictAuthMapper, properytMapper, redisTemplate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void headquartersOrderFilterAddsNoBoundary() {
|
||||
User user = user(46L, 1L, null);
|
||||
when(authorityMapper.selectById(1L)).thenReturn(authority(1L, 0));
|
||||
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
|
||||
assertTrue(wrapper.getSqlSegment().isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
void agentOrderFilterUsesSelfSubordinateAndManagedSaleCompanies() {
|
||||
User user = user(1000L, 108L, 1L);
|
||||
when(authorityMapper.selectById(108L)).thenReturn(authority(108L, 1));
|
||||
Agent subordinate = new Agent();
|
||||
subordinate.setId(2L);
|
||||
when(agentMapper.selectList(any())).thenReturn(List.of(subordinate));
|
||||
WarehouseAgentAuth managedAuth = new WarehouseAgentAuth();
|
||||
managedAuth.setManagerAgentId(3L);
|
||||
when(warehouseAgentAuthMapper.selectList(any())).thenReturn(List.of(managedAuth));
|
||||
when(agentManageDistrictAuthMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
|
||||
String sql = wrapper.getSqlSegment();
|
||||
Collection<Object> values = wrapper.getParamNameValuePairs().values();
|
||||
assertTrue(sql.contains("sale_company_id IN"));
|
||||
assertFalse(sql.contains("property_id"));
|
||||
assertTrue(values.contains(1L));
|
||||
assertTrue(values.contains(2L));
|
||||
assertTrue(values.contains(3L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void agentOrderFilterAddsManagedPropertyOrBranch() {
|
||||
User user = user(1000L, 108L, 1L);
|
||||
when(authorityMapper.selectById(108L)).thenReturn(authority(108L, 1));
|
||||
when(agentMapper.selectList(any())).thenReturn(List.of());
|
||||
when(warehouseAgentAuthMapper.selectList(any())).thenReturn(List.of());
|
||||
AgentManageDistrictAuth districtAuth = new AgentManageDistrictAuth();
|
||||
districtAuth.setPropertyId(44L);
|
||||
when(agentManageDistrictAuthMapper.selectList(any())).thenReturn(List.of(districtAuth));
|
||||
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
|
||||
String sql = wrapper.getSqlSegment();
|
||||
Collection<Object> values = wrapper.getParamNameValuePairs().values();
|
||||
assertTrue(sql.contains("sale_company_id IN"));
|
||||
assertTrue(sql.contains("property_id IN"));
|
||||
assertTrue(sql.contains("OR"));
|
||||
assertTrue(values.contains(1L));
|
||||
assertTrue(values.contains(44L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void salesNonLeaderOrderFilterUsesOwnCreatedPropertyIds() {
|
||||
User user = user(1000L, 108L, 1L);
|
||||
user.setDeptId(5L);
|
||||
user.setPostId(7L);
|
||||
when(authorityMapper.selectById(108L)).thenReturn(authority(108L, 1));
|
||||
when(agentMapper.selectList(any())).thenReturn(List.of());
|
||||
when(warehouseAgentAuthMapper.selectList(any())).thenReturn(List.of());
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
when(hashOperations.get("Dictionary", "authDeptId")).thenReturn(",5,");
|
||||
when(hashOperations.get("Dictionary", "authDeptPostId")).thenReturn("8,");
|
||||
Properyt property = new Properyt();
|
||||
property.setId(55L);
|
||||
when(properytMapper.selectList(any())).thenReturn(List.of(property));
|
||||
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
|
||||
String sql = wrapper.getSqlSegment();
|
||||
assertTrue(sql.contains("property_id IN"));
|
||||
assertFalse(sql.contains("sale_company_id"));
|
||||
assertTrue(wrapper.getParamNameValuePairs().values().contains(55L));
|
||||
verifyNoInteractions(agentManageDistrictAuthMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void salesNonLeaderWithoutVisiblePropertiesFiltersToZero() {
|
||||
User user = user(1000L, 108L, 1L);
|
||||
user.setDeptId(5L);
|
||||
user.setPostId(7L);
|
||||
when(authorityMapper.selectById(108L)).thenReturn(authority(108L, 1));
|
||||
when(agentMapper.selectList(any())).thenReturn(List.of());
|
||||
when(warehouseAgentAuthMapper.selectList(any())).thenReturn(List.of());
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
when(hashOperations.get("Dictionary", "authDeptId")).thenReturn(",5,");
|
||||
when(hashOperations.get("Dictionary", "authDeptPostId")).thenReturn("8,");
|
||||
when(properytMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
LambdaQueryWrapper<MallGoodsOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
permissionService.applyOrderRowFilter(wrapper, user);
|
||||
|
||||
String sql = wrapper.getSqlSegment();
|
||||
assertTrue(sql.contains("property_id IN"));
|
||||
assertFalse(sql.contains("sale_company_id"));
|
||||
assertTrue(wrapper.getParamNameValuePairs().values().contains(0L));
|
||||
verifyNoInteractions(agentManageDistrictAuthMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void agentUserWithoutAgentIdIsDeniedForOrderFilter() {
|
||||
User user = user(1000L, 108L, null);
|
||||
when(authorityMapper.selectById(108L)).thenReturn(authority(108L, 1));
|
||||
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> permissionService.applyOrderRowFilter(new LambdaQueryWrapper<>(), user));
|
||||
}
|
||||
|
||||
private static Authority authority(Long id, int isAgent) {
|
||||
Authority authority = new Authority();
|
||||
authority.setId(id);
|
||||
authority.setIsagent(isAgent);
|
||||
authority.setSoftDelete(0);
|
||||
return authority;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user