diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 93bc615..d3b9812 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -4,11 +4,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index b074d70..357a9f3 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -1,6 +1,11 @@
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 2f4db4e..76f3d78 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,5 +8,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
deleted file mode 100644
index 77496bb..0000000
--- a/.idea/sqldialects.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
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 fe3a444..b786846 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
@@ -96,17 +96,19 @@ public class FirmReportsController {
return firmReportsService.reject(idList);
}
- //查询本年度已经提交的收入和成本
- @Operation(summary = "查询本年度已经提交的收入 @author wzh")
+ //查询指定年份已经提交的收入和成本
+ @Operation(summary = "查询指定年份已经提交的收入 @author wzh")
@PostMapping("/firmReports/income")
- public ResponseDTO income() {
- return ResponseDTO.ok(firmReportsService.income());
+ public ResponseDTO income(@RequestBody(required = false) FirmReportsQueryForm queryForm) {
+ Integer year = queryForm != null && queryForm.getDeclareYear() != null ? queryForm.getDeclareYear() : DateTimeUtil.getCurrentYear();
+ return ResponseDTO.ok(firmReportsService.income(year));
}
- //查询本年度已经提交的成本
- @Operation(summary = "查询本年度已经提交的成本 @author wzh")
+ //查询指定年份已经提交的成本
+ @Operation(summary = "查询指定年份已经提交的成本 @author wzh")
@PostMapping("/firmReports/cost")
- public ResponseDTO firmReportsCost() {
- return ResponseDTO.ok(firmReportsService.firmReportsCost());
+ public ResponseDTO firmReportsCost(@RequestBody(required = false) FirmReportsQueryForm queryForm) {
+ Integer year = queryForm != null && queryForm.getDeclareYear() != null ? queryForm.getDeclareYear() : DateTimeUtil.getCurrentYear();
+ return ResponseDTO.ok(firmReportsService.firmReportsCost(year));
}
/**
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 1222c6b..467f617 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
@@ -70,4 +70,15 @@ public interface FirmReportsDao extends BaseMapper {
FirmReportsCountVO income(@Param("departmentId") Long departmentId, @Param("currentYear") Integer currentYear);
Long firmReportsCost(@Param("departmentId") Long departmentId,@Param("currentYear") Integer currentYear);
+
+ /**
+ * 根据律所ID、年份和季度查询成本管理表记录
+ * @param firmId 律所ID
+ * @param year 年份
+ * @param quarter 季度
+ * @return 成本管理表记录列表
+ */
+ List selectByFirmIdYearAndQuarter(@Param("firmId") Integer firmId,
+ @Param("year") Integer year,
+ @Param("quarter") String quarter);
}
diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/entity/FirmReportsEntity.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/entity/FirmReportsEntity.java
index f092947..10252e1 100644
--- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/entity/FirmReportsEntity.java
+++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/entity/FirmReportsEntity.java
@@ -70,6 +70,16 @@ public class FirmReportsEntity {
*/
private BigDecimal costIncomeRatio;
+ /**
+ * 审核后实际公益成本支出(单位:万元)
+ */
+ private BigDecimal actualPublicWelfareCost;
+
+ /**
+ * 实际成本收入比(%)
+ */
+ private BigDecimal actualCostIncomeRatio;
+
/**
* 审批状态(存储当前审批人姓名,"-"表示待审批)
*/
diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/vo/FirmReportsVO.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/vo/FirmReportsVO.java
index e0b1d7a..c3ee1ec 100644
--- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/vo/FirmReportsVO.java
+++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/cost/domain/vo/FirmReportsVO.java
@@ -46,6 +46,12 @@ public class FirmReportsVO {
@Schema(description = "成本收入比(%),计算公式:总成本/收入×100%")
private BigDecimal costIncomeRatio;
+ @Schema(description = "审核后实际公益成本支出(单位:万元)")
+ private BigDecimal actualPublicWelfareCost;
+
+ @Schema(description = "实际成本收入比(%)")
+ private BigDecimal actualCostIncomeRatio;
+
@Schema(description = "审批状态(存储当前审批人姓名,-表示待审批)")
private Integer approvalStatus;
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 772b63b..21dc3fd 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
@@ -56,24 +56,23 @@ public class FirmReportsService {
public PageResult queryPage(FirmReportsQueryForm queryForm) {
Page> page = SmartPageUtil.convert2PageQuery(queryForm);
- // 检查当前用户是否为CEO角色
RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
List roleIdList = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId());
String roleCode = AdminRequestUtil.getRoleCode(roleIdList);
if (!UserTypeEnum.Admin.getDesc().equals(roleCode)) {
- // 检查角色类型
if (AdminRequestUtil.isAssociationRole(roleIdList)) {
- // 如果是CEO角色,查询自己的数据和已经提交的数据(审批状态大于等于3的数据)
queryForm.setUserId(requestUser.getEmployeeId());
- queryForm.setIncludeSubmitted(true); // 设置查询参数以包括已提交的数据
- }else {
+ queryForm.setIncludeSubmitted(true);
+ } else if (AdminRequestUtil.isFirmRole(roleIdList)) {
+ queryForm.setFirmId(Math.toIntExact(requestUser.getDepartmentId()));
+ queryForm.setIncludeSubmitted(true);
+ } else {
queryForm.setUserId(requestUser.getEmployeeId());
}
}
List list = firmReportsDao.queryPage(page, queryForm);
- //查询字典值
PageResult firmReportsVOPageResult = SmartPageUtil.convert2PageResult(page, list);
firmReportsVOPageResult.getList().forEach(item -> {
@@ -235,19 +234,21 @@ public class FirmReportsService {
return ResponseDTO.ok(canReport);
}
- public FirmReportsCountVO income() {
+ public FirmReportsCountVO income(Integer year) {
// 获取当前用户
RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
- //本年度
- Integer currentYear = DateTimeUtil.getCurrentYear();
- return firmReportsDao.income(requestUser.getDepartmentId(), currentYear);
+ if (year == null) {
+ year = DateTimeUtil.getCurrentYear();
+ }
+ return firmReportsDao.income(requestUser.getDepartmentId(), year);
}
- public Long firmReportsCost() {
+ public Long firmReportsCost(Integer year) {
// 获取当前用户
RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
- //本年度
- Integer currentYear = DateTimeUtil.getCurrentYear();
- return firmReportsDao.firmReportsCost(requestUser.getDepartmentId(), currentYear);
+ if (year == null) {
+ year = DateTimeUtil.getCurrentYear();
+ }
+ return firmReportsDao.firmReportsCost(requestUser.getDepartmentId(), year);
}
}
\ No newline at end of file
diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/controller/ServiceApplicationsController.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/controller/ServiceApplicationsController.java
index dc00640..b9257d5 100644
--- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/controller/ServiceApplicationsController.java
+++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/controller/ServiceApplicationsController.java
@@ -240,7 +240,7 @@ public class ServiceApplicationsController {
*/
@Operation(summary = "导出活动明细 @author wzh")
@PostMapping("/serviceApplications/export/activityDetail")
- public void exportActivityDetail(HttpServletResponse response) {
- serviceApplicationsService.exportActivityDetail( response);
+ public void exportActivityDetail(@RequestBody ServiceApplicationsQueryForm queryForm, HttpServletResponse response) {
+ serviceApplicationsService.exportActivityDetail(queryForm, response);
}
}
\ No newline at end of file
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 4c705ed..1ee3985 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
@@ -111,12 +111,12 @@ public interface ServiceApplicationsDao extends BaseMapper fileKeyList, @Param("value") Integer value, @Param("userId") Long userId, @Param("reviewTime") String reviewTime);
+ void batchReviewAsFirm(@Param("fileKeyList") List fileKeyList, @Param("value") Integer value, @Param("userId") Long userId, @Param("reviewTime") String reviewTime, @Param("auditOpinion") String auditOpinion);
/**
* 批量审核 - 协会审核
*/
- void batchReviewAsAssociation(@Param("fileKeyList") List fileKeyList, @Param("value") Integer value, @Param("userId") Long userId, @Param("reviewTime") String reviewTime);
+ void batchReviewAsAssociation(@Param("fileKeyList") List fileKeyList, @Param("value") Integer value, @Param("userId") Long userId, @Param("reviewTime") String reviewTime, @Param("auditOpinion") String auditOpinion);
/**
* 服务上报统计
@@ -218,4 +218,15 @@ 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/ActivityDetailExportForm.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ActivityDetailExportForm.java
new file mode 100644
index 0000000..744bc7b
--- /dev/null
+++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ActivityDetailExportForm.java
@@ -0,0 +1,54 @@
+package net.lab1024.sa.admin.module.service.domain.form;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 活动明细导出表单
+ *
+ * @Author wzh
+ * @Date 2025-12-22
+ * @Copyright 1.0
+ */
+@Data
+public class ActivityDetailExportForm {
+
+ @ExcelProperty("备案编号")
+ private String recordNo;
+
+ @ExcelProperty("律师姓名")
+ private String userName;
+
+ @ExcelProperty("律所名称")
+ private String departmentName;
+
+ @ExcelProperty("活动名称")
+ private String activityName;
+
+ @ExcelProperty("服务开始时间")
+ @ColumnWidth(value = 25)
+ private LocalDateTime serviceStart;
+
+ @ExcelProperty("服务结束时间")
+ @ColumnWidth(value = 25)
+ private LocalDateTime serviceEnd;
+
+ @ExcelProperty("服务时长(小时)")
+ private Double serviceDuration;
+
+ @ExcelProperty("受益人数")
+ private Integer beneficiaryCount;
+
+ @ExcelProperty("组织单位")
+ private String organizerName;
+
+ @ExcelProperty("负责人")
+ private String organizerContact;
+
+ @ExcelProperty("联系方式")
+ private String organizerPhone;
+}
\ No newline at end of file
diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsUpdateForm.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsUpdateForm.java
index 2744eee..1a580f5 100644
--- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsUpdateForm.java
+++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/form/ServiceApplicationsUpdateForm.java
@@ -77,6 +77,8 @@ public class ServiceApplicationsUpdateForm{
private String associationAuditOpinion;
private String firmAuditOpinion;
private Integer auditResult;
+ @Schema(description = "审核意见(非必填)")
+ private String auditRemark;
/**
* 职务id
*/
diff --git a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/vo/ServiceApplicationsVO.java b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/vo/ServiceApplicationsVO.java
index 4448f12..658b6e7 100644
--- a/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/vo/ServiceApplicationsVO.java
+++ b/yun-admin/src/main/java/net/lab1024/sa/admin/module/service/domain/vo/ServiceApplicationsVO.java
@@ -120,4 +120,13 @@ public class ServiceApplicationsVO {
private Long positionId;
private String positionName;
private String associationAuditUserName;
+
+ @Schema(description = "服务类型:TIME-时间类型,AMOUNT-金额类型")
+ private String serviceType;
+
+ @Schema(description = "活动固定小时数(TIME类型时使用)")
+ private String activityPrice;
+
+ @Schema(description = "服务金额")
+ private BigDecimal serviceAmount;
}
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 fbdc32b..c4bb112 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
@@ -8,11 +8,12 @@ import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
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 net.lab1024.sa.base.common.util.SmartRequestUtil;
-import org.apache.poi.ss.usermodel.HorizontalAlignment;
-import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.base.common.code.UserErrorCode;
@@ -36,7 +37,8 @@ import net.lab1024.sa.admin.module.service.dao.ServiceApplicationsDao;
import net.lab1024.sa.admin.module.service.domain.entity.ServiceApplicationsEntity;
import net.lab1024.sa.admin.module.service.domain.form.*;
import net.lab1024.sa.admin.module.service.domain.vo.*;
-import net.lab1024.sa.admin.module.service.domain.form.ActivityDetailExportForm;
+import net.lab1024.sa.admin.module.cost.dao.FirmReportsDao;
+import net.lab1024.sa.admin.module.cost.domain.entity.FirmReportsEntity;
import java.util.stream.Collectors;
import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum;
import net.lab1024.sa.admin.module.system.datascope.service.DataScopeViewService;
@@ -53,12 +55,9 @@ import net.lab1024.sa.admin.util.CellStyleStrategy;
import net.lab1024.sa.admin.util.DateTimeUtil;
import net.lab1024.sa.admin.util.TimeVo;
import org.apache.commons.collections4.CollectionUtils;
-import org.apache.poi.ss.usermodel.*;
-import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
-
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@@ -67,8 +66,8 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
-import java.util.*;
import java.time.LocalDateTime;
+import java.util.*;
import java.util.function.Function;
@@ -90,6 +89,8 @@ public class ServiceApplicationsService {
@Resource
private EmployeeService employeeService;
@Resource
+ private net.lab1024.sa.admin.module.system.employee.dao.EmployeeDao employeeDao;
+ @Resource
private DepartmentService departmentService;
@Resource
private CategoryService categoryService;
@@ -103,6 +104,12 @@ public class ServiceApplicationsService {
PositionService positionService;
@Resource
private DictService dictService;
+
+ @Resource
+ private FirmReportsDao firmReportsDao;
+
+ @Resource
+ private net.lab1024.sa.base.module.support.message.service.MessageService messageService;
/**
* 案号重复性校验(用于提交操作)
@@ -178,7 +185,7 @@ public class ServiceApplicationsService {
List longs = new ArrayList<>();
Page> page = SmartPageUtil.convert2PageQuery(queryForm);
//根据用户角色的查询数据范围来查询数据
- RequestUser requestUser = AdminRequestUtil.getRequestUser();
+ RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
List roleIdList = roleEmployeeService.getRoleIdList(requestUser.getUserId());
String roleCode = AdminRequestUtil.getRoleCode(roleIdList);
@@ -274,7 +281,7 @@ public class ServiceApplicationsService {
Page> page = SmartPageUtil.convert2PageQuery(queryForm);
//根据用户角色的查询数据范围来查询数据
- RequestUser requestUser = AdminRequestUtil.getRequestUser();
+ RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
queryForm.setUserId(requestUser.getUserId());
List list = serviceApplicationsDao.queryPage(page, queryForm);
if (!CollectionUtils.isEmpty(list)) {
@@ -371,8 +378,139 @@ public class ServiceApplicationsService {
ServiceApplicationsEntity serviceApplicationsEntity = SmartBeanUtil.copy(updateForm, ServiceApplicationsEntity.class);
//serviceApplicationsEntity.setFirmAuditStatus(ReviewEnum.APPROVAL.getValue());
serviceApplicationsDao.updateById(serviceApplicationsEntity);
+
return ResponseDTO.ok();
}
+
+ /**
+ * 发送审核结果通知
+ * @param entity 服务申请实体
+ * @param auditStatus 审核状态
+ * @param auditOpinion 审核意见
+ * @param isAssociationReview 是否为协会审核(true=协会审核,false=律所审核)
+ */
+ private void sendAuditNotification(ServiceApplicationsEntity entity, Integer auditStatus, String auditOpinion, boolean isAssociationReview) {
+ try {
+ boolean isPass = ReviewEnum.PASS.getValue().equals(auditStatus);
+ String statusText = isPass ? "通过" : "拒绝";
+ String reviewerType = isAssociationReview ? "律协" : "律所";
+
+ // 获取律师姓名
+ String lawyerName = "律师";
+ if (entity.getUserId() != null) {
+ net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity employee = employeeDao.selectById(entity.getUserId());
+ if (employee != null) {
+ lawyerName = employee.getActualName();
+ }
+ }
+
+ // 构建服务简介(显示活动时间)
+ String serviceBrief = "";
+ if (entity.getServiceStart() != null && entity.getServiceEnd() != null) {
+ java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ serviceBrief = entity.getServiceStart().format(formatter) + " 至 " + entity.getServiceEnd().format(formatter);
+ } else if (entity.getServiceStart() != null) {
+ java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ serviceBrief = entity.getServiceStart().format(formatter) + " 开始";
+ } else {
+ serviceBrief = "服务申报";
+ }
+
+ String title;
+ if (isAssociationReview) {
+ if (isPass) {
+ // 律协通过:发给律师(个人)
+ title = "你的服务申报「" + serviceBrief + "」律协通过";
+ } else {
+ // 律协拒绝/驳回:发给律所管理员
+ title = "律师" + lawyerName + "的服务申报「" + serviceBrief + "」律协拒绝";
+ }
+ } else {
+ // 律所审核完成:发给律师(个人)
+ title = "你的服务申报「" + serviceBrief + "」律所通过";
+ }
+
+ net.lab1024.sa.base.module.support.message.domain.MessageSendForm sendForm = new net.lab1024.sa.base.module.support.message.domain.MessageSendForm();
+ sendForm.setMessageType(net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum.AUDIT.getValue());
+ sendForm.setReceiverUserType(net.lab1024.sa.base.common.enumeration.UserTypeEnum.ADMIN_EMPLOYEE.getValue());
+ sendForm.setTitle(title);
+ sendForm.setDataId(entity.getApplicationId());
+
+ // 构建审核意见(显示律所和律协双方的意见)
+ String firmOpinion = entity.getFirmAuditOpinion();
+ String associationOpinion = entity.getAssociationAuditOpinion();
+ StringBuilder opinions = new StringBuilder();
+ if (firmOpinion != null && !firmOpinion.trim().isEmpty()) {
+ opinions.append("\n律所审核意见:").append(firmOpinion);
+ }
+ if (associationOpinion != null && !associationOpinion.trim().isEmpty()) {
+ opinions.append("\n律协审核意见:").append(associationOpinion);
+ }
+
+ if (isAssociationReview) {
+ if (isPass) {
+ // 律协通过:发给服务填报人(个人)
+ String lawyerContent = "律师" + lawyerName + "的服务申报「" + serviceBrief + "」律协已通过。" + opinions;
+ sendForm.setContent(lawyerContent);
+ sendForm.setReceiverUserId(entity.getUserId());
+ messageService.sendMessage(sendForm);
+ } else {
+ // 律协拒绝/驳回:发给律所主任和行政(消息中包含律师姓名)
+ String adminContent = "律师" + lawyerName + "的服务申报「" + serviceBrief + "」律协已拒绝。" + opinions;
+ sendNotificationsToFirmAdmins(entity.getFirmId(), sendForm, adminContent);
+ }
+ } else {
+ if (isPass) {
+ // 律所通过:发给服务填报人(律师)
+ String lawyerContent = "律师" + lawyerName + "的服务申报「" + serviceBrief + "」律所已通过。" + opinions;
+ sendForm.setContent(lawyerContent);
+ sendForm.setReceiverUserId(entity.getUserId());
+ messageService.sendMessage(sendForm);
+ } else {
+ // 律所拒绝:发给服务填报人(律师)
+ String lawyerContent = "律师" + lawyerName + "的服务申报「" + serviceBrief + "」律所已拒绝。" + opinions;
+ sendForm.setContent(lawyerContent);
+ sendForm.setReceiverUserId(entity.getUserId());
+ messageService.sendMessage(sendForm);
+ }
+ }
+ } catch (Exception e) {
+ log.error("发送审核通知失败: {}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 发送通知给律所管理员(主任和行政)
+ */
+ private void sendNotificationsToFirmAdmins(Long firmId, net.lab1024.sa.base.module.support.message.domain.MessageSendForm baseSendForm) {
+ sendNotificationsToFirmAdmins(firmId, baseSendForm, null);
+ }
+
+ /**
+ * 发送通知给律所管理员(主任和行政)
+ * @param customContent 自定义消息内容(如果为null则使用baseSendForm中的内容)
+ */
+ private void sendNotificationsToFirmAdmins(Long firmId, net.lab1024.sa.base.module.support.message.domain.MessageSendForm baseSendForm, String customContent) {
+ if (firmId == null) return;
+
+ List employees = employeeDao.selectByDepartmentId(firmId, false);
+ for (net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity emp : employees) {
+ Long empId = emp.getEmployeeId();
+ List empRoles = roleEmployeeService.getRoleIdList(empId);
+ boolean isCto = AdminRequestUtil.isFirmDirectorRole(empRoles);
+ boolean isStaff = AdminRequestUtil.isFirmStaffRole(empRoles);
+
+ if (isCto || isStaff) {
+ net.lab1024.sa.base.module.support.message.domain.MessageSendForm sendForm = new net.lab1024.sa.base.module.support.message.domain.MessageSendForm();
+ SmartBeanUtil.copyProperties(baseSendForm, sendForm);
+ sendForm.setReceiverUserId(empId);
+ if (customContent != null) {
+ sendForm.setContent(customContent);
+ }
+ messageService.sendMessage(sendForm);
+ }
+ }
+ }
/**
* 提交
@@ -389,11 +527,11 @@ public class ServiceApplicationsService {
serviceApplications.setReportTime(LocalDateTime.now());
serviceApplicationsDao.insert(serviceApplications);
}else {
- // 编辑提交
+ // 编辑提交(二次提交/驳回后重新提交)
ServiceApplicationsEntity serviceApplications = serviceApplicationsDao.selectById(addForm.getApplicationId());
SmartBeanUtil.copyProperties(addForm, serviceApplications);
serviceApplications.setFirmAuditStatus(ReviewEnum.APPROVAL.getValue());
- serviceApplications.setReportTime(LocalDateTime.now());
+ // 二次提交时保持原有的提交时间不变,不修改 reportTime
serviceApplicationsDao.updateById(serviceApplications);
}
return ResponseDTO.ok();
@@ -631,7 +769,7 @@ public class ServiceApplicationsService {
// 检查当前用户角色是否为CEO
/*RequestUser requestUser = AdminRequestUtil.getRequestUser();
- List roles = roleEmployeeService.getRoleIdList(requestUser.getUserId());
+ List roles = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId());
if (!roles.isEmpty()) {
String roleCode = roles.get(0).getRoleCode();
// 如果是CEO角色创建申报,默认通过律所审核和协会审核
@@ -685,16 +823,30 @@ public class ServiceApplicationsService {
return ResponseDTO.ok();
}
+ /**
+ * 单条审核
+ * 前端传递 associationAuditStatus(律协)或 firmAuditStatus(律所)
+ * 后端根据角色自动判断是律协审核还是律所审核
+ */
public ResponseDTO review(@Valid ServiceApplicationsUpdateForm updateForm) {
RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
- // 查询用户角色是律所管理员,则修改律所的审核状态
- // 如果是ceo则修改协会审核状态
List roles = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId());
if (roles.isEmpty()) {
+ log.warn("审核失败:用户无角色, employeeId={}", requestUser.getEmployeeId());
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
+ log.info("审核请求:applicationId={}, 用户角色={}, firmAuditStatus={}, associationAuditStatus={}, auditResult={}",
+ updateForm.getApplicationId(),
+ roles.get(0).getRoleCode(),
+ updateForm.getFirmAuditStatus(),
+ updateForm.getAssociationAuditStatus(),
+ updateForm.getAuditResult());
+
ServiceApplicationsEntity serviceApplicationsEntity = serviceApplicationsDao.selectById(updateForm.getApplicationId());
+ if (serviceApplicationsEntity == null) {
+ return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+ }
// 权限验证:如果只能查看成本填报律所的数据,需要验证该数据是否属于有成本查看权限的律所
if (Boolean.TRUE.equals(requestUser.getCostReportViewOnly())) {
@@ -703,29 +855,290 @@ public class ServiceApplicationsService {
}
}
- // 根据不同角色执行不同的审核操作
+ // ============================================================
+ // 律协审核(协会角色:CEO/律协)
+ // ============================================================
if (AdminRequestUtil.isAssociationRole(roles)) {
- // CEO角色:修改协会审核状态
- serviceApplicationsEntity.setAssociationAuditStatus(updateForm.getFirmAuditStatus());
+ Integer auditResult = updateForm.getAssociationAuditStatus();
+ // 容错:如果 auditResult 不为空,也尝试读取
+ if (auditResult == null && updateForm.getAuditResult() != null) {
+ auditResult = updateForm.getAuditResult();
+ }
+ if (auditResult == null) {
+ return ResponseDTO.userErrorParam("审核结果不能为空");
+ }
+
+ // 设置协会审核状态
+ serviceApplicationsEntity.setAssociationAuditStatus(auditResult);
serviceApplicationsEntity.setAssociationAuditUser(requestUser.getEmployeeId());
serviceApplicationsEntity.setAssociationAuditTime(LocalDateTime.now());
serviceApplicationsEntity.setAssociationAuditOpinion(updateForm.getAssociationAuditOpinion());
- } else if (AdminRequestUtil.isFirmRole(roles)) {
- // 律所主任或行政:修改律所审核状态
- if (ReviewEnum.REFUSE.getValue() == updateForm.getAssociationAuditStatus()){
- serviceApplicationsEntity.setFirmAuditStatus(ReviewEnum.REFUSE.getValue());
- }else {
- serviceApplicationsEntity.setFirmAuditStatus(updateForm.getFirmAuditStatus());
+
+ serviceApplicationsDao.updateById(serviceApplicationsEntity);
+
+ // 当协会审核通过时,更新成本管理表中的实际公益成本和比例
+ if (ReviewEnum.PASS.getValue().equals(auditResult)) {
+ updateActualCostInFirmReports(serviceApplicationsEntity);
+ }
+
+ // 发送通知:律协审核结果通知给填报人
+ sendAuditNotification(serviceApplicationsEntity, auditResult, updateForm.getAssociationAuditOpinion(), true);
+ }
+ // ============================================================
+ // 律所审核(律所角色:主任/行政)
+ // ============================================================
+ else if (AdminRequestUtil.isFirmRole(roles)) {
+ Integer auditResult = updateForm.getFirmAuditStatus();
+ // 容错:如果 auditResult 不为空,也尝试读取
+ if (auditResult == null && updateForm.getAuditResult() != null) {
+ auditResult = updateForm.getAuditResult();
}
+ if (auditResult == null) {
+ log.warn("律所审核失败:firmAuditStatus和auditResult都为空");
+ return ResponseDTO.userErrorParam("审核结果不能为空");
+ }
+
+ // 设置律所审核状态
+ serviceApplicationsEntity.setFirmAuditStatus(auditResult);
serviceApplicationsEntity.setFirmAuditUser(requestUser.getEmployeeId());
serviceApplicationsEntity.setFirmAuditTime(LocalDateTime.now());
- } else {
+ serviceApplicationsEntity.setFirmAuditOpinion(updateForm.getFirmAuditOpinion());
+
+ serviceApplicationsDao.updateById(serviceApplicationsEntity);
+
+ // 发送通知:律所审核结果通知给填报人
+ sendAuditNotification(serviceApplicationsEntity, auditResult, updateForm.getFirmAuditOpinion(), false);
+ }
+ // ============================================================
+ // 无权限
+ // ============================================================
+ else {
+ log.warn("审核失败:用户角色不是律协或律所, 角色={}", roles.get(0).getRoleCode());
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
-
- serviceApplicationsDao.updateById(serviceApplicationsEntity);
+
return ResponseDTO.ok();
}
+
+ /**
+ * 更新成本管理表中的实际公益成本和比例
+ * 财务逻辑:
+ * - 用服务上报时间(reportTime)确定成本归属的年份和季度
+ * - 全年实际公益成本 = 四个季度通过审核的服务申请成本之和
+ * - 全年营业收入 = 四个季度的营业收入之和
+ * - 实际成本收入比 = 全年实际公益成本 / 全年营业收入(上限25%)
+ *
+ * 关键:上报时间与成本季度的对应关系:
+ * - 1-3月上报 → 成本归属:上年 Q4
+ * - 4-6月上报 → 成本归属:当年 Q1
+ * - 7-9月上报 → 成本归属:当年 Q2
+ * - 10-12月上报 → 成本归属:当年 Q3
+ *
+ * @param serviceApplicationsEntity 服务申请实体
+ */
+ private void updateActualCostInFirmReports(ServiceApplicationsEntity serviceApplicationsEntity) {
+ Long firmId = serviceApplicationsEntity.getFirmId();
+ LocalDateTime reportTime = serviceApplicationsEntity.getReportTime();
+ if (firmId == null || reportTime == null) {
+ log.warn("更新成本失败:firmId或reportTime为空, firmId={}, reportTime={}", firmId, reportTime);
+ return;
+ }
+
+ // 检查律所是否有成本查看权限
+ if (!isFirmHasCostPermission(firmId)) {
+ log.warn("更新成本失败:律所无成本查看权限, firmId={}", firmId);
+ return;
+ }
+
+ // ============================================================
+ // 根据上报时间确定成本归属的年份和季度
+ // ============================================================
+ 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;
+ }
+
+ log.info("更新成本:firmId={}, reportTime={}, reportMonth={}, 成本归属年份={}, 成本归属季度=Q{}",
+ firmId, reportTime, reportMonth, costYear, costQuarter);
+
+ // ============================================================
+ // 查询该律所该成本年份的所有季度记录
+ // ============================================================
+ List allQuarterReports = new ArrayList<>();
+ for (int quarter = 1; quarter <= 4; quarter++) {
+ List quarterReports = firmReportsDao.selectByFirmIdYearAndQuarter(firmId.intValue(), costYear, String.valueOf(quarter));
+ if (CollectionUtils.isNotEmpty(quarterReports)) {
+ allQuarterReports.addAll(quarterReports);
+ }
+ }
+
+ if (CollectionUtils.isEmpty(allQuarterReports)) {
+ log.warn("更新成本失败:未找到成本管理记录, firmId={}, costYear={}", firmId, costYear);
+ return;
+ }
+
+ // ============================================================
+ // 计算全年实际公益成本(四个季度通过审核的成本之和)
+ // ============================================================
+ BigDecimal annualActualCost = BigDecimal.ZERO;
+ for (int quarter = 1; quarter <= 4; quarter++) {
+ BigDecimal quarterCost = calculateActualPublicWelfareCost(firmId, costYear, quarter);
+ annualActualCost = annualActualCost.add(quarterCost);
+ }
+
+ // 转换为万元
+ BigDecimal annualActualCostInWan = annualActualCost.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP);
+
+ log.info("更新成本:firmId={}, costYear={}, 全年实际公益成本={}元, 折合{}万元",
+ firmId, costYear, annualActualCost, annualActualCostInWan);
+
+ // ============================================================
+ // 计算全年营业收入(四个季度之和)
+ // ============================================================
+ BigDecimal annualRevenue = BigDecimal.ZERO;
+ for (FirmReportsEntity report : allQuarterReports) {
+ if (report.getRevenue() != null) {
+ annualRevenue = annualRevenue.add(report.getRevenue());
+ }
+ }
+
+ log.info("更新成本:firmId={}, costYear={}, 全年营业收入={}万元", firmId, costYear, annualRevenue);
+
+ // ============================================================
+ // 计算全年实际成本收入比 = 全年实际公益成本 / 全年营业收入(上限25%)
+ // ============================================================
+ BigDecimal annualCostIncomeRatio = BigDecimal.ZERO;
+ if (annualRevenue.compareTo(BigDecimal.ZERO) > 0) {
+ annualCostIncomeRatio = annualActualCostInWan.divide(annualRevenue, 4, RoundingMode.HALF_UP)
+ .multiply(new BigDecimal(100))
+ .setScale(2, RoundingMode.HALF_UP);
+
+ // 上限25%
+ if (annualCostIncomeRatio.compareTo(new BigDecimal("25.00")) > 0) {
+ annualCostIncomeRatio = new BigDecimal("25.00");
+ }
+ }
+
+ log.info("更新成本:firmId={}, costYear={}, 全年实际成本收入比={}%", firmId, costYear, annualCostIncomeRatio);
+
+ // ============================================================
+ // 更新该服务所属季度的实际公益成本和实际成本收入比
+ // actualPublicWelfareCost = 当前季度的实际公益成本
+ // actualCostIncomeRatio = 全年累计实际成本收入比
+ // 注意:只修改当前服务所属季度的记录,不能修改其他季度
+ // ============================================================
+ FirmReportsEntity targetQuarterReport = null;
+ for (FirmReportsEntity report : allQuarterReports) {
+ String quarterStr = report.getDeclareQuarter();
+ int quarter = quarterStr != null ? Integer.parseInt(quarterStr) : 0;
+ if (quarter == costQuarter) {
+ targetQuarterReport = report;
+ break;
+ }
+ }
+
+ if (targetQuarterReport != null) {
+ // 计算当前季度的实际公益成本(元转万元)
+ BigDecimal quarterActualCost = calculateActualPublicWelfareCost(firmId, costYear, costQuarter);
+ BigDecimal quarterActualCostInWan = quarterActualCost.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP);
+
+ // 更新当前季度的实际公益成本
+ targetQuarterReport.setActualPublicWelfareCost(quarterActualCostInWan);
+
+ // 更新当前季度的全年累计实际成本收入比
+ targetQuarterReport.setActualCostIncomeRatio(annualCostIncomeRatio);
+
+ firmReportsDao.updateById(targetQuarterReport);
+
+ log.info("更新季度成本:firmId={}, costYear={}, quarter={}, 季度实际成本={}万元, 全年实际成本收入比={}%",
+ firmId, costYear, costQuarter, quarterActualCostInWan, annualCostIncomeRatio);
+ } else {
+ log.warn("更新成本失败:未找到季度记录, firmId={}, costYear={}, quarter={}", firmId, costYear, costQuarter);
+ }
+
+ log.info("更新成本成功:firmId={}, costYear={}, quarter={}", firmId, costYear, costQuarter);
+ }
+
+ /**
+ * 计算实际公益成本(用于后端审核/驳回时计算实际成本)
+ * @param firmId 律所ID
+ * @param year 年份
+ * @param quarter 季度
+ * @return 实际公益成本
+ */
+ private BigDecimal calculateActualPublicWelfareCost(Long firmId, int year, int quarter) {
+ // 计算季度的开始和结束月份
+ int startMonth = (quarter - 1) * 3 + 1;
+ int endMonth = startMonth + 2;
+
+ // 计算实际公益成本
+ BigDecimal actualCost = BigDecimal.ZERO;
+
+ for (int month = startMonth; month <= endMonth; month++) {
+ // 查询该月份的已审核通过的服务申请
+ ServiceApplicationsQueryForm queryForm = new ServiceApplicationsQueryForm();
+ queryForm.setFirmId(firmId);
+
+ // 设置月份的时间范围
+ LocalDateTime monthStart = LocalDateTime.of(year, month, 1, 0, 0, 0);
+ LocalDateTime monthEnd = monthStart.plusMonths(1).minusSeconds(1);
+ queryForm.setStartTime(monthStart.toString());
+ queryForm.setEndTime(monthEnd.toString());
+
+ // 查询已审核通过的服务申请
+ List serviceApplications = serviceApplicationsDao.queryByFirmIdAndTimeRange(firmId, monthStart.toString(), monthEnd.toString());
+
+ // 计算成本
+ DictEntity dictItem = dictService.getOne("FILECOST");
+ if (dictItem != null) {
+ for (ServiceApplicationsVO application : serviceApplications) {
+ // 统计律协审核通过的服务申请(associationAuditStatus = 3)
+ if (application.getAssociationAuditStatus() == ReviewEnum.PASS.getValue()) {
+ String serviceType = application.getServiceType();
+ if ("TIME".equals(serviceType)) {
+ double fixedHours = 0;
+ String activityPrice = application.getActivityPrice();
+ if (activityPrice != null && !activityPrice.isEmpty() && !activityPrice.contains("-")) {
+ try {
+ fixedHours = Double.parseDouble(activityPrice);
+ } catch (NumberFormatException e) {
+ fixedHours = 0;
+ }
+ }
+ BigDecimal cost = BigDecimal.valueOf(fixedHours * Long.valueOf(dictItem.getRemark()));
+ actualCost = actualCost.add(cost);
+ } else if ("AMOUT".equals(serviceType)) {
+ BigDecimal amount = application.getServiceAmount() != null ? application.getServiceAmount() : BigDecimal.ZERO;
+ actualCost = actualCost.add(amount);
+ } else if ("DICT".equals(serviceType)) {
+ double duration = application.getServiceDuration() != null ? application.getServiceDuration() : 0;
+ BigDecimal cost = BigDecimal.valueOf(duration * Long.valueOf(dictItem.getRemark()));
+ actualCost = actualCost.add(cost);
+ }
+ }
+ }
+ }
+ }
+
+ return actualCost;
+ }
/**
* 根据用户权限屏蔽成本数据
@@ -1320,12 +1733,15 @@ public class ServiceApplicationsService {
}
+ /**
+ * 批量审核
+ * 前端统一传递 auditResult 字段(3=通过,4=拒绝)
+ * 后端根据角色自动判断是律协审核还是律所审核
+ */
@Transactional
public ResponseDTO batchReview(@Valid ServiceApplicationsUpdateForm updateForm) {
- RequestUser requestUser = AdminRequestUtil.getRequestUser();
- // 查询用户角色是律所管理员,则修改律所的审核状态
- // 如果是ceo则修改协会审核状态
- List roles = roleEmployeeService.getRoleIdList(requestUser.getUserId());
+ RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
+ List roles = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId());
if (roles.isEmpty()) {
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
@@ -1335,23 +1751,57 @@ public class ServiceApplicationsService {
return ResponseDTO.ok();
}
+ // 统一从 auditResult 获取审核结果(前端传 auditResult,值为 3=通过 或 4=拒绝)
+ Integer auditResult = updateForm.getAuditResult();
+ if (auditResult == null) {
+ return ResponseDTO.userErrorParam("审核结果不能为空");
+ }
+
String[] fileKeyArray = applicationIds.split(",");
List fileKeyList = Arrays.asList(fileKeyArray);
// 设置审核时间
String reviewTime = LocalDateTime.now().toString();
-
- // 根据不同角色执行不同的审核操作
- if (AdminRequestUtil.isAssociationRole(roles)) {
- // CEO角色:修改协会审核状态
- serviceApplicationsDao.batchReviewAsAssociation(fileKeyList, updateForm.getAuditResult(), requestUser.getUserId(), reviewTime);
- } else if (AdminRequestUtil.isFirmRole(roles)) {
- // 律所主任或行政:修改律所审核状态
- serviceApplicationsDao.batchReviewAsFirm(fileKeyList, updateForm.getAuditResult(), requestUser.getUserId(), reviewTime);
- } else {
+ String auditOpinion = updateForm.getAuditRemark();
+ boolean isAssociationReview = AdminRequestUtil.isAssociationRole(roles);
+
+ // ============================================================
+ // 律协批量审核(协会角色:CEO/律协)
+ // ============================================================
+ if (isAssociationReview) {
+ serviceApplicationsDao.batchReviewAsAssociation(fileKeyList, auditResult, requestUser.getEmployeeId(), reviewTime, auditOpinion);
+ }
+ // ============================================================
+ // 律所批量审核(律所角色:主任/行政)
+ // ============================================================
+ else if (AdminRequestUtil.isFirmRole(roles)) {
+ serviceApplicationsDao.batchReviewAsFirm(fileKeyList, auditResult, requestUser.getEmployeeId(), reviewTime, auditOpinion);
+ }
+ // ============================================================
+ // 无权限
+ // ============================================================
+ else {
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
+ // 批量发送通知和更新成本
+ for (String applicationId : fileKeyList) {
+ try {
+ ServiceApplicationsEntity entity = serviceApplicationsDao.selectById(Long.valueOf(applicationId));
+ if (entity != null) {
+ // 律协审核通过时,更新成本管理表中的实际公益成本和比例
+ if (isAssociationReview && ReviewEnum.PASS.getValue().equals(auditResult)) {
+ updateActualCostInFirmReports(entity);
+ }
+ // 发送通知
+ String opinion = isAssociationReview ? updateForm.getAssociationAuditOpinion() : updateForm.getFirmAuditOpinion();
+ sendAuditNotification(entity, auditResult, opinion, isAssociationReview);
+ }
+ } catch (Exception e) {
+ log.error("批量审核发送通知失败, applicationId={}: {}", applicationId, e.getMessage());
+ }
+ }
+
return ResponseDTO.ok();
}
@@ -1360,44 +1810,121 @@ public class ServiceApplicationsService {
* @param queryForm
* @return
*/
+ /**
+ * 统计服务申请成本(万元)
+ * 根据传入参数决定返回范围:
+ * - 如果传 quarter:返回该季度的成本
+ * - 如果传 month:返回该月的成本
+ * - 如果都不传:返回全年累计成本(四个季度通过审核的服务申请成本之和)
+ *
+ * @param queryForm 包含 firmId、year、可选的 quarter 或 month
+ * @return 成本(万元)
+ */
public BigDecimal getServiceApplicationsCost(ServiceLawyerQueryForm queryForm) {
- // 根据指定的季度设置时间范围
- LocalDateTime quarterStart = DateTimeEnum.getMonthStart(queryForm.getYear(), queryForm.getMonth());
- LocalDateTime quarterEnd = DateTimeEnum.getMonthEnd(queryForm.getYear(), queryForm.getMonth());
- queryForm.setStartTime(quarterStart.toString());
- queryForm.setEndTime(quarterEnd.toString());
- queryForm.setFirmId(queryForm.getFirmId());
- //获取多少小时
- Long time = serviceApplicationsDao.getServiceApplicationsCost(queryForm);
- //获取字典表的设置
- DictEntity dictItem = dictService.getOne("FILECOST");
- if (dictItem == null){
- return BigDecimal.valueOf(0L);
- }
- // 获取是否有amount类型的的案件数据,获取金额
- BigDecimal workloadScore = serviceApplicationsDao.getServiceApplicationsAmount(queryForm);
- //除1w
- if (time != null && time != 0L) {
- Long timeCost = time * Long.valueOf(dictItem.getRemark());
-
- // 将long类型转换为BigDecimal
- BigDecimal bigDecimalDividend = BigDecimal.valueOf(timeCost);
- // 将获取到的金额加到时间金额上
- bigDecimalDividend = workloadScore.add(bigDecimalDividend);
- BigDecimal bigDecimalDivisor = BigDecimal.valueOf(10000);
-
- // 执行除法运算
- BigDecimal result = bigDecimalDividend.divide(bigDecimalDivisor, 2, RoundingMode.HALF_UP);
-
- return result;
- }else {
+ Long firmId = queryForm.getFirmId();
+ int year = queryForm.getYear();
+ Integer quarter = queryForm.getQuarter();
+ Integer month = queryForm.getMonth();
+
+ if (firmId == null || year == 0) {
return BigDecimal.ZERO;
}
+
+ BigDecimal totalCost;
+
+ // ============================================================
+ // 根据传入参数决定计算范围
+ // ============================================================
+ if (month != null && month > 0) {
+ // 计算单月成本
+ totalCost = calculateMonthlyCost(firmId, year, month);
+ log.info("获取月度成本:firmId={}, year={}, month={}, 成本={}元",
+ firmId, year, month, totalCost);
+ } else if (quarter != null && quarter > 0) {
+ // 计算单季成本
+ totalCost = calculateActualPublicWelfareCost(firmId, year, quarter);
+ log.info("获取季度成本:firmId={}, year={}, quarter={}, 成本={}元",
+ firmId, year, quarter, totalCost);
+ } else {
+ // 计算全年累计成本(四个季度之和)
+ totalCost = BigDecimal.ZERO;
+ for (int q = 1; q <= 4; q++) {
+ BigDecimal quarterCost = calculateActualPublicWelfareCost(firmId, year, q);
+ totalCost = totalCost.add(quarterCost);
+ }
+ log.info("获取全年累计成本:firmId={}, year={}, 全年累计={}元",
+ firmId, year, totalCost);
+ }
+
+ // 转换为万元
+ BigDecimal costInWan = totalCost.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP);
+
+ return costInWan;
+ }
+
+ /**
+ * 计算单月的实际公益成本(元)
+ * @param firmId 律所ID
+ * @param year 年份
+ * @param month 月份(1-12)
+ * @return 实际公益成本(元)
+ */
+ private BigDecimal calculateMonthlyCost(Long firmId, int year, int month) {
+ BigDecimal actualCost = BigDecimal.ZERO;
+
+ // 查询该月份的服务申请
+ LocalDateTime monthStart = LocalDateTime.of(year, month, 1, 0, 0, 0);
+ LocalDateTime monthEnd = monthStart.plusMonths(1).minusSeconds(1);
+
+ List serviceApplications = serviceApplicationsDao.queryByFirmIdAndTimeRange(firmId, monthStart.toString(), monthEnd.toString());
+
+ // 获取每小时成本配置
+ DictEntity dictItem = dictService.getOne("FILECOST");
+ if (dictItem != null) {
+ for (ServiceApplicationsVO application : serviceApplications) {
+ // 只统计审核通过的申请
+ if (application.getAssociationAuditStatus() == ReviewEnum.PASS.getValue()) {
+ String serviceType = application.getServiceType();
+ if ("TIME".equals(serviceType)) {
+ double fixedHours = 0;
+ String activityPrice = application.getActivityPrice();
+ if (activityPrice != null && !activityPrice.isEmpty() && !activityPrice.contains("-")) {
+ try {
+ fixedHours = Double.parseDouble(activityPrice);
+ } catch (NumberFormatException e) {
+ fixedHours = 0;
+ }
+ }
+ BigDecimal cost = BigDecimal.valueOf(fixedHours * Long.valueOf(dictItem.getRemark()));
+ actualCost = actualCost.add(cost);
+ } else if ("AMOUT".equals(serviceType)) {
+ BigDecimal amount = application.getServiceAmount() != null ? application.getServiceAmount() : BigDecimal.ZERO;
+ actualCost = actualCost.add(amount);
+ } else if ("DICT".equals(serviceType)) {
+ double duration = application.getServiceDuration() != null ? application.getServiceDuration() : 0;
+ BigDecimal cost = BigDecimal.valueOf(duration * Long.valueOf(dictItem.getRemark()));
+ actualCost = actualCost.add(cost);
+ }
+ }
+ }
+ }
+
+ return actualCost;
}
+ /**
+ * 按部门批量审核(仅律协角色可用)
+ * 前端统一传递 auditResult 字段(3=通过,4=拒绝)
+ */
public ResponseDTO batchReviewByDepartmentId(@Valid ServiceApplicationsUpdateForm updateForm) {
- //根据部门id批量审核
RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
+
+ // 统一从 auditResult 获取审核结果
+ Integer auditResult = updateForm.getAuditResult();
+ if (auditResult == null) {
+ return ResponseDTO.userErrorParam("审核结果不能为空");
+ }
+
updateForm.setAssociationAuditUser(requestUser.getEmployeeId());
updateForm.setAssociationAuditTime(LocalDateTime.now());
serviceApplicationsDao.batchReviewByDepartmentId(updateForm);
@@ -1419,14 +1946,24 @@ public class ServiceApplicationsService {
return serviceApplicationsDao.queryNoReview(departmentId);
}
+ /**
+ * 单条驳回
+ * 前端统一传递 auditResult 字段(4=驳回/拒绝)
+ * 后端根据角色自动判断是律协驳回还是律所驳回
+ */
public ResponseDTO noreview(@Valid ServiceApplicationsUpdateForm updateForm) {
RequestEmployee requestUser = AdminRequestUtil.getRequestUser();
List roles = roleEmployeeService.getRoleIdList(requestUser.getEmployeeId());
if (roles.isEmpty()) {
+ log.warn("驳回失败:用户无角色, employeeId={}", requestUser.getEmployeeId());
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
+
ServiceApplicationsEntity serviceApplicationsEntity = serviceApplicationsDao.selectById(updateForm.getApplicationId());
+ if (serviceApplicationsEntity == null) {
+ return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+ }
// 权限验证:如果只能查看成本填报律所的数据,需要验证该数据是否属于有成本查看权限的律所
if (Boolean.TRUE.equals(requestUser.getCostReportViewOnly())) {
@@ -1435,25 +1972,129 @@ public class ServiceApplicationsService {
}
}
+ // ============================================================
+ // 律协驳回(协会角色:CEO/律协)
+ // ============================================================
if (AdminRequestUtil.isAssociationRole(roles)) {
- // CEO角色:修改协会审核状态
- serviceApplicationsEntity.setAssociationAuditStatus(updateForm.getAssociationAuditStatus());
+ Integer auditResult = updateForm.getAssociationAuditStatus();
+ // 容错:如果 auditResult 不为空,也尝试读取
+ if (auditResult == null && updateForm.getAuditResult() != null) {
+ auditResult = updateForm.getAuditResult();
+ }
+ if (auditResult == null) {
+ log.warn("律协驳回失败:associationAuditStatus和auditResult都为空");
+ return ResponseDTO.userErrorParam("审核结果不能为空");
+ }
+
+ // 设置协会审核状态
+ serviceApplicationsEntity.setAssociationAuditStatus(auditResult);
serviceApplicationsEntity.setAssociationAuditUser(requestUser.getEmployeeId());
serviceApplicationsEntity.setAssociationAuditTime(LocalDateTime.now());
serviceApplicationsEntity.setAssociationAuditOpinion(updateForm.getAssociationAuditOpinion());
- } else if (AdminRequestUtil.isFirmRole(roles)) {
- // 律所主任或行政:修改律所审核状态
- serviceApplicationsEntity.setFirmAuditStatus(updateForm.getFirmAuditStatus());
+
+ // 保存服务申报状态到数据库
+ serviceApplicationsDao.updateById(serviceApplicationsEntity);
+
+ // 当协会审核驳回时,更新成本管理表中的实际公益成本和比例
+ updateActualCostInFirmReports(serviceApplicationsEntity);
+
+ // 发送通知:律协驳回通知给律所主任和行政
+ sendNoReviewNotification(serviceApplicationsEntity, true);
+ }
+ // ============================================================
+ // 律所驳回(律所角色:主任/行政)
+ // ============================================================
+ else if (AdminRequestUtil.isFirmRole(roles)) {
+ Integer auditResult = updateForm.getFirmAuditStatus();
+ // 容错:如果 auditResult 不为空,也尝试读取
+ if (auditResult == null && updateForm.getAuditResult() != null) {
+ auditResult = updateForm.getAuditResult();
+ }
+ if (auditResult == null) {
+ log.warn("律所驳回失败:firmAuditStatus和auditResult都为空");
+ return ResponseDTO.userErrorParam("审核结果不能为空");
+ }
+
+ // 设置律所审核状态
+ serviceApplicationsEntity.setFirmAuditStatus(auditResult);
serviceApplicationsEntity.setFirmAuditUser(requestUser.getEmployeeId());
serviceApplicationsEntity.setFirmAuditTime(LocalDateTime.now());
serviceApplicationsEntity.setFirmAuditOpinion(updateForm.getFirmAuditOpinion());
- } else {
+
+ serviceApplicationsDao.updateById(serviceApplicationsEntity);
+
+ // 发送通知:律所驳回通知给服务创建人
+ sendNoReviewNotification(serviceApplicationsEntity, false);
+ }
+ // ============================================================
+ // 无权限
+ // ============================================================
+ else {
+ log.warn("驳回失败:用户角色不是律协或律所, 角色={}", roles.get(0).getRoleCode());
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
+
+ return ResponseDTO.ok();
+ }
+ /**
+ * 发送驳回/拒绝通知
+ * @param entity 服务申报实体
+ * @param isAssociationReject true=协会驳回,发给律所主任和行政;false=律所拒绝,发给服务创建人
+ */
+ private void sendNoReviewNotification(ServiceApplicationsEntity entity, boolean isAssociationReject) {
+ try {
+ String reviewerType = isAssociationReject ? "律协" : "律所";
+ String statusText = isAssociationReject ? "驳回" : "拒绝";
+
+ // 获取律师姓名
+ String lawyerName = "律师";
+ if (entity.getUserId() != null) {
+ net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity employee = employeeDao.selectById(entity.getUserId());
+ if (employee != null) {
+ lawyerName = employee.getActualName();
+ }
+ }
+
+ // 构建服务简介
+ String serviceBrief = entity.getServiceContent() != null && entity.getServiceContent().length() > 20
+ ? entity.getServiceContent().substring(0, 20) + "..."
+ : (entity.getServiceContent() != null ? entity.getServiceContent() : "服务申报");
+
+ String title = "服务申报被" + reviewerType + statusText;
+
+ net.lab1024.sa.base.module.support.message.domain.MessageSendForm sendForm = new net.lab1024.sa.base.module.support.message.domain.MessageSendForm();
+ sendForm.setMessageType(net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum.AUDIT.getValue());
+ sendForm.setReceiverUserType(net.lab1024.sa.base.common.enumeration.UserTypeEnum.ADMIN_EMPLOYEE.getValue());
+ sendForm.setTitle(title);
+ sendForm.setDataId(entity.getApplicationId());
+
+ // 构建审核意见(显示律所和律协双方的意见)
+ String firmOpinion = entity.getFirmAuditOpinion();
+ String associationOpinion = entity.getAssociationAuditOpinion();
+ StringBuilder opinions = new StringBuilder();
+ if (firmOpinion != null && !firmOpinion.trim().isEmpty()) {
+ opinions.append("\n律所审核意见:").append(firmOpinion);
+ }
+ if (associationOpinion != null && !associationOpinion.trim().isEmpty()) {
+ opinions.append("\n律协审核意见:").append(associationOpinion);
+ }
- serviceApplicationsDao.updateById(serviceApplicationsEntity);
- return ResponseDTO.ok();
+ if (isAssociationReject) {
+ // 律协驳回:发给律所主任和行政(消息中包含律师姓名)
+ String adminContent = "律师" + lawyerName + "的服务申报「" + serviceBrief + "」已被" + reviewerType + statusText + "。" + opinions;
+ sendForm.setContent(adminContent);
+ sendNotificationsToFirmAdmins(entity.getFirmId(), sendForm);
+ } else {
+ // 律所拒绝:发给服务创建人(律师)
+ String lawyerContent = "您的服务申报「" + serviceBrief + "」已被" + reviewerType + statusText + "。" + opinions;
+ sendForm.setContent(lawyerContent);
+ sendForm.setReceiverUserId(entity.getUserId());
+ messageService.sendMessage(sendForm);
+ }
+ } catch (Exception e) {
+ log.error("发送驳回通知失败: {}", e.getMessage(), e);
+ }
}
@@ -2009,30 +2650,30 @@ public class ServiceApplicationsService {
// 设置季度时间范围
if (quarterQueryForm.getQuarter() == null) {
- TimeVo startQuarter = DateTimeUtil.getStartQuarter();
- quarterQueryForm.setStartTime(startQuarter.getStartTime());
- quarterQueryForm.setEndTime(startQuarter.getEndTime());
+ // 没有选择季度时,季度数据设为0
+ serviceLawyerImportForm.setQuarterlyServiceDuration(0.0);
+ serviceLawyerImportForm.setQuarterlyServiceCost(BigDecimal.ZERO);
} else {
LocalDateTime quarterStart = DateTimeEnum.getQuarterStart(quarterQueryForm.getYear(), quarterQueryForm.getQuarter());
LocalDateTime quarterEnd = DateTimeEnum.getQuarterEnd(quarterQueryForm.getYear(), quarterQueryForm.getQuarter());
quarterQueryForm.setStartTime(quarterStart.toString());
quarterQueryForm.setEndTime(quarterEnd.toString());
- }
-
- // 查询该律师的季度数据
- LawyerStatisticsVO quarterData = serviceApplicationsDao.getLawyerStatistic(quarterQueryForm);
- if (quarterData != null && quarterData.getQuarterlyServiceDuration() != null) {
- serviceLawyerImportForm.setQuarterlyServiceDuration(quarterData.getQuarterlyServiceDuration());
- serviceLawyerImportForm.setQuarterlyServiceCost(BigDecimal.valueOf(quarterData.getQuarterlyServiceDuration() * Long.valueOf(one.getRemark())));
- // 统计季度特殊金额
- BigDecimal quarterAmount = serviceApplicationsDao.getServiceAmount(quarterQueryForm);
- if (quarterAmount != null) {
- serviceLawyerImportForm.setQuarterlyServiceCost(serviceLawyerImportForm.getQuarterlyServiceCost().add(quarterAmount));
+
+ // 查询该律师的季度数据
+ LawyerStatisticsVO quarterData = serviceApplicationsDao.getLawyerStatistic(quarterQueryForm);
+ if (quarterData != null && quarterData.getQuarterlyServiceDuration() != null) {
+ serviceLawyerImportForm.setQuarterlyServiceDuration(quarterData.getQuarterlyServiceDuration());
+ serviceLawyerImportForm.setQuarterlyServiceCost(BigDecimal.valueOf(quarterData.getQuarterlyServiceDuration() * Long.valueOf(one.getRemark())));
+ // 统计季度特殊金额
+ BigDecimal quarterAmount = serviceApplicationsDao.getServiceAmount(quarterQueryForm);
+ if (quarterAmount != null) {
+ serviceLawyerImportForm.setQuarterlyServiceCost(serviceLawyerImportForm.getQuarterlyServiceCost().add(quarterAmount));
+ }
+ } else {
+ // 如果没有季度数据,设置季度成本为0
+ serviceLawyerImportForm.setQuarterlyServiceDuration(0.0);
+ serviceLawyerImportForm.setQuarterlyServiceCost(BigDecimal.ZERO);
}
- } else {
- // 如果没有季度数据,设置季度成本为0
- serviceLawyerImportForm.setQuarterlyServiceDuration(0.0);
- serviceLawyerImportForm.setQuarterlyServiceCost(BigDecimal.ZERO);
}
}
}
@@ -2399,6 +3040,7 @@ public class ServiceApplicationsService {
* 导出律师活动统计(不分页)
*/
public void exportLawyerActivityStatistics(LawyerStatisticsQueryForm queryForm, HttpServletResponse response) {
+ log.info("开始导出律师活动统计,year={}, quarter={}", queryForm.getYear(), queryForm.getQuarter());
// 应用权限控制
applyUserPermissionControl(queryForm);
@@ -2407,9 +3049,11 @@ public class ServiceApplicationsService {
// 直接查询有数据的律师活动统计(有什么导出什么)
List lawyerList = serviceApplicationsDao.exportLawyerActivity(queryForm);
+ log.info("查询到律师活动统计数据条数: {}", lawyerList != null ? lawyerList.size() : 0);
// 导出Excel
exportLawyerActivityExcel(lawyerList, response);
+ log.info("律师活动统计导出完成");
}
/**
@@ -2433,31 +3077,32 @@ public class ServiceApplicationsService {
* 导出律师活动统计Excel
*/
private void exportLawyerActivityExcel(List rawList, HttpServletResponse response) {
+ log.info("导出律师活动统计,原始数据条数: {}", rawList != null ? rawList.size() : 0);
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
- String fileName = URLEncoder.encode("律师活动统计", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
+ String fileName = URLEncoder.encode("律师公益活动统计", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
- try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build()) {
+ try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).inMemory(true).build()) {
// 按律师分组
Map lawyerMap = new LinkedHashMap<>();
// 查询所有活动及其分类信息
List allGoods = goodsService.getAllGoods();
Map goodsMap = allGoods.stream()
.collect(Collectors.toMap(GoodsEntity::getGoodsId, g -> g));
-
+
// 获取活动分类信息
Map categoryMap = getActivityCategoryMap(allGoods);
-
+
// 收集所有涉及的活动ID
Set activityIds = new LinkedHashSet<>();
-
+
if (rawList != null && !rawList.isEmpty()) {
for (LawyerActivityCountVO record : rawList) {
Long userId = record.getUserId();
LawyerActivityCountVO lawyer = lawyerMap.get(userId);
-
+
if (lawyer == null) {
lawyer = new LawyerActivityCountVO();
lawyer.setUserId(userId);
@@ -2468,10 +3113,10 @@ public class ServiceApplicationsService {
lawyer.setActivityList(new ArrayList<>());
lawyerMap.put(userId, lawyer);
}
-
+
// 添加活动
if (record.getActivityId() != null && record.getCount() != null) {
- LawyerActivityCountVO.ActivityParticipationVO activity =
+ LawyerActivityCountVO.ActivityParticipationVO activity =
new LawyerActivityCountVO.ActivityParticipationVO();
activity.setActivityId(record.getActivityId());
activity.setActivityName(record.getActivityName());
@@ -2481,128 +3126,335 @@ public class ServiceApplicationsService {
}
}
}
-
- // 按分类组织所有活动(不是只包含有数据的)
+
+ // 按分类组织所有活动(保持顺序)
Map> categoryActivities = new LinkedHashMap<>();
for (GoodsEntity goods : allGoods) {
String categoryName = categoryMap.getOrDefault(goods.getCategoryId(), "未分类");
categoryActivities.computeIfAbsent(categoryName, k -> new ArrayList<>()).add(goods.getGoodsId());
}
-
- // 构建两级表头 - 使用 List> 格式,每个内部列表是一列
+
+ // 构建两级表头
List> head = new ArrayList<>();
-
- // 第一列:律所名称(合并两行)- EasyExcel合并规则:两行内容相同才会合并
- head.add(Arrays.asList("律所名称", "律所名称"));
- // 第二列:律师名称(合并两行)
- head.add(Arrays.asList("律师名称", "律师名称"));
-
- // 构建表头
- List activityIdList = new ArrayList<>();
+
+ // 固定列(合并两行)
+ head.add(Arrays.asList("序号", " "));
+ head.add(Arrays.asList("律师名称", " "));
+
+ // 构建分类表头 - 记录每列的元数据信息(合计列在每个分类的前面)
+ List columnMetaList = new ArrayList<>();
for (Map.Entry> entry : categoryActivities.entrySet()) {
String categoryName = entry.getKey();
List activities = entry.getValue();
-
+
+ // 分类前添加合计列(合并两行)
+ if (activities.size() > 1) {
+ head.add(Arrays.asList("合计", " "));
+ columnMetaList.add(new ColumnMetaInfo(categoryName, true, null));
+ }
+
// 为每个活动添加一列
for (Long activityId : activities) {
GoodsEntity goods = goodsMap.get(activityId);
String activityName = goods != null ? goods.getGoodsName() : "未知活动";
- // 第一行是大类,第二行是活动名称
+ // 两级表头:第一行是分类名,第二行是活动名
head.add(Arrays.asList(categoryName, activityName));
- activityIdList.add(activityId);
+ columnMetaList.add(new ColumnMetaInfo(categoryName, false, activityId));
}
}
-
- // 最后一列:服务总次数(合并两行)
- head.add(Arrays.asList("服务总次数", "服务总次数"));
+
+ // 最后一列(合并两行)
+ head.add(Arrays.asList("服务总次数", " "));
+
+ log.info("导出律师活动统计,律师数量: {}", lawyerMap.size());
// 构建数据
List> data = new ArrayList<>();
+ int rowIndex = 0;
for (LawyerActivityCountVO lawyer : lawyerMap.values()) {
List