Merge pull request 'feat(mcp): 新增合同查询工具 contract.list/detail.query (REQ-20260911-0002)' (#8) from feat/REQ-20260911-0002-contract-tools into main

This commit was merged in pull request #8.
This commit is contained in:
2026-09-11 10:22:34 +08:00
12 changed files with 1248 additions and 1 deletions
@@ -335,6 +335,64 @@
---
## 12. contract.list.query
分页查询合同列表,对应老系统 `/contract/listByPage` 合同列表页(权限库 `webpage` id=394 `contractList`,页面字段定义 `ContractListOutput`)。
- 分类:customer | 类型:读
- 数据来源:m2 库 `contract_new` 表,固定 `soft_delete = 0`、`id DESC`
- 行级权限(`PermissionService.applyContractRowFilter`,与站点/出纸机同一 `getByIsAgent(propertyFlag=true)` 变体,agent 维度落 `contract_agent_id`,property 维度落 `property_id`):
- 总部(isagent=0):全部;
- 代理商(isagent=1):`contract_agent_id IN (自己 + 直属下级 + warehouse_agent_auth 代管代理)`,有代管物业授权时再 `OR property_id IN (agent_manage_district_auth 代管物业)`;
- 销售部门非负责人:`property_id IN (自己创建的物业 + 代管代理名下物业)`,与上一条互斥。
- 富化(按页批量 IN 查询,消除 N+1):`contractAgentName`/`customerAgentName` 查 m0 `agent`,`createName` 查 m0 `user`;`saleName`/`propertyName`/`customerName`/`customerNo` 为合同表冗余快照,直接返回。
**入参**:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| contractNo | string | 否 | 合同编号,模糊匹配 |
| contractName | string | 否 | 合同名称,模糊匹配 |
| propertyName | string | 否 | 物业名称,模糊匹配 |
| customerName | string | 否 | 客户名称(金蝶客户名称快照),模糊匹配 |
| customerNo | string | 否 | 客户编号(=物业 serialnumber),精确匹配 |
| contractAgentId | number | 否 | 合同所属公司 id,精确匹配 |
| customerAgentId | number | 否 | 客户所属公司 id,精确匹配 |
| propertyId | number | 否 | 所属物业 id,精确匹配 |
| contractStatus | number | 否 | 合同状态:0=待审 1=驳回 2=正常 3=过期 4=终止 |
| expireRemind | number | 否 | 到期提醒开关:0=关 1=开 |
| endDateFrom | string | 否 | 合同结束日期起,格式 yyyy-MM-dd(查即将到期合同用) |
| endDateTo | string | 否 | 合同结束日期止,格式 yyyy-MM-dd(含当天) |
| page | number | 否 | 页码,从 1 开始,默认 1 |
| rows | number | 否 | 每页条数,默认 20,上限 100(超出截断为 100 并在 notice 提示) |
**出参 records 字段**:`contract_new` 主要字段(id、contractNo、contractName、contractAgentId/customerAgentId、propertyId/propertyName、customerNo/customerName、amount、contact、startDate/endDate(`yyyy-MM-dd`)、expireRemind/remindDays/remindTime/reminded、saleId/saleName、contractStatus、stopReasonType/stopReason、createId、createTime)+ 富化字段 `contractAgentName`/`customerAgentName`/`createName` + 状态文案 `contractStatusText`。
---
## 13. contract.detail.query
按 id 或 contractNo 查询合同详情(二选一必填),行级权限与 `contract.list.query` 一致。
- 分类:customer | 类型:读
- 数据来源:m2 库 `contract_new` + `contract_file`(附件)+ `contract_process`(流程/待办/操作记录)
- 合同不存在或无权查看时 `record=null` 并在 notice 说明(两者不区分,不泄露合同是否存在);id/contractNo 都未传时 notice 提示"必须传一个"。
**入参**:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 条件必填 | 合同 id;与 contractNo 二选一 |
| contractNo | string | 条件必填 | 合同编号,精确匹配;与 id 二选一 |
**出参**:
- `record`:与列表同一套字段(含富化和 `contractStatusText`)。
- `files`:合同附件列表,`id`/`fileName`/`fileUrl`/`createTime`,按 id 正序。
- `processes`:审批流程记录,按 id 正序,字段 `nodeType`(0=创建 1=部门内审核 2=层级审核 3=终止 4=抄送)、`auditLevel`、`operationType`(0=创建 1=审核通过 2=审核驳回 3=终止 4=修改再提交 5=抄送)、`userId`/`userName`、`scheduleState`(0=无需待办 1=待办 2=已办)、`remark`、`operateTime`,均附 `nodeTypeText`/`operationTypeText`/`scheduleStateText` 文案。
---
## 附录:在线查看工具清单
```bash
+2 -1
View File
@@ -16,9 +16,10 @@ 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
-- / contract.list.query / contract.detail.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', 'mall.order.list.query', 'mall.order.detail.query', 'mall.order.logistics.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', 'contract.list.query', 'contract.detail.query')
AND NOT EXISTS (SELECT 1 FROM scope_tool st WHERE st.scope_id = s.id AND st.tool_id = t.id);
-- 作用域:hardware(出纸机查询)
@@ -0,0 +1,71 @@
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;
/**
* <p>
* 合同附件
* </p>
*
* @author codegen
* @since 2026-09-11
*/
@Getter
@Setter
@ToString
@TableName("contract_file")
public class ContractFile 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:正常;1:删除
*/
private Integer softDelete;
/**
* 合同ID(0=上传后未提交的孤儿附件,定时清理)
*/
private Long contractId;
/**
* 原始文件名
*/
private String fileName;
/**
* 文件访问地址
*/
private String fileUrl;
}
@@ -0,0 +1,162 @@
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.math.BigDecimal;
import java.util.Date;
/**
* <p>
* 合同主表
* </p>
*
* @author codegen
* @since 2026-09-11
*/
@Getter
@Setter
@ToString
@TableName("contract_new")
public class ContractNew 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:正常;1:删除
*/
private Integer softDelete;
/**
* 合同编号(properyt.serialnumber + "C" + 三位流水,如452542C001)
*/
private String contractNo;
/**
* 合同名称
*/
private String contractName;
/**
* 合同所属公司(=物业所属销售所属公司,审批流按此公司匹配)
*/
private Long contractAgentId;
/**
* 客户所属公司(=创建人提交时选择的所属公司)
*/
private Long customerAgentId;
/**
* 所属物业ID(关联权限库properyt)
*/
private Long propertyId;
/**
* 物业名称(冗余快照)
*/
private String propertyName;
/**
* 客户编号快照(=properyt.serialnumber,编号流水/按客户归集用)
*/
private String customerNo;
/**
* 客户名称快照(=properyt.jinide_name 金蝶客户名称)
*/
private String customerName;
/**
* 合同金额(元)
*/
private BigDecimal amount;
/**
* 联系人
*/
private String contact;
/**
* 合同开始日期
*/
private Date startDate;
/**
* 合同结束日期
*/
private Date endDate;
/**
* 到期提醒开关0:关;1:开
*/
private Integer expireRemind;
/**
* 到期提醒天数
*/
private Integer remindDays;
/**
* 到期提醒时间(=end_date-remind_days 当天09:30:00)
*/
private Date remindTime;
/**
* 到期提醒是否已发送0:未发送;1:已发送
*/
private Integer reminded;
/**
* 所属销售ID(=物业所属销售)
*/
private Long saleId;
/**
* 所属销售姓名(冗余快照)
*/
private String saleName;
/**
* 合同状态0:待审;1:驳回;2:正常;3:过期;4:终止
*/
private Integer contractStatus;
/**
* 终止原因分类(cancel_reason.content快照,type=6)
*/
private String stopReasonType;
/**
* 终止详细原因
*/
private String stopReason;
}
@@ -0,0 +1,106 @@
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;
/**
* <p>
* 合同流程/待办/操作记录表
* </p>
*
* @author codegen
* @since 2026-09-11
*/
@Getter
@Setter
@ToString
@TableName("contract_process")
public class ContractProcess 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:正常;1:删除
*/
private Integer softDelete;
/**
* 合同ID
*/
private Long contractId;
/**
* 合同编号(冗余)
*/
private String contractNo;
/**
* 节点类型0:创建;1:部门内审核;2:层级审核;3:终止;4:抄送
*/
private Integer nodeType;
/**
* 审核层级(层级审核为第几级,其余0)
*/
private Integer auditLevel;
/**
* 操作类型0:创建;1:审核通过;2:审核驳回;3:终止;4:修改再提交;5:抄送
*/
private Integer operationType;
/**
* 待办人/操作人ID
*/
private Long userId;
/**
* 待办人/操作人姓名
*/
private String userName;
/**
* 待办状态0:无需待办;1:待办;2:已办
*/
private Integer scheduleState;
/**
* 备注/驳回原因/终止原因/抄送文案
*/
private String remark;
/**
* 办理时间(待办未办为NULL)
*/
private Date operateTime;
}
@@ -0,0 +1,16 @@
package me.iiv.agent.mapper.m2;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import me.iiv.agent.entity.m2.ContractFile;
/**
* <p>
* 合同附件 Mapper 接口
* </p>
*
* @author codegen
* @since 2026-09-11
*/
public interface ContractFileMapper extends BaseMapper<ContractFile> {
}
@@ -0,0 +1,16 @@
package me.iiv.agent.mapper.m2;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import me.iiv.agent.entity.m2.ContractNew;
/**
* <p>
* 合同主表 Mapper 接口
* </p>
*
* @author codegen
* @since 2026-09-11
*/
public interface ContractNewMapper extends BaseMapper<ContractNew> {
}
@@ -0,0 +1,16 @@
package me.iiv.agent.mapper.m2;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import me.iiv.agent.entity.m2.ContractProcess;
/**
* <p>
* 合同流程/待办/操作记录 Mapper 接口
* </p>
*
* @author codegen
* @since 2026-09-11
*/
public interface ContractProcessMapper extends BaseMapper<ContractProcess> {
}
@@ -0,0 +1,321 @@
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.User;
import me.iiv.agent.entity.m2.ContractFile;
import me.iiv.agent.entity.m2.ContractNew;
import me.iiv.agent.entity.m2.ContractProcess;
import me.iiv.agent.mapper.m0.AgentMapper;
import me.iiv.agent.mapper.m0.UserMapper;
import me.iiv.agent.mapper.m2.ContractFileMapper;
import me.iiv.agent.mapper.m2.ContractNewMapper;
import me.iiv.agent.mapper.m2.ContractProcessMapper;
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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 合同域工具:contract.list.query / contract.detail.query(对应老系统 /contract/listByPage
* 合同列表页 web_id=394,数据在 m2 库 contract_new/contract_file/contract_process,
* 行权限走 PermissionService.applyContractRowFilter)。
*/
@Component
public class ContractTools implements ToolObject {
private static final int DEFAULT_ROWS = 20;
private static final int MAX_ROWS = 100;
private static final Map<Integer, String> CONTRACT_STATUS_TEXT = Map.of(
0, "待审", 1, "驳回", 2, "正常", 3, "过期", 4, "终止");
private static final Map<Integer, String> NODE_TYPE_TEXT = Map.of(
0, "创建", 1, "部门内审核", 2, "层级审核", 3, "终止", 4, "抄送");
private static final Map<Integer, String> OPERATION_TYPE_TEXT = Map.of(
0, "创建", 1, "审核通过", 2, "审核驳回", 3, "终止", 4, "修改再提交", 5, "抄送");
private static final Map<Integer, String> SCHEDULE_STATE_TEXT = Map.of(
0, "无需待办", 1, "待办", 2, "已办");
final ContractNewMapper contractNewMapper;
final ContractFileMapper contractFileMapper;
final ContractProcessMapper contractProcessMapper;
final AgentMapper agentMapper;
final UserMapper userMapper;
final PermissionService permissionService;
public ContractTools(ContractNewMapper contractNewMapper, ContractFileMapper contractFileMapper,
ContractProcessMapper contractProcessMapper, AgentMapper agentMapper,
UserMapper userMapper, PermissionService permissionService) {
this.contractNewMapper = contractNewMapper;
this.contractFileMapper = contractFileMapper;
this.contractProcessMapper = contractProcessMapper;
this.agentMapper = agentMapper;
this.userMapper = userMapper;
this.permissionService = permissionService;
}
@XqTool(category = "customer", kind = "read")
@Tool(name = "contract.list.query", description = "查询合同列表(分页),对应老系统 /contract/listByPage 合同列表页,"
+ "数据来源 m2 库 contract_new 表。"
+ "行级权限:代理商身份(权限组 isagent=1)只能看到 自己+直属下级代理+被授权代管代理 作为合同所属公司的合同,"
+ "以及被授权代管物业名下的合同;销售部门非负责人只能看到 自己创建的物业 及 被授权代管代理名下物业 的合同;"
+ "总部身份(isagent=0)可看全部。"
+ "分页:page 从 1 开始,rows 默认 20、上限 100,超出自动截断为 100 并在返回的 notice 字段提示。"
+ "contractStatus 含义:0=待审 1=驳回 2=正常 3=过期 4=终止;expireRemind 含义:0=关 1=开。"
+ "返回 total(符合条件的总数)、records(本页记录,按 id 倒序,含 contractStatusText 状态文案)。")
public Map<String, Object> contractListQuery(
@ToolParam(required = false, description = "合同编号,模糊匹配") String contractNo,
@ToolParam(required = false, description = "合同名称,模糊匹配") String contractName,
@ToolParam(required = false, description = "物业名称,模糊匹配") String propertyName,
@ToolParam(required = false, description = "客户名称(金蝶客户名称快照),模糊匹配") String customerName,
@ToolParam(required = false, description = "客户编号(=物业 serialnumber),精确匹配") String customerNo,
@ToolParam(required = false, description = "合同所属公司 id,精确匹配") Long contractAgentId,
@ToolParam(required = false, description = "客户所属公司 id,精确匹配") Long customerAgentId,
@ToolParam(required = false, description = "所属物业 id,精确匹配") Long propertyId,
@ToolParam(required = false, description = "合同状态:0=待审 1=驳回 2=正常 3=过期 4=终止") Integer contractStatus,
@ToolParam(required = false, description = "到期提醒开关:0=关 1=开") Integer expireRemind,
@ToolParam(required = false, description = "合同结束日期起,格式 yyyy-MM-dd(查即将到期合同用)") String endDateFrom,
@ToolParam(required = false, description = "合同结束日期止,格式 yyyy-MM-dd(含当天)") String endDateTo,
@ToolParam(required = false, description = "页码,从 1 开始,默认 1") Integer page,
@ToolParam(required = false, description = "每页条数,默认 20,上限 100(超出截断为 100)") Integer rows) {
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<ContractNew> wrapper = new LambdaQueryWrapper<ContractNew>()
.eq(ContractNew::getSoftDelete, 0);
if (StringUtils.hasText(contractNo)) {
wrapper.like(ContractNew::getContractNo, contractNo);
}
if (StringUtils.hasText(contractName)) {
wrapper.like(ContractNew::getContractName, contractName);
}
if (StringUtils.hasText(propertyName)) {
wrapper.like(ContractNew::getPropertyName, propertyName);
}
if (StringUtils.hasText(customerName)) {
wrapper.like(ContractNew::getCustomerName, customerName);
}
if (StringUtils.hasText(customerNo)) {
wrapper.eq(ContractNew::getCustomerNo, customerNo);
}
if (contractAgentId != null) {
wrapper.eq(ContractNew::getContractAgentId, contractAgentId);
}
if (customerAgentId != null) {
wrapper.eq(ContractNew::getCustomerAgentId, customerAgentId);
}
if (propertyId != null) {
wrapper.eq(ContractNew::getPropertyId, propertyId);
}
if (contractStatus != null) {
wrapper.eq(ContractNew::getContractStatus, contractStatus);
}
if (expireRemind != null) {
wrapper.eq(ContractNew::getExpireRemind, expireRemind);
}
if (StringUtils.hasText(endDateFrom)) {
wrapper.ge(ContractNew::getEndDate, endDateFrom);
}
if (StringUtils.hasText(endDateTo)) {
wrapper.le(ContractNew::getEndDate, endDateTo);
}
// 行级权限(getByIsAgent propertyFlag 变体,统一走 PermissionService)
permissionService.applyContractRowFilter(wrapper, user);
Long total = contractNewMapper.selectCount(wrapper);
wrapper.orderByDesc(ContractNew::getId)
.last("LIMIT " + (long) (pageNo - 1) * pageSize + "," + pageSize);
List<ContractNew> contracts = contractNewMapper.selectList(wrapper);
// 合同所属公司 / 客户所属公司 / 创建人 名称批量富化(一次 IN 查询,消除 N+1)
List<Long> agentIds = contracts.stream()
.flatMap(c -> Stream.of(c.getContractAgentId(), c.getCustomerAgentId()))
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
Map<Long, String> agentNames = agentIds.isEmpty()
? Map.of()
: agentMapper.selectBatchIds(agentIds).stream()
.collect(Collectors.toMap(Agent::getId, Agent::getName, (a, b) -> a));
List<Long> creatorIds = contracts.stream().map(ContractNew::getCreateId)
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
Map<Long, String> creatorNames = creatorIds.isEmpty()
? Map.of()
: userMapper.selectBatchIds(creatorIds).stream()
.collect(Collectors.toMap(User::getId, User::getName, (a, b) -> a));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
List<Map<String, Object>> records = new ArrayList<>();
for (ContractNew contract : contracts) {
records.add(toRecord(contract, agentNames, creatorNames, sdf, df));
}
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;
}
/**
* 合同详情:行权限与 contract.list.query 一致 + 精确 id/contractNo 叠加。
* files 来自 m2.contract_file,processes 来自 m2.contract_process(按 id 正序)。
*/
@XqTool(category = "customer", kind = "read")
@Tool(name = "contract.detail.query", description = "按 id 或 contractNo 查询合同详情(二选一必填),数据来源 m2 库 contract_new 表。"
+ "行级权限与 contract.list.query 一致;合同不存在或当前身份无权查看时 record 为 null 并在 notice 字段说明(两者不区分)。"
+ "返回 record(合同全字段,含终止原因和状态文案 contractStatusText)、"
+ "files(合同附件列表,含 fileName/fileUrl)、"
+ "processes(审批流程/待办/操作记录,按 id 正序;nodeType 含义:0=创建 1=部门内审核 2=层级审核 3=终止 4=抄送;"
+ "operationType 含义:0=创建 1=审核通过 2=审核驳回 3=终止 4=修改再提交 5=抄送;"
+ "scheduleState 含义:0=无需待办 1=待办 2=已办,均附对应文案字段)。")
public Map<String, Object> contractDetailQuery(
@ToolParam(required = false, description = "合同 id;与 contractNo 二选一必填") Long id,
@ToolParam(required = false, description = "合同编号,精确匹配;与 id 二选一必填") String contractNo) {
User user = AuthContextHolder.get().getUser();
Map<String, Object> result = new LinkedHashMap<>();
if (id == null && !StringUtils.hasText(contractNo)) {
result.put("record", null);
result.put("files", List.of());
result.put("processes", List.of());
result.put("notice", "id 和 contractNo 必须传一个");
return result;
}
LambdaQueryWrapper<ContractNew> wrapper = new LambdaQueryWrapper<ContractNew>()
.eq(ContractNew::getSoftDelete, 0);
if (id != null) {
wrapper.eq(ContractNew::getId, id);
} else {
wrapper.eq(ContractNew::getContractNo, contractNo);
}
permissionService.applyContractRowFilter(wrapper, user);
ContractNew contract = contractNewMapper.selectOne(wrapper);
if (contract == null) {
result.put("record", null);
result.put("files", List.of());
result.put("processes", List.of());
result.put("notice", (id != null ? "id=" + id : "contractNo=" + contractNo) + " 不存在或当前身份无权查看");
return result;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Map<Long, String> agentNames = agentNamesOf(contract);
String createName = null;
if (contract.getCreateId() != null) {
User creator = userMapper.selectById(contract.getCreateId());
createName = creator == null ? null : creator.getName();
}
result.put("record", toRecord(contract, agentNames,
Map.of(contract.getCreateId() == null ? 0L : contract.getCreateId(),
createName == null ? "未查询到" : createName),
sdf, df));
List<ContractFile> files = contractFileMapper.selectList(new LambdaQueryWrapper<ContractFile>()
.eq(ContractFile::getSoftDelete, 0)
.eq(ContractFile::getContractId, contract.getId())
.orderByAsc(ContractFile::getId));
List<Map<String, Object>> fileList = new ArrayList<>();
for (ContractFile file : files) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("id", file.getId());
item.put("fileName", file.getFileName());
item.put("fileUrl", file.getFileUrl());
item.put("createTime", file.getCreateTime() == null ? null : sdf.format(file.getCreateTime()));
fileList.add(item);
}
result.put("files", fileList);
List<ContractProcess> processes = contractProcessMapper.selectList(new LambdaQueryWrapper<ContractProcess>()
.eq(ContractProcess::getSoftDelete, 0)
.eq(ContractProcess::getContractId, contract.getId())
.orderByAsc(ContractProcess::getId));
List<Map<String, Object>> processList = new ArrayList<>();
for (ContractProcess process : processes) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("id", process.getId());
item.put("nodeType", process.getNodeType());
item.put("nodeTypeText", NODE_TYPE_TEXT.getOrDefault(process.getNodeType(), "未知"));
item.put("auditLevel", process.getAuditLevel());
item.put("operationType", process.getOperationType());
item.put("operationTypeText", OPERATION_TYPE_TEXT.getOrDefault(process.getOperationType(), "未知"));
item.put("userId", process.getUserId());
item.put("userName", process.getUserName());
item.put("scheduleState", process.getScheduleState());
item.put("scheduleStateText", SCHEDULE_STATE_TEXT.getOrDefault(process.getScheduleState(), "未知"));
item.put("remark", process.getRemark());
item.put("operateTime", process.getOperateTime() == null ? null : sdf.format(process.getOperateTime()));
processList.add(item);
}
result.put("processes", processList);
return result;
}
private Map<String, Object> toRecord(ContractNew contract, Map<Long, String> agentNames,
Map<Long, String> creatorNames,
SimpleDateFormat sdf, SimpleDateFormat df) {
Map<String, Object> record = new LinkedHashMap<>();
record.put("id", contract.getId());
record.put("contractNo", contract.getContractNo());
record.put("contractName", contract.getContractName());
record.put("contractAgentId", contract.getContractAgentId());
record.put("contractAgentName", contract.getContractAgentId() == null
? "未查询到" : agentNames.getOrDefault(contract.getContractAgentId(), "未查询到"));
record.put("customerAgentId", contract.getCustomerAgentId());
record.put("customerAgentName", contract.getCustomerAgentId() == null
? "未查询到" : agentNames.getOrDefault(contract.getCustomerAgentId(), "未查询到"));
record.put("propertyId", contract.getPropertyId());
record.put("propertyName", contract.getPropertyName());
record.put("customerNo", contract.getCustomerNo());
record.put("customerName", contract.getCustomerName());
record.put("amount", contract.getAmount());
record.put("contact", contract.getContact());
record.put("startDate", contract.getStartDate() == null ? null : df.format(contract.getStartDate()));
record.put("endDate", contract.getEndDate() == null ? null : df.format(contract.getEndDate()));
record.put("expireRemind", contract.getExpireRemind());
record.put("remindDays", contract.getRemindDays());
record.put("remindTime", contract.getRemindTime() == null ? null : sdf.format(contract.getRemindTime()));
record.put("reminded", contract.getReminded());
record.put("saleId", contract.getSaleId());
record.put("saleName", contract.getSaleName());
record.put("contractStatus", contract.getContractStatus());
record.put("contractStatusText", CONTRACT_STATUS_TEXT.getOrDefault(contract.getContractStatus(), "未知"));
record.put("stopReasonType", contract.getStopReasonType());
record.put("stopReason", contract.getStopReason());
record.put("createId", contract.getCreateId());
record.put("createName", contract.getCreateId() == null
? "未查询到" : creatorNames.getOrDefault(contract.getCreateId(), "未查询到"));
record.put("createTime", contract.getCreateTime() == null ? null : sdf.format(contract.getCreateTime()));
return record;
}
private Map<Long, String> agentNamesOf(ContractNew contract) {
List<Long> agentIds = Stream.of(contract.getContractAgentId(), contract.getCustomerAgentId())
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
if (agentIds.isEmpty()) {
return Map.of();
}
return agentMapper.selectBatchIds(agentIds).stream()
.collect(Collectors.toMap(Agent::getId, Agent::getName, (a, b) -> a));
}
}
@@ -9,6 +9,7 @@ import me.iiv.agent.entity.m0.HardwareView;
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.ContractNew;
import me.iiv.agent.entity.m2.WarehouseAgentAuth;
import me.iiv.agent.mapper.m0.AgentManageDistrictAuthMapper;
import me.iiv.agent.mapper.m0.AgentMapper;
@@ -400,4 +401,28 @@ public class PermissionService {
wrapper.in(HardwareView::getAgentId, scope.ids);
}
}
/**
* 合同(m2 contract_new)行权限:与站点/出纸机同一 getByIsAgent 变体
* (propertyFlag=true),agent 维度落到 contract_agent_id(合同所属公司,
* =物业所属销售所属公司),property 维度落到 property_id。
* 老系统 /contract/listByPage 源码不在本工作区,按合同表字段语义对齐
* district/hardware 的过滤组合(2026-09-11 决策)。
*/
public void applyContractRowFilter(LambdaQueryWrapper<ContractNew> wrapper, User user) {
RowScope scope = resolveRowScope(user);
if (scope.headquarters) {
return;
}
if (scope.salesPropertyIds != null) {
wrapper.in(ContractNew::getPropertyId, scope.salesPropertyIds);
return;
}
if (!scope.managedPropertyIds.isEmpty()) {
wrapper.and(w -> w.in(ContractNew::getContractAgentId, scope.ids)
.or().in(ContractNew::getPropertyId, scope.managedPropertyIds));
} else {
wrapper.in(ContractNew::getContractAgentId, scope.ids);
}
}
}
@@ -0,0 +1,258 @@
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.Agent;
import me.iiv.agent.entity.m0.User;
import me.iiv.agent.entity.m2.ContractFile;
import me.iiv.agent.entity.m2.ContractNew;
import me.iiv.agent.entity.m2.ContractProcess;
import me.iiv.agent.mapper.m0.AgentMapper;
import me.iiv.agent.mapper.m0.UserMapper;
import me.iiv.agent.mapper.m2.ContractFileMapper;
import me.iiv.agent.mapper.m2.ContractNewMapper;
import me.iiv.agent.mapper.m2.ContractProcessMapper;
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.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ContractToolsTest {
@Mock
ContractNewMapper contractNewMapper;
@Mock
ContractFileMapper contractFileMapper;
@Mock
ContractProcessMapper contractProcessMapper;
@Mock
AgentMapper agentMapper;
@Mock
UserMapper userMapper;
@Mock
PermissionService permissionService;
ContractTools tools;
User user;
@BeforeEach
void setUp() {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
ContractNew.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
ContractFile.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
ContractProcess.class);
tools = new ContractTools(contractNewMapper, contractFileMapper, contractProcessMapper,
agentMapper, userMapper, permissionService);
user = new User();
user.setId(7L);
AuthContextHolder.set(new AuthContext(null, user));
}
@AfterEach
void tearDown() {
AuthContextHolder.clear();
}
@Test
@SuppressWarnings("unchecked")
void listBuildsLegacyFiltersAndTruncatesRows() {
ContractNew contract = sampleContract();
when(contractNewMapper.selectCount(any())).thenReturn(1L);
when(contractNewMapper.selectList(any())).thenReturn(List.of(contract));
Agent contractAgent = new Agent();
contractAgent.setId(17L);
contractAgent.setName("合同所属公司A");
Agent customerAgent = new Agent();
customerAgent.setId(18L);
customerAgent.setName("客户所属公司B");
when(agentMapper.selectBatchIds(anyList())).thenReturn(List.of(contractAgent, customerAgent));
User creator = new User();
creator.setId(1000L);
creator.setName("张三");
when(userMapper.selectBatchIds(anyList())).thenReturn(List.of(creator));
Map<String, Object> result = tools.contractListQuery(
"452542C", "服务合同", "望京", "金蝶客户", "452542",
17L, 18L, 1L, 2, 1, "2026-01-01", "2026-12-31", 1, 500);
assertEquals(1, result.get("page"));
assertEquals(100, result.get("rows"));
assertEquals("rows 超过上限 100,已截断为 100", result.get("notice"));
ArgumentCaptor<LambdaQueryWrapper<ContractNew>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(contractNewMapper).selectList(captor.capture());
String sql = captor.getValue().getSqlSegment();
assertTrue(sql.contains("contract_no LIKE"));
assertTrue(sql.contains("contract_name LIKE"));
assertTrue(sql.contains("property_name LIKE"));
assertTrue(sql.contains("customer_name LIKE"));
assertTrue(sql.contains("customer_no ="));
assertTrue(sql.contains("contract_agent_id ="));
assertTrue(sql.contains("customer_agent_id ="));
assertTrue(sql.contains("property_id ="));
assertTrue(sql.contains("contract_status ="));
assertTrue(sql.contains("expire_remind ="));
assertTrue(sql.contains("end_date >="));
assertTrue(sql.contains("end_date <="));
assertTrue(sql.contains("soft_delete ="));
assertTrue(sql.contains("ORDER BY id DESC"));
verify(permissionService).applyContractRowFilter(any(), any());
Map<String, Object> record = ((List<Map<String, Object>>) result.get("records")).get(0);
assertEquals("452542C001", record.get("contractNo"));
assertEquals("合同所属公司A", record.get("contractAgentName"));
assertEquals("客户所属公司B", record.get("customerAgentName"));
assertEquals("张三", record.get("createName"));
assertEquals(2, record.get("contractStatus"));
assertEquals("正常", record.get("contractStatusText"));
assertEquals(new BigDecimal("120000.00"), record.get("amount"));
assertEquals("2026-01-01", record.get("startDate"));
assertEquals("2026-12-31", record.get("endDate"));
}
@Test
void detailRequiresIdOrContractNo() {
Map<String, Object> result = tools.contractDetailQuery(null, " ");
assertNull(result.get("record"));
assertEquals("id 和 contractNo 必须传一个", result.get("notice"));
verifyNoInteractions(contractNewMapper);
}
@Test
void detailReturnsNullRecordWithNoticeWhenMissingOrUnauthorized() {
when(contractNewMapper.selectOne(any())).thenReturn(null);
Map<String, Object> result = tools.contractDetailQuery(404L, null);
assertNull(result.get("record"));
assertEquals("id=404 不存在或当前身份无权查看", result.get("notice"));
verify(permissionService).applyContractRowFilter(any(), any());
}
@Test
@SuppressWarnings("unchecked")
void detailByContractNoReturnsRecordFilesAndProcesses() {
ContractNew contract = sampleContract();
when(contractNewMapper.selectOne(any())).thenReturn(contract);
Agent contractAgent = new Agent();
contractAgent.setId(17L);
contractAgent.setName("合同所属公司A");
when(agentMapper.selectBatchIds(anyList())).thenReturn(List.of(contractAgent));
User creator = new User();
creator.setId(1000L);
creator.setName("张三");
when(userMapper.selectById(1000L)).thenReturn(creator);
ContractFile file = new ContractFile();
file.setId(1L);
file.setContractId(9L);
file.setFileName("合同扫描件.pdf");
file.setFileUrl("https://oss.example.com/contract/9/scan.pdf");
when(contractFileMapper.selectList(any())).thenReturn(List.of(file));
ContractProcess process = new ContractProcess();
process.setId(1L);
process.setContractId(9L);
process.setNodeType(1);
process.setAuditLevel(0);
process.setOperationType(1);
process.setUserId(2000L);
process.setUserName("李四");
process.setScheduleState(2);
process.setRemark("同意");
when(contractProcessMapper.selectList(any())).thenReturn(List.of(process));
Map<String, Object> result = tools.contractDetailQuery(null, "452542C001");
ArgumentCaptor<LambdaQueryWrapper<ContractNew>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(contractNewMapper).selectOne(captor.capture());
assertTrue(captor.getValue().getSqlSegment().contains("contract_no ="));
verify(permissionService).applyContractRowFilter(any(), any());
Map<String, Object> record = (Map<String, Object>) result.get("record");
assertEquals("452542C001", record.get("contractNo"));
assertEquals("合同所属公司A", record.get("contractAgentName"));
assertEquals("未查询到", record.get("customerAgentName"));
assertEquals("张三", record.get("createName"));
assertEquals("正常", record.get("contractStatusText"));
List<Map<String, Object>> files = (List<Map<String, Object>>) result.get("files");
assertEquals(1, files.size());
assertEquals("合同扫描件.pdf", files.get(0).get("fileName"));
assertEquals("https://oss.example.com/contract/9/scan.pdf", files.get(0).get("fileUrl"));
List<Map<String, Object>> processes = (List<Map<String, Object>>) result.get("processes");
assertEquals(1, processes.size());
assertEquals("部门内审核", processes.get(0).get("nodeTypeText"));
assertEquals("审核通过", processes.get(0).get("operationTypeText"));
assertEquals("已办", processes.get(0).get("scheduleStateText"));
assertEquals("李四", processes.get(0).get("userName"));
assertEquals("同意", processes.get(0).get("remark"));
}
@Test
void springAiPublishesBothReviewedToolSchemas() {
var callbacks = org.springframework.ai.tool.method.MethodToolCallbackProvider.builder()
.toolObjects(tools).build().getToolCallbacks();
Map<String, String> schemas = Arrays.stream(callbacks).collect(Collectors.toMap(
callback -> callback.getToolDefinition().name(),
callback -> callback.getToolDefinition().inputSchema()));
assertEquals(2, schemas.size());
assertTrue(schemas.containsKey("contract.list.query"));
assertTrue(schemas.containsKey("contract.detail.query"));
assertTrue(schemas.get("contract.list.query").contains("contractStatus"));
assertTrue(schemas.get("contract.detail.query").contains("contractNo"));
}
private static ContractNew sampleContract() {
ContractNew contract = new ContractNew();
contract.setId(9L);
contract.setContractNo("452542C001");
contract.setContractName("望京SOHO厕纸服务合同");
contract.setContractAgentId(17L);
contract.setCustomerAgentId(18L);
contract.setPropertyId(1L);
contract.setPropertyName("望京SOHO");
contract.setCustomerNo("452542");
contract.setCustomerName("金蝶客户名称");
contract.setAmount(new BigDecimal("120000.00"));
contract.setContact("王五");
contract.setStartDate(java.sql.Date.valueOf("2026-01-01"));
contract.setEndDate(java.sql.Date.valueOf("2026-12-31"));
contract.setExpireRemind(1);
contract.setRemindDays(30);
contract.setSaleId(1001L);
contract.setSaleName("销售甲");
contract.setContractStatus(2);
contract.setCreateId(1000L);
return contract;
}
}
@@ -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.m2.ContractNew;
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.applyContractRowFilter 直接单测:contract_new 行级权限各分支(与站点/出纸机同一 getByIsAgent 变体)。 */
@ExtendWith(MockitoExtension.class)
class PermissionServiceContractRowFilterTest {
@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(), ""),
ContractNew.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
Properyt.class);
permissionService = new PermissionService(authorityMapper, agentMapper,
warehouseAgentAuthMapper, agentManageDistrictAuthMapper, properytMapper, redisTemplate);
}
@Test
void headquartersContractFilterAddsNoBoundary() {
User user = user(46L, 1L, null);
when(authorityMapper.selectById(1L)).thenReturn(authority(1L, 0));
LambdaQueryWrapper<ContractNew> wrapper = new LambdaQueryWrapper<>();
permissionService.applyContractRowFilter(wrapper, user);
assertTrue(wrapper.getSqlSegment().isBlank());
}
@Test
void agentContractFilterUsesSelfSubordinateAndManagedAgents() {
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<ContractNew> wrapper = new LambdaQueryWrapper<>();
permissionService.applyContractRowFilter(wrapper, user);
String sql = wrapper.getSqlSegment();
Collection<Object> values = wrapper.getParamNameValuePairs().values();
assertTrue(sql.contains("contract_agent_id IN"));
assertFalse(sql.contains("property_id"));
assertTrue(values.contains(1L));
assertTrue(values.contains(2L));
assertTrue(values.contains(3L));
}
@Test
void agentContractFilterAddsManagedPropertyOrBranch() {
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<ContractNew> wrapper = new LambdaQueryWrapper<>();
permissionService.applyContractRowFilter(wrapper, user);
String sql = wrapper.getSqlSegment();
Collection<Object> values = wrapper.getParamNameValuePairs().values();
assertTrue(sql.contains("contract_agent_id IN"));
assertTrue(sql.contains("property_id IN"));
assertTrue(sql.contains("OR"));
assertTrue(values.contains(1L));
assertTrue(values.contains(44L));
}
@Test
void salesNonLeaderContractFilterUsesOwnCreatedPropertyIds() {
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<ContractNew> wrapper = new LambdaQueryWrapper<>();
permissionService.applyContractRowFilter(wrapper, user);
String sql = wrapper.getSqlSegment();
assertTrue(sql.contains("property_id IN"));
assertFalse(sql.contains("contract_agent_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<ContractNew> wrapper = new LambdaQueryWrapper<>();
permissionService.applyContractRowFilter(wrapper, user);
String sql = wrapper.getSqlSegment();
assertTrue(sql.contains("property_id IN"));
assertFalse(sql.contains("contract_agent_id"));
assertTrue(wrapper.getParamNameValuePairs().values().contains(0L));
verifyNoInteractions(agentManageDistrictAuthMapper);
}
@Test
void agentUserWithoutAgentIdIsDeniedForContractFilter() {
User user = user(1000L, 108L, null);
when(authorityMapper.selectById(108L)).thenReturn(authority(108L, 1));
assertThrows(IllegalStateException.class,
() -> permissionService.applyContractRowFilter(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;
}
}