xiaoqu-agent-service P3 首批工具完整版:agent.list.query 补齐入出参与 supervisorName 批量富化,新增 property.list.query(完整版行权限),rows 上限截断提示,LLM 友好 description,种子与 README 客户端接入说明,curl 实测隔离正确

This commit is contained in:
xiaoqu
2026-09-03 12:40:19 +08:00
parent 70a45cf3b9
commit 674c4ea7e6
5 changed files with 306 additions and 16 deletions
+44 -2
View File
@@ -2,7 +2,7 @@
面向 MCP 客户端(Kimi Code / Claude 等)的小趣业务数据服务:独立 Spring Boot 应用,只读查询现有三库(m0 权限库 / m1 商城库 / m2 业务库),以 MCP(Streamable HTTP)形式对外暴露工具。
设计方案见 `../xiaoqu-franchisee/doc/agent-mcp-service-design.md`。当前进度:**P2 权限层已完成**(ak 鉴权、scope 校验、行级数据权限引擎、审计落库,均经 curl 实测)。管理后台为 P4,未实现。
设计方案见 `../xiaoqu-franchisee/doc/agent-mcp-service-design.md`。当前进度:**P3 首批工具完成**(P1 骨架 + P2 权限层 + agent.list.query / property.list.query 完整版,均经 curl 实测)。管理后台为 P4,未实现。
## 技术栈与已验证版本组合
@@ -72,7 +72,10 @@ xiaoqu-agent-service/
|---|---|---|---|
| `sys.ping` | system | read | 服务自检:服务名、版本、当前时间、Redis PING |
| `agent.count` | system | read | 查 m0 库 `agent` 表 `soft_delete=0` 总数 |
| `agent.list.query` | customer | read | 代理商分页查询(行级权限),入参 id/name/level/channelNo/page/rows |
| `agent.list.query` | customer | read | 代理商分页查询。入参 id/name/level/channelNo/province/city/serialnumber/page/rows;出参 id/name/level/channelNo/serialnumber/supervisorName/province/city/countyseat/contact/phone/agencyarea/memo/createTime。行权限:isagent=1 只查直属下级,id 精确查可看自身。supervisorName 批量 IN 富化 |
| `property.list.query` | customer | read | 物业(客户)分页查询(m0 properyt 表)。入参 name/agentId/serialnumber/province/city/contact/phone/page/rows;出参 id/name/serialnumber/agentName/createName/province/city/countyseat/address/contact/phone/createTime。行权限走 PermissionService 完整版 getByIsAgent 语义。agentName/createName 批量 IN 富化 |
分页统一规范:page 从 1 开始,rows 默认 20、上限 100,超出截断为 100 并在返回 `notice` 字段提示。
工具名使用点号分隔(MCP 规范允许 `[a-zA-Z0-9._-]`,实测 Spring AI 1.1.8 与握手流程均正常)。工具的 category/kind 由代码 `@XqTool` 注解声明,启动时同步进 `tool` 表(只插不删,管理员停用状态不会被覆盖)。新工具默认不属于任何作用域 = 任何 token 调不到,需管理员配 scope。
@@ -88,6 +91,8 @@ mysql -h192.168.1.181 -uroot -p xiaoqu_agent_svc_d < sql/seed-dev.sql
java -jar target/xiaoqu-agent-service.jar
# 或
./mvnw spring-boot:run
# 后台常驻(本机兼任 dev 部署机时):
nohup java -jar target/xiaoqu-agent-service.jar > target/app.log 2>&1 &
```
- 默认 `spring.profiles.active=dev`,端口 **8088**,MCP 端点 `http://localhost:8088/mcp`。
@@ -123,6 +128,43 @@ curl -X POST http://localhost:8088/mcp \
响应为 SSE 帧(`event: message` / `data: {...}`)。2026-09-03 实测:无 ak/无效 ak → 401;总部 ak 查 agent.list.query total=62(全量);代理商 ak total=26(与 `SELECT COUNT(*) FROM agent WHERE soft_delete=0 AND supervisor_id=1` 一致);无 scope 的 ak → `isError:true` + "该 token 的作用域不包含工具 [agent.list.query],请联系管理员在对应作用域开通";audit_log 记录 success/denied 与 client_type。
property.list.query 权限隔离实测(2026-09-03,均与直连 SQL 核对一致):
| 条件 | 总部 ak | 代理商 ak |
|---|---|---|
| 无过滤 | total=141 | total=73 |
| province=北京市 | total=38 | total=31 |
| name 模糊 '%测试%' | — | total=7(与 SQL 一致) |
agent.list.query 抽测:channel_no='D' + province='北京市' → 总部 11 / 代理商 9(均与 SQL 一致);id=79 精确查 → 1;rows=500 → 截断为 100 并返回 notice。
## 客户端接入(dev 阶段裸跑 HTTP)
dev 服务地址示例:`http://192.168.4.236:8088/mcp`(本机内网 IP;本机自测可用 localhost)。生产必须 HTTPS(Nginx 终结 TLS)。
**Kimi Code** — 项目级 `.kimi-code/mcp.json`:
```json
{
"mcpServers": {
"xiaoqu": {
"url": "http://192.168.4.236:8088/mcp",
"bearerTokenEnvVar": "XQ_API_KEY"
}
}
}
```
shell 里 `export XQ_API_KEY=<测试ak>` 后启动 Kimi Code 即可自发现工具。
**Claude Code**:
```bash
export XQ_API_KEY=<测试ak>
claude mcp add --transport http xiaoqu http://192.168.4.236:8088/mcp \
--header "Authorization: Bearer $XQ_API_KEY"
```
## codegen(run-once)
表白名单:m0 `user, agent, authority, properyt, agent_manage_district_auth`;m2 `warehouse_agent_auth`;svc `agent_api_key, agent_scope, tool, scope_tool, token_scope, audit_log, admin_user`(注:物业表真实表名为老系统遗留拼写 `properyt`)。生成物落在 `src/main/java` 对应分包并提交进仓库。
+2 -2
View File
@@ -31,10 +31,10 @@ INSERT INTO agent_scope (name, code, memo, status, create_id)
SELECT '运营', 'operation', 'dev 种子:运营作用域', 1, 0
WHERE NOT EXISTS (SELECT 1 FROM agent_scope WHERE code = 'operation');
-- 作用域-工具:sys.ping / agent.count / agent.list.query
-- 作用域-工具:sys.ping / agent.count / agent.list.query / property.list.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')
WHERE s.code = 'operation' AND t.name IN ('sys.ping', 'agent.count', 'agent.list.query', 'property.list.query')
AND NOT EXISTS (SELECT 1 FROM scope_tool st WHERE st.scope_id = s.id AND st.tool_id = t.id);
-- token-作用域:HQ 与 AGENT 两把 token 授权"运营";NOSCOPE 不授权
@@ -18,10 +18,11 @@ 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;
/**
* 代理商域工具:agent.count(demo)+ agent.list.query(行级权限验证用基础版,
* 语义照搬老系统 AgentController.listByPage)。
* 代理商域工具:agent.count(demo)+ agent.list.query(对应老系统 AgentController.listByPage)。
*/
@Component
public class AgentTools implements ToolObject {
@@ -51,19 +52,28 @@ public class AgentTools implements ToolObject {
* 查询代理商列表。行权限照搬老系统 AgentController.listByPage:
* 权限组 isagent=1 时只查直属下级(supervisor_id = 用户 agentId);isagent=0 不过滤。
* 入参传 id 时精确查询(可查到自身,此时不加 supervisor 限制)。
* supervisorName 批量 IN 查询富化,无 N+1。
*/
@XqTool(category = "customer", kind = "read")
@Tool(name = "agent.list.query", description = "查询代理商列表(分页)。代理商身份的用户只能查到直属下级代理;"
+ "总部身份看全部。传 id 时为精确查询。")
@Tool(name = "agent.list.query", description = "查询代理商列表(分页)。"
+ "行级权限:代理商身份的用户(权限组 isagent=1)只能查到自己名下的直属下级代理;总部身份(isagent=0)可看全部。"
+ "传 id 时为精确查询、可查到自身,其余过滤条件失效。"
+ "level 含义:0=总部 1=一级 2=二级;channelNo 含义:Z=总部 F=分公司 D=代理。"
+ "分页:page 从 1 开始,rows 默认 20、上限 100,超出自动截断为 100 并在返回的 notice 字段提示。"
+ "返回 total(符合条件的总数)、records(本页记录)。")
public Map<String, Object> agentListQuery(
@ToolParam(required = false, description = "代理商 id,精确查询(可查到自身)") Long id,
@ToolParam(required = false, description = "代理商 id,精确查询(可查到自身);传入后忽略其他过滤条件") Long id,
@ToolParam(required = false, description = "代理商名称,模糊匹配") String name,
@ToolParam(required = false, description = "级别:0总部/1一级/2二级") Integer level,
@ToolParam(required = false, description = "渠道编号:Z/F/D") String channelNo,
@ToolParam(required = false, description = "页码,默认 1") Integer page,
@ToolParam(required = false, description = "每页条数,默认 20,上限 100") Integer rows) {
@ToolParam(required = false, description = "级别:0=总部 1=一级 2=二级") Integer level,
@ToolParam(required = false, description = "渠道编号:Z=总部 F=分公司 D=代理") String channelNo,
@ToolParam(required = false, description = "省份,精确匹配,如 北京市") String province,
@ToolParam(required = false, description = "城市,精确匹配,如 北京市") String city,
@ToolParam(required = false, description = "代理商编号(serialnumber),精确匹配") String serialnumber,
@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<Agent> wrapper = new LambdaQueryWrapper<Agent>()
@@ -80,6 +90,15 @@ public class AgentTools implements ToolObject {
if (StringUtils.hasText(channelNo)) {
wrapper.eq(Agent::getChannelNo, channelNo);
}
if (StringUtils.hasText(province)) {
wrapper.eq(Agent::getProvince, province);
}
if (StringUtils.hasText(city)) {
wrapper.eq(Agent::getCity, city);
}
if (StringUtils.hasText(serialnumber)) {
wrapper.eq(Agent::getSerialnumber, serialnumber);
}
if (permissionService.isAgentAuthority(user.getAuthorityId())) {
wrapper.eq(Agent::getSupervisorId, user.getAgentId());
}
@@ -87,12 +106,25 @@ public class AgentTools implements ToolObject {
Long total = agentMapper.selectCount(wrapper);
wrapper.select(Agent::getId, Agent::getName, Agent::getLevel, Agent::getChannelNo,
Agent::getSerialnumber, Agent::getProvince, Agent::getCity,
Agent::getContact, Agent::getPhone, Agent::getCreateTime)
Agent::getSerialnumber, Agent::getSupervisorId, Agent::getProvince, Agent::getCity,
Agent::getCountyseat, Agent::getContact, Agent::getPhone, Agent::getAgencyarea,
Agent::getMemo, Agent::getCreateTime)
.orderByDesc(Agent::getId)
.last("LIMIT " + (long) (pageNo - 1) * pageSize + "," + pageSize);
List<Agent> agents = agentMapper.selectList(wrapper);
// supervisorName 批量富化(一次 IN 查询,消除 N+1)
List<Long> supervisorIds = agents.stream()
.map(Agent::getSupervisorId)
.filter(Objects::nonNull)
.filter(sid -> sid != 0)
.distinct()
.collect(Collectors.toList());
Map<Long, String> supervisorNames = supervisorIds.isEmpty()
? Map.of()
: agentMapper.selectBatchIds(supervisorIds).stream()
.collect(Collectors.toMap(Agent::getId, Agent::getName, (a, b) -> a));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<Map<String, Object>> records = new ArrayList<>();
for (Agent agent : agents) {
@@ -102,10 +134,16 @@ public class AgentTools implements ToolObject {
record.put("level", agent.getLevel());
record.put("channelNo", agent.getChannelNo());
record.put("serialnumber", agent.getSerialnumber());
record.put("supervisorName",
agent.getSupervisorId() == null || agent.getSupervisorId() == 0
? "-" : supervisorNames.getOrDefault(agent.getSupervisorId(), "-"));
record.put("province", agent.getProvince());
record.put("city", agent.getCity());
record.put("countyseat", agent.getCountyseat());
record.put("contact", agent.getContact());
record.put("phone", agent.getPhone());
record.put("agencyarea", agent.getAgencyarea());
record.put("memo", agent.getMemo());
record.put("createTime", agent.getCreateTime() == null ? null : sdf.format(agent.getCreateTime()));
records.add(record);
}
@@ -114,6 +152,9 @@ public class AgentTools implements ToolObject {
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;
}
@@ -0,0 +1,152 @@
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.mapper.m0.AgentMapper;
import me.iiv.agent.mapper.m0.ProperytMapper;
import me.iiv.agent.mapper.m0.UserMapper;
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;
/**
* 物业(客户)域工具:property.list.query(对应老系统 PropertyController.listByPage,
* 行权限走 PermissionService.applyPropertyRowFilter)。
*/
@Component
public class PropertyTools implements ToolObject {
private static final int DEFAULT_ROWS = 20;
private static final int MAX_ROWS = 100;
final ProperytMapper properytMapper;
final AgentMapper agentMapper;
final UserMapper userMapper;
final PermissionService permissionService;
public PropertyTools(ProperytMapper properytMapper, AgentMapper agentMapper,
UserMapper userMapper, PermissionService permissionService) {
this.properytMapper = properytMapper;
this.agentMapper = agentMapper;
this.userMapper = userMapper;
this.permissionService = permissionService;
}
@XqTool(category = "customer", kind = "read")
@Tool(name = "property.list.query", description = "查询物业(客户)列表(分页),数据来源 m0 库 properyt 表。"
+ "行级权限:代理商身份的用户(权限组 isagent=1)只能看到 自己+直属下级代理+被授权代管代理 名下的物业,"
+ "以及被授权代管的物业(agent_manage_district_auth);总部身份(isagent=0)可看全部;"
+ "物业账号(enterprise_type=2)只能看自己物业;销售部门非负责人只能看自己创建的物业。"
+ "分页:page 从 1 开始,rows 默认 20、上限 100,超出自动截断为 100 并在返回的 notice 字段提示。"
+ "返回 total(符合条件的总数)、records(本页记录)。")
public Map<String, Object> propertyListQuery(
@ToolParam(required = false, description = "物业名称,模糊匹配") String name,
@ToolParam(required = false, description = "所属代理商 id,精确匹配") Long agentId,
@ToolParam(required = false, description = "物业编号(serialnumber),精确匹配") String serialnumber,
@ToolParam(required = false, description = "省份,精确匹配,如 北京市") String province,
@ToolParam(required = false, description = "城市,精确匹配,如 北京市") String city,
@ToolParam(required = false, description = "联系人,精确匹配") String contact,
@ToolParam(required = false, description = "联系电话,精确匹配") String phone,
@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<Properyt> wrapper = new LambdaQueryWrapper<Properyt>()
.eq(Properyt::getSoftDelete, 0);
if (StringUtils.hasText(name)) {
wrapper.like(Properyt::getName, name);
}
if (agentId != null) {
wrapper.eq(Properyt::getAgentId, agentId);
}
if (StringUtils.hasText(serialnumber)) {
wrapper.eq(Properyt::getSerialnumber, serialnumber);
}
if (StringUtils.hasText(province)) {
wrapper.eq(Properyt::getProvince, province);
}
if (StringUtils.hasText(city)) {
wrapper.eq(Properyt::getCity, city);
}
if (StringUtils.hasText(contact)) {
wrapper.eq(Properyt::getContact, contact);
}
if (StringUtils.hasText(phone)) {
wrapper.eq(Properyt::getPhone, phone);
}
// 行级权限(完整版 getByIsAgent 语义,统一走 PermissionService)
permissionService.applyPropertyRowFilter(wrapper, user);
Long total = properytMapper.selectCount(wrapper);
wrapper.select(Properyt::getId, Properyt::getName, Properyt::getSerialnumber,
Properyt::getAgentId, Properyt::getCreateId, Properyt::getProvince, Properyt::getCity,
Properyt::getCountyseat, Properyt::getAddress, Properyt::getContact, Properyt::getPhone,
Properyt::getCreateTime)
.orderByDesc(Properyt::getId)
.last("LIMIT " + (long) (pageNo - 1) * pageSize + "," + pageSize);
List<Properyt> properties = properytMapper.selectList(wrapper);
// agentName / createName 批量富化(一次 IN 查询,消除 N+1)
List<Long> agentIds = properties.stream().map(Properyt::getAgentId)
.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 = properties.stream().map(Properyt::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");
List<Map<String, Object>> records = new ArrayList<>();
for (Properyt properyt : properties) {
Map<String, Object> record = new LinkedHashMap<>();
record.put("id", properyt.getId());
record.put("name", properyt.getName());
record.put("serialnumber", properyt.getSerialnumber());
record.put("agentName", properyt.getAgentId() == null
? "未查询到" : agentNames.getOrDefault(properyt.getAgentId(), "未查询到"));
record.put("createName", properyt.getCreateId() == null
? "未分配" : creatorNames.getOrDefault(properyt.getCreateId(), "未分配"));
record.put("province", properyt.getProvince());
record.put("city", properyt.getCity());
record.put("countyseat", properyt.getCountyseat());
record.put("address", properyt.getAddress());
record.put("contact", properyt.getContact());
record.put("phone", properyt.getPhone());
record.put("createTime", properyt.getCreateTime() == null ? null : sdf.format(properyt.getCreateTime()));
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;
}
}
@@ -2,9 +2,12 @@ package me.iiv.agent.permission;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.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.m2.WarehouseAgentAuthMapper;
@@ -37,13 +40,17 @@ public class PermissionService {
final AuthorityMapper authorityMapper;
final AgentMapper agentMapper;
final WarehouseAgentAuthMapper warehouseAgentAuthMapper;
final AgentManageDistrictAuthMapper agentManageDistrictAuthMapper;
final StringRedisTemplate redisTemplate;
public PermissionService(AuthorityMapper authorityMapper, AgentMapper agentMapper,
WarehouseAgentAuthMapper warehouseAgentAuthMapper, StringRedisTemplate redisTemplate) {
WarehouseAgentAuthMapper warehouseAgentAuthMapper,
AgentManageDistrictAuthMapper agentManageDistrictAuthMapper,
StringRedisTemplate redisTemplate) {
this.authorityMapper = authorityMapper;
this.agentMapper = agentMapper;
this.warehouseAgentAuthMapper = warehouseAgentAuthMapper;
this.agentManageDistrictAuthMapper = agentManageDistrictAuthMapper;
this.redisTemplate = redisTemplate;
}
@@ -103,4 +110,52 @@ public class PermissionService {
public boolean isPropertyUser(User user) {
return user.getEnterpriseType() != null && user.getEnterpriseType() == 2;
}
/**
* 代管物业 id 集合:agent_manage_district_auth 中 manage_agent_id 命中的
* property_id 去重(老系统 getByIsAgent propertyFlag=true 分支,group by property_id)。
*/
public List<Long> managedPropertyIds(List<Long> manageAgentIds) {
if (manageAgentIds == null || manageAgentIds.isEmpty()) {
return new ArrayList<>();
}
List<AgentManageDistrictAuth> list = agentManageDistrictAuthMapper.selectList(
new LambdaQueryWrapper<AgentManageDistrictAuth>()
.eq(AgentManageDistrictAuth::getSoftDelete, 0)
.in(AgentManageDistrictAuth::getManageAgentId, manageAgentIds)
.groupBy(AgentManageDistrictAuth::getPropertyId));
return list.stream().map(AgentManageDistrictAuth::getPropertyId).collect(Collectors.toList());
}
/**
* 物业(properyt 表)行级过滤组合,语义对应老系统 PropertyController.listByPage 使用的
* getByIsAgent(agentFlag, agentId, wrapper, user, propertyFlag=true, className="id", redisTemplate):
* <ol>
* <li>isagent=1:agent_id IN (自己+下级+warehouse_agent_auth 代管),
* 若存在代管物业则 OR id IN (agent_manage_district_auth 代管物业 id 集合);</li>
* <li>isagent!=1(总部):不加 agent/id 过滤;</li>
* <li>enterprise_type=2(物业用户):再 AND id = 用户 propertyId
* (注:老 PropertyController 未挂此限制,来自设计文档 §3.3 通用规则);</li>
* <li>销售部门非负责人:再 AND create_id = 自己
* (注:老代码 saleFlag 分支是 id IN (自建 ∪ 代管 agent 的物业),
* 此处按设计文档 §3.3"只看 create_id=自己"收窄)。</li>
* </ol>
*/
public void applyPropertyRowFilter(LambdaQueryWrapper<Properyt> wrapper, User user) {
List<Long> agentIds = visibleAgentIds(user);
if (agentIds != null) {
List<Long> propertyIds = managedPropertyIds(agentIds);
if (!propertyIds.isEmpty()) {
wrapper.and(w -> w.in(Properyt::getAgentId, agentIds).or().in(Properyt::getId, propertyIds));
} else {
wrapper.in(Properyt::getAgentId, agentIds);
}
}
if (isPropertyUser(user)) {
wrapper.eq(Properyt::getId, user.getPropertyId());
}
if (isSalesNonLeader(user)) {
wrapper.eq(Properyt::getCreateId, user.getId());
}
}
}