diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/controller/FirmReportsController.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/controller/FirmReportsController.java index b786846..772c471 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/controller/FirmReportsController.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/controller/FirmReportsController.java @@ -3,6 +3,7 @@ package net.lab1024.sa.admin.module.cost.controller; import net.lab1024.sa.admin.module.cost.domain.form.FirmReportsAddForm; import net.lab1024.sa.admin.module.cost.domain.form.FirmReportsQueryForm; import net.lab1024.sa.admin.module.cost.domain.form.FirmReportsUpdateForm; +import net.lab1024.sa.admin.module.cost.domain.form.FirmReportsBatchCreateForm; import net.lab1024.sa.admin.module.cost.domain.vo.FirmReportsCountVO; import net.lab1024.sa.admin.module.cost.domain.vo.FirmReportsVO; import net.lab1024.sa.admin.module.cost.service.FirmReportsService; @@ -19,6 +20,7 @@ import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDate; import java.time.Month; +import java.util.Map; /** * 律师事务所年度经营报表 Controller @@ -89,6 +91,20 @@ public class FirmReportsController { return firmReportsService.commit(idList); } + //提交前预检查 + @Operation(summary = "提交前预检查 @author wzh") + @PostMapping("/firmReports/preCommitCheck") + public ResponseDTO> preCommitCheck(@RequestBody ValidateList idList) { + return firmReportsService.preCommitCheck(idList); + } + + //批量创建缺失季度记录 + @Operation(summary = "批量创建缺失季度记录 @author wzh") + @PostMapping("/firmReports/batchCreateMissing") + public ResponseDTO batchCreateMissing(@RequestBody @Valid FirmReportsBatchCreateForm form) { + return firmReportsService.batchCreateMissing(form.getFirmId(), form.getYear(), form.getQuarters()); + } + //驳回 @Operation(summary = "驳回 @author wzh") @PostMapping("/firmReports/reject") diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/dao/FirmReportsDao.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/dao/FirmReportsDao.java index 467f617..1ab3f5d 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/dao/FirmReportsDao.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/dao/FirmReportsDao.java @@ -81,4 +81,15 @@ public interface FirmReportsDao extends BaseMapper { List selectByFirmIdYearAndQuarter(@Param("firmId") Integer firmId, @Param("year") Integer year, @Param("quarter") String quarter); + + /** + * 查询当前季度之前尚未提交的季度列表(支持跨年) + * @param firmId 律所ID + * @param year 年份 + * @param currentQuarter 当前季度 + * @return 未提交的季度描述列表,如["2025年第1季度", "2025年第2季度"] + */ + List findUnsubmittedEarlierQuarters(@Param("firmId") Integer firmId, + @Param("year") Integer year, + @Param("currentQuarter") Integer currentQuarter); } diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/service/FirmReportsService.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/service/FirmReportsService.java index 21dc3fd..529c4e7 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/service/FirmReportsService.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/service/FirmReportsService.java @@ -1,7 +1,11 @@ package net.lab1024.sa.admin.module.cost.service; +import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import net.lab1024.sa.admin.common.enums.ReviewEnum; import net.lab1024.sa.admin.module.cost.dao.FirmReportsDao; @@ -18,6 +22,7 @@ import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; import net.lab1024.sa.admin.util.AdminRequestUtil; import net.lab1024.sa.admin.util.DateTimeUtil; import net.lab1024.sa.base.common.code.UnexpectedErrorCode; +import net.lab1024.sa.base.common.code.UserErrorCode; import net.lab1024.sa.base.common.domain.ValidateList; import net.lab1024.sa.base.common.enumeration.UserTypeEnum; import net.lab1024.sa.base.common.util.SmartBeanUtil; @@ -57,12 +62,14 @@ public class FirmReportsService { Page page = SmartPageUtil.convert2PageQuery(queryForm); RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); + if (!hasCostPermission(requestUser)) { + return SmartPageUtil.convert2PageResult(page, Collections.emptyList()); + } List roleIdList = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId()); String roleCode = AdminRequestUtil.getRoleCode(roleIdList); if (!UserTypeEnum.Admin.getDesc().equals(roleCode)) { if (AdminRequestUtil.isAssociationRole(roleIdList)) { - queryForm.setUserId(requestUser.getEmployeeId()); queryForm.setIncludeSubmitted(true); } else if (AdminRequestUtil.isFirmRole(roleIdList)) { queryForm.setFirmId(Math.toIntExact(requestUser.getDepartmentId())); @@ -94,6 +101,9 @@ public class FirmReportsService { * 添加 */ public ResponseDTO add(FirmReportsAddForm addForm) { + if (!hasCostPermission(AdminRequestUtil.getRequestUser())) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } // 使用季度检查重复数据 FirmReportsEntity existing = firmReportsDao.selectList(addForm.getDeclareQuarter(), addForm.getDeclareYear(), addForm.getFirmId()); if (existing != null ) { @@ -114,6 +124,9 @@ public class FirmReportsService { * */ public ResponseDTO update(FirmReportsUpdateForm updateForm) { + if (!hasCostPermission(AdminRequestUtil.getRequestUser())) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } // 检查记录是否存在 FirmReportsEntity currentEntity = firmReportsDao.selectById(updateForm.getId()); if (currentEntity == null) { @@ -140,6 +153,9 @@ public class FirmReportsService { * 批量删除 */ public ResponseDTO batchDelete(List idList) { + if (!hasCostPermission(AdminRequestUtil.getRequestUser())) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } if (CollectionUtils.isEmpty(idList)){ return ResponseDTO.ok(); } @@ -160,6 +176,9 @@ public class FirmReportsService { * 单个删除 */ public ResponseDTO delete(Integer id) { + if (!hasCostPermission(AdminRequestUtil.getRequestUser())) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } if (null == id){ return ResponseDTO.ok(); } @@ -175,6 +194,9 @@ public class FirmReportsService { } public ResponseDTO commit(ValidateList idList) { + if (!hasCostPermission(AdminRequestUtil.getRequestUser())) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } if (CollectionUtils.isEmpty(idList)) { return ResponseDTO.ok(); } @@ -189,16 +211,92 @@ public class FirmReportsService { if (entity.getApprovalStatus() != null && (entity.getApprovalStatus() == 3 || entity.getApprovalStatus() == 4)) { return ResponseDTO.userErrorParam("记录已审批,不能重复提交"); } + + // 校验:提交当前季度前,必须已提交前面所有季度的数据(支持跨年) + Integer firmId = entity.getFirmId(); + Integer year = entity.getDeclareYear(); + Integer currentQuarter = Integer.valueOf(entity.getDeclareQuarter()); + + List unsubmittedQuarters = firmReportsDao.findUnsubmittedEarlierQuarters(firmId, year, currentQuarter); + if (unsubmittedQuarters != null && !unsubmittedQuarters.isEmpty()) { + String quarterNames = String.join("、", unsubmittedQuarters); + return ResponseDTO.userErrorParam( + "请先提交" + quarterNames + "的成本报表后,再提交" + year + "年第" + currentQuarter + "季度"); + } } firmReportsDao.commit(idList); return ResponseDTO.ok(); } + /** + * 提交前预检查,返回缺失的前置季度列表 + */ + public ResponseDTO> preCommitCheck(ValidateList idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(Map.of("hasMissing", false)); + } + + for (Integer id : idList) { + FirmReportsEntity entity = firmReportsDao.selectById(id); + if (entity == null) { + continue; + } + + Integer firmId = entity.getFirmId(); + Integer year = entity.getDeclareYear(); + Integer currentQuarter = Integer.valueOf(entity.getDeclareQuarter()); + + List unsubmittedQuarters = firmReportsDao.findUnsubmittedEarlierQuarters(firmId, year, currentQuarter); + if (unsubmittedQuarters != null && !unsubmittedQuarters.isEmpty()) { + Map result = new HashMap<>(); + result.put("hasMissing", true); + result.put("missingQuarters", unsubmittedQuarters); + result.put("firmId", firmId); + result.put("year", year); + result.put("currentQuarter", currentQuarter); + return ResponseDTO.ok(result); + } + } + + return ResponseDTO.ok(Map.of("hasMissing", false)); + } + + /** + * 批量创建缺失季度记录(空白报表) + */ + public ResponseDTO batchCreateMissing(Integer firmId, Integer year, List quarters) { + if (firmId == null || year == null || quarters == null || quarters.isEmpty()) { + return ResponseDTO.userErrorParam("参数不能为空"); + } + + RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); + for (Integer quarter : quarters) { + FirmReportsEntity entity = new FirmReportsEntity(); + entity.setFirmId(firmId); + entity.setDeclareYear(year); + entity.setDeclareQuarter(String.valueOf(quarter)); + entity.setUserId(requestUser.getEmployeeId()); + entity.setApprovalStatus(0); + entity.setDelFlag(0); + entity.setRevenue(BigDecimal.ZERO); + entity.setTotalCost(BigDecimal.ZERO); + entity.setPublicWelfareCost(BigDecimal.ZERO); + entity.setCostIncomeRatio(BigDecimal.ZERO); + entity.setActualPublicWelfareCost(BigDecimal.ZERO); + entity.setActualCostIncomeRatio(BigDecimal.ZERO); + firmReportsDao.insert(entity); + } + return ResponseDTO.ok(); + } + /** * 驳回(将已提交的数据驳回为未提交状态) */ public ResponseDTO reject(ValidateList idList) { + if (!hasCostPermission(AdminRequestUtil.getRequestUser())) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } if (CollectionUtils.isEmpty(idList)) { return ResponseDTO.ok(); } @@ -224,6 +322,9 @@ public class FirmReportsService { */ public ResponseDTO query(Integer quarter, Integer Year) { RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); + if (!hasCostPermission(requestUser)) { + return ResponseDTO.ok(false); + } List roleIdList = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId()); // 只有律所主任或行政可以填报 @@ -237,6 +338,12 @@ public class FirmReportsService { public FirmReportsCountVO income(Integer year) { // 获取当前用户 RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); + if (!hasCostPermission(requestUser)) { + FirmReportsCountVO empty = new FirmReportsCountVO(); + empty.setRevenue(BigDecimal.ZERO); + empty.setTotalCost(BigDecimal.ZERO); + return empty; + } if (year == null) { year = DateTimeUtil.getCurrentYear(); } @@ -246,9 +353,16 @@ public class FirmReportsService { public Long firmReportsCost(Integer year) { // 获取当前用户 RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); + if (!hasCostPermission(requestUser)) { + return 0L; + } if (year == null) { year = DateTimeUtil.getCurrentYear(); } return firmReportsDao.firmReportsCost(requestUser.getDepartmentId(), year); } -} \ No newline at end of file + + private boolean hasCostPermission(RequestEmployee requestUser) { + return requestUser != null && Boolean.TRUE.equals(requestUser.getCostVisibleFlag()); + } +} diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/dao/ServiceApplicationsDao.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/dao/ServiceApplicationsDao.java index 1ee3985..c37ee53 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/dao/ServiceApplicationsDao.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/dao/ServiceApplicationsDao.java @@ -42,6 +42,11 @@ public interface ServiceApplicationsDao extends BaseMapper idList, @Param("deletedFlag")Integer b); + /** + * 标记为律师已提交:首次提交补提交时间,后续提交不覆盖第一次提交时间 + */ + void markSubmitted(@Param("applicationId")Long applicationId, @Param("firmAuditStatus")Integer firmAuditStatus); + /** * 批量提交律所审核 */ @@ -214,6 +219,12 @@ public interface ServiceApplicationsDao extends BaseMapper queryByFirmIdAndTimeRange(@Param("firmId") Long firmId, @Param("startTime") String startTime, @Param("endTime") String endTime); -} \ No newline at end of file +} diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsQueryForm.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsQueryForm.java index 4cb1d43..1bce797 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsQueryForm.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsQueryForm.java @@ -99,6 +99,8 @@ public class ServiceApplicationsQueryForm extends PageParam { * 律所审核时间 */ private LocalDateTime firmAuditTime; + private String firmAuditTimeStart; + private String firmAuditTimeEnd; /** * 协会审核状态:0-待审核,1-通过,2-退回 @@ -119,6 +121,8 @@ public class ServiceApplicationsQueryForm extends PageParam { * 协会审核时间 */ private LocalDateTime associationAuditTime; + private String associationAuditTimeStart; + private String associationAuditTimeEnd; /** * 备案编号 @@ -191,7 +195,7 @@ public class ServiceApplicationsQueryForm extends PageParam { private String endTime; /** - * 是否只查询成本填报律所的数据 + * 审批数据查看范围:null/0=不限制,1=只查看有成本权限的律所数据,2=只查看无成本权限的律所数据 */ - private Boolean costReportViewOnly; + private Integer costReportViewOnly; } diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceLawyerImportForm.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceLawyerImportForm.java index 0a08d18..b19af9c 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceLawyerImportForm.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceLawyerImportForm.java @@ -20,6 +20,9 @@ public class ServiceLawyerImportForm { @ExcelIgnore private Long userId; + @ExcelProperty("律所名称") + private String firmName; + @ExcelProperty("执业律师") private String lawyerName; diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/service/ServiceApplicationsService.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/service/ServiceApplicationsService.java index 9cd6da3..f8dc128 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/service/ServiceApplicationsService.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/service/ServiceApplicationsService.java @@ -11,6 +11,7 @@ import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import com.alibaba.excel.write.style.column.SimpleColumnWidthStyleStrategy; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; @@ -197,10 +198,11 @@ public class ServiceApplicationsService { // 协会用户(CEO):可以看到所有律所提交的数据,但只显示associationAuditStatus为待审核(1)、通过(3)和拒绝(4)的数据和自己创建的数据 // CEO作为最高权限用户,不需要限制在特定部门范围内,直接设置includeAssociationReviewed即可 queryForm.setIncludeAssociationReviewed(true); - // 检查是否只能查看成本填报律所的数据 + // 检查审批数据查看范围(1=只看有成本的律所,2=只看无成本的律所) RequestEmployee requestEmployee = (RequestEmployee) requestUser; - if (Boolean.TRUE.equals(requestEmployee.getCostReportViewOnly())) { - queryForm.setCostReportViewOnly(true); + Integer costReportViewOnly = requestEmployee.getCostReportViewOnly(); + if (costReportViewOnly != null && (costReportViewOnly == 1 || costReportViewOnly == 2)) { + queryForm.setCostReportViewOnly(costReportViewOnly); } } else if (isFirmRole) { // 律所主任或行政:能看到自己部门范围内的数据,但只显示firmAuditStatus为待审核、通过和拒绝的数据和自己创建的数据 @@ -242,6 +244,8 @@ public class ServiceApplicationsService { queryForm.setEmployeeIdList(longs); // 普通用户不需要审核状态过滤,可以看到自己所有状态的数据 queryForm.setNoStatusFilter(true); + // 普通用户只看自己的数据 + queryForm.setUserId(requestUser.getUserId()); } queryForm.setCurrentUserId(requestUser.getUserId()); // 传递当前用户ID用于SQL中的特殊过滤 } @@ -278,64 +282,17 @@ public class ServiceApplicationsService { public PageResult queryPageReport(@Valid ServiceApplicationsQueryForm queryForm) { - List longs = new ArrayList<>(); Page page = SmartPageUtil.convert2PageQuery(queryForm); - //根据用户角色的查询数据范围来查询数据 RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); List roleIdList = roleEmployeeService.getRoleIdList(requestUser.getUserId()); String roleCode = AdminRequestUtil.getRoleCode(roleIdList); - // 检查角色类型 - boolean isAssociationRole = AdminRequestUtil.isAssociationRole(roleIdList); - boolean isFirmRole = AdminRequestUtil.isFirmRole(roleIdList); // 律所主任或行政 + // 服务申报页:非超管仅查看本人创建的数据(含草稿);审批范围逻辑见 queryPage if (!UserTypeEnum.Admin.getDesc().equals(roleCode)) { - if (isAssociationRole) { - // 协会用户(CEO):可以看到所有律所提交的数据,但只显示associationAuditStatus为待审核(1)、通过(3)和拒绝(4)的数据和自己创建的数据 - queryForm.setIncludeAssociationReviewed(true); - } else if (isFirmRole) { - // 律所主任或行政:能看到自己部门范围内的数据,但只显示firmAuditStatus为待审核、通过和拒绝的数据和自己创建的数据 - Integer oneByRoleId = dataScopeViewService.getOneByRoleId(roleIdList.get(0).getRoleId()); - // 获取自己部门范围内的数据 - List departmentEmployees = new ArrayList<>(); - if (DataScopeViewTypeEnum.ME.getValue().equals(oneByRoleId)) { - departmentEmployees = dataScopeViewService.getCanViewEmployeeId(DataScopeViewTypeEnum.ME, requestUser.getUserId()); - } else if (DataScopeViewTypeEnum.DEPARTMENT.getValue().equals(oneByRoleId)) { - departmentEmployees = dataScopeViewService.getCanViewEmployeeId(DataScopeViewTypeEnum.DEPARTMENT, requestUser.getUserId()); - } - - // 确保包含当前用户ID,以便能看到自己创建的数据 - if (departmentEmployees == null) { - departmentEmployees = new ArrayList<>(); - } - if (!departmentEmployees.contains(requestUser.getUserId())) { - departmentEmployees.add(requestUser.getUserId()); - } - - queryForm.setEmployeeIdList(departmentEmployees); - // 律所主任或行政可以查看部门内所有已审核数据以及自己创建的有效数据(明确排除未提交的草稿) - queryForm.setIncludeFirmReviewed(true); - } else { - // 律所普通用户:只能看到自己提交的数据,包括所有firmAuditStatus状态 - Integer oneByRoleId = dataScopeViewService.getOneByRoleId(roleIdList.get(0).getRoleId()); - if (DataScopeViewTypeEnum.ME.getValue().equals(oneByRoleId)) { - longs = dataScopeViewService.getCanViewEmployeeId(DataScopeViewTypeEnum.ME, requestUser.getUserId()); - } else if (DataScopeViewTypeEnum.DEPARTMENT.getValue().equals(oneByRoleId)) { - longs = dataScopeViewService.getCanViewEmployeeId(DataScopeViewTypeEnum.DEPARTMENT, requestUser.getUserId()); - } - // 确保普通用户至少能看到自己的数据 - if (longs == null) { - longs = new ArrayList<>(); - } - if (!longs.contains(requestUser.getUserId())) { - longs.add(requestUser.getUserId()); - } - queryForm.setEmployeeIdList(longs); - // 普通用户不需要审核状态过滤,可以看到自己所有状态的数据 - queryForm.setNoStatusFilter(true); - } + queryForm.setUserId(requestUser.getUserId()); + queryForm.setNoStatusFilter(true); queryForm.setCurrentUserId(requestUser.getUserId()); } - queryForm.setUserId(requestUser.getUserId()); List list = serviceApplicationsDao.queryPage(page, queryForm); if (!CollectionUtils.isEmpty(list)) { //翻译单位名称和用户名称 @@ -480,7 +437,7 @@ public class ServiceApplicationsService { } } else { // 律所审核完成:发给律师(个人) - title = "你的服务申报「" + serviceBrief + "」律所通过"; + title = "你的服务申报「" + serviceBrief + "」律所" + statusText; } net.lab1024.sa.base.module.support.message.domain.MessageSendForm sendForm = new net.lab1024.sa.base.module.support.message.domain.MessageSendForm(); @@ -579,13 +536,18 @@ public class ServiceApplicationsService { serviceApplications.setFirmAuditStatus(ReviewEnum.APPROVAL.getValue()); serviceApplications.setReportTime(LocalDateTime.now()); serviceApplicationsDao.insert(serviceApplications); + serviceApplicationsDao.markSubmitted(serviceApplications.getApplicationId(), ReviewEnum.APPROVAL.getValue()); }else { // 编辑提交(二次提交/驳回后重新提交) ServiceApplicationsEntity serviceApplications = serviceApplicationsDao.selectById(addForm.getApplicationId()); SmartBeanUtil.copyProperties(addForm, serviceApplications); serviceApplications.setFirmAuditStatus(ReviewEnum.APPROVAL.getValue()); - // 二次提交时保持原有的提交时间不变,不修改 reportTime + // 首次从草稿/已有记录提交时补提交时间;二次提交保持原有提交时间不变。 + if (serviceApplications.getReportTime() == null) { + serviceApplications.setReportTime(LocalDateTime.now()); + } serviceApplicationsDao.updateById(serviceApplications); + serviceApplicationsDao.markSubmitted(serviceApplications.getApplicationId(), ReviewEnum.APPROVAL.getValue()); } return ResponseDTO.ok(); } @@ -818,13 +780,22 @@ public class ServiceApplicationsService { return ResponseDTO.error(UserErrorCode.NO_PERMISSION); } + Long targetFirmId = null; + LocalDateTime firstReportTime = null; + // 查询这些记录的详细信息,进行权限和状态校验 for (Long id : idList) { - ServiceApplicationsVO record = serviceApplicationsDao.queryById(id); + ServiceApplicationsEntity record = serviceApplicationsDao.selectById(id); if (record == null) { return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST, "记录ID:" + id + "不存在"); } + // 记录律所ID和上报时间(用于后续成本校验) + if (targetFirmId == null) { + targetFirmId = record.getFirmId(); + firstReportTime = record.getReportTime(); + } + // 校验1:只能上报本所的数据 if (!record.getFirmId().equals(requestUser.getDepartmentId())) { return ResponseDTO.error(UserErrorCode.NO_PERMISSION, "只能上报本所的数据"); @@ -842,6 +813,20 @@ public class ServiceApplicationsService { return ResponseDTO.error(UserErrorCode.PARAM_ERROR, "记录ID:" + id + "已上报或正在审核中,无法重复上报"); } } + + // 校验4:仅对有成本查看权限的用户(参与成本核算的律所)检查前置季度成本是否已提交 + if (Boolean.TRUE.equals(requestUser.getCostVisibleFlag()) && targetFirmId != null && firstReportTime != null) { + int reportYear = firstReportTime.getYear(); + int reportMonth = firstReportTime.getMonthValue(); + int reportQuarter = (reportMonth - 1) / 3 + 1; + + List unsubmittedQuarters = firmReportsDao.findUnsubmittedEarlierQuarters(targetFirmId.intValue(), reportYear, reportQuarter); + if (unsubmittedQuarters != null && !unsubmittedQuarters.isEmpty()) { + String quarterNames = String.join("、", unsubmittedQuarters); + return ResponseDTO.userErrorParam( + "请先提交" + quarterNames + "的成本报表后,再批量上报服务数据"); + } + } serviceApplicationsDao.batchSubmitAsAssociation(idList, ReviewEnum.APPROVAL.getValue()); @@ -883,6 +868,7 @@ public class ServiceApplicationsService { serviceApplications.setFirmAuditStatus(ReviewEnum.APPROVAL.getValue()); serviceApplications.setReportTime(LocalDateTime.now()); serviceApplicationsDao.insert(serviceApplications); + serviceApplicationsDao.markSubmitted(serviceApplications.getApplicationId(), ReviewEnum.APPROVAL.getValue()); }else { //判断活动类型查询案号有没有存在 if("TIME".equals(addForm.getServiceType())) { @@ -903,8 +889,11 @@ public class ServiceApplicationsService { ServiceApplicationsEntity serviceApplications = serviceApplicationsDao.selectById(addForm.getApplicationId()); SmartBeanUtil.copyProperties(addForm, serviceApplications); serviceApplications.setFirmAuditStatus(ReviewEnum.APPROVAL.getValue()); - serviceApplications.setReportTime(LocalDateTime.now()); + if (serviceApplications.getReportTime() == null) { + serviceApplications.setReportTime(LocalDateTime.now()); + } serviceApplicationsDao.updateById(serviceApplications); + serviceApplicationsDao.markSubmitted(serviceApplications.getApplicationId(), ReviewEnum.APPROVAL.getValue()); } return ResponseDTO.ok(); @@ -1038,29 +1027,11 @@ public class ServiceApplicationsService { } // ============================================================ - // 根据上报时间确定成本归属的年份和季度 + // 根据上报时间确定成本归属的年份和季度(直接对应自然账期) // ============================================================ int reportMonth = reportTime.getMonthValue(); - int costYear; - int costQuarter; - - if (reportMonth >= 1 && reportMonth <= 3) { - // 1-3月上报 → 上年 Q4 - costYear = reportTime.getYear() - 1; - costQuarter = 4; - } else if (reportMonth >= 4 && reportMonth <= 6) { - // 4-6月上报 → 当年 Q1 - costYear = reportTime.getYear(); - costQuarter = 1; - } else if (reportMonth >= 7 && reportMonth <= 9) { - // 7-9月上报 → 当年 Q2 - costYear = reportTime.getYear(); - costQuarter = 2; - } else { - // 10-12月上报 → 当年 Q3 - costYear = reportTime.getYear(); - costQuarter = 3; - } + int costYear = reportTime.getYear(); + int costQuarter = (reportMonth - 1) / 3 + 1; log.info("更新成本:firmId={}, reportTime={}, reportMonth={}, 成本归属年份={}, 成本归属季度=Q{}", firmId, reportTime, reportMonth, costYear, costQuarter); @@ -1876,8 +1847,8 @@ public class ServiceApplicationsService { try { ServiceApplicationsEntity entity = serviceApplicationsDao.selectById(Long.valueOf(applicationId)); if (entity != null) { - // 律协审核通过时,更新成本管理表中的实际公益成本和比例 - if (isAssociationReview && ReviewEnum.PASS.getValue().equals(auditResult)) { + // 律协批量通过/拒绝都要重算,保证驳回后实际成本能扣回。 + if (isAssociationReview && isCostAffectingAssociationAudit(auditResult)) { updateActualCostInFirmReports(entity); } // 发送通知 @@ -1908,6 +1879,20 @@ public class ServiceApplicationsService { * @return 成本(万元) */ public BigDecimal getServiceApplicationsCost(ServiceLawyerQueryForm queryForm) { + if (queryForm == null) { + return BigDecimal.ZERO; + } + RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); + if (requestUser == null || !Boolean.TRUE.equals(requestUser.getCostVisibleFlag())) { + return BigDecimal.ZERO; + } + List roles = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId()); + boolean isAssociationOrAdmin = AdminRequestUtil.isAssociationRole(roles) + || UserTypeEnum.Admin.getDesc().equals(AdminRequestUtil.getRoleCode(roles)); + if (!isAssociationOrAdmin) { + queryForm.setFirmId(requestUser.getDepartmentId()); + } + Long firmId = queryForm.getFirmId(); int year = queryForm.getYear(); Integer quarter = queryForm.getQuarter(); @@ -2003,32 +1988,68 @@ public class ServiceApplicationsService { * 按部门批量审核(仅律协角色可用) * 前端统一传递 auditResult 字段(3=通过,4=拒绝) */ + @Transactional public ResponseDTO batchReviewByDepartmentId(@Valid ServiceApplicationsUpdateForm updateForm) { RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); // 统一从 auditResult 获取审核结果 Integer auditResult = updateForm.getAuditResult(); + if (auditResult == null) { + auditResult = updateForm.getAssociationAuditStatus(); + } if (auditResult == null) { return ResponseDTO.userErrorParam("审核结果不能为空"); } + if (updateForm.getFirmId() == null) { + return ResponseDTO.userErrorParam("律所不能为空"); + } + + List affectedApplications = serviceApplicationsDao.selectList( + new LambdaQueryWrapper() + .eq(ServiceApplicationsEntity::getFirmId, updateForm.getFirmId()) + .eq(ServiceApplicationsEntity::getAssociationAuditStatus, ReviewEnum.APPROVAL.getValue()) + .eq(ServiceApplicationsEntity::getDeletedFlag, 0) + ); + String auditOpinion = updateForm.getAuditRemark(); + if (auditOpinion == null) { + auditOpinion = updateForm.getAssociationAuditOpinion(); + } + + updateForm.setAssociationAuditStatus(auditResult); updateForm.setAssociationAuditUser(requestUser.getEmployeeId()); updateForm.setAssociationAuditTime(LocalDateTime.now()); + updateForm.setAssociationAuditOpinion(auditOpinion); serviceApplicationsDao.batchReviewByDepartmentId(updateForm); + + for (ServiceApplicationsEntity entity : affectedApplications) { + if (isCostAffectingAssociationAudit(auditResult)) { + updateActualCostInFirmReports(entity); + } + entity.setAssociationAuditStatus(auditResult); + entity.setAssociationAuditOpinion(auditOpinion); + sendAuditNotification(entity, auditResult, auditOpinion, true); + } return ResponseDTO.ok(); } + private boolean isCostAffectingAssociationAudit(Integer auditResult) { + return ReviewEnum.PASS.getValue().equals(auditResult) || ReviewEnum.REFUSE.getValue().equals(auditResult); + } + public Long queryNoReview() { //查询上个月是否有未审核的数据,注意跨年情况 RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); Long departmentId = requestUser.getDepartmentId(); - // 检查是否只能查看成本填报律所的数据 - Boolean costReportViewOnly = requestUser.getCostReportViewOnly(); - if (Boolean.TRUE.equals(costReportViewOnly)) { - // 只统计律所主任或行政有成本查看权限的律所的未审核数据 + // 检查审批数据查看范围(1=只看有成本的律所,2=只看无成本的律所) + Integer costReportViewOnly = requestUser.getCostReportViewOnly(); + if (costReportViewOnly != null && costReportViewOnly == 1) { return serviceApplicationsDao.queryNoReviewWithCostFilter(); } + if (costReportViewOnly != null && costReportViewOnly == 2) { + return serviceApplicationsDao.queryNoReviewWithoutCostFilter(); + } return serviceApplicationsDao.queryNoReview(departmentId); } @@ -2948,10 +2969,11 @@ public class ServiceApplicationsService { public PageResult getServiceReportStatistics(ServiceApplicationsQueryForm queryForm) { Page page = SmartPageUtil.convert2PageQuery(queryForm); - // 添加权限控制:检查是否只能查看成本填报律所的数据 + // 添加权限控制:检查审批数据查看范围(1=只看有成本的律所,2=只看无成本的律所) RequestEmployee requestUser = AdminRequestUtil.getRequestUser(); - if (Boolean.TRUE.equals(requestUser.getCostReportViewOnly())) { - queryForm.setCostReportViewOnly(true); + Integer costReportViewOnly = requestUser.getCostReportViewOnly(); + if (costReportViewOnly != null && (costReportViewOnly == 1 || costReportViewOnly == 2)) { + queryForm.setCostReportViewOnly(costReportViewOnly); } // 如果没有指定季度,则使用年度范围,否则使用季度范围 @@ -3226,6 +3248,7 @@ public class ServiceApplicationsService { // 固定列(合并两行) head.add(Arrays.asList("序号", " ")); + head.add(Arrays.asList("律所名称", " ")); head.add(Arrays.asList("律师名称", " ")); // 构建分类表头 - 记录每列的元数据信息(合计列在每个分类的前面) @@ -3261,6 +3284,7 @@ public class ServiceApplicationsService { for (LawyerActivityCountVO lawyer : lawyerMap.values()) { List row = new ArrayList<>(); row.add(++rowIndex); // 序号 + row.add(lawyer.getFirmName() != null ? lawyer.getFirmName() : ""); // 律所名称 row.add(lawyer.getLawyerName()); // 按列元数据顺序填充数据 @@ -3302,6 +3326,7 @@ public class ServiceApplicationsService { // 添加合计行 List totalRow = new ArrayList<>(); totalRow.add("-"); + totalRow.add("-"); totalRow.add("合计"); int grandTotal = 0; @@ -3360,7 +3385,7 @@ public class ServiceApplicationsService { .head(head) .registerWriteHandler(styleStrategy) .registerWriteHandler(new SimpleColumnWidthStyleStrategy(12)) - .registerWriteHandler(new SubtotalMergeHandler(columnMetaList, 2)) + .registerWriteHandler(new SubtotalMergeHandler(columnMetaList, 3)) .registerWriteHandler(new CustomRowHeightHandler((short) 500)) .build(); excelWriter.write(data, writeSheet); diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java index 6e445fc..6423dc9 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java @@ -106,7 +106,7 @@ public class EmployeeEntity { private Boolean penaltyFlag; /** - * 审批数据是否只能查看成本填报律所 + * 审批数据查看范围:null/0=不限制,1=只查看有成本权限的律所数据,2=只查看无成本权限的律所数据 */ - private Boolean costReportViewOnly; + private Integer costReportViewOnly; } diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java index 76811ae..43a3f25 100644 --- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java +++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java @@ -73,9 +73,9 @@ public class RequestEmployee implements RequestUser, Serializable { private Boolean penaltyFlag; /** - * 审批数据是否只能查看成本填报律所 + * 审批数据查看范围:null/0=不限制,1=只查看有成本权限的律所数据,2=只查看无成本权限的律所数据 */ - private Boolean costReportViewOnly; + private Integer costReportViewOnly; @Override public Long getUserId() { diff --git a/yun-admin/src/main/resources/mapper/cost/FirmReportsMapper.xml b/yun-admin/src/main/resources/mapper/cost/FirmReportsMapper.xml index 611404a..451145d 100644 --- a/yun-admin/src/main/resources/mapper/cost/FirmReportsMapper.xml +++ b/yun-admin/src/main/resources/mapper/cost/FirmReportsMapper.xml @@ -128,4 +128,24 @@ AND declare_quarter = #{quarter} + + + diff --git a/yun-admin/src/main/resources/mapper/service/ServiceApplicationsMapper.xml b/yun-admin/src/main/resources/mapper/service/ServiceApplicationsMapper.xml index f59c65b..a9cb5ec 100644 --- a/yun-admin/src/main/resources/mapper/service/ServiceApplicationsMapper.xml +++ b/yun-admin/src/main/resources/mapper/service/ServiceApplicationsMapper.xml @@ -98,6 +98,18 @@ + + and firm_audit_time >= #{queryForm.firmAuditTimeStart} + + + and firm_audit_time <= #{queryForm.firmAuditTimeEnd} + + + and association_audit_time >= #{queryForm.associationAuditTimeStart} + + + and association_audit_time <= #{queryForm.associationAuditTimeEnd} + and activity_name_id = #{queryForm.activityNameId} @@ -130,7 +142,7 @@ - + AND t_service_applications.firm_id IN ( SELECT DISTINCT e.department_id FROM t_employee e @@ -141,6 +153,17 @@ AND e.cost_visible_flag = 1 ) + + AND t_service_applications.firm_id NOT IN ( + SELECT DISTINCT e.department_id + FROM t_employee e + INNER JOIN t_role_employee re ON e.employee_id = re.employee_id + INNER JOIN t_role r ON re.role_id = r.role_id + WHERE (e.deleted_flag = 0 OR e.deleted_flag IS NULL) + AND (r.role_code = 'cto' OR r.role_code = 'staff') + AND e.cost_visible_flag = 1 + ) + AND ( @@ -190,19 +213,26 @@ - update t_service_applications set firm_audit_status = #{deletedFlag}, report_time = NOW() + update t_service_applications set firm_audit_status = #{deletedFlag}, report_time = IFNULL(report_time, NOW()) where application_id in #{item} + + update t_service_applications + set firm_audit_status = #{firmAuditStatus}, + report_time = IFNULL(report_time, NOW()) + where application_id = #{applicationId} + + update t_service_applications set firm_audit_status = #{firmAuditStatus}, firm_audit_user = #{userId}, firm_audit_time = #{submitTime}, - report_time = NOW() + report_time = IFNULL(report_time, NOW()) where application_id in #{item} @@ -256,7 +286,8 @@ update t_service_applications set association_audit_status = #{queryForm.associationAuditStatus}, association_audit_user = #{queryForm.associationAuditUser}, - association_audit_time = #{queryForm.associationAuditTime} + association_audit_time = #{queryForm.associationAuditTime}, + association_audit_opinion = #{queryForm.associationAuditOpinion} where association_audit_status = 1 and firm_id = #{queryForm.firmId} @@ -283,6 +314,7 @@ SELECT e.actual_name AS lawyerName, e.employee_id as userId, + MAX(d.department_name) AS firmName, tsa.certificate_number AS certificateNumber, COALESCE(SUM(tsa.service_duration), 0) AS annualServiceDuration FROM t_service_applications tsa @@ -542,7 +574,7 @@ LEFT JOIN t_employee e ON e.department_id = d.department_id AND e.deleted_flag = 0 LEFT JOIN t_service_applications tsa ON tsa.user_id = e.employee_id AND tsa.deleted_flag = 0 - AND tsa.association_audit_status IN (1, 2, 3) + AND tsa.association_audit_status IN (1, 2, 3, 4) AND tsa.firm_id = #{queryForm.firmId} @@ -558,7 +590,7 @@ AND d.department_id = #{queryForm.firmId} - + AND d.department_id IN ( SELECT DISTINCT e2.department_id FROM t_employee e2 @@ -569,6 +601,18 @@ AND e2.cost_visible_flag = 1 ) + + + AND d.department_id NOT IN ( + SELECT DISTINCT e2.department_id + FROM t_employee e2 + INNER JOIN t_role_employee re ON e2.employee_id = re.employee_id + INNER JOIN t_role r ON re.role_id = r.role_id + WHERE (e2.deleted_flag = 0 OR e2.deleted_flag IS NULL) + AND (r.role_code = 'cto' OR r.role_code = 'staff') + AND e2.cost_visible_flag = 1 + ) + GROUP BY d.department_id, d.department_name @@ -779,6 +823,29 @@ ) + + +