feat(hrm): 新增「我的薪资」,员工可自助查看本人工资条
背景:系统原先只有薪酬核算/薪资发放两个管理端页面,均需 hrm:salary:query
权限。普通员工既看不到菜单,也没有任何只看自己的接口,工资完全不可自助查询。
改动:
- Controller 新增两个接口,均不校验薪酬查询权限,登录即可访问:
* GET /hrm/salary/calculation/my-page 我的薪资分页
* GET /hrm/salary/calculation/my-get 我的薪资详情
- Service 新增对应方法:my-page 强制把查询条件中的 userId 覆盖为登录人、
并清空 deptId;my-get 校验记录归属,非本人记录一律返回 null
- 新增菜单脚本 sql/mysql/hrm_my_payslip_init.sql:顶级菜单「我的薪资」
(id=1065900),授权给所有角色,可重复执行
安全设计(已实测):
- 不传 userId 也能保证只查本人;传了也会被服务端覆盖,无法越权
- my-get 越权查他人 id 统一返回记录不存在,不泄露记录是否存在
- 原有 /page /get 管理端接口保持 hrm:salary:query 权限不变
联调结果(普通角色 ly101 / ly102):
- ly101 登录 => my-page total=1(17858, userId=234)
- ly102 登录 => my-page total=1(13418, userId=235)
- ly102 查他人记录 => 返回薪酬核算记录不存在
- 普通角色访问 /page => 403 没有该操作权限
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ===================================================================== |
| | | -- æçèªèµï¼åå·¥èªå©æ¥çå·¥èµæ¡ï¼åå§åèæ¬ |
| | | -- |
| | | -- èæ¯ï¼ç³»ç»åå
åªæãèªé
¬æ ¸ç® / èªèµåæ¾ã两个管ç端页é¢ï¼ä¸é hrm:salary:query |
| | | -- æéæè½è®¿é®ï¼æ®éåå·¥çä¸å°èªå·±çå·¥èµãæ¬èæ¬æ°å¢ä¸ä¸ªå¯¹ææäººå¯è§ç |
| | | -- ãæçèªèµãèåï¼åç«¯å¯¹åº /hrm/salary/calculation/my-page æ¥å£ï¼ |
| | | -- 该æ¥å£ä¸æ ¡éªèªé
¬æ¥è¯¢æéï¼ç±æå¡ç«¯å¼ºå¶æç»å½äººè¿æ»¤ï¼åªè½çå°æ¬äººè®°å½ã |
| | | -- |
| | | -- ç¹æ§ï¼å¯é夿§è¡ï¼å
æä¸»é®å é¤åæå
¥ï¼ |
| | | -- ===================================================================== |
| | | |
| | | -- --------------------------------------------------------------------- |
| | | -- 1. èåï¼æçèªèµï¼é¡¶çº§èåï¼å¯¹ææç»å½ç¨æ·å¯è§ï¼ |
| | | -- --------------------------------------------------------------------- |
| | | DELETE FROM system_role_menu WHERE menu_id = 1065900; |
| | | |
| | | DELETE FROM system_menu WHERE id = 1065900; |
| | | |
| | | INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, |
| | | component, component_name, status, visible, keep_alive, always_show, |
| | | creator, create_time, updater, update_time, deleted) |
| | | VALUES (1065900, 'æçèªèµ', '', 2, 60, 0, '/my-payslip', 'ep:money', |
| | | 'hrm/salary/my-payslip/index', 'MyPayslip', 0, b'1', b'1', b'1', |
| | | '1', NOW(), '1', NOW(), b'0'); |
| | | |
| | | -- --------------------------------------------------------------------- |
| | | -- 2. ææç»ææè§è²ï¼å«åç»æ°å¢è§è²éæå¨ææï¼æå¨è§è²ç®¡çä¸å¾éï¼ |
| | | -- --------------------------------------------------------------------- |
| | | INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted) |
| | | SELECT r.id, 1065900, '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_role r |
| | | WHERE r.deleted = b'0'; |
| | | |
| | | -- --------------------------------------------------------------------- |
| | | -- 3. éªè¯ |
| | | -- --------------------------------------------------------------------- |
| | | SELECT m.id, m.name, m.path, m.component, m.type |
| | | FROM system_menu m |
| | | WHERE m.id = 1065900; |
| | | |
| | | SELECT r.id AS role_id, r.name AS role_name |
| | | FROM system_role r |
| | | JOIN system_role_menu rm ON rm.role_id = r.id AND rm.menu_id = 1065900 |
| | | WHERE r.deleted = b'0' |
| | | ORDER BY r.id; |
| | |
| | | import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryCalculationService; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_CALCULATION_NOT_EXISTS; |
| | | |
| | | @Tag(name = "管çåå° - HRM èªé
¬æ ¸ç®") |
| | | @RestController |
| | |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | |
| | | @GetMapping("/my-page") |
| | | @Operation(summary = "è·å¾æçèªèµæ ¸ç®å页") |
| | | public CommonResult<PageResult<HrmSalaryCalculationRespVO>> getMySalaryCalculationPage(@Valid HrmSalaryCalculationPageReqVO pageReqVO) { |
| | | // åªæ¥å½åç»å½äººèªå·±çèªèµï¼æ éèªé
¬æ¥è¯¢æéï¼ç»å½å³å¯è®¿é® |
| | | PageResult<HrmSalaryCalculationDO> pageResult = salaryCalculationService |
| | | .getMySalaryCalculationPage(SecurityFrameworkUtils.getLoginUserId(), pageReqVO); |
| | | List<HrmSalaryCalculationRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmSalaryCalculationRespVO.class); |
| | | return success(new PageResult<>(list, pageResult.getTotal())); |
| | | } |
| | | |
| | | @GetMapping("/my-get") |
| | | @Operation(summary = "è·å¾æçèªèµæ ¸ç®") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | public CommonResult<HrmSalaryCalculationRespVO> getMySalaryCalculation(@RequestParam("id") Long id) { |
| | | HrmSalaryCalculationDO calculation = salaryCalculationService |
| | | .getMySalaryCalculation(id, SecurityFrameworkUtils.getLoginUserId()); |
| | | // è®°å½ä¸åå¨ãæä¸å±äºå½åç»å½äººæ¶ç»ä¸è¿åâä¸åå¨âï¼é¿å
æ³é²ä»äººèªèµæ¯å¦åå¨ |
| | | if (calculation == null) { |
| | | throw exception(SALARY_CALCULATION_NOT_EXISTS); |
| | | } |
| | | return success(BeanUtils.toBean(calculation, HrmSalaryCalculationRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾èªé
¬æ ¸ç®å页") |
| | | @PreAuthorize("@ss.hasPermission('hrm:salary:query')") |
| | |
| | | PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾ãæçãèªé
¬æ ¸ç®å页 |
| | | * |
| | | * åªè¿åå½åç»å½äººèªå·±çèªèµè®°å½ï¼å¿½ç¥è¯·æ±åæ°ä¸ç userIdãdeptIdï¼é¿å
è¶ææ¥çä»äººå·¥èµ |
| | | * |
| | | * @param userId å½åç»å½äººID |
| | | * @param pageReqVO æ¥è¯¢æ¡ä»¶ï¼ä»
periodãstatus ççæï¼ |
| | | */ |
| | | PageResult<HrmSalaryCalculationDO> getMySalaryCalculationPage(Long userId, HrmSalaryCalculationPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾ãæçãèªé
¬æ ¸ç® |
| | | * |
| | | * @param id æ ¸ç®ID |
| | | * @param userId å½åç»å½äººID |
| | | * @return æ ¸ç®è®°å½ï¼è®°å½ä¸å卿ä¸å±äºè¯¥ç¨æ·æ¶è¿å null |
| | | */ |
| | | HrmSalaryCalculationDO getMySalaryCalculation(Long id, Long userId); |
| | | |
| | | /** |
| | | * è·å¾èªé
¬æ ¸ç® |
| | | */ |
| | | HrmSalaryCalculationDO getSalaryCalculation(Long id); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<HrmSalaryCalculationDO> getMySalaryCalculationPage(Long userId, HrmSalaryCalculationPageReqVO pageReqVO) { |
| | | // 强å¶éå®ä¸ºå½åç»å½äººï¼å¿½ç¥åç«¯ä¼ å
¥ç userId / deptIdï¼é²æ¢è¶ææ¥çä»äººå·¥èµ |
| | | pageReqVO.setUserId(userId); |
| | | pageReqVO.setDeptId(null); |
| | | return salaryCalculationMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public HrmSalaryCalculationDO getMySalaryCalculation(Long id, Long userId) { |
| | | HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(id); |
| | | if (calculation == null) { |
| | | return null; |
| | | } |
| | | // ä¸å±äºæ¬äººçè®°å½ä¸å¾è§ä¸ºä¸åå¨ï¼é¿å
éè¿ç id è¶ææ¥çä»äººå·¥èµ |
| | | if (!java.util.Objects.equals(calculation.getUserId(), userId)) { |
| | | return null; |
| | | } |
| | | return calculation; |
| | | } |
| | | |
| | | @Override |
| | | public HrmSalaryCalculationDO getSalaryCalculation(Long id) { |
| | | return salaryCalculationMapper.selectById(id); |
| | | } |