✨ 小程序用户支持组织与数据权限,调度端按项目业务部门收口运单

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-22 10:59:21 +08:00
parent 167cea020c
commit 83b2144407
24 changed files with 717 additions and 36 deletions
@@ -44,6 +44,7 @@ import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
import org.springblade.transport.service.IDriverWaybillService;
import org.springblade.transport.service.IExceptionDisposalService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.WaybillProcessSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -79,13 +80,16 @@ public class ExceptionDisposalServiceImpl
private final ExceptionDisposalFollowRecordMapper followRecordMapper;
private final IWaybillService waybillService;
private final IDriverWaybillService driverWaybillService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
public ExceptionDisposalServiceImpl(ExceptionDisposalFollowRecordMapper followRecordMapper,
IWaybillService waybillService,
IDriverWaybillService driverWaybillService) {
IDriverWaybillService driverWaybillService,
MpUserDataScopeSupport mpUserDataScopeSupport) {
this.followRecordMapper = followRecordMapper;
this.waybillService = waybillService;
this.driverWaybillService = driverWaybillService;
this.mpUserDataScopeSupport = mpUserDataScopeSupport;
}
@Override
@@ -409,6 +413,8 @@ public class ExceptionDisposalServiceImpl
.like(Func.isNotEmpty(query.getCarrierName()), ExceptionDisposal::getCarrierName, query.getCarrierName())
.eq(Func.isNotEmpty(query.getExceptionType()), ExceptionDisposal::getExceptionType, query.getExceptionType())
.eq(Func.isNotEmpty(query.getDisposalStatus()), ExceptionDisposal::getDisposalStatus, query.getDisposalStatus());
// 调度端列表按「权限组织 → 项目业务部门 → 项目」收口;司机端按运单查自己的异常时不套此过滤
mpUserDataScopeSupport.applyProjectScopeIfMpDispatcher(wrapper, ExceptionDisposal::getProjectId);
return wrapper.orderByDesc(ExceptionDisposal::getCreateTime);
}
@@ -33,6 +33,7 @@ import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserDisplayCache;
import org.springblade.system.utils.MpUserAuth;
import org.springblade.transport.pojo.entity.Driver;
import org.springblade.transport.pojo.entity.ExceptionDisposal;
import org.springblade.transport.pojo.entity.LoadingManage;
@@ -59,6 +60,7 @@ import org.springblade.transport.service.IManageWaybillService;
import org.springblade.transport.service.IRiskDisposalService;
import org.springblade.transport.service.ITripQueryService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.support.WaybillCargoSupport;
import org.springblade.transport.support.WaybillProcessSupport;
@@ -89,8 +91,8 @@ import java.util.stream.Collectors;
* 列表与详情与司机端同粒度:取数、状态聚合、接单需求合并都调共享运次读服务,
* 本类只做字段裁剪与调度端独有的筛选(组织类型 / 异常 / 创建时间 / 待重新派单)。
* <p>
* 小程序调度账号(如「小程序管理」)组织常与运单业务组织不一致,故不做 dept 过滤,
* 仅依赖租户隔离,口径接近后台 {@code /waybill-manage/list?allDept=1}。
* 小程序调度端:所属组织 + 数据权限范围 + 数据层级范围 → 业务部门在范围内的项目 → 再取这些项目的运单;
* 异常/风险与运单同一项目口径。系统用户仍按「所属组织 / 超管不过滤」。
*/
@Service
@RequiredArgsConstructor
@@ -137,13 +139,14 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
private final IDriverWaybillService driverWaybillService;
private final ITripQueryService tripQueryService;
private final ILoadingManageService loadingManageService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
@Override
public AdminHomeStatsVO stats() {
AdminHomeStatsVO vo = new AdminHomeStatsVO();
// 三档状态按运次计:有配载单的一趟车算一个,与列表和司机端同一份聚合
TripQueryVO query = TripQueryVO.forAdmin();
query.setDeptIds(manageableDeptIds());
applyAdminDataScope(query);
TripTabCountsVO tripCounts = tripQueryService.tabCounts(query);
vo.setPendingAccept(tripCounts.getPending());
vo.setTransporting(tripCounts.getDoing());
@@ -181,7 +184,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
// 组织类型 / 异常 / 创建时间都是子单行级条件,与状态、关键字、分页一起交给运次读服务,
// 一次取数取完整趟车:分组、分档、筛选、排序与分页都在它那边算完
TripQueryVO query = TripQueryVO.forAdmin();
query.setDeptIds(manageableDeptIds());
applyAdminDataScope(query);
query.setStatus(toTripStatus(status));
query.setKeyword(keyword);
query.setTransportOrgType(transportType);
@@ -229,7 +232,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
Set<Long> exceptionWaybillIds = loadIncompleteExceptionWaybillIds();
TripQueryVO query = TripQueryVO.forAdmin();
query.setDeptIds(manageableDeptIds());
applyAdminDataScope(query);
query.setKeyword(keyword);
// 待处理只要待接单与进行中两档;「未拒单」也是交给运次读服务按子单行筛
query.setStatuses(List.of(0, 1));
@@ -667,11 +670,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
SQL_MAX_REJECT_TIME + " AS driver_reject_time", SQL_MAX_CREATE_TIME + " AS create_time");
wrapper.eq("driver_accept_status", ACCEPT_REJECTED);
wrapper.in("business_status", STATUS_PENDING, STATUS_RUNNING);
// 这条查询不经运次读服务,组织收口得自己带上,否则待重派列表与首页重派角标会出别的组织的单
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in("dept_id", deptIds);
}
// 这条查询不经运次读服务,数据权限收口得自己带上,否则待重派列表与首页重派角标会越权
applyAdminDataScope(wrapper);
wrapper.groupBy(TripAggregation.SQL_TRIP_KEY);
return wrapper;
}
@@ -884,10 +884,50 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
/**
* 调度端可见组织:普通用户(含各级角色)只出自己所属组织,超级管理员不过滤。
* <p>
* 小程序不校验「所属组织不能为空」:账号没有组织时不按组织收口,避免首页、待处理等接口直接报错。
* 详情与重新派单本身不走这里。
* 调度端数据权限落到运次查询条件:小程序按可见项目,系统用户按所属组织。
*/
private void applyAdminDataScope(TripQueryVO query) {
if (query == null) {
return;
}
if (MpUserAuth.isMpUser()) {
query.setProjectIds(manageableProjectIds());
return;
}
query.setDeptIds(manageableDeptIds());
}
/**
* 待重派等直查运单表时的数据权限:小程序按 project_id,系统用户按 dept_id。
*/
private void applyAdminDataScope(QueryWrapper<Waybill> wrapper) {
if (wrapper == null) {
return;
}
if (MpUserAuth.isMpUser()) {
List<Long> projectIds = manageableProjectIds();
if (Func.isNotEmpty(projectIds)) {
wrapper.in("project_id", projectIds);
}
return;
}
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in("dept_id", deptIds);
}
}
/**
* 小程序调度端可见项目:所属组织 + 数据权限范围 + 数据层级范围 → 业务部门在范围内的项目。
* 空列表不过滤;[-1] 表示无权限。
*/
private List<Long> manageableProjectIds() {
List<Long> projectIds = mpUserDataScopeSupport.resolveVisibleProjectIdsForQuery();
return projectIds == null ? List.of() : projectIds;
}
/**
* 系统用户可见组织:普通账号只出所属组织,超管不过滤;组织为空不拦截。
*/
private List<Long> manageableDeptIds() {
if (TransportBusinessSupport.isAdministrator()) {
@@ -911,10 +951,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
private Set<Long> loadIncompleteExceptionWaybillIds() {
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
.select(ExceptionDisposal::getWaybillId)
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
.isNotNull(ExceptionDisposal::getWaybillId));
.isNotNull(ExceptionDisposal::getWaybillId);
applyExceptionDeptScope(wrapper);
List<ExceptionDisposal> list = exceptionDisposalService.list(wrapper);
Set<Long> ids = new HashSet<>();
for (ExceptionDisposal item : list) {
if (item.getWaybillId() != null) {
@@ -924,6 +966,38 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return ids;
}
/** 异常与运单同一套项目口径(小程序);系统用户仍按组织 */
private void applyExceptionDeptScope(LambdaQueryWrapper<ExceptionDisposal> wrapper) {
if (MpUserAuth.isMpUser()) {
List<Long> projectIds = manageableProjectIds();
if (Func.isNotEmpty(projectIds)) {
wrapper.in(ExceptionDisposal::getProjectId, projectIds);
}
return;
}
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in(ExceptionDisposal::getDeptId, deptIds);
}
}
/** 风险无项目字段:小程序按关联运单的项目收口;系统用户按组织 */
private void applyRiskDeptScope(LambdaQueryWrapper<RiskDisposal> wrapper) {
if (MpUserAuth.isMpUser()) {
List<Long> projectIds = manageableProjectIds();
if (Func.isNotEmpty(projectIds)) {
String inList = projectIds.stream().map(String::valueOf).collect(Collectors.joining(","));
wrapper.inSql(RiskDisposal::getWaybillId,
"SELECT id FROM blade_waybill WHERE is_deleted = 0 AND project_id IN (" + inList + ")");
}
return;
}
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in(RiskDisposal::getDeptId, deptIds);
}
}
private AdminWaybillCardVO toCard(Waybill waybill, boolean hasException) {
AdminWaybillCardVO card = new AdminWaybillCardVO();
card.setId(waybill.getId());
@@ -1051,13 +1125,17 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
private long countIncompleteExceptions() {
return exceptionDisposalService.count(Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING));
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING);
applyExceptionDeptScope(wrapper);
return exceptionDisposalService.count(wrapper);
}
private long countPendingRisks() {
return riskDisposalService.count(Wrappers.<RiskDisposal>lambdaQuery()
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING));
LambdaQueryWrapper<RiskDisposal> wrapper = Wrappers.<RiskDisposal>lambdaQuery()
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING);
applyRiskDeptScope(wrapper);
return riskDisposalService.count(wrapper);
}
/**
@@ -1099,10 +1177,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
private List<TimedTodo> buildExceptionFeed() {
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
.orderByDesc(ExceptionDisposal::getReportTime)
.last("LIMIT " + FEED_LIMIT));
.last("LIMIT " + FEED_LIMIT);
applyExceptionDeptScope(wrapper);
List<ExceptionDisposal> list = exceptionDisposalService.list(wrapper);
return list.stream()
.map(disposal -> new TimedTodo(toTodoItem(disposal), exceptionTimeOf(disposal)))
.collect(Collectors.toList());
@@ -3,9 +3,9 @@ package org.springblade.transport.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserCache;
@@ -16,6 +16,7 @@ import org.springblade.transport.pojo.entity.RiskDisposal;
import org.springblade.transport.pojo.vo.RiskDisposalBatchResultVO;
import org.springblade.transport.pojo.vo.RiskDisposalVO;
import org.springblade.transport.service.IRiskDisposalService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -25,12 +26,15 @@ import java.util.List;
import java.util.Objects;
@Service
@RequiredArgsConstructor
public class RiskDisposalServiceImpl extends BaseServiceImpl<RiskDisposalMapper, RiskDisposal> implements IRiskDisposalService {
private static final String STATUS_PENDING = "pending";
private static final String STATUS_PROCESSED = "processed";
private static final String STATUS_IGNORED = "ignored";
private final MpUserDataScopeSupport mpUserDataScopeSupport;
@Override
public IPage<RiskDisposalVO> selectPage(IPage<RiskDisposal> page, RiskDisposalVO query) {
IPage<RiskDisposal> entityPage = page(page, buildQuery(query));
@@ -84,15 +88,16 @@ public class RiskDisposalServiceImpl extends BaseServiceImpl<RiskDisposalMapper,
}
private LambdaQueryWrapper<RiskDisposal> buildQuery(RiskDisposalVO query) {
return Wrappers.<RiskDisposal>lambdaQuery()
LambdaQueryWrapper<RiskDisposal> wrapper = Wrappers.<RiskDisposal>lambdaQuery()
.eq(RiskDisposal::getIsDeleted, 0)
.like(Func.isNotEmpty(query.getRiskNo()), RiskDisposal::getRiskNo, query.getRiskNo())
.like(Func.isNotEmpty(query.getRuleName()), RiskDisposal::getRuleName, query.getRuleName())
.like(Func.isNotEmpty(query.getWaybillNo()), RiskDisposal::getWaybillNo, query.getWaybillNo())
.like(Func.isNotEmpty(query.getProjectName()), RiskDisposal::getProjectName, query.getProjectName())
.eq(Func.isNotEmpty(query.getRiskLevel()), RiskDisposal::getRiskLevel, query.getRiskLevel())
.eq(Func.isNotEmpty(query.getDisposalStatus()), RiskDisposal::getDisposalStatus, query.getDisposalStatus())
.orderByDesc(RiskDisposal::getCreateTime);
.eq(Func.isNotEmpty(query.getDisposalStatus()), RiskDisposal::getDisposalStatus, query.getDisposalStatus());
mpUserDataScopeSupport.applyWaybillProjectScopeIfMpDispatcher(wrapper, RiskDisposal::getWaybillId);
return wrapper.orderByDesc(RiskDisposal::getCreateTime);
}
private RiskDisposalVO toVO(RiskDisposal entity) {
@@ -244,6 +244,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED));
applyScope(wrapper, condition);
applyDeptScope(wrapper, condition);
applyProjectScope(wrapper, condition);
applyOrgTypeFilter(wrapper, condition);
applyKeyword(wrapper, condition.getKeyword());
List<Waybill> rows = waybillService.list(wrapper);
@@ -264,6 +265,18 @@ public class TripQueryServiceImpl implements ITripQueryService {
wrapper.in(Waybill::getDeptId, condition.getDeptIds());
}
/**
* 项目收口:条件里给了项目 ID 集合时只出这些项目的运单行,空集合表示不过滤。
* <p>
* 小程序调度端先按用户数据权限解析可见项目(业务部门在范围内),再把项目 ID 传进来。
*/
private void applyProjectScope(LambdaQueryWrapper<Waybill> wrapper, TripQueryVO condition) {
if (Func.isEmpty(condition.getProjectIds())) {
return;
}
wrapper.in(Waybill::getProjectId, condition.getProjectIds());
}
/**
* 运输组织类型按子单行筛:它在同一趟车里必然是同一个值(配载的每张子单带同一个配载单号,
* 未配载的每张自成一趟车),所以这一层不会把一趟车拆开。
@@ -803,6 +816,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
copy.setDriverPhone(source.getDriverPhone());
copy.setAllDrivers(source.isAllDrivers());
copy.setDeptIds(source.getDeptIds());
copy.setProjectIds(source.getProjectIds());
copy.setKeyword(source.getKeyword());
copy.setTransportOrgType(source.getTransportOrgType());
copy.setException(source.getException());
@@ -0,0 +1,184 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.support;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.RequiredArgsConstructor;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.feign.IMpUserClient;
import org.springblade.system.pojo.enums.MpUserIdentity;
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
import org.springblade.system.utils.MpUserAuth;
import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.service.IProjectApplyService;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 小程序调度端数据权限。
* <p>
* 口径:所属组织 + 数据权限范围 + 数据层级范围 → 可见组织 →
* 业务部门落在可见组织内的项目 → 再按这些项目收口运单/异常/风险。
*
* @author Chill
*/
@Component
@RequiredArgsConstructor
public class MpUserDataScopeSupport {
/**
* 无权限时的占位 ID:查询 in(-1) 保证查不到数据(空列表在运次查询里表示不过滤)。
*/
private static final Long NONE_ID = -1L;
private final IMpUserClient mpUserClient;
private final IProjectApplyService projectApplyService;
/**
* 当前登录是否为小程序调度端用户(含 admin 身份)
*/
public boolean isMpDispatcher() {
return MpUserAuth.isMpUser()
&& MpUserIdentity.has(AuthUtil.getUserRole(), MpUserIdentity.ADMIN);
}
/**
* 解析当前小程序用户可见组织;非小程序用户返回 null(调用方走原逻辑)。
*/
public MpUserDataScopeVO resolveCurrentMpUserScope() {
if (!MpUserAuth.isMpUser()) {
return null;
}
return resolveByUserId(AuthUtil.getUserId());
}
/**
* 解析指定小程序用户可见组织;Feign 失败时按无权限处理。
*/
public MpUserDataScopeVO resolveByUserId(Long userId) {
if (Func.isEmpty(userId) || userId <= 0) {
return MpUserDataScopeVO.empty();
}
R<MpUserDataScopeVO> result = mpUserClient.resolveDataScope(userId);
if (result == null || !result.isSuccess() || result.getData() == null) {
return MpUserDataScopeVO.empty();
}
return result.getData();
}
/**
* 转为查询用的组织 ID 约定:空=不过滤;非空=in;无权限=[-1]。
*/
public List<Long> toQueryDeptIds(MpUserDataScopeVO scope) {
if (scope == null || scope.isAllDept()) {
return List.of();
}
if (Func.isEmpty(scope.getDeptIds())) {
return List.of(NONE_ID);
}
return scope.getDeptIds();
}
/**
* 解析当前小程序用户可见项目 ID(业务部门落在数据权限组织范围内)。
* <ul>
* <li>空列表 = 全部组织 / 不过滤</li>
* <li>非空 = 仅这些项目</li>
* <li>无权限或范围内无项目 = [-1]</li>
* </ul>
* 非小程序用户返回 null。
*/
public List<Long> resolveVisibleProjectIdsForQuery() {
MpUserDataScopeVO scope = resolveCurrentMpUserScope();
if (scope == null) {
return null;
}
return resolveProjectIds(scope);
}
/**
* 按数据权限结果解析可见项目 ID。
*/
public List<Long> resolveProjectIds(MpUserDataScopeVO scope) {
if (scope == null || scope.isAllDept()) {
return List.of();
}
List<Long> deptIds = scope.getDeptIds();
if (Func.isEmpty(deptIds)) {
return List.of(NONE_ID);
}
List<ProjectApply> projectList = projectApplyService.list(Wrappers.<ProjectApply>lambdaQuery()
.select(ProjectApply::getId)
.in(ProjectApply::getBusinessDeptId, deptIds));
if (Func.isEmpty(projectList)) {
return List.of(NONE_ID);
}
List<Long> projectIds = projectList.stream()
.map(ProjectApply::getId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
return Func.isEmpty(projectIds) ? List.of(NONE_ID) : projectIds;
}
/**
* 当前为小程序调度用户时,按可见项目写入 project 列;非调度用户不改动。
*/
public <T> void applyProjectScopeIfMpDispatcher(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> projectColumn) {
if (!isMpDispatcher() || wrapper == null || projectColumn == null) {
return;
}
List<Long> projectIds = resolveVisibleProjectIdsForQuery();
if (Func.isEmpty(projectIds)) {
return;
}
wrapper.in(projectColumn, projectIds);
}
/**
* 风险等无 projectId 字段时:按关联运单的项目收口。
*/
public <T> void applyWaybillProjectScopeIfMpDispatcher(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> waybillIdColumn) {
if (!isMpDispatcher() || wrapper == null || waybillIdColumn == null) {
return;
}
List<Long> projectIds = resolveVisibleProjectIdsForQuery();
if (Func.isEmpty(projectIds)) {
return;
}
String inList = projectIds.stream().map(String::valueOf).collect(Collectors.joining(","));
wrapper.inSql(waybillIdColumn,
"SELECT id FROM blade_waybill WHERE is_deleted = 0 AND project_id IN (" + inList + ")");
}
}