添加指导价法明细表

This commit is contained in:
lzm
2026-04-29 15:46:24 +08:00
parent a319567f65
commit 29005c5ee8
18 changed files with 886 additions and 109 deletions

View File

@@ -19,12 +19,6 @@ public class ProjectPlanningRespVO {
@Schema(description = "归属类型")
private String ownershipType;
@Schema(description = "设计部位")
private String designPart;
@Schema(description = "建筑类型")
private String buildingType;
@Schema(description = "产值计算方式")
private String calculationMethod;
@@ -34,7 +28,7 @@ public class ProjectPlanningRespVO {
@Schema(description = "规划金额")
private BigDecimal planningAmount;
@Schema(description = "管理费")
@Schema(description = "管理费率")
private BigDecimal managementFeeRate;
@Schema(description = "管理费")
@@ -76,19 +70,19 @@ public class ProjectPlanningRespVO {
@Schema(description = "楼栋数或户型数")
private Integer buildingOrUnitCount;
@Schema(description = "套图系数,保留两位小数")
@Schema(description = "套图系数")
private BigDecimal drawingSetFactor;
@Schema(description = "规模系数,保留两位小数")
@Schema(description = "规模系数")
private BigDecimal scaleFactor;
@Schema(description = "修改系数,保留两位小数")
@Schema(description = "修改系数")
private BigDecimal modificationFactor;
@Schema(description = "复杂系数/复杂等级按比例值返回100%=1.0000")
@Schema(description = "复杂系数")
private BigDecimal complexityFactor;
@Schema(description = "内部指导单价(元/")
@Schema(description = "内部指导单价(元/")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "虚拟产值计算方式")
@@ -109,7 +103,7 @@ public class ProjectPlanningRespVO {
@Schema(description = "产值计算比例")
private BigDecimal calculationRatio;
@Schema(description = "合同单价(元/")
@Schema(description = "合同单价(元/")
private BigDecimal contractUnitPrice;
@Schema(description = "合计调整系数")

View File

@@ -26,14 +26,6 @@ public class ProjectPlanningSaveReqVO {
@Size(max = 20, message = "归属类型长度不能超过 20 个字符")
private String ownershipType;
@Schema(description = "设计部位", example = "地上部分")
@Size(max = 20, message = "设计部位长度不能超过 20 个字符")
private String designPart;
@Schema(description = "建筑类型", example = "住宅")
@Size(max = 100, message = "建筑类型长度不能超过 100 个字符")
private String buildingType;
@Schema(description = "产值计算方式,页面 2 维护", example = "指导价法")
@Size(max = 30, message = "产值计算方式长度不能超过 30 个字符")
private String calculationMethod;
@@ -47,8 +39,8 @@ public class ProjectPlanningSaveReqVO {
@NotNull(message = "规划金额不能为空")
private BigDecimal planningAmount;
@Schema(description = "管理费", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
@NotNull(message = "管理费率不能为空")
@Schema(description = "管理费率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
@NotNull(message = "管理费率不能为空")
@DecimalMin(value = "0.0000", message = "managementFeeRate must be >= 0")
@DecimalMax(value = "1.0000", message = "managementFeeRate must be <= 1")
private BigDecimal managementFeeRate;
@@ -106,10 +98,10 @@ public class ProjectPlanningSaveReqVO {
@DecimalMax(value = "1.0000", message = "complexityFactor must be <= 1")
private BigDecimal complexityFactor;
@Schema(description = "内部指导单价(元/", example = "80.00")
@Schema(description = "内部指导单价(元/", example = "80.00")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "虚拟产值计算方式:指导单价法 / 指导总价法 / 工日法", example = "工日法")
@Schema(description = "虚拟产值计算方式:指导单价法/指导总价法/工日法", example = "工日法")
@Size(max = 30, message = "虚拟产值计算方式长度不能超过 30 个字符")
private String virtualCalculationMethod;

View File

@@ -0,0 +1,66 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailRespVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 合约规划指导价法明细")
@RestController
@RequestMapping("/tjt/planning-guide-detail")
@Validated
public class ProjectPlanningGuideDetailController {
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@GetMapping("/list-by-planning")
@Operation(summary = "根据合约规划获得指导价法明细列表")
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
public CommonResult<List<ProjectPlanningGuideDetailRespVO>> getProjectPlanningGuideDetailListByPlanningId(
@RequestParam("planningId") Long planningId) {
List<ProjectPlanningGuideDetailDO> list =
projectPlanningGuideDetailService.getProjectPlanningGuideDetailListByPlanningId(planningId);
return success(BeanUtils.toBean(list, ProjectPlanningGuideDetailRespVO.class));
}
@PostMapping("/batch-save")
@Operation(summary = "批量保存指导价法明细")
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
public CommonResult<Boolean> batchSaveProjectPlanningGuideDetail(
@Valid @RequestBody ProjectPlanningGuideDetailBatchSaveReqVO reqVO) {
projectPlanningGuideDetailService.batchSaveProjectPlanningGuideDetail(reqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除指导价法明细")
@Parameter(name = "id", description = "明细 ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
public CommonResult<Boolean> deleteProjectPlanningGuideDetail(@RequestParam("id") Long id) {
projectPlanningGuideDetailService.deleteProjectPlanningGuideDetail(id);
return success(true);
}
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - 合约规划指导价法明细批量保存 Request VO")
@Data
public class ProjectPlanningGuideDetailBatchSaveReqVO {
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合约规划 ID 不能为空")
private Long planningId;
@Schema(description = "指导价法明细列表")
@Valid
private List<ProjectPlanningGuideDetailSaveReqVO> details;
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 合约规划指导价法明细 Response VO")
@Data
public class ProjectPlanningGuideDetailRespVO {
@Schema(description = "明细 ID", example = "1")
private Long id;
@Schema(description = "合约规划 ID", example = "1")
private Long planningId;
@Schema(description = "项目 ID", example = "1")
private Long projectId;
@Schema(description = "设计部位")
private String designPart;
@Schema(description = "建筑类型")
private String buildingType;
@Schema(description = "设计面积")
private BigDecimal designArea;
@Schema(description = "内部指导单价(元/m²)")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "楼栋数/户型数")
private Integer buildingOrUnitCount;
@Schema(description = "套图系数")
private BigDecimal drawingSetFactor;
@Schema(description = "规模系数")
private BigDecimal scaleFactor;
@Schema(description = "修改系数")
private BigDecimal modificationFactor;
@Schema(description = "复杂系数")
private BigDecimal complexityFactor;
@Schema(description = "合计调整系数")
private BigDecimal totalAdjustmentFactor;
@Schema(description = "设计占比")
private BigDecimal designRatio;
@Schema(description = "考核面积")
private BigDecimal assessmentArea;
@Schema(description = "考核产值")
private BigDecimal assessmentOutputValue;
@Schema(description = "排序号")
private Integer sortNo;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,76 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 合约规划指导价法明细新增/修改 Request VO")
@Data
public class ProjectPlanningGuideDetailSaveReqVO {
@Schema(description = "明细 ID", example = "1")
private Long id;
@Schema(description = "设计部位", requiredMode = Schema.RequiredMode.REQUIRED, example = "地上部分")
@NotBlank(message = "设计部位不能为空")
@Size(max = 20, message = "设计部位长度不能超过 20 个字符")
private String designPart;
@Schema(description = "建筑类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "住宅")
@NotBlank(message = "建筑类型不能为空")
@Size(max = 100, message = "建筑类型长度不能超过 100 个字符")
private String buildingType;
@Schema(description = "设计面积", requiredMode = Schema.RequiredMode.REQUIRED, example = "12000")
@NotNull(message = "设计面积不能为空")
@DecimalMin(value = "0", message = "designArea must be >= 0")
private BigDecimal designArea;
@Schema(description = "内部指导单价(元/m²)", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
@NotNull(message = "内部指导单价不能为空")
@DecimalMin(value = "0", message = "internalGuidanceUnitPrice must be >= 0")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "楼栋数/户型数", example = "10")
private Integer buildingOrUnitCount;
@Schema(description = "套图系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "套图系数不能为空")
@DecimalMin(value = "0", message = "drawingSetFactor must be >= 0")
private BigDecimal drawingSetFactor;
@Schema(description = "规模系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "规模系数不能为空")
@DecimalMin(value = "0", message = "scaleFactor must be >= 0")
private BigDecimal scaleFactor;
@Schema(description = "修改系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "修改系数不能为空")
@DecimalMin(value = "0", message = "modificationFactor must be >= 0")
private BigDecimal modificationFactor;
@Schema(description = "复杂系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "复杂系数不能为空")
@DecimalMin(value = "0", message = "complexityFactor must be >= 0")
private BigDecimal complexityFactor;
@Schema(description = "设计占比", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000")
@NotNull(message = "设计占比不能为空")
@DecimalMin(value = "0", message = "designRatio must be >= 0")
@DecimalMax(value = "1", message = "designRatio must be <= 1")
private BigDecimal designRatio;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
@Schema(description = "备注", example = "样板房")
@Size(max = 500, message = "备注长度不能超过 500 个字符")
private String remark;
}

View File

@@ -27,10 +27,6 @@ public class ProjectPlanningDO extends TenantBaseDO {
private String ownershipType;
private String designPart;
private String buildingType;
private String calculationMethod;
private String planningContent;

View File

@@ -0,0 +1,55 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@TableName("tjt_project_planning_guide_detail")
@KeySequence("tjt_project_planning_guide_detail_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProjectPlanningGuideDetailDO extends TenantBaseDO {
@TableId
private Long id;
private Long planningId;
private Long projectId;
private String designPart;
private String buildingType;
private BigDecimal designArea;
private BigDecimal internalGuidanceUnitPrice;
private Integer buildingOrUnitCount;
private BigDecimal drawingSetFactor;
private BigDecimal scaleFactor;
private BigDecimal modificationFactor;
private BigDecimal complexityFactor;
private BigDecimal totalAdjustmentFactor;
private BigDecimal designRatio;
private BigDecimal assessmentArea;
private BigDecimal assessmentOutputValue;
private Integer sortNo;
private String remark;
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.planningguidedetail;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Mapper
public interface ProjectPlanningGuideDetailMapper extends BaseMapperX<ProjectPlanningGuideDetailDO> {
default List<ProjectPlanningGuideDetailDO> selectListByPlanningId(Long planningId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningGuideDetailDO>()
.eq(ProjectPlanningGuideDetailDO::getPlanningId, planningId)
.orderByAsc(ProjectPlanningGuideDetailDO::getSortNo)
.orderByAsc(ProjectPlanningGuideDetailDO::getId));
}
default List<ProjectPlanningGuideDetailDO> selectListByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<ProjectPlanningGuideDetailDO>()
.in(ProjectPlanningGuideDetailDO::getPlanningId, planningIds)
.orderByAsc(ProjectPlanningGuideDetailDO::getPlanningId)
.orderByAsc(ProjectPlanningGuideDetailDO::getSortNo)
.orderByAsc(ProjectPlanningGuideDetailDO::getId));
}
}

View File

@@ -23,6 +23,8 @@ public interface ErrorCodeConstants {
ErrorCode PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED = new ErrorCode(1_020_002_007, "工日法下工日不能为空");
ErrorCode PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED = new ErrorCode(1_020_002_008, "工日法下工日单价不能为空");
ErrorCode PROJECT_PLANNING_DESIGN_PART_INVALID = new ErrorCode(1_020_002_009, "设计部位不正确");
ErrorCode PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS = new ErrorCode(1_020_002_010, "指导价法明细不存在");
ErrorCode PROJECT_PLANNING_GUIDE_DETAIL_SCENE_INVALID = new ErrorCode(1_020_002_011, "当前合约规划不是专业所指导价法,不能维护指导价法明细");
// ========== 季度分配管理 1-020-003-000 ==========
ErrorCode PROJECT_PLANNING_QUARTER_NOT_EXISTS = new ErrorCode(1_020_003_000, "季度分配明细不存在");

View File

@@ -83,6 +83,10 @@ public final class ProjectPlanningBizTypeConstants {
return CALCULATION_METHOD_GUIDANCE_PRICE.equals(value);
}
public static boolean isMajorGuidanceScene(String ownershipType, String calculationMethod) {
return isMajor(ownershipType) && isGuidancePrice(calculationMethod);
}
public static boolean isContractPrice(String value) {
return CALCULATION_METHOD_CONTRACT_PRICE.equals(value);
}

View File

@@ -13,6 +13,7 @@ import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
import org.springframework.stereotype.Service;
@@ -30,7 +31,6 @@ import java.util.Objects;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_CALCULATION_METHOD_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_DESIGN_PART_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID;
@@ -71,6 +71,8 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
private ProjectOutputSplitService projectOutputSplitService;
@Resource
private SpecialtyRoleSplitService specialtyRoleSplitService;
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@Override
public Long createProjectPlanning(ProjectPlanningSaveReqVO createReqVO) {
@@ -80,6 +82,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
prepareProjectPlanning(planning, project);
calculateProjectPlanning(planning);
projectPlanningMapper.insert(planning);
projectPlanningGuideDetailService.handlePlanningSaved(planning, null);
return planning.getId();
}
@@ -92,6 +95,11 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
prepareProjectPlanning(updateObj, project);
calculateProjectPlanning(updateObj);
projectPlanningMapper.updateById(updateObj);
projectPlanningGuideDetailService.handlePlanningSaved(updateObj, dbPlanning);
if (ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
updateObj.getOwnershipType(), updateObj.getCalculationMethod())) {
return;
}
refreshQuarterDistributionAmounts(updateObj);
}
@@ -104,6 +112,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
projectOutputSplitService.deleteByPlanningId(id);
}
projectPlanningQuarterMapper.delete(ProjectPlanningQuarterDO::getPlanningId, id);
projectPlanningGuideDetailService.deleteByPlanningId(id);
projectPlanningMapper.deleteById(id);
}
@@ -118,6 +127,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
projectOutputSplitService.deleteByPlanningIds(ids);
}
projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, ids);
projectPlanningGuideDetailService.deleteByPlanningIds(ids);
projectPlanningMapper.deleteBatchIds(ids);
}
@@ -161,10 +171,6 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
if (!ProjectPlanningBizTypeConstants.isValidOwnershipType(reqVO.getOwnershipType())) {
throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID);
}
if (StrUtil.isNotBlank(reqVO.getDesignPart())
&& !ProjectPlanningBizTypeConstants.isValidDesignPart(reqVO.getDesignPart())) {
throw exception(PROJECT_PLANNING_DESIGN_PART_INVALID);
}
if (StrUtil.isNotBlank(reqVO.getCalculationMethod())
&& !ProjectPlanningBizTypeConstants.isValidCalculationMethod(reqVO.getCalculationMethod())) {
throw exception(PROJECT_PLANNING_CALCULATION_METHOD_INVALID);
@@ -256,16 +262,9 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
private void calculateMajorPlanning(ProjectPlanningDO planning) {
if (ProjectPlanningBizTypeConstants.isGuidancePrice(planning.getCalculationMethod())) {
BigDecimal totalAdjustmentFactor = calculateTotalAdjustmentFactor(planning);
planning.setTotalAdjustmentFactor(totalAdjustmentFactor);
planning.setAssessmentArea(multiplyAmount(planning.getPlanningArea(), totalAdjustmentFactor));
BigDecimal applicableUnitPrice = calculateApplicableUnitPrice(
planning.getContractUnitPrice(), planning.getInternalGuidanceUnitPrice());
planning.setAssessmentOutputValue(multiplyAmount(
applicableUnitPrice,
planning.getAssessmentArea(),
planning.getCurrentDesignStageRatio(),
oneMinus(planning.getReviewOutsourceRatio())));
planning.setTotalAdjustmentFactor(ZERO_RATIO);
planning.setAssessmentArea(ZERO_AMOUNT);
planning.setAssessmentOutputValue(ZERO_AMOUNT);
return;
}
if (ProjectPlanningBizTypeConstants.isContractPrice(planning.getCalculationMethod())) {

View File

@@ -0,0 +1,27 @@
package cn.iocoder.lyzsys.module.tjt.service.planningguidedetail;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface ProjectPlanningGuideDetailService {
void batchSaveProjectPlanningGuideDetail(ProjectPlanningGuideDetailBatchSaveReqVO reqVO);
void deleteProjectPlanningGuideDetail(Long id);
List<ProjectPlanningGuideDetailDO> getProjectPlanningGuideDetailListByPlanningId(Long planningId);
Map<Long, List<ProjectPlanningGuideDetailDO>> getProjectPlanningGuideDetailMapByPlanningIds(Collection<Long> planningIds);
void deleteByPlanningId(Long planningId);
void deleteByPlanningIds(Collection<Long> planningIds);
void handlePlanningSaved(ProjectPlanningDO planning, ProjectPlanningDO dbPlanning);
}

View File

@@ -0,0 +1,289 @@
package cn.iocoder.lyzsys.module.tjt.service.planningguidedetail;
import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningguidedetail.ProjectPlanningGuideDetailMapper;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_DESIGN_PART_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_GUIDE_DETAIL_SCENE_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_NOT_EXISTS;
@Service
@Validated
public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGuideDetailService {
private static final int AMOUNT_SCALE = 2;
private static final int RATIO_SCALE = 4;
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
@Resource
private ProjectPlanningGuideDetailMapper projectPlanningGuideDetailMapper;
@Resource
private ProjectPlanningMapper projectPlanningMapper;
@Resource
private ProjectPlanningQuarterMapper projectPlanningQuarterMapper;
@Override
public void batchSaveProjectPlanningGuideDetail(ProjectPlanningGuideDetailBatchSaveReqVO reqVO) {
ProjectPlanningDO planning = validateGuideDetailPlanning(reqVO.getPlanningId());
List<ProjectPlanningGuideDetailSaveReqVO> reqDetails = reqVO.getDetails() == null
? Collections.emptyList() : reqVO.getDetails();
List<ProjectPlanningGuideDetailDO> existingList = projectPlanningGuideDetailMapper.selectListByPlanningId(reqVO.getPlanningId());
Map<Long, ProjectPlanningGuideDetailDO> existingMap =
CollectionUtils.convertMap(existingList, ProjectPlanningGuideDetailDO::getId);
Set<Long> retainedIds = reqDetails.stream()
.map(ProjectPlanningGuideDetailSaveReqVO::getId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (int i = 0; i < reqDetails.size(); i++) {
ProjectPlanningGuideDetailSaveReqVO detailReqVO = reqDetails.get(i);
validateGuideDetailForSave(detailReqVO);
ProjectPlanningGuideDetailDO detail = BeanUtils.toBean(detailReqVO, ProjectPlanningGuideDetailDO.class);
detail.setPlanningId(planning.getId());
detail.setProjectId(planning.getProjectId());
detail.setSortNo(detailReqVO.getSortNo() == null ? i + 1 : detailReqVO.getSortNo());
calculateGuideDetail(detail, planning.getReviewOutsourceRatio());
if (detail.getId() == null) {
projectPlanningGuideDetailMapper.insert(detail);
continue;
}
if (!existingMap.containsKey(detail.getId())) {
throw exception(PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS);
}
projectPlanningGuideDetailMapper.updateById(detail);
}
List<Long> deletedIds = existingList.stream()
.map(ProjectPlanningGuideDetailDO::getId)
.filter(id -> !retainedIds.contains(id))
.collect(Collectors.toList());
if (!deletedIds.isEmpty()) {
projectPlanningGuideDetailMapper.deleteBatchIds(deletedIds);
}
recalculatePlanningSummary(planning.getId());
}
@Override
public void deleteProjectPlanningGuideDetail(Long id) {
ProjectPlanningGuideDetailDO detail = projectPlanningGuideDetailMapper.selectById(id);
if (detail == null) {
throw exception(PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS);
}
projectPlanningGuideDetailMapper.deleteById(id);
recalculatePlanningSummary(detail.getPlanningId());
}
@Override
public List<ProjectPlanningGuideDetailDO> getProjectPlanningGuideDetailListByPlanningId(Long planningId) {
validatePlanningExists(planningId);
return projectPlanningGuideDetailMapper.selectListByPlanningId(planningId);
}
@Override
public Map<Long, List<ProjectPlanningGuideDetailDO>> getProjectPlanningGuideDetailMapByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return Collections.emptyMap();
}
return CollectionUtils.convertMultiMap(
projectPlanningGuideDetailMapper.selectListByPlanningIds(planningIds),
ProjectPlanningGuideDetailDO::getPlanningId
);
}
@Override
public void deleteByPlanningId(Long planningId) {
if (planningId == null) {
return;
}
projectPlanningGuideDetailMapper.delete(ProjectPlanningGuideDetailDO::getPlanningId, planningId);
}
@Override
public void deleteByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return;
}
projectPlanningGuideDetailMapper.deleteBatch(ProjectPlanningGuideDetailDO::getPlanningId, planningIds);
}
@Override
public void handlePlanningSaved(ProjectPlanningDO planning, ProjectPlanningDO dbPlanning) {
boolean currentGuideScene = isGuideDetailScene(planning);
boolean previousGuideScene = isGuideDetailScene(dbPlanning);
if (!currentGuideScene) {
if (previousGuideScene) {
deleteByPlanningId(planning.getId());
}
return;
}
recalculateGuideDetails(planning.getId(), ratio(planning.getReviewOutsourceRatio()));
recalculatePlanningSummary(planning.getId());
}
private void recalculateGuideDetails(Long planningId, BigDecimal reviewOutsourceRatio) {
List<ProjectPlanningGuideDetailDO> detailList = projectPlanningGuideDetailMapper.selectListByPlanningId(planningId);
if (detailList.isEmpty()) {
return;
}
for (ProjectPlanningGuideDetailDO detail : detailList) {
calculateGuideDetail(detail, reviewOutsourceRatio);
projectPlanningGuideDetailMapper.updateById(detail);
}
}
private void recalculatePlanningSummary(Long planningId) {
ProjectPlanningDO planning = validatePlanningExists(planningId);
List<ProjectPlanningGuideDetailDO> detailList = projectPlanningGuideDetailMapper.selectListByPlanningId(planningId);
BigDecimal totalDesignArea = ZERO_AMOUNT;
BigDecimal totalAssessmentArea = ZERO_AMOUNT;
BigDecimal totalAssessmentOutputValue = ZERO_AMOUNT;
for (ProjectPlanningGuideDetailDO detail : detailList) {
totalDesignArea = totalDesignArea.add(amount(detail.getDesignArea())).setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
totalAssessmentArea = totalAssessmentArea.add(amount(detail.getAssessmentArea())).setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
totalAssessmentOutputValue = totalAssessmentOutputValue.add(amount(detail.getAssessmentOutputValue()))
.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
ProjectPlanningDO updateObj = new ProjectPlanningDO();
updateObj.setId(planningId);
updateObj.setPlanningArea(totalDesignArea);
updateObj.setContractUnitPrice(divideAmount(planning.getPlanningAmount(), totalDesignArea));
updateObj.setTotalAdjustmentFactor(ZERO_RATIO);
updateObj.setAssessmentArea(totalAssessmentArea);
updateObj.setVirtualOutputValue(ZERO_AMOUNT);
updateObj.setAssessmentOutputValue(totalAssessmentOutputValue);
projectPlanningMapper.updateById(updateObj);
ProjectPlanningDO refreshedPlanning = projectPlanningMapper.selectById(planningId);
refreshQuarterDistributionAmounts(refreshedPlanning);
}
private void validateGuideDetailForSave(ProjectPlanningGuideDetailSaveReqVO reqVO) {
if (!ProjectPlanningBizTypeConstants.isValidDesignPart(reqVO.getDesignPart())) {
throw exception(PROJECT_PLANNING_DESIGN_PART_INVALID);
}
}
private ProjectPlanningDO validateGuideDetailPlanning(Long planningId) {
ProjectPlanningDO planning = validatePlanningExists(planningId);
if (!isGuideDetailScene(planning)) {
throw exception(PROJECT_PLANNING_GUIDE_DETAIL_SCENE_INVALID);
}
return planning;
}
private ProjectPlanningDO validatePlanningExists(Long planningId) {
ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId);
if (planning == null) {
throw exception(PROJECT_PLANNING_NOT_EXISTS);
}
return planning;
}
private boolean isGuideDetailScene(ProjectPlanningDO planning) {
return planning != null && ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
planning.getOwnershipType(), planning.getCalculationMethod());
}
private void calculateGuideDetail(ProjectPlanningGuideDetailDO detail, BigDecimal reviewOutsourceRatio) {
detail.setDesignArea(amount(detail.getDesignArea()));
detail.setInternalGuidanceUnitPrice(amount(detail.getInternalGuidanceUnitPrice()));
detail.setDrawingSetFactor(ratio(detail.getDrawingSetFactor()));
detail.setScaleFactor(ratio(detail.getScaleFactor()));
detail.setModificationFactor(ratio(detail.getModificationFactor()));
detail.setComplexityFactor(ratio(detail.getComplexityFactor()));
detail.setDesignRatio(ratio(detail.getDesignRatio()));
detail.setTotalAdjustmentFactor(multiplyRatio(
detail.getDrawingSetFactor(),
detail.getScaleFactor(),
detail.getModificationFactor(),
detail.getComplexityFactor()
));
detail.setAssessmentArea(multiplyAmount(detail.getDesignArea(), detail.getTotalAdjustmentFactor()));
detail.setAssessmentOutputValue(multiplyAmount(
detail.getInternalGuidanceUnitPrice(),
detail.getAssessmentArea(),
detail.getDesignRatio(),
oneMinus(reviewOutsourceRatio)
));
}
private void refreshQuarterDistributionAmounts(ProjectPlanningDO planning) {
if (planning == null) {
return;
}
List<ProjectPlanningQuarterDO> quarterList = projectPlanningQuarterMapper.selectListByPlanningId(planning.getId());
for (ProjectPlanningQuarterDO quarter : quarterList) {
quarter.setDistributionAmount(multiplyAmount(
planning.getAssessmentOutputValue(),
planning.getTotalDistributionAmount(),
ratio(quarter.getDistributionRatio())
));
projectPlanningQuarterMapper.updateById(quarter);
}
}
private BigDecimal amount(BigDecimal value) {
return value == null ? ZERO_AMOUNT : value.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal ratio(BigDecimal value) {
return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal oneMinus(BigDecimal value) {
return BigDecimal.ONE.subtract(ratio(value)).setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal multiplyAmount(BigDecimal... values) {
BigDecimal result = BigDecimal.ONE;
for (BigDecimal value : values) {
result = result.multiply(value == null ? BigDecimal.ZERO : value);
}
return result.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal multiplyRatio(BigDecimal... values) {
BigDecimal result = BigDecimal.ONE;
for (BigDecimal value : values) {
result = result.multiply(value == null ? BigDecimal.ZERO : value);
}
return result.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal divideAmount(BigDecimal dividend, BigDecimal divisor) {
if (dividend == null || divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) {
return ZERO_AMOUNT;
}
return dividend.divide(divisor, AMOUNT_SCALE, RoundingMode.HALF_UP);
}
}

View File

@@ -20,6 +20,7 @@ import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -69,6 +70,8 @@ public class ProjectServiceImpl implements ProjectService {
private ProjectOutputSplitService projectOutputSplitService;
@Resource
private SpecialtyRoleSplitService specialtyRoleSplitService;
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@Override
public Long createProject(ProjectSaveReqVO createReqVO) {
@@ -300,6 +303,7 @@ public class ProjectServiceImpl implements ProjectService {
projectOutputSplitService.deleteByPlanningIds(planningIds);
}
projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, planningIds);
projectPlanningGuideDetailService.deleteByPlanningIds(planningIds);
projectPlanningMapper.deleteBatchIds(planningIds);
}

View File

@@ -18,6 +18,7 @@ import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO;
@@ -32,6 +33,7 @@ import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper;
import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import cn.iocoder.lyzsys.module.tjt.service.report.builder.EmployeeOutputSummaryExcelBuilder;
import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectBudgetExcelBuilder;
import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectLeadQuarterOutputExcelBuilder;
@@ -105,6 +107,8 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
@Resource
private SpecialtyRoleSplitService specialtyRoleSplitService;
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@Resource
private ProjectBudgetExcelBuilder projectBudgetExcelBuilder;
@Resource
private ProjectQuarterOutputExcelBuilder projectQuarterOutputExcelBuilder;
@@ -124,6 +128,10 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
List<ProjectPlanningDO> planningList = sortPlanningList(projectPlanningMapper.selectListByProjectId(project.getId()));
List<ProjectRolePersonDO> rolePersons = projectRolePersonMapper.selectListByProjectId(project.getId());
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = getQuarterMap(planningList);
Map<Long, List<ProjectPlanningGuideDetailDO>> guideDetailMap = projectPlanningGuideDetailService
.getProjectPlanningGuideDetailMapByPlanningIds(planningList.stream()
.map(ProjectPlanningDO::getId)
.collect(Collectors.toList()));
ProjectBudgetExcelBuilder.ExportData data = new ProjectBudgetExcelBuilder.ExportData();
data.setProjectCode("");
@@ -146,7 +154,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
totalAssessmentOutputValue = totalAssessmentOutputValue.add(amount(planning.getAssessmentOutputValue()))
.setScale(2, RoundingMode.HALF_UP);
}
data.setBudgetRows(buildProjectBudgetRows(planningList));
data.setBudgetRows(buildProjectBudgetRows(planningList, guideDetailMap));
data.setTotalAssessmentArea(totalAssessmentArea);
data.setTotalAssessmentOutputValue(totalAssessmentOutputValue);
data.setTotalAssessmentOutputValueWan(amountToWan(totalAssessmentOutputValue));
@@ -191,7 +199,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
List<ProjectPlanningDO> planningList = projectPlanningMapper.selectListByProjectId(project.getId()).stream()
.sorted(Comparator
.comparingInt((ProjectPlanningDO item) -> projectLeadOutputTypeOrder(item.getOwnershipType()))
.thenComparingInt((ProjectPlanningDO item) -> designPartOrder(item.getDesignPart()))
.thenComparing(ProjectPlanningDO::getId))
.collect(Collectors.toList());
List<ProjectRolePersonDO> projectRolePersons = projectRolePersonMapper.selectListByProjectId(project.getId());
@@ -737,102 +744,128 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
return rows;
}
private List<ProjectBudgetExcelBuilder.BudgetRow> buildProjectBudgetRows(List<ProjectPlanningDO> planningList) {
private List<ProjectBudgetExcelBuilder.BudgetRow> buildProjectBudgetRows(
List<ProjectPlanningDO> planningList,
Map<Long, List<ProjectPlanningGuideDetailDO>> guideDetailMap) {
if (planningList == null || planningList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<ProjectPlanningDO>> planningContentMap = new LinkedHashMap<>();
List<ProjectBudgetExcelBuilder.BudgetRow> rows = new ArrayList<>();
planningList.stream()
.sorted(Comparator.comparing(ProjectPlanningDO::getId))
.forEach(planning -> planningContentMap
.computeIfAbsent(resolveBudgetPlanningGroupKey(planning), key -> new ArrayList<>())
.add(planning));
List<ProjectBudgetExcelBuilder.BudgetRow> rows = new ArrayList<>();
for (List<ProjectPlanningDO> planningGroup : planningContentMap.values()) {
appendBudgetPlanningRows(rows, planningGroup);
}
.forEach(planning -> {
if (!ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
planning.getOwnershipType(), planning.getCalculationMethod())) {
return;
}
List<ProjectPlanningGuideDetailDO> detailList = guideDetailMap == null
? Collections.emptyList()
: guideDetailMap.getOrDefault(planning.getId(), Collections.emptyList());
if (detailList.isEmpty()) {
return;
}
appendBudgetPlanningRows(rows, planning, sortBudgetGuideDetailList(detailList));
});
return rows;
}
private void appendBudgetPlanningRows(List<ProjectBudgetExcelBuilder.BudgetRow> rows,
List<ProjectPlanningDO> planningGroup) {
ProjectPlanningDO planning,
List<ProjectPlanningGuideDetailDO> planningGroup) {
if (planningGroup == null || planningGroup.isEmpty()) {
return;
}
List<ProjectPlanningDO> groundPlanningList = filterBudgetPartPlanningList(
String planningContent = resolveBudgetPlanningContent(planning);
List<ProjectPlanningGuideDetailDO> groundPlanningList = filterBudgetPartPlanningList(
planningGroup, ProjectPlanningBizTypeConstants.DESIGN_PART_REAL_ESTATE);
List<ProjectPlanningDO> undergroundPlanningList = filterBudgetPartPlanningList(
List<ProjectPlanningGuideDetailDO> undergroundPlanningList = filterBudgetPartPlanningList(
planningGroup, ProjectPlanningBizTypeConstants.DESIGN_PART_UNDERGROUND);
appendBudgetPartRows(rows, ProjectPlanningBizTypeConstants.DESIGN_PART_REAL_ESTATE, groundPlanningList);
appendBudgetPartRows(rows, ProjectPlanningBizTypeConstants.DESIGN_PART_UNDERGROUND, undergroundPlanningList);
rows.add(buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType.PLANNING_TOTAL, "总计", planningGroup));
appendBudgetPartRows(rows, planning, planningContent,
ProjectPlanningBizTypeConstants.DESIGN_PART_REAL_ESTATE, groundPlanningList);
appendBudgetPartRows(rows, planning, planningContent,
ProjectPlanningBizTypeConstants.DESIGN_PART_UNDERGROUND, undergroundPlanningList);
rows.add(buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType.PLANNING_TOTAL,
planning, planningContent, "总计", planningGroup));
}
private void appendBudgetPartRows(List<ProjectBudgetExcelBuilder.BudgetRow> rows, String designPart,
List<ProjectPlanningDO> partPlanningList) {
private void appendBudgetPartRows(List<ProjectBudgetExcelBuilder.BudgetRow> rows,
ProjectPlanningDO planning,
String planningContent,
String designPart,
List<ProjectPlanningGuideDetailDO> partPlanningList) {
if (partPlanningList == null || partPlanningList.isEmpty()) {
rows.add(buildBudgetEmptyRow(designPart));
return;
}
for (ProjectPlanningDO planning : partPlanningList) {
rows.add(buildBudgetDetailRow(designPart, planning));
for (ProjectPlanningGuideDetailDO detail : partPlanningList) {
rows.add(buildBudgetDetailRow(planning, planningContent, designPart, detail));
}
if (partPlanningList.size() > 2) {
rows.add(buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType.PART_SUBTOTAL,
designPart + "总计", partPlanningList));
planning, planningContent, designPart + "总计", partPlanningList));
}
}
private List<ProjectPlanningDO> filterBudgetPartPlanningList(List<ProjectPlanningDO> planningGroup, String designPart) {
private List<ProjectPlanningGuideDetailDO> filterBudgetPartPlanningList(
List<ProjectPlanningGuideDetailDO> planningGroup, String designPart) {
return planningGroup.stream()
.filter(item -> Objects.equals(item.getDesignPart(), designPart))
.sorted(Comparator.comparing(ProjectPlanningDO::getId))
.sorted(Comparator
.comparingInt((ProjectPlanningGuideDetailDO item) ->
item.getSortNo() == null ? Integer.MAX_VALUE : item.getSortNo())
.thenComparing(ProjectPlanningGuideDetailDO::getId))
.collect(Collectors.toList());
}
private String resolveBudgetPlanningGroupKey(ProjectPlanningDO planning) {
String planningContent = defaultString(planning == null ? null : planning.getPlanningContent());
return planningContent.isEmpty() ? "__PLANNING__" + (planning == null ? "" : planning.getId()) : planningContent;
private String resolveBudgetPlanningContent(ProjectPlanningDO planning) {
return defaultString(planning == null ? null : planning.getPlanningContent());
}
private ProjectBudgetExcelBuilder.BudgetRow buildBudgetDetailRow(String designPart, ProjectPlanningDO planning) {
ProjectBudgetExcelBuilder.BudgetRow row = initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType.DETAIL, designPart);
row.setPlanningContent(defaultString(planning.getPlanningContent()));
row.setDisplayBuildingType(defaultString(planning.getBuildingType()));
row.setInternalGuidanceUnitPrice(planning.getInternalGuidanceUnitPrice());
row.setDesignArea(planning.getPlanningArea());
row.setBuildingOrUnitCount(planning.getBuildingOrUnitCount());
row.setDrawingSetFactor(planning.getDrawingSetFactor());
row.setScaleFactor(planning.getScaleFactor());
row.setModificationFactor(planning.getModificationFactor());
row.setComplexityFactor(planning.getComplexityFactor());
row.setSubtotalArea(planning.getAssessmentArea());
row.setCurrentDesignStageRatio(planning.getCurrentDesignStageRatio());
row.setAssessmentOutputValueWan(planning.getAssessmentOutputValue() == null
? null : amountToWan(planning.getAssessmentOutputValue()));
private ProjectBudgetExcelBuilder.BudgetRow buildBudgetDetailRow(ProjectPlanningDO planning,
String planningContent,
String designPart,
ProjectPlanningGuideDetailDO detail) {
ProjectBudgetExcelBuilder.BudgetRow row = initBudgetRow(
ProjectBudgetExcelBuilder.BudgetRowType.DETAIL, planning, planningContent, designPart);
row.setDisplayBuildingType(defaultString(detail.getBuildingType()));
row.setInternalGuidanceUnitPrice(detail.getInternalGuidanceUnitPrice());
row.setDesignArea(detail.getDesignArea());
row.setBuildingOrUnitCount(detail.getBuildingOrUnitCount());
row.setDrawingSetFactor(detail.getDrawingSetFactor());
row.setScaleFactor(detail.getScaleFactor());
row.setModificationFactor(detail.getModificationFactor());
row.setComplexityFactor(detail.getComplexityFactor());
row.setSubtotalArea(detail.getAssessmentArea());
row.setCurrentDesignStageRatio(detail.getDesignRatio());
row.setAssessmentOutputValueWan(detail.getAssessmentOutputValue() == null
? null : amountToWan(detail.getAssessmentOutputValue()));
return row;
}
private ProjectBudgetExcelBuilder.BudgetRow buildBudgetEmptyRow(String designPart) {
return initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType.PART_EMPTY, designPart);
return initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType.PART_EMPTY, null, "", designPart);
}
private ProjectBudgetExcelBuilder.BudgetRow buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType rowType,
ProjectPlanningDO planning,
String planningContent,
String displayDesignPart,
List<ProjectPlanningDO> planningList) {
ProjectBudgetExcelBuilder.BudgetRow row = initBudgetRow(rowType, displayDesignPart);
row.setDesignArea(sumPlanningArea(planningList));
row.setSubtotalArea(sumAssessmentArea(planningList));
row.setAssessmentOutputValueWan(amountToWan(sumAssessmentOutputValue(planningList)));
List<ProjectPlanningGuideDetailDO> detailList) {
ProjectBudgetExcelBuilder.BudgetRow row = initBudgetRow(rowType, planning, planningContent, displayDesignPart);
row.setDesignArea(sumGuideDetailDesignArea(detailList));
row.setSubtotalArea(sumGuideDetailAssessmentArea(detailList));
row.setAssessmentOutputValueWan(amountToWan(sumGuideDetailAssessmentOutputValue(detailList)));
return row;
}
private ProjectBudgetExcelBuilder.BudgetRow initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType rowType,
ProjectPlanningDO planning,
String planningContent,
String displayDesignPart) {
ProjectBudgetExcelBuilder.BudgetRow row = new ProjectBudgetExcelBuilder.BudgetRow();
row.setRowType(rowType);
row.setPlanningId(planning == null ? null : planning.getId());
row.setPlanningContent(planningContent);
row.setDisplayDesignPart(displayDesignPart);
return row;
}
@@ -870,6 +903,48 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
return total;
}
private List<ProjectPlanningGuideDetailDO> sortBudgetGuideDetailList(List<ProjectPlanningGuideDetailDO> detailList) {
return detailList.stream()
.sorted(Comparator
.comparingInt((ProjectPlanningGuideDetailDO item) -> designPartOrder(item.getDesignPart()))
.thenComparingInt(item -> item.getSortNo() == null ? Integer.MAX_VALUE : item.getSortNo())
.thenComparing(ProjectPlanningGuideDetailDO::getId))
.collect(Collectors.toList());
}
private BigDecimal sumGuideDetailDesignArea(List<ProjectPlanningGuideDetailDO> detailList) {
BigDecimal total = ZERO_AMOUNT;
if (detailList == null || detailList.isEmpty()) {
return total;
}
for (ProjectPlanningGuideDetailDO detail : detailList) {
total = total.add(amount(detail.getDesignArea())).setScale(2, RoundingMode.HALF_UP);
}
return total;
}
private BigDecimal sumGuideDetailAssessmentArea(List<ProjectPlanningGuideDetailDO> detailList) {
BigDecimal total = ZERO_AMOUNT;
if (detailList == null || detailList.isEmpty()) {
return total;
}
for (ProjectPlanningGuideDetailDO detail : detailList) {
total = total.add(amount(detail.getAssessmentArea())).setScale(2, RoundingMode.HALF_UP);
}
return total;
}
private BigDecimal sumGuideDetailAssessmentOutputValue(List<ProjectPlanningGuideDetailDO> detailList) {
BigDecimal total = ZERO_AMOUNT;
if (detailList == null || detailList.isEmpty()) {
return total;
}
for (ProjectPlanningGuideDetailDO detail : detailList) {
total = total.add(amount(detail.getAssessmentOutputValue())).setScale(2, RoundingMode.HALF_UP);
}
return total;
}
private List<ProjectBudgetExcelBuilder.QuarterBudgetRow> buildQuarterBudgetRows(
List<ProjectPlanningDO> planningList, Map<Long, List<ProjectPlanningQuarterDO>> quarterMap,
Integer reportYear) {
@@ -958,9 +1033,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
private List<ProjectPlanningDO> sortPlanningList(List<ProjectPlanningDO> planningList) {
return planningList.stream()
.sorted(Comparator
.comparingInt((ProjectPlanningDO item) -> designPartOrder(item.getDesignPart()))
.thenComparing(ProjectPlanningDO::getId))
.sorted(Comparator.comparing(ProjectPlanningDO::getId))
.collect(Collectors.toList());
}

View File

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
@Component
public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder {
@@ -22,8 +23,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
private static final String BUDGET_SHEET_TITLE = "附件4 建筑(装饰)工程项目考核产值预算表";
private static final String QUARTER_BUDGET_SHEET_NAME = "项目考核产值年度季度预算计取表";
private static final String QUARTER_BUDGET_SHEET_FALLBACK_NAME = "年度季度预算计取表";
private static final String QUARTER_BUDGET_REMARK = "注:\n"
+ "1、设计阶段考核产值为总考核产值×阶段占比本年度/季度考核产值为阶段考核产值×本年度/季度发放比例。";
private static final String QUARTER_BUDGET_REMARK = "注:1、设计阶段考核产值为总考核产值×阶段占比本年度/季度考核产值为阶段考核产值×本年度/季度发放比例。";
private static final String DATE_TEXT = "日期: 年 月 日";
public Workbook build(ExportData data) {
@@ -52,6 +52,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
int rowIndex = 0;
rowIndex = writeMergedTitleRow(sheet, rowIndex, BUDGET_SHEET_TITLE, titleStyle, 12);
rowIndex = buildBudgetHeader(sheet, rowIndex, headerStyle);
int dataStartRow = rowIndex;
if (data.getBudgetRows() != null) {
for (BudgetRow rowData : data.getBudgetRows()) {
@@ -75,6 +76,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
}
}
}
mergeBudgetPlanningContentColumn(sheet, data.getBudgetRows(), dataStartRow);
Row remarkRow = sheet.createRow(rowIndex++);
setMergedRegionText(sheet, remarkRow.getRowNum(), remarkRow.getRowNum(), 0, 12,
@@ -116,7 +118,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
private void writeBudgetDetailRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", style);
setText(row, 0, safeText(rowData.getPlanningContent()), style);
setText(row, 1, safeText(rowData.getDisplayDesignPart()), style);
setText(row, 2, safeText(rowData.getDisplayBuildingType()), style);
setText(row, 3, textOrBlank(rowData.getInternalGuidanceUnitPrice()), style);
@@ -133,7 +135,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
private void writeBudgetEmptyRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", style);
setText(row, 0, safeText(rowData.getPlanningContent()), style);
setText(row, 1, safeText(rowData.getDisplayDesignPart()), style);
for (int i = 2; i <= 12; i++) {
setText(row, i, "", style);
@@ -142,7 +144,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
private void writeBudgetSummaryRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", style);
setText(row, 0, safeText(rowData.getPlanningContent()), style);
setText(row, 1, safeText(rowData.getDisplayDesignPart()), style);
setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, SUMMARY_PLACEHOLDER, style);
setText(row, 4, text(rowData.getDesignArea()), style);
@@ -152,6 +154,31 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
setText(row, 12, text(rowData.getAssessmentOutputValueWan()), style);
}
private void mergeBudgetPlanningContentColumn(Sheet sheet, List<BudgetRow> budgetRows, int dataStartRow) {
if (budgetRows == null || budgetRows.isEmpty()) {
return;
}
int mergeStartRow = dataStartRow;
Long currentPlanningId = budgetRows.get(0).getPlanningId();
for (int i = 1; i < budgetRows.size(); i++) {
Long planningId = budgetRows.get(i).getPlanningId();
if (Objects.equals(currentPlanningId, planningId)) {
continue;
}
mergeBudgetPlanningContentRange(sheet, mergeStartRow, dataStartRow + i - 1);
mergeStartRow = dataStartRow + i;
currentPlanningId = planningId;
}
mergeBudgetPlanningContentRange(sheet, mergeStartRow, dataStartRow + budgetRows.size() - 1);
}
private void mergeBudgetPlanningContentRange(Sheet sheet, int startRow, int endRow) {
if (endRow <= startRow) {
return;
}
merge(sheet, startRow, endRow, 0, 0);
}
private void buildQuarterBudgetSheet(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, QUARTER_BUDGET_SHEET_NAME, QUARTER_BUDGET_SHEET_FALLBACK_NAME);
setColumnWidths(sheet, 10, 20, 18, 24, 16, 16, 16, 12, 14, 14, 12, 12, 12, 12, 14, 14, 12, 12, 12, 12, 14, 24);
@@ -167,9 +194,12 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle centeredCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle rightAlignedInfoStyle = createDerivedStyle(workbook, infoValueStyle,
null, HorizontalAlignment.RIGHT, false, (short) 10);
int rowIndex = 0;
rowIndex = buildQuarterBudgetHeader(sheet, rowIndex, data, headerStyle, centeredCellStyle);
int dataStartRow = rowIndex;
for (QuarterBudgetRow rowData : data.getQuarterBudgetRows()) {
Row row = sheet.createRow(rowIndex++);
@@ -201,6 +231,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
setText(row, 20, textOrBlank(rowData.getYearTotalAmountWan()), cellStyle);
setText(row, 21, safeText(rowData.getRatioRemark()), cellStyle);
}
mergeQuarterBudgetProjectAndOutputType(sheet, data.getQuarterBudgetRows(), dataStartRow);
Row remarkRow = sheet.createRow(rowIndex++);
setMergedRegionText(sheet, remarkRow.getRowNum(), remarkRow.getRowNum(), 0, 21,
@@ -215,12 +246,55 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
"市场运营中心负责人:", infoValueStyle);
Row dateRow = sheet.createRow(rowIndex);
setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 0, 6,
DATE_TEXT, infoValueStyle);
setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 7, 13,
DATE_TEXT, infoValueStyle);
setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 14, 21,
DATE_TEXT, infoValueStyle);
setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 0, 21,
DATE_TEXT, rightAlignedInfoStyle);
}
private void mergeQuarterBudgetProjectAndOutputType(Sheet sheet,
List<QuarterBudgetRow> rows,
int dataStartRow) {
if (rows == null || rows.isEmpty()) {
return;
}
int detailRowCount = 0;
for (QuarterBudgetRow row : rows) {
if (row != null && !row.isTotalRow()) {
detailRowCount++;
}
}
if (detailRowCount <= 0) {
return;
}
if (detailRowCount > 1) {
merge(sheet, dataStartRow, dataStartRow + detailRowCount - 1, 1, 1);
}
int currentStartRow = -1;
String currentOutputType = null;
int currentRowIndex = dataStartRow;
for (QuarterBudgetRow row : rows) {
if (row == null || row.isTotalRow()) {
continue;
}
String outputType = safeText(row.getOutputType());
if (currentStartRow < 0) {
currentStartRow = currentRowIndex;
currentOutputType = outputType;
} else if (!Objects.equals(currentOutputType, outputType)) {
mergeQuarterBudgetOutputTypeRange(sheet, currentStartRow, currentRowIndex - 1);
currentStartRow = currentRowIndex;
currentOutputType = outputType;
}
currentRowIndex++;
}
mergeQuarterBudgetOutputTypeRange(sheet, currentStartRow, dataStartRow + detailRowCount - 1);
}
private void mergeQuarterBudgetOutputTypeRange(Sheet sheet, int startRow, int endRow) {
if (startRow < 0 || endRow <= startRow) {
return;
}
merge(sheet, startRow, endRow, 2, 2);
}
private int buildQuarterBudgetHeader(Sheet sheet, int rowIndex, ExportData data,
@@ -344,6 +418,7 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder
@Data
public static class BudgetRow {
private BudgetRowType rowType;
private Long planningId;
private String planningContent;
private String displayDesignPart;
private String displayBuildingType;

View File

@@ -52,7 +52,7 @@ spring:
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
url: jdbc:sqlserver://192.168.1.34:1433;DatabaseName=tjt_czjs;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例
url: jdbc:sqlserver://47.106.184.244:14333;DatabaseName=tjt_czjs;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例
# # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例