diff --git a/bms-back/AR_ROW_SCOPE.md b/bms-back/AR_ROW_SCOPE.md new file mode 100644 index 00000000..44f95894 --- /dev/null +++ b/bms-back/AR_ROW_SCOPE.md @@ -0,0 +1,38 @@ +# 应收账单行级权限 + +规则独立于 `@DataScope` 和角色管理的 `dataScope`,现有菜单、按钮权限保持不变。 + +| roleKey | 数据范围 | +| --- | --- | +| ORA_6041 / ORA_6163 | 销售员、商务专员:本人负责客户 | +| ORA_6040 | 本部门人员负责客户,不含下级部门 | +| ORA_6042 / ORA_6043 | 财务经理、应收财务:全部账单 | + +超级管理员沿用系统判断。仅参与当前入口功能授权的有效角色合并范围;未知角色、空权限对象默认无数据。财务本期不使用账单 `create_user_id`。应付财务不参与应收账单权限规则。 + +归属链为 `ar_bill.customer_id → ctr_customer.sales_user_id → sys_user.user_id / dept_id`。销售员和商务专员均通过客户的 `sales_user_id` 与当前登录用户 ID 匹配本人客户。客户转交后,历史账单跟随当前归属变化。全量角色可见未关联销售、销售已删除、客户已删除的历史账单;受限角色不放行无有效归属的数据。业务员仅停用但未删除时,其部门经理仍可查看。 + +核心实现位于 `bms-system/src/main/java/com/bms/ar/security` 和 `mapper/ar/ArBillScopeMapper.xml`。内部权限对象作为独立 Mapper 参数传递,不绑定 HTTP 查询对象;后台生成和导出任务继续使用独立的内部读取方法。 + +接入范围包括列表、汇总、列表导出、详情/费用、日志、生成预览及提交、生成任务、导出任务及下载、客户账单入口,以及客户/收款页面的待核销账单、核销记录、核销和反核销。应收筛选及生成客户选项分别使用 `/ar/bill/customer-options`、`/ar/bill/generate/customer-options`。 + +用户编辑、调部门、停用、分配角色,以及角色编辑、停用、删除和授权关系调整,会在事务提交后让受影响的登录会话失效;相关用户需要重新登录。不改变角色配置本身。 + +## 本地验证 + +在 `bms-back` 下执行(PowerShell): + +```powershell +mvn -o -pl bms-admin -am -DskipTests compile +``` + +本次新增的行级权限测试用例及其辅助脚本已按要求删除。 + +在 `bms-front` 下执行 `npm run build:prod` 验证前端构建。 + +## 部署前核实 + +- 本次没有新增数据库字段、权限配置表或迁移 SQL。 +- 目标环境 `ctr_customer.sales_user_id` 必须已经统一为系统用户 ID,不能仍是旧业务员工号。现有迁移文件位于 `sql/customer+supplier/20261028_customer_sales_user_migration.sql`,需要按原有流程确认执行状态,本次不自动执行。 +- 使用真实销售 A/B、经理、财务和商务专员账号检查数据范围与功能授权,特别是生产角色实际拥有的接口权限。 +- 上线前仍需在 MySQL 5.7 环境联调,并检查实际数据的执行计划。 diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBillController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBillController.java index 10f354c7..16864fb9 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBillController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/ar/ArBillController.java @@ -35,6 +35,7 @@ import com.bms.ar.domain.vo.ArBillGenerateRequest; import com.bms.ar.domain.vo.ArBillListExportRow; import com.bms.ar.domain.vo.ArBillQuery; import com.bms.ar.service.IArBillService; +import com.bms.ar.security.ArBillScopeResolver; import com.bms.common.annotation.Log; import com.bms.common.core.controller.BaseController; import com.bms.common.core.domain.AjaxResult; @@ -59,6 +60,24 @@ public class ArBillController extends BaseController { @Autowired private IArBillService billService; + @Autowired + private ArBillScopeResolver billScope; + + /** 应收筛选专用客户选项,与账单使用同一角色规则。 */ + @PreAuthorize("@ss.hasPermi('ar:bill:list')") + @GetMapping("/customer-options") + public TableDataInfo customerOptions(@RequestParam(required = false) String keywords) { + startPage(); + return getDataTable(billScope.selectCustomerOptions(keywords)); + } + + @PreAuthorize("@ss.hasPermi('ar:bill:generate')") + @GetMapping("/generate/customer-options") + public TableDataInfo generateCustomerOptions(@RequestParam(required = false) String keywords) { + startPage(); + return getDataTable(billScope.selectCustomerOptions(keywords)); + } + /** * 查询参数中的日期必须严格按 yyyy-MM-dd 解析,禁止非法日期自动滚动到其他日期。 */ diff --git a/bms-back/bms-common/src/main/java/com/bms/common/utils/PermissionContext.java b/bms-back/bms-common/src/main/java/com/bms/common/utils/PermissionContext.java new file mode 100644 index 00000000..802079cf --- /dev/null +++ b/bms-back/bms-common/src/main/java/com/bms/common/utils/PermissionContext.java @@ -0,0 +1,21 @@ +package com.bms.common.utils; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +/** 由服务端功能鉴权写入的请求上下文,不读取请求参数。 */ +public final class PermissionContext { + private static final String ATTRIBUTE = "PERMISSION_CONTEXT"; + + private PermissionContext() {} + + public static void set(String permission) { + RequestContextHolder.currentRequestAttributes().setAttribute(ATTRIBUTE, permission, RequestAttributes.SCOPE_REQUEST); + } + + public static String get() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + Object value = attributes == null ? null : attributes.getAttribute(ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); + return value instanceof String ? (String) value : null; + } +} diff --git a/bms-back/bms-framework/src/main/java/com/bms/framework/aspectj/AuthorizationSessionAspect.java b/bms-back/bms-framework/src/main/java/com/bms/framework/aspectj/AuthorizationSessionAspect.java new file mode 100644 index 00000000..76ec5f63 --- /dev/null +++ b/bms-back/bms-framework/src/main/java/com/bms/framework/aspectj/AuthorizationSessionAspect.java @@ -0,0 +1,78 @@ +package com.bms.framework.aspectj; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import com.bms.common.core.domain.entity.SysRole; +import com.bms.common.core.domain.entity.SysUser; +import com.bms.framework.web.service.TokenService; +import com.bms.system.domain.SysUserRole; + +/** 授权关系修改提交后,让旧登录态失效;不参与业务 SQL 数据范围计算。 */ +@Aspect +@Component +@Order(1) +public class AuthorizationSessionAspect { + private final TokenService tokenService; + + public AuthorizationSessionAspect(TokenService tokenService) { + this.tokenService = tokenService; + } + + @AfterReturning(pointcut = "execution(* com.bms.system.service.impl.SysUserServiceImpl.updateUser(..))" + + " || execution(* com.bms.system.service.impl.SysUserServiceImpl.updateUserStatus(..))" + + " || execution(* com.bms.system.service.impl.SysUserServiceImpl.insertUserAuth(..))" + + " || execution(* com.bms.system.service.impl.SysUserServiceImpl.deleteUserBy*(..))", returning = "result") + public void userChanged(JoinPoint point, Object result) { + if (result instanceof Number && ((Number) result).intValue() <= 0) return; + Object target = point.getArgs()[0]; + Set users = new HashSet<>(); + if (target instanceof SysUser) users.add(((SysUser) target).getUserId()); + else if (target instanceof Long[]) users.addAll(Arrays.asList((Long[]) target)); + else if (target instanceof Long) users.add((Long) target); + invalidate(users, Set.of()); + } + + @AfterReturning(pointcut = "execution(* com.bms.system.service.impl.SysRoleServiceImpl.updateRole(..))" + + " || execution(* com.bms.system.service.impl.SysRoleServiceImpl.updateRoleStatus(..))" + + " || execution(* com.bms.system.service.impl.SysRoleServiceImpl.deleteRoleBy*(..))" + + " || execution(* com.bms.system.service.impl.SysRoleServiceImpl.deleteAuthUser*(..))" + + " || execution(* com.bms.system.service.impl.SysRoleServiceImpl.insertAuthUsers(..))", returning = "result") + public void roleChanged(JoinPoint point, Object result) { + if (result instanceof Number && ((Number) result).intValue() <= 0) return; + Object target = point.getArgs()[0]; + Set users = new HashSet<>(); + Set roles = new HashSet<>(); + if (target instanceof SysUserRole) users.add(((SysUserRole) target).getUserId()); + else if (point.getArgs().length > 1 && point.getArgs()[1] instanceof Long[]) + users.addAll(Arrays.asList((Long[]) point.getArgs()[1])); + else if (target instanceof SysRole) roles.add(((SysRole) target).getRoleId()); + else if (target instanceof Long[]) roles.addAll(Arrays.asList((Long[]) target)); + else if (target instanceof Long) roles.add((Long) target); + invalidate(users, roles); + } + + private void invalidate(Set users, Set roles) { + users.remove(null); + Set roleIds = new HashSet<>(roles); + roleIds.remove(null); + Set userIds = Set.copyOf(users); + if (userIds.isEmpty() && roleIds.isEmpty()) return; + Runnable action = () -> tokenService.evictAuthorizationSessions(userIds, roleIds); + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { action.run(); } + }); + } else { + action.run(); + } + } +} diff --git a/bms-back/bms-framework/src/main/java/com/bms/framework/security/context/PermissionContextHolder.java b/bms-back/bms-framework/src/main/java/com/bms/framework/security/context/PermissionContextHolder.java index 61a55172..ec5f7a0f 100644 --- a/bms-back/bms-framework/src/main/java/com/bms/framework/security/context/PermissionContextHolder.java +++ b/bms-back/bms-framework/src/main/java/com/bms/framework/security/context/PermissionContextHolder.java @@ -1,8 +1,6 @@ package com.bms.framework.security.context; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; -import com.bms.common.core.text.Convert; +import com.bms.common.utils.PermissionContext; /** * 权限信息 @@ -11,17 +9,13 @@ import com.bms.common.core.text.Convert; */ public class PermissionContextHolder { - private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; - public static void setContext(String permission) { - RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, - RequestAttributes.SCOPE_REQUEST); + PermissionContext.set(permission); } public static String getContext() { - return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, - RequestAttributes.SCOPE_REQUEST)); + return PermissionContext.get(); } } diff --git a/bms-back/bms-framework/src/main/java/com/bms/framework/web/service/TokenService.java b/bms-back/bms-framework/src/main/java/com/bms/framework/web/service/TokenService.java index 0c3cbc39..0bac6c99 100644 --- a/bms-back/bms-framework/src/main/java/com/bms/framework/web/service/TokenService.java +++ b/bms-back/bms-framework/src/main/java/com/bms/framework/web/service/TokenService.java @@ -3,6 +3,7 @@ package com.bms.framework.web.service; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -231,6 +232,22 @@ public class TokenService return CacheConstants.LOGIN_TOKEN_KEY + uuid; } + /** 授权或部门变更后失效受影响会话,避免缓存中的旧角色继续扩大业务范围。 */ + public void evictAuthorizationSessions(Set userIds, Set roleIds) + { + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + if (keys == null) return; + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (user == null || user.getUser() == null) continue; + boolean affected = userIds.contains(user.getUserId()) + || (user.getUser().getRoles() != null && user.getUser().getRoles().stream() + .anyMatch(role -> role != null && roleIds.contains(role.getRoleId()))); + if (affected) redisCache.deleteObject(key); + } + } + /** * 角色权限变更后,刷新所有持有该角色的在线用户权限 * diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBalanceWriteoffMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBalanceWriteoffMapper.java index 08842035..3f97cca2 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBalanceWriteoffMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBalanceWriteoffMapper.java @@ -1,6 +1,8 @@ package com.bms.ar.mapper; import java.util.List; +import com.bms.ar.security.ArBillRowScope; + import org.apache.ibatis.annotations.Param; import com.bms.ar.domain.ArBalanceWriteoff; @@ -33,5 +35,5 @@ public interface ArBalanceWriteoffMapper @Param("effectiveStatus") String effectiveStatus); /** 查询核销记录列表(关联账单名称/客户名称) */ - List selectWriteoffList(ArBalanceWriteoff query); + List selectWriteoffList(@Param("query") ArBalanceWriteoff query, @Param("scope") ArBillRowScope scope); } diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailFeeMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailFeeMapper.java index 7b0a0909..5c75df9c 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailFeeMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailFeeMapper.java @@ -1,11 +1,14 @@ package com.bms.ar.mapper; import java.util.List; +import com.bms.ar.security.ArBillRowScope; import org.apache.ibatis.annotations.Param; import com.bms.ar.domain.ArBillDetailFee; public interface ArBillDetailFeeMapper { + List selectByBillIdAndDetailIdScoped(@Param("arBillId") Long arBillId, @Param("detailId") Long detailId, @Param("scope") ArBillRowScope scope); + List selectByDetailId(Long detailId); List selectByBillIdAndDetailId(@Param("arBillId") Long arBillId, diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailMapper.java index 6defde18..15ccdfa9 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillDetailMapper.java @@ -1,12 +1,16 @@ package com.bms.ar.mapper; import java.util.List; +import com.bms.ar.security.ArBillRowScope; + import java.util.Map; import org.apache.ibatis.annotations.Param; import com.bms.ar.domain.ArBillDetail; public interface ArBillDetailMapper { + List selectByBillIdScoped(@Param("arBillId") Long arBillId, @Param("scope") ArBillRowScope scope); + List selectByBillId(Long arBillId); List selectByBillIdAndSourceIds(@Param("arBillId") Long arBillId, diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillGenerateTaskMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillGenerateTaskMapper.java index 5e0d9af4..b0c398ea 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillGenerateTaskMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillGenerateTaskMapper.java @@ -10,6 +10,9 @@ import com.bms.ar.domain.ArBillGenerateTask; * 应收账单异步生成任务 Mapper。 */ public interface ArBillGenerateTaskMapper { + List selectActiveByCreateByScoped(@Param("createBy") String createBy, + @Param("scope") com.bms.ar.security.ArBillRowScope scope); + ArBillGenerateTask selectById(Long taskId); ArBillGenerateTask selectByRequestToken(String requestToken); diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillLogMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillLogMapper.java index d98f5087..01c569bf 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillLogMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillLogMapper.java @@ -1,12 +1,14 @@ package com.bms.ar.mapper; import java.util.List; +import com.bms.ar.security.ArBillRowScope; + import org.apache.ibatis.annotations.Param; import com.bms.ar.domain.ArBillLog; public interface ArBillLogMapper { - List selectList(ArBillLog query); + List selectList(@Param("query") ArBillLog query, @Param("scope") ArBillRowScope scope); ArBillLog selectSuccessByRequestId(@Param("requestId") String requestId, @Param("objectType") String objectType, diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillMapper.java index 2b0b16d3..2c1bd595 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillMapper.java @@ -3,6 +3,7 @@ package com.bms.ar.mapper; import java.math.BigDecimal; import java.util.Date; import java.util.List; +import com.bms.ar.security.ArBillRowScope; import org.apache.ibatis.annotations.Param; import com.bms.ar.domain.ArBill; @@ -10,12 +11,14 @@ import com.bms.ar.domain.vo.ArBillQuery; import com.bms.ar.domain.vo.ArBillSummary; public interface ArBillMapper { - List selectArBillList(ArBillQuery query); + List selectArBillList(@Param("query") ArBillQuery query, @Param("scope") ArBillRowScope scope); - ArBillSummary selectArBillSummary(ArBillQuery query); + ArBillSummary selectArBillSummary(@Param("query") ArBillQuery query, @Param("scope") ArBillRowScope scope); ArBill selectArBillById(Long arBillId); + ArBill selectArBillByIdScoped(@Param("arBillId") Long arBillId, @Param("scope") ArBillRowScope scope); + ArBill selectArBillByBillNo(String billNo); ArBill selectGeneratingByCustomerPeriod(@Param("customerId") Long customerId, @@ -29,7 +32,7 @@ public interface ArBillMapper { /** * 查询客户可核销账单(已确认/部分核销),按账单日期升序,供余额核销选择 */ - List selectUnsettledByCustomer(@Param("customerId") Long customerId); + List selectUnsettledByCustomer(@Param("customerId") Long customerId, @Param("scope") ArBillRowScope scope); int insertArBill(ArBill bill); diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillScopeMapper.java b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillScopeMapper.java new file mode 100644 index 00000000..15cc7490 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/ar/mapper/ArBillScopeMapper.java @@ -0,0 +1,17 @@ +package com.bms.ar.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.bms.ar.security.ArBillRowScope; +import com.bms.customer.customerinfo.domain.BmsCustomerOption; + +public interface ArBillScopeMapper { + int countCustomerSourceBills(@Param("customerId") Long customerId, @Param("sourceIds") List sourceIds); + + int countAccessibleBill(@Param("arBillId") Long arBillId, @Param("scope") ArBillRowScope scope); + + BmsCustomerOption selectAccessibleCustomer(@Param("customerId") Long customerId, @Param("scope") ArBillRowScope scope); + + List selectCustomerOptions(@Param("keywords") List keywords, + @Param("scope") ArBillRowScope scope); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillRoleKeys.java b/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillRoleKeys.java new file mode 100644 index 00000000..850d1427 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillRoleKeys.java @@ -0,0 +1,23 @@ +package com.bms.ar.security; + +import java.util.Set; + +/** 应收业务范围,与角色管理的 dataScope 配置无关。 */ +public final class ArBillRoleKeys { + public static final String SALES = "ORA_6041"; + public static final String BUSINESS_SPECIALIST = "ORA_6163"; + public static final String SALES_MANAGER = "ORA_6040"; + /** 销售员、商务专员:本人负责客户的账单。 */ + public static final Set SELF_DATA_ROLES = Set.of(SALES, BUSINESS_SPECIALIST); + /** 财务经理、应收财务;仍须具备对应接口的功能权限。 */ + public static final Set ALL_DATA_ROLES = Set.of("ORA_6042", "ORA_6043"); + + static { + if (SELF_DATA_ROLES.contains(SALES_MANAGER) || ALL_DATA_ROLES.contains(SALES_MANAGER) + || SELF_DATA_ROLES.stream().anyMatch(ALL_DATA_ROLES::contains)) { + throw new IllegalStateException("应收数据权限角色映射冲突"); + } + } + + private ArBillRoleKeys() {} +} diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillRowScope.java b/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillRowScope.java new file mode 100644 index 00000000..38eec00b --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillRowScope.java @@ -0,0 +1,24 @@ +package com.bms.ar.security; + +/** 仅由后端构造,不能绑定到 HTTP 查询对象。 */ +public final class ArBillRowScope { + private final boolean all; + private final boolean self; + private final boolean department; + private final Long userId; + private final Long deptId; + + ArBillRowScope(boolean all, boolean self, boolean department, Long userId, Long deptId) { + this.all = all; + this.self = self && userId != null && userId > 0; + this.department = department && deptId != null && deptId > 0; + this.userId = userId; + this.deptId = deptId; + } + + public boolean isAll() { return all; } + public boolean isSelf() { return self; } + public boolean isDepartment() { return department; } + public Long getUserId() { return userId; } + public Long getDeptId() { return deptId; } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillScopeResolver.java b/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillScopeResolver.java new file mode 100644 index 00000000..6bf5c4ab --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/ar/security/ArBillScopeResolver.java @@ -0,0 +1,101 @@ +package com.bms.ar.security; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import org.springframework.stereotype.Component; +import com.bms.ar.mapper.ArBillScopeMapper; +import com.bms.common.constant.Constants; +import com.bms.common.core.domain.entity.SysRole; +import com.bms.common.core.domain.entity.SysUser; +import com.bms.common.core.domain.model.LoginUser; +import com.bms.common.exception.ServiceException; +import com.bms.common.utils.PermissionContext; +import com.bms.common.utils.SecurityUtils; +import com.bms.customer.customerinfo.domain.BmsCustomerOption; + +@Component +public class ArBillScopeResolver { + private final ArBillScopeMapper mapper; + + public ArBillScopeResolver(ArBillScopeMapper mapper) { + this.mapper = mapper; + } + + /** 只读取登录态;功能上下文由 @ss.hasPermi 写入,缺失时不授权。 */ + public ArBillRowScope resolve() { + return resolve(SecurityUtils.getLoginUser(), PermissionContext.get()); + } + + public ArBillRowScope resolve(LoginUser actor, String permission) { + if (actor == null || actor.getUser() == null || actor.getUserId() == null) { + throw new ServiceException("登录状态无效,请重新登录", 401); + } + SysUser user = actor.getUser(); + if (!"0".equals(user.getStatus()) || !"0".equals(user.getDelFlag())) { + return new ArBillRowScope(false, false, false, actor.getUserId(), actor.getDeptId()); + } + if (user.isAdmin()) { + return new ArBillRowScope(true, false, false, actor.getUserId(), actor.getDeptId()); + } + boolean all = false; + boolean self = false; + boolean department = false; + if (permission != null && !permission.isBlank() && user.getRoles() != null) { + for (SysRole role : user.getRoles()) { + if (role == null || !"0".equals(role.getStatus()) || !permits(role.getPermissions(), permission)) continue; + String key = role.getRoleKey(); + all |= ArBillRoleKeys.ALL_DATA_ROLES.contains(key == null ? "" : key); + self |= ArBillRoleKeys.SELF_DATA_ROLES.contains(key == null ? "" : key); + department |= ArBillRoleKeys.SALES_MANAGER.equals(key); + } + } + return new ArBillRowScope(all, self, department, actor.getUserId(), actor.getDeptId()); + } + + private boolean permits(Set permissions, String required) { + return permissions != null && (permissions.contains(Constants.ALL_PERMISSION) + || Arrays.stream(required.split(Constants.PERMISSION_DELIMITER)).map(String::trim).anyMatch(permissions::contains)); + } + + public void requireBillAccessible(Long arBillId) { + ArBillRowScope scope = resolve(); + if (arBillId == null || withoutPagination(() -> mapper.countAccessibleBill(arBillId, scope)) == 0) { + throw new ServiceException("账单不存在或无权访问"); + } + } + + public BmsCustomerOption requireCustomerAccessible(Long customerId) { + ArBillRowScope scope = resolve(); + BmsCustomerOption customer = customerId == null ? null + : withoutPagination(() -> mapper.selectAccessibleCustomer(customerId, scope)); + if (customer == null) throw new ServiceException("客户不存在或无权访问"); + return customer; + } + + public List selectCustomerOptions(String keywords) { + List values = keywords == null ? List.of() + : Arrays.stream(keywords.split("\\R")).map(String::trim).filter(s -> !s.isEmpty()).distinct().toList(); + return mapper.selectCustomerOptions(values, resolve()); + } + + public void requireCustomerSourceBills(Long customerId, List sourceIds) { + if (!sourceIds.isEmpty() && withoutPagination(() -> mapper.countCustomerSourceBills(customerId, sourceIds)) != sourceIds.size()) { + throw new ServiceException("所选订单不存在或不属于当前客户"); + } + } + + /** 资源预校验不能消耗 Controller 已设置的分页,最终业务查询仍使用原分页对象。 */ + private T withoutPagination(Supplier query) { + Page page = PageHelper.getLocalPage(); + PageHelper.clearPage(); + try { + return query.get(); + } finally { + if (page != null) PageHelper.setLocalPage(page); + } + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBalanceWriteoffServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBalanceWriteoffServiceImpl.java index 00e91757..44738f62 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBalanceWriteoffServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBalanceWriteoffServiceImpl.java @@ -23,6 +23,7 @@ import com.bms.ar.enums.ArLogOperationType; import com.bms.ar.enums.ArLogResult; import com.bms.ar.mapper.ArBalanceWriteoffMapper; import com.bms.ar.mapper.ArBillMapper; +import com.bms.ar.security.ArBillScopeResolver; import com.bms.ar.service.IArBalanceWriteoffService; import com.bms.ar.service.IArBillLogService; import com.bms.common.exception.ServiceException; @@ -46,6 +47,8 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService { @Autowired private ArBillMapper billMapper; + @Autowired + private ArBillScopeResolver billScope; @Autowired private ArBalanceWriteoffMapper writeoffMapper; @@ -67,7 +70,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService { if (customerId == null) { return List.of(); } - return billMapper.selectUnsettledByCustomer(customerId); + return billMapper.selectUnsettledByCustomer(customerId, billScope.resolve()); } /** @@ -75,7 +78,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService { */ @Override public List selectWriteoffList(ArBalanceWriteoff query) { - return writeoffMapper.selectWriteoffList(query); + return writeoffMapper.selectWriteoffList(query, billScope.resolve()); } /** @@ -103,6 +106,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService { if (bill == null) { throw new ServiceException("应收账单不存在"); } + billScope.requireBillAccessible(bill.getArBillId()); ArBillStatus status = ArBillStatus.of(bill.getStatus()); if (status == null || !status.canWriteOff()) { throw new ServiceException("当前账单状态不允许核销:" + (status == null ? bill.getStatus() : status.getLabel())); @@ -170,6 +174,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService { if (writeoff == null) { throw new ServiceException("核销记录不存在"); } + billScope.requireBillAccessible(writeoff.getArBillId()); if (!ArBalanceWriteoffStatus.EFFECTIVE.getCode().equals(writeoff.getStatus())) { throw new ServiceException("只有有效状态的核销记录可以反核销"); } diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillLogServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillLogServiceImpl.java index b98e5b38..4231daa2 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillLogServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillLogServiceImpl.java @@ -12,6 +12,7 @@ import com.bms.ar.enums.ArLogOperationSource; import com.bms.ar.enums.ArLogOperationType; import com.bms.ar.enums.ArLogResult; import com.bms.ar.mapper.ArBillLogMapper; +import com.bms.ar.security.ArBillScopeResolver; import com.bms.ar.service.IArBillLogService; import com.bms.common.utils.uuid.IdUtils; @@ -19,10 +20,12 @@ import com.bms.common.utils.uuid.IdUtils; public class ArBillLogServiceImpl implements IArBillLogService { @Autowired private ArBillLogMapper logMapper; + @Autowired + private ArBillScopeResolver billScope; @Override public List selectLogList(ArBillLog query) { - return logMapper.selectList(query); + return logMapper.selectList(query, billScope.resolve()); } @Override diff --git a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java index 16d18791..890d047c 100644 --- a/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/ar/service/impl/ArBillServiceImpl.java @@ -70,6 +70,8 @@ import com.bms.ar.mapper.ArBillDetailMapper; import com.bms.ar.mapper.ArBillExportMapper; import com.bms.ar.mapper.ArBillLogMapper; import com.bms.ar.mapper.ArBillMapper; +import com.bms.ar.security.ArBillScopeResolver; +import com.bms.customer.customerinfo.domain.BmsCustomerOption; import com.bms.ar.mapper.ArBillGenerateTaskMapper; import com.bms.ar.mapper.ArBankAccountMapper; import com.bms.ar.mapper.ArExportTemplateMapper; @@ -182,6 +184,8 @@ public class ArBillServiceImpl implements IArBillService { @Autowired private ArBillMapper billMapper; @Autowired + private ArBillScopeResolver billScope; + @Autowired private ArBillGenerateTaskMapper generateTaskMapper; @Autowired private ArBankAccountMapper bankAccountMapper; @@ -214,7 +218,8 @@ public class ArBillServiceImpl implements IArBillService { @Override public List selectArBillList(ArBillQuery query) { long startedAt = System.nanoTime(); - List bills = billMapper.selectArBillList(query); + query = query == null ? new ArBillQuery() : query; + List bills = billMapper.selectArBillList(query, billScope.resolve()); for (ArBill bill : bills) { bill.setBankAccountNo(maskAccount(bill.getBankAccountNo())); } @@ -229,7 +234,7 @@ public class ArBillServiceImpl implements IArBillService { */ @Override public ArBillSummary selectArBillSummary(ArBillQuery query) { - return billMapper.selectArBillSummary(query); + return billMapper.selectArBillSummary(query == null ? new ArBillQuery() : query, billScope.resolve()); } /** @@ -238,9 +243,9 @@ public class ArBillServiceImpl implements IArBillService { @Override public ArBill selectArBillById(Long arBillId) { long startedAt = System.nanoTime(); - ArBill bill = billMapper.selectArBillById(arBillId); + ArBill bill = billMapper.selectArBillByIdScoped(arBillId, billScope.resolve()); if (bill == null) { - throw new ServiceException("应收账单不存在"); + throw new ServiceException("账单不存在或无权访问"); } bill.setBankAccountNo(maskAccount(bill.getBankAccountNo())); bill.setExports(exportMapper.selectByBillId(arBillId)); @@ -256,7 +261,7 @@ public class ArBillServiceImpl implements IArBillService { @Override public List selectArBillDetails(Long arBillId) { long startedAt = System.nanoTime(); - List details = detailMapper.selectByBillId(arBillId); + List details = detailMapper.selectByBillIdScoped(arBillId, billScope.resolve()); log.debug("应收账单明细查询完成,arBillId={}, resultCount={}, costMs={}", arBillId, details.size(), elapsedMillis(startedAt)); return details; @@ -268,7 +273,7 @@ public class ArBillServiceImpl implements IArBillService { @Override public List selectArBillDetailFees(Long arBillId, Long detailId) { long startedAt = System.nanoTime(); - List fees = detailFeeMapper.selectByBillIdAndDetailId(arBillId, detailId); + List fees = detailFeeMapper.selectByBillIdAndDetailIdScoped(arBillId, detailId, billScope.resolve()); log.debug("应收账单费用明细查询完成,arBillId={}, detailId={}, resultCount={}, costMs={}", arBillId, detailId, fees.size(), elapsedMillis(startedAt)); return fees; @@ -308,6 +313,8 @@ public class ArBillServiceImpl implements IArBillService { public List previewBill(ArBillGenerateQuery query) { long startedAt = System.nanoTime(); validatePreviewQuery(query); + BmsCustomerOption customer = billScope.requireCustomerAccessible(query.getCustomerId()); + query.setCustomerCode(customer.getCustomerCode()); List orderIdentifiers = normalizeOrderIdentifiers(query.getOrderOrTrackingNo()); List excludeOrderIdentifiers = normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo()); List result = sourceBillMapper.selectPrcOrderBillByCustomerPeriod(query.getCustomerId(), @@ -336,6 +343,8 @@ public class ArBillServiceImpl implements IArBillService { public BigDecimal sumPreviewBill(ArBillGenerateQuery query) { long startedAt = System.nanoTime(); validatePreviewQuery(query); + BmsCustomerOption customer = billScope.requireCustomerAccessible(query.getCustomerId()); + query.setCustomerCode(customer.getCustomerCode()); List orderIdentifiers = normalizeOrderIdentifiers(query.getOrderOrTrackingNo()); List excludeOrderIdentifiers = normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo()); BigDecimal total = nvl(sourceBillMapper.sumPrcOrderBillIncomeByCustomerPeriod(query.getCustomerId(), @@ -359,8 +368,12 @@ public class ArBillServiceImpl implements IArBillService { long startedAt = System.nanoTime(); Long createUserId = SecurityUtils.getUserId(); validatePeriod(request); + BmsCustomerOption customer = billScope.requireCustomerAccessible(request.getCustomerId()); + request.setCustomerCode(customer.getCustomerCode()); + request.setCustomerName(customer.getCustomerName()); ArBillGenerateTask existed = generateTaskMapper.selectByRequestToken(requestId); if (existed != null) { + billScope.requireBillAccessible(existed.getArBillId()); log.info("应收账单生成请求命中幂等记录,operator={}, requestId={}, taskId={}, status={}, costMs={}", safeLogValue(operator), safeLogValue(requestId), existed.getTaskId(), existed.getStatus(), elapsedMillis(startedAt)); @@ -401,6 +414,7 @@ public class ArBillServiceImpl implements IArBillService { validateOrderIdentifierParameterCount( orderIdentifierCount, allMatched ? excludedSourceBillIds.size() : sourceBillIds.size()); + billScope.requireCustomerSourceBills(request.getCustomerId(), allMatched ? excludedSourceBillIds : sourceBillIds); request.setSelectionMode(selectionMode); String currency = StringUtils.isEmpty(request.getCurrency()) ? "CNY" : request.getCurrency().trim(); @@ -478,8 +492,9 @@ public class ArBillServiceImpl implements IArBillService { long startedAt = System.nanoTime(); ArBillGenerateTask task = generateTaskMapper.selectById(taskId); if (task == null) { - throw new ServiceException("账单生成任务不存在"); + throw new ServiceException("账单不存在或无权访问"); } + billScope.requireBillAccessible(task.getArBillId()); log.debug("应收账单生成任务状态查询,taskId={}, status={}, processedCount={}, totalCount={}, costMs={}", taskId, task.getStatus(), task.getProcessedCount(), task.getTotalCount(), elapsedMillis(startedAt)); return task; @@ -491,7 +506,7 @@ public class ArBillServiceImpl implements IArBillService { @Override public List selectActiveGenerateTasks(String operator) { long startedAt = System.nanoTime(); - List tasks = generateTaskMapper.selectActiveByCreateBy(operator); + List tasks = generateTaskMapper.selectActiveByCreateByScoped(operator, billScope.resolve()); log.debug("当前用户应收账单生成任务查询,operator={}, taskCount={}, costMs={}", safeLogValue(operator), tasks.size(), elapsedMillis(startedAt)); return tasks; @@ -504,7 +519,9 @@ public class ArBillServiceImpl implements IArBillService { @Transactional(rollbackFor = Exception.class) public ArBillGenerateTask cancelGenerateTask(Long taskId, String operator, String requestId) { long startedAt = System.nanoTime(); - ArBillGenerateTask task = selectGenerateTask(taskId); + ArBillGenerateTask task = generateTaskMapper.selectById(taskId); + if (task == null) throw new ServiceException("账单不存在或无权访问"); + billScope.requireBillAccessible(task.getArBillId()); if (!operator.equals(task.getCreateBy())) { throw new ServiceException("只能取消自己创建的账单生成任务"); } @@ -533,7 +550,9 @@ public class ArBillServiceImpl implements IArBillService { @Transactional(rollbackFor = Exception.class) public ArBillGenerateTask retryGenerateTask(Long taskId, String operator, String requestId) { long startedAt = System.nanoTime(); - ArBillGenerateTask task = selectGenerateTask(taskId); + ArBillGenerateTask task = generateTaskMapper.selectById(taskId); + if (task == null) throw new ServiceException("账单不存在或无权访问"); + billScope.requireBillAccessible(task.getArBillId()); if (!operator.equals(task.getCreateBy())) { throw new ServiceException("只能重新生成自己创建的账单任务"); } @@ -923,6 +942,7 @@ public class ArBillServiceImpl implements IArBillService { public ArBill confirmBill(Long arBillId, ArBillActionRequest request, String operator, String requestId) { long startedAt = System.nanoTime(); ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId); + billScope.requireBillAccessible(arBillId); if (bill == null) { throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM, null, "应收账单不存在", operator, requestId); @@ -972,6 +992,7 @@ public class ArBillServiceImpl implements IArBillService { null, "作废必须填写原因", operator, requestId); } ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId); + billScope.requireBillAccessible(arBillId); if (bill == null) { throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID, null, "应收账单不存在", operator, requestId); @@ -1011,11 +1032,13 @@ public class ArBillServiceImpl implements IArBillService { public ArBillExport exportBill(Long arBillId, String operator, String requestId) { long startedAt = System.nanoTime(); Long exportUserId = SecurityUtils.getUserId(); + billScope.requireBillAccessible(arBillId); ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, ArLogObjectType.EXPORT.getCode(), ArLogResult.SUCCESS.getCode()); if (previous != null && isNumber(previous.getObjectId())) { ArBillExport previousExport = exportMapper.selectById(Long.valueOf(previous.getObjectId())); if (previousExport != null) { + billScope.requireBillAccessible(previousExport.getArBillId()); log.info("应收账单导出请求命中幂等记录,operator={}, requestId={}, arBillId={}, exportId={}, status={}, costMs={}", safeLogValue(operator), safeLogValue(requestId), arBillId, previousExport.getExportId(), previousExport.getExportStatus(), elapsedMillis(startedAt)); @@ -1084,9 +1107,8 @@ public class ArBillServiceImpl implements IArBillService { public ArBillExport selectExport(Long exportId) { long startedAt = System.nanoTime(); ArBillExport export = exportMapper.selectById(exportId); - if (export == null) { - throw new ServiceException("导出任务不存在"); - } + if (export == null) throw new ServiceException("账单不存在或无权访问"); + billScope.requireBillAccessible(export.getArBillId()); log.debug("应收账单导出任务状态查询,exportId={}, status={}, costMs={}", exportId, export.getExportStatus(), elapsedMillis(startedAt)); return export; @@ -1167,6 +1189,7 @@ public class ArBillServiceImpl implements IArBillService { @Override public List selectExports(Long arBillId) { long startedAt = System.nanoTime(); + billScope.requireBillAccessible(arBillId); List exports = exportMapper.selectByBillId(arBillId); log.debug("应收账单导出记录查询,arBillId={}, exportCount={}, costMs={}", arBillId, exports.size(), elapsedMillis(startedAt)); @@ -1177,7 +1200,11 @@ public class ArBillServiceImpl implements IArBillService { public void downloadExport(Long exportId, HttpServletResponse response) throws Exception { long startedAt = System.nanoTime(); ArBillExport export = exportMapper.selectById(exportId); - if (export == null || !ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey())) { + if (export == null) { + throw new ServiceException("账单不存在或无权访问"); + } + billScope.requireBillAccessible(export.getArBillId()); + if (!ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey())) { throw new ServiceException("导出文件不存在或尚未生成成功"); } String resource = export.getStorageKey(); diff --git a/bms-back/bms-system/src/main/resources/mapper/ar/ArBalanceWriteoffMapper.xml b/bms-back/bms-system/src/main/resources/mapper/ar/ArBalanceWriteoffMapper.xml index 852f39fb..3ddd6c46 100644 --- a/bms-back/bms-system/src/main/resources/mapper/ar/ArBalanceWriteoffMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/ar/ArBalanceWriteoffMapper.xml @@ -77,22 +77,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where writeoff_id = #{writeoffId} and status = #{effectiveStatus} - - and w.customer_id = #{customerId} - and w.ar_bill_id = #{arBillId} - and w.status = #{status} - and w.currency = #{currency} - and w.writeoff_no like concat('%', #{writeoffNo}, '%') - and w.bill_no like concat('%', #{billNo}, '%') - and w.receipt_id = #{receiptId} - - and date_format(w.writeoff_date,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + and w.customer_id = #{query.customerId} + and w.ar_bill_id = #{query.arBillId} + and w.status = #{query.status} + and w.currency = #{query.currency} + and w.writeoff_no like concat('%', #{query.writeoffNo}, '%') + and w.bill_no like concat('%', #{query.billNo}, '%') + and w.receipt_id = #{query.receiptId} + + and date_format(w.writeoff_date,'%y%m%d') >= date_format(#{query.params.beginTime},'%y%m%d') - - and date_format(w.writeoff_date,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + + and date_format(w.writeoff_date,'%y%m%d') <= date_format(#{query.params.endTime},'%y%m%d') + order by w.create_time desc, w.writeoff_id desc diff --git a/bms-back/bms-system/src/main/resources/mapper/ar/ArBillDetailFeeMapper.xml b/bms-back/bms-system/src/main/resources/mapper/ar/ArBillDetailFeeMapper.xml index a4347a19..3866eeb9 100644 --- a/bms-back/bms-system/src/main/resources/mapper/ar/ArBillDetailFeeMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/ar/ArBillDetailFeeMapper.xml @@ -78,6 +78,34 @@ order by f.fee_no, f.detail_fee_id + + + + + + + diff --git a/bms-back/bms-system/src/main/resources/mapper/ar/ArBillMapper.xml b/bms-back/bms-system/src/main/resources/mapper/ar/ArBillMapper.xml index e695f45e..ed8d8445 100644 --- a/bms-back/bms-system/src/main/resources/mapper/ar/ArBillMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/ar/ArBillMapper.xml @@ -76,34 +76,35 @@ and (generation_status is null or generation_status = 'READY') - and bill_no like concat('%', #{billNo}, '%') - and bill_name like concat('%', #{billName}, '%') - and customer_code like concat('%', #{customerCode}, + and bill_no like concat('%', #{query.billNo}, '%') + and bill_name like concat('%', #{query.billName}, '%') + and customer_code like concat('%', #{query.customerCode}, '%') - and customer_name like concat('%', #{customerName}, + and customer_name like concat('%', #{query.customerName}, '%') - and customer_id = #{customerId} - and status = #{status} - and currency = #{currency} - and bill_date >= #{billDateStart} - and bill_date <= #{billDateEnd} - and bill_period_end = #{billPeriodEnd} + and customer_id = #{query.customerId} + and status = #{query.status} + and currency = #{query.currency} + and bill_date >= #{query.billDateStart} + and bill_date <= #{query.billDateEnd} + and bill_period_end = #{query.billPeriodEnd} + - + select b.* from ar_bill b order by create_time desc, ar_bill_id desc - select coalesce(sum(total_amount), 0) as total_amount, coalesce(sum(written_off_amount), 0) as written_off_amount, coalesce(sum(unwritten_amount), 0) as unwritten_amount - from ar_bill + from ar_bill b @@ -112,6 +113,11 @@ where ar_bill_id = #{arBillId} + + diff --git a/bms-back/bms-system/src/main/resources/mapper/ar/ArBillScopeMapper.xml b/bms-back/bms-system/src/main/resources/mapper/ar/ArBillScopeMapper.xml new file mode 100644 index 00000000..90c88d43 --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/ar/ArBillScopeMapper.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + AND c.del_flag = '0' + AND EXISTS ( + SELECT 1 FROM sys_user su + WHERE CAST(su.user_id AS CHAR) = c.sales_user_id AND su.del_flag = '0' + AND (1 = 0 + OR su.user_id = #{scope.userId} + OR su.dept_id = #{scope.deptId} + ) + ) + + AND 1 = 0 + + + + + + + + AND EXISTS (SELECT 1 FROM ctr_customer c WHERE c.customer_id = b.customer_id + + ) + + + + + + + diff --git a/bms-front/src/api/ar/bill.js b/bms-front/src/api/ar/bill.js index bb82aee1..6e080c87 100644 --- a/bms-front/src/api/ar/bill.js +++ b/bms-front/src/api/ar/bill.js @@ -1,5 +1,13 @@ import request from '@/utils/request' +export function listArBillCustomerOptions(query) { + return request({ url: '/ar/bill/customer-options', method: 'get', params: query }) +} + +export function listArBillGenerateCustomerOptions(query) { + return request({ url: '/ar/bill/generate/customer-options', method: 'get', params: query }) +} + // 应收账单接口统一放在这里;生成和导出均为异步任务,提交接口不等待后台处理完成。 export function listArBill(query) { return request({ url: '/ar/bill/list', method: 'get', params: query }) diff --git a/bms-front/src/components/CustomerSelect/index.vue b/bms-front/src/components/CustomerSelect/index.vue index bb457dea..b47e2a1e 100644 --- a/bms-front/src/components/CustomerSelect/index.vue +++ b/bms-front/src/components/CustomerSelect/index.vue @@ -36,6 +36,11 @@ import { listCustomer, listCustomerOption } from '@/api/customersuppliermanageme import { listSupplier } from '@/api/customersuppliermanagement/supplierinfo/supplier.js' const props = defineProps({ + // 业务页面可传入专用选项接口,默认行为保持不变。 + fetchOptions: { + type: Function, + default: undefined + }, // 单选时为 String/Number;multiple 时为数组 modelValue: { type: [String, Number, Array], @@ -143,9 +148,9 @@ const displayOptions = computed(() => { function fetchList(keyword = '') { if (!props.remoteSearch && loaded.value) return Promise.resolve(options.value) loading.value = true - const api = !isCustomer.value + const api = props.fetchOptions || (!isCustomer.value ? listSupplier - : (props.optionSelect ? listCustomerOption : listCustomer) + : (props.optionSelect ? listCustomerOption : listCustomer)) const currentRequest = ++requestSerial const query = String(keyword || '').trim() return api({ diff --git a/bms-front/src/views/ar/bill/components/GenerateBillDialog.vue b/bms-front/src/views/ar/bill/components/GenerateBillDialog.vue index 0fc159b2..eb1e7094 100644 --- a/bms-front/src/views/ar/bill/components/GenerateBillDialog.vue +++ b/bms-front/src/views/ar/bill/components/GenerateBillDialog.vue @@ -3,7 +3,7 @@ - + @@ -146,7 +146,7 @@