fix(hrm/system): 修复调岗申请与提交报错、公积金误扣、用户搜索,新增核算批量删除

一、调岗申请报错修复
1. 新增报错:hrm_transfer_application.current_dept_id / current_post_id 为 NOT NULL 无默认值,
而客户 133 个员工全部未配置岗位(hrm_employee.post_id 为 NULL),
insert 报 "Field 'current_post_id' doesn't have a default value"。
改为允许 NULL(原部门/原岗位是申请快照,员工未配置时为空合理),目标字段仍必填。
2. 提交报错:submit 接口的 processDefinitionKey 为必填,但前端只传 id,
导致 Spring 参数缺失 → 500 系统异常。改为可选,不传时自动取
「调动审批(hrm_transfer_approve)」分类下最新活跃流程。
3. 撤销报错:前端「撤销」调用的 /hrm/transfer/cancel 后端不存在 → 404。
补端点:校验审批中 → 取消 BPM 流程实例 → 状态置为已撤销。

二、薪资核算
1. 公积金误扣:原来方案未配置公积金比例时会回退默认 12%,
且员工档案无公积金基数时回退基本工资 → 「公司没交公积金却扣了钱」。
改为必须「方案比例 > 0」且「员工公积金基数 > 0」才扣款,否则为 0。
2. 新增批量删除:DELETE /delete-batch?ids=,不限制状态(全选后一键删)。

三、用户搜索(流程模型 - 发起选择人)
UserPageReqVO 新增 keyword,AdminUserMapper 按「账号 OR 姓名」模糊匹配。
原来只支持 username(登录账号),输入中文姓名搜不到。

SQL:hrm_transfer_application_nullable_current.sql(幂等)

实测(mom-zyjc):
- 调岗 create → code 0;submit(不传 key)→ code 0 且生成流程实例;cancel 仅审批中可撤销 ✔
- 核算 2026-12 共 133 条:公积金扣款 0 条、社保扣款 133 条 ✔
- 批量删除 2 条(含已发放状态)→ 删除成功,不存在的 id 返回 0 ✔
- keyword=刘 → 刘卫/刘梦缘/刘占昌;keyword=刘卫 → 1 条;keyword=手机号 → 1 条 ✔
已添加1个文件
已修改9个文件
190 ■■■■■ 文件已修改
sql/mysql/hrm_transfer_application_nullable_current.sql 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_transfer_application_nullable_current.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
-- =============================================
-- è°ƒå²—申请:允许「原部门」「原岗位」为空
--
-- é—®é¢˜ï¼šhrm_transfer_application.current_dept_id / current_post_id ä¸º NOT NULL ä¸”无默认值,
--       è€Œå®¢æˆ·çŽ¯å¢ƒ 133 ä¸ªå‘˜å·¥å…¨éƒ¨æ²¡æœ‰é…ç½®å²—位(hrm_employee.post_id ä¸º NULL),
--       å‰ç«¯æäº¤è°ƒå²—申请时这两个字段为空 â†’ insert æŠ¥
--       "Field 'current_post_id' doesn't have a default value" â†’ è°ƒå²—申请一直提交失败。
--
-- å¤„理:把这两列改为允许 NULL。原部门/原岗位属于「申请时的快照」,员工档案未配置时为空是合理的,
--       ä¸åº”因此阻断申请。目标部门/目标岗位仍保持 NOT NULL(前端必填,属于业务必需)。
--
-- è„šæœ¬å¯é‡å¤æ‰§è¡Œï¼ˆå¹‚ç­‰ï¼‰
-- =============================================
SET NAMES utf8mb4;
SET @sch := DATABASE();
-- 1. åŽŸå²—ä½å…è®¸ä¸ºç©º
SET @nullable := (SELECT IS_NULLABLE FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = @sch AND TABLE_NAME = 'hrm_transfer_application' AND COLUMN_NAME = 'current_post_id');
SET @sql := IF(@nullable = 'NO',
    'ALTER TABLE `hrm_transfer_application` MODIFY COLUMN `current_post_id` bigint NULL DEFAULT NULL COMMENT ''原岗位ID(员工未配置岗位时为空)''',
    'SELECT ''current_post_id å·²å…è®¸ä¸ºç©º'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 2. åŽŸéƒ¨é—¨å…è®¸ä¸ºç©º
SET @nullable := (SELECT IS_NULLABLE FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = @sch AND TABLE_NAME = 'hrm_transfer_application' AND COLUMN_NAME = 'current_dept_id');
SET @sql := IF(@nullable = 'NO',
    'ALTER TABLE `hrm_transfer_application` MODIFY COLUMN `current_dept_id` bigint NULL DEFAULT NULL COMMENT ''原部门ID(员工未配置部门时为空)''',
    'SELECT ''current_dept_id å·²å…è®¸ä¸ºç©º'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 3. æ ¸å¯¹
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @sch AND TABLE_NAME = 'hrm_transfer_application'
  AND COLUMN_NAME IN ('current_dept_id','current_post_id','target_dept_id','target_post_id')
ORDER BY ORDINAL_POSITION;
SELECT '调岗申请 åŽŸéƒ¨é—¨/原岗位 å…è®¸ä¸ºç©º åˆå§‹åŒ–完成' AS result;
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -52,6 +52,7 @@
    ErrorCode TRANSFER_APPLICATION_SAME_DEPT = new ErrorCode(1_030_002_005, "调岗失败,原因:目标部门与原部门相同");
    ErrorCode TRANSFER_APPLICATION_SAME_POST = new ErrorCode(1_030_002_006, "调岗失败,原因:目标岗位与原岗位相同");
    ErrorCode TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_002_007, "调岗审批流程不存在");
    ErrorCode TRANSFER_APPLICATION_CANCEL_FAIL_NOT_PROCESS = new ErrorCode(1_030_002_008, "调岗申请撤销失败,原因:不是审批中状态");
    // ========== ç¦»èŒç”³è¯· 1-030-003-000 ==========
    ErrorCode RESIGNATION_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_003_000, "离职申请不存在");
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java
@@ -82,10 +82,10 @@
    @PostMapping("/submit")
    @Operation(summary = "提交调岗申请审批")
    @Parameter(name = "id", description = "申请ID", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义Key", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义Key(不传时自动使用「调动审批」分类下最新的活跃流程)")
    @PreAuthorize("@ss.hasPermission('hrm:transfer:submit')")
    public CommonResult<Boolean> submitTransferApplication(@RequestParam("id") Long id,
                                                           @RequestParam("processDefinitionKey") String processDefinitionKey) {
                                                           @RequestParam(value = "processDefinitionKey", required = false) String processDefinitionKey) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        transferApplicationService.submitTransferApplication(id, processDefinitionKey, userId);
        return success(true);
@@ -98,6 +98,15 @@
        return success(transferApplicationService.getApproveProcessDefinitionList());
    }
    @PostMapping("/cancel")
    @Operation(summary = "撤销调岗申请")
    @Parameter(name = "id", description = "申请ID", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:transfer:cancel')")
    public CommonResult<Boolean> cancelTransferApplication(@RequestParam("id") Long id) {
        transferApplicationService.cancelTransferApplication(id, SecurityFrameworkUtils.getLoginUserId());
        return success(true);
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出调岗申请 Excel")
    @PreAuthorize("@ss.hasPermission('hrm:transfer:export')")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java
@@ -161,6 +161,14 @@
        return success(true);
    }
    @DeleteMapping("/delete-batch")
    @Operation(summary = "批量删除薪酬核算记录", description = "全选后一键删除,不限制状态")
    @Parameter(name = "ids", description = "核算ID列表", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:salary-calculation:delete')")
    public CommonResult<Integer> deleteSalaryCalculationList(@RequestParam("ids") List<Long> ids) {
        return success(salaryCalculationService.deleteSalaryCalculationList(ids));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出薪酬核算 Excel")
    @PreAuthorize("@ss.hasPermission('hrm:salary-calculation:export')")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java
@@ -83,6 +83,14 @@
    void submitTransferApplication(Long id, String processDefinitionKey, Long userId);
    /**
     * æ’¤é”€è°ƒå²—申请(审批中 â†’ å·²æ’¤é”€ï¼‰
     *
     * @param id     ç”³è¯·ID
     * @param userId æ“ä½œäººï¼ˆå½“前登录用户)
     */
    void cancelTransferApplication(Long id, Long userId);
    /**
     * èŽ·å–å¯ç”¨çš„å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -1,14 +1,17 @@
package cn.iocoder.yudao.module.hrm.service.approval;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO;
@@ -35,6 +38,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -77,6 +81,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -269,17 +275,41 @@
            throw exception(TRANSFER_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
        }
        validateApproveCategoryAndProcessDefinition();
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        // æœªæŒ‡å®šæµç¨‹å®šä¹‰ Key æ—¶ï¼Œè‡ªåŠ¨å–ã€Œè°ƒåŠ¨å®¡æ‰¹ã€åˆ†ç±»ä¸‹å½“å‰æ´»è·ƒçš„æµç¨‹ï¼Œ
        // é¿å…å‰ç«¯æœªä¼  key å¯¼è‡´æŽ¥å£å‚数缺失直接报系统异常
        String key = resolveProcessDefinitionKey(processDefinitionKey);
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(key);
        if (processDefinition == null) {
            throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
                .setProcessDefinitionKey(key).setBusinessKey(String.valueOf(id)));
        transferApplicationMapper.updateById(HrmTransferApplicationDO.builder()
                .id(id)
                .processInstanceId(processInstanceId)
                .status(HrmAuditStatusEnum.PROCESS.getStatus())
                .build());
    }
    /**
     * è§£æžæµç¨‹å®šä¹‰ Key:优先使用调用方传入值,为空时取「调动审批」分类下最新的活跃流程
     */
    private String resolveProcessDefinitionKey(String processDefinitionKey) {
        if (StrUtil.isNotBlank(processDefinitionKey)) {
            return processDefinitionKey.trim();
        }
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
        return processDefinitions.stream()
                .filter(pd -> !pd.isSuspended())
                .max(Comparator.comparingInt(ProcessDefinition::getVersion))
                .map(ProcessDefinition::getKey)
                .orElseThrow(() -> exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS));
    }
    private void validateApproveCategoryAndProcessDefinition() {
@@ -302,6 +332,26 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelTransferApplication(Long id, Long userId) {
        HrmTransferApplicationDO application = validateTransferApplicationExists(id);
        if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(TRANSFER_APPLICATION_CANCEL_FAIL_NOT_PROCESS);
        }
        // å–消对应的 BPM æµç¨‹å®žä¾‹ï¼ˆä»¥å‘起人身份撤销)
        if (StrUtil.isNotBlank(application.getProcessInstanceId())) {
            BpmProcessInstanceCancelReqVO cancelReqVO = new BpmProcessInstanceCancelReqVO();
            cancelReqVO.setId(application.getProcessInstanceId());
            cancelReqVO.setReason("员工撤销调岗申请");
            bpmProcessInstanceService.cancelProcessInstanceByStartUser(userId, cancelReqVO);
        }
        transferApplicationMapper.updateById(HrmTransferApplicationDO.builder()
                .id(id)
                .status(HrmAuditStatusEnum.CANCEL.getStatus())
                .build());
    }
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList() {
        validateApproveCategoryAndProcessDefinition();
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java
@@ -61,6 +61,14 @@
    void deleteSalaryCalculation(Long id);
    /**
     * æ‰¹é‡åˆ é™¤æ ¸ç®—记录(不限制状态,勾选即删)
     *
     * @param ids æ ¸ç®—记录 ID åˆ—表
     * @return å®žé™…删除的条数
     */
    Integer deleteSalaryCalculationList(List<Long> ids);
    /**
     * èŽ·å¾—æŒ‡å®šå‘¨æœŸçš„è–ªé…¬æ ¸ç®—åˆ—è¡¨
     */
    List<HrmSalaryCalculationDO> getSalaryCalculationListByPeriod(String period);
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -277,19 +277,17 @@
        BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
                ? socialSecurity.getSocialSecurityBase() : baseSalary;
        // èŽ·å–å…¬ç§¯é‡‘åŸºæ•°ï¼šå‘˜å·¥ç¤¾ä¿æ¡£æ¡ˆ > å‘˜å·¥åŸºæœ¬å·¥èµ„
        BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
                ? socialSecurity.getHousingFundBase() : baseSalary;
        // ä»Žæ–¹æ¡ˆä¸­èŽ·å–ç¼´çº³æ¯”ä¾‹ï¼ˆä¼˜å…ˆä½¿ç”¨æ–¹æ¡ˆä¸­çš„æ¯”ä¾‹ï¼Œå¦åˆ™ä½¿ç”¨é»˜è®¤æ¯”ä¾‹ï¼‰
        // ä»Žæ–¹æ¡ˆä¸­èŽ·å–ç¤¾ä¿ç¼´çº³æ¯”ä¾‹ï¼ˆæ–¹æ¡ˆæœªé…ç½®æ—¶å›žé€€é»˜è®¤æ¯”ä¾‹ï¼‰
        BigDecimal pensionPersonalRatio = (scheme != null && scheme.getPensionPersonalRatio() != null)
                ? scheme.getPensionPersonalRatio() : DEFAULT_PENSION_PERSONAL_RATIO;
        BigDecimal medicalPersonalRatio = (scheme != null && scheme.getMedicalPersonalRatio() != null)
                ? scheme.getMedicalPersonalRatio() : DEFAULT_MEDICAL_PERSONAL_RATIO;
        BigDecimal unemploymentPersonalRatio = (scheme != null && scheme.getUnemploymentPersonalRatio() != null)
                ? scheme.getUnemploymentPersonalRatio() : DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO;
        BigDecimal housingFundPersonalRatio = (scheme != null && scheme.getHousingFundPersonalRatio() != null)
                ? scheme.getHousingFundPersonalRatio() : DEFAULT_HOUSING_FUND_PERSONAL_RATIO;
        // å…¬ç§¯é‡‘个人比例:只取方案配置值,不再回退默认比例。
        // å¦åˆ™ã€Œå…¬å¸æ²¡é…å…¬ç§¯é‡‘方案」时会被按默认 12% æ‰£æ¬¾ï¼ˆè¿™å°±æ˜¯å®¢æˆ·åé¦ˆçš„问题)。
        BigDecimal housingFundPersonalRatio = scheme != null ? scheme.getHousingFundPersonalRatio() : null;
        // ç¤¾ä¿æ‰£é™¤ = åŸºæ•° Ã— (养老个人比例 + åŒ»ç–—个人比例 + å¤±ä¸šä¸ªäººæ¯”例)
        BigDecimal socialSecurityDeduction = socialSecurityBase
@@ -298,8 +296,15 @@
                .add(socialSecurityBase.multiply(unemploymentPersonalRatio))
                .setScale(2, RoundingMode.HALF_UP);
        // å…¬ç§¯é‡‘扣除 = åŸºæ•° Ã— å…¬ç§¯é‡‘个人比例
        BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP);
        // å…¬ç§¯é‡‘扣除:必须同时满足「方案配置了公积金比例 > 0」且「员工档案配置了公积金基数 > 0」才扣款,
        // ä»»ä¸€ä¸æ»¡è¶³éƒ½è§†ä¸ºå…¬å¸æœªç¼´çº³å…¬ç§¯é‡‘,扣款为 0。
        BigDecimal housingFundDeduction = BigDecimal.ZERO;
        BigDecimal housingFundBase = socialSecurity != null ? socialSecurity.getHousingFundBase() : null;
        if (housingFundPersonalRatio != null && housingFundPersonalRatio.signum() > 0
                && housingFundBase != null && housingFundBase.signum() > 0) {
            housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio)
                    .setScale(2, RoundingMode.HALF_UP);
        }
        // è®¡ç®—请假扣款
        BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
@@ -873,4 +878,25 @@
        salaryCalculationMapper.deleteById(id);
    }
    /**
     * æ‰¹é‡åˆ é™¤æ ¸ç®—记录(全选后一键删除)
     *
     * è¯´æ˜Žï¼šæŒ‰ä¸šåŠ¡è¦æ±‚ï¼Œæ‰¹é‡åˆ é™¤ä¸é™åˆ¶çŠ¶æ€ï¼Œå‹¾é€‰å³åˆ ã€‚
     * ä¸å­˜åœ¨çš„ ID ç›´æŽ¥è·³è¿‡ï¼Œä¸æŠ¥é”™ï¼ˆé¿å…å‰ç«¯å‹¾é€‰åˆ—表与库不一致时整体失败)。
     */
    @Override
    public Integer deleteSalaryCalculationList(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return 0;
        }
        List<Long> existIds = salaryCalculationMapper.selectBatchIds(ids).stream()
                .map(HrmSalaryCalculationDO::getId)
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(existIds)) {
            return 0;
        }
        salaryCalculationMapper.deleteByIds(existIds);
        return existIds.size();
    }
}
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java
@@ -22,6 +22,9 @@
    @Schema(description = "用户账号,模糊匹配", example = "yudao")
    private String username;
    @Schema(description = "关键词,同时模糊匹配「账号」与「姓名」(供选择用户弹窗的搜索框使用)", example = "张三")
    private String keyword;
    @Schema(description = "手机号码,模糊匹配", example = "yudao")
    private String mobile;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.dal.mysql.user;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -26,7 +27,14 @@
    }
    default PageResult<AdminUserDO> selectPage(UserPageReqVO reqVO, Collection<Long> deptIds, Collection<Long> userIds) {
        return selectPage(reqVO, new LambdaQueryWrapperX<AdminUserDO>()
        LambdaQueryWrapperX<AdminUserDO> wrapper = new LambdaQueryWrapperX<>();
        // å…³é”®è¯ï¼šåŒæ—¶æ¨¡ç³ŠåŒ¹é…ã€Œè´¦å·ã€ä¸Žã€Œå§“名」,供选择用户弹窗的搜索框使用
        if (StrUtil.isNotBlank(reqVO.getKeyword())) {
            String keyword = reqVO.getKeyword().trim();
            wrapper.and(w -> w.like(AdminUserDO::getUsername, keyword)
                    .or().like(AdminUserDO::getNickname, keyword));
        }
        return selectPage(reqVO, wrapper
                .likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername())
                .likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile())
                .eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus())