diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/ProjectOutputSplitController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/ProjectOutputSplitController.java index 9def010..e45718a 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/ProjectOutputSplitController.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/ProjectOutputSplitController.java @@ -23,7 +23,10 @@ import javax.annotation.Resource; import javax.validation.Valid; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -35,7 +38,9 @@ import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; @Validated public class ProjectOutputSplitController { + 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 @@ -72,7 +77,7 @@ public class ProjectOutputSplitController { List quarterList = projectPlanningQuarterService.getProjectPlanningQuarterListByPlanningId(planningId); - respVO.setQuarters(BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class)); + respVO.setQuarters(buildPlanningQuarterRespList(planning, quarterList)); return success(respVO); } @@ -94,4 +99,61 @@ public class ProjectOutputSplitController { .setScale(RATIO_SCALE, RoundingMode.HALF_UP)); } + private List buildPlanningQuarterRespList( + ProjectPlanningDO planning, List quarterList) { + if (quarterList == null || quarterList.isEmpty()) { + return Collections.emptyList(); + } + boolean hasGuideDetailQuarter = quarterList.stream().anyMatch(item -> item.getGuideDetailId() != null); + if (!hasGuideDetailQuarter) { + return BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class); + } + BigDecimal baseAmount = amount(planning.getAssessmentOutputValue()) + .multiply(ratio(planning.getTotalDistributionAmount())) + .setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + Map aggregateMap = new LinkedHashMap<>(); + for (ProjectPlanningQuarterDO quarter : quarterList) { + if (quarter.getGuideDetailId() == null) { + continue; + } + String key = quarter.getDistributionYear() + "_" + quarter.getQuarterNo(); + ProjectPlanningQuarterRespVO aggregate = aggregateMap.computeIfAbsent(key, ignored -> { + ProjectPlanningQuarterRespVO item = new ProjectPlanningQuarterRespVO(); + item.setPlanningId(planning.getId()); + item.setDistributionYear(quarter.getDistributionYear()); + item.setQuarterNo(quarter.getQuarterNo()); + item.setDistributionAmount(ZERO_AMOUNT); + item.setDistributionRatio(ZERO_RATIO); + return item; + }); + BigDecimal distributionAmount = amount(aggregate.getDistributionAmount()) + .add(amount(quarter.getDistributionAmount())) + .setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + aggregate.setDistributionAmount(distributionAmount); + aggregate.setDistributionRatio(amountToDistributionRatio(distributionAmount, baseAmount)); + } + List result = new ArrayList<>(aggregateMap.values()); + result.sort(Comparator + .comparing(ProjectPlanningQuarterRespVO::getDistributionYear, + Comparator.nullsLast(Integer::compareTo)) + .thenComparing(ProjectPlanningQuarterRespVO::getQuarterNo, + Comparator.nullsLast(Integer::compareTo))); + return result; + } + + private BigDecimal amountToDistributionRatio(BigDecimal amount, BigDecimal baseAmount) { + if (baseAmount == null || baseAmount.compareTo(BigDecimal.ZERO) == 0) { + return ZERO_RATIO; + } + return amount(amount).divide(baseAmount, RATIO_SCALE, RoundingMode.HALF_UP); + } + + 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); + } + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java index d0bcb19..cda9c57 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java @@ -17,7 +17,7 @@ public class ProjectPlanningRespVO { private Long projectId; @Schema(description = "排序") - private Integer sortNo; + private String sortNo; @Schema(description = "归属类型") private String ownershipType; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java index 2a3f5aa..e71711d 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java @@ -21,8 +21,9 @@ public class ProjectPlanningSaveReqVO { @NotNull(message = "项目 ID 不能为空") private Long projectId; - @Schema(description = "排序", example = "0") - private Integer sortNo; + @Schema(description = "排序", example = "A-01") + @Size(max = 50, message = "排序长度不能超过 50 个字符") + private String sortNo; @Schema(description = "归属类型:major/comprehensive/special_subcontract_major/special_subcontract_source_coop/special_subcontract_comprehensive", requiredMode = Schema.RequiredMode.REQUIRED, example = "major") @NotBlank(message = "归属类型不能为空") @@ -140,4 +141,8 @@ public class ProjectPlanningSaveReqVO { @DecimalMax(value = "1.0000", message = "calculationRatio must be <= 1") private BigDecimal calculationRatio; + @Schema(description = "合同单价(元/m²),为空时系统按分项合同产值 / 面积计算", example = "120.00") + @DecimalMin(value = "0.0000", message = "contractUnitPrice must be >= 0") + private BigDecimal contractUnitPrice; + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/ProjectPlanningQuarterController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/ProjectPlanningQuarterController.java index 5d6fe87..8477600 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/ProjectPlanningQuarterController.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/ProjectPlanningQuarterController.java @@ -3,12 +3,16 @@ package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter; 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.planning.vo.ProjectPlanningRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterGuideDetailRespVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterPlanningDetailRespVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterRespVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterSaveReqVO; import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO; import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO; +import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; import cn.iocoder.lyzsys.module.tjt.service.planning.ProjectPlanningService; +import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService; import cn.iocoder.lyzsys.module.tjt.service.planningquarter.ProjectPlanningQuarterService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -21,9 +25,12 @@ import javax.annotation.Resource; import javax.validation.Valid; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; @@ -40,6 +47,8 @@ public class ProjectPlanningQuarterController { private ProjectPlanningQuarterService projectPlanningQuarterService; @Resource private ProjectPlanningService projectPlanningService; + @Resource + private ProjectPlanningGuideDetailService projectPlanningGuideDetailService; @PostMapping("/create") @Operation(summary = "创建季度分配") @@ -111,10 +120,78 @@ public class ProjectPlanningQuarterController { projectPlanningService.getAllocatedAmountMap(Collections.singleton(planningId)); respVO.setPlanning(BeanUtils.toBean(planning, ProjectPlanningRespVO.class, planningRespVO -> fillDistributionSummary(planningRespVO, allocatedAmountMap))); - respVO.setQuarters(BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class)); + List parentQuarterList = quarterList.stream() + .filter(item -> item.getGuideDetailId() == null) + .collect(Collectors.toList()); + List detailQuarterList = quarterList.stream() + .filter(item -> item.getGuideDetailId() != null) + .collect(Collectors.toList()); + boolean guideDetailMode = ProjectPlanningBizTypeConstants.isMajorGuidanceScene( + planning.getOwnershipType(), planning.getCalculationMethod()); + respVO.setGuideDetailMode(guideDetailMode); + respVO.setParentQuarters(BeanUtils.toBean(parentQuarterList, ProjectPlanningQuarterRespVO.class)); + respVO.setQuarters(guideDetailMode + ? respVO.getParentQuarters() + : BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class)); + if (guideDetailMode) { + List guideDetailList = + projectPlanningGuideDetailService.getProjectPlanningGuideDetailListByPlanningId(planningId); + respVO.setGuideDetails(buildGuideDetailRespList(planning, guideDetailList, detailQuarterList)); + boolean historyParentMode = !parentQuarterList.isEmpty() && detailQuarterList.isEmpty(); + respVO.setHistoryParentMode(historyParentMode); + if (historyParentMode) { + respVO.setMessage("当前合约规划存在历史父级季度分配。请先清空历史父级分配后,再按指导价法明细维护。"); + } + } return success(respVO); } + private List buildGuideDetailRespList( + ProjectPlanningDO planning, + List guideDetailList, + List detailQuarterList) { + if (guideDetailList == null || guideDetailList.isEmpty()) { + return Collections.emptyList(); + } + Map> quarterMap = detailQuarterList == null + ? Collections.emptyMap() + : detailQuarterList.stream() + .filter(item -> item.getGuideDetailId() != null) + .collect(Collectors.groupingBy(ProjectPlanningQuarterDO::getGuideDetailId)); + List result = new ArrayList<>(); + for (ProjectPlanningGuideDetailDO guideDetail : guideDetailList) { + ProjectPlanningQuarterGuideDetailRespVO respVO = + BeanUtils.toBean(guideDetail, ProjectPlanningQuarterGuideDetailRespVO.class); + List quarterList = + quarterMap.getOrDefault(guideDetail.getId(), Collections.emptyList()); + respVO.setQuarters(BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class)); + fillGuideDetailDistributionSummary(respVO, planning, quarterList); + result.add(respVO); + } + return result; + } + + private void fillGuideDetailDistributionSummary(ProjectPlanningQuarterGuideDetailRespVO respVO, + ProjectPlanningDO planning, + List quarterList) { + BigDecimal allocatedRatio = ZERO_RATIO; + if (quarterList != null) { + for (ProjectPlanningQuarterDO quarter : quarterList) { + allocatedRatio = allocatedRatio.add(ratio(quarter.getDistributionRatio())) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + } + BigDecimal totalDistributionAmount = planning.getTotalDistributionAmount() == null + ? BigDecimal.ONE.setScale(RATIO_SCALE, RoundingMode.HALF_UP) + : planning.getTotalDistributionAmount().setScale(RATIO_SCALE, RoundingMode.HALF_UP); + BigDecimal allocatedAmount = totalDistributionAmount.multiply(allocatedRatio) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP); + respVO.setAllocatedAmount(allocatedAmount); + respVO.setPendingAmount(totalDistributionAmount.subtract(allocatedAmount) + .max(BigDecimal.ZERO) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP)); + } + private void fillDistributionSummary(ProjectPlanningRespVO respVO, Map allocatedAmountMap) { BigDecimal allocatedRatio = allocatedAmountMap.getOrDefault(respVO.getId(), ZERO_RATIO); BigDecimal totalDistributionAmount = respVO.getTotalDistributionAmount() == null @@ -126,4 +203,8 @@ public class ProjectPlanningQuarterController { .setScale(RATIO_SCALE, RoundingMode.HALF_UP)); } + private BigDecimal ratio(BigDecimal value) { + return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterGuideDetailRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterGuideDetailRespVO.java new file mode 100644 index 0000000..450ddca --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterGuideDetailRespVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo; + +import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +@Schema(description = "管理后台 - 指导价法明细季度分配 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectPlanningQuarterGuideDetailRespVO extends ProjectPlanningGuideDetailRespVO { + + @Schema(description = "明细已分配比例") + private BigDecimal allocatedAmount; + + @Schema(description = "明细待分配比例") + private BigDecimal pendingAmount; + + @Schema(description = "明细季度分配列表") + private List quarters = Collections.emptyList(); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterPlanningDetailRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterPlanningDetailRespVO.java index 6d46853..61c62f1 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterPlanningDetailRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterPlanningDetailRespVO.java @@ -17,4 +17,19 @@ public class ProjectPlanningQuarterPlanningDetailRespVO { @Schema(description = "季度分配列表") private List quarters = Collections.emptyList(); + @Schema(description = "是否指导价法明细分配模式") + private Boolean guideDetailMode = Boolean.FALSE; + + @Schema(description = "是否存在历史父级季度分配") + private Boolean historyParentMode = Boolean.FALSE; + + @Schema(description = "提示信息") + private String message; + + @Schema(description = "父级季度分配列表") + private List parentQuarters = Collections.emptyList(); + + @Schema(description = "指导价法明细及各自季度分配列表") + private List guideDetails = Collections.emptyList(); + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java index 7a1092b..d8a88a7 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java @@ -16,6 +16,12 @@ public class ProjectPlanningQuarterRespVO { @Schema(description = "Planning ID", example = "1") private Long planningId; + @Schema(description = "Guide detail ID") + private Long guideDetailId; + + @Schema(description = "Guide detail sort number snapshot") + private Integer guideDetailSortNo; + @Schema(description = "Distribution year") private Integer distributionYear; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java index 11a2a4b..e26a74b 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java @@ -19,6 +19,12 @@ public class ProjectPlanningQuarterSaveReqVO { @NotNull(message = "planningId cannot be null") private Long planningId; + @Schema(description = "Guide detail ID, required only for major + guidance price planning", example = "1") + private Long guideDetailId; + + @Schema(description = "Guide detail sort number snapshot", example = "1") + private Integer guideDetailSortNo; + @Schema(description = "Distribution year", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") @NotNull(message = "distributionYear cannot be null") private Integer distributionYear; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/ProjectProfitController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/ProjectProfitController.java index 1404bb6..e36b390 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/ProjectProfitController.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/ProjectProfitController.java @@ -4,6 +4,7 @@ import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; import cn.iocoder.lyzsys.framework.common.pojo.PageResult; import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitPageReqVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitSettlementSaveReqVO; import cn.iocoder.lyzsys.module.tjt.service.profit.ProjectProfitService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -11,6 +12,9 @@ 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.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +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; @@ -44,4 +48,27 @@ public class ProjectProfitController { return success(projectProfitService.getProjectProfitPage(pageReqVO)); } + @PostMapping("/lock-budget") + @Operation(summary = "锁定项目成本预算测算") + @Parameter(name = "projectId", description = "项目 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:profit:query')") + public CommonResult lockBudgetSnapshot(@RequestParam("projectId") Long projectId) { + return success(projectProfitService.lockBudgetSnapshot(projectId)); + } + + @PostMapping("/lock-accounting") + @Operation(summary = "锁定项目成本核算测算") + @Parameter(name = "projectId", description = "项目 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:profit:query')") + public CommonResult lockAccountingSnapshot(@RequestParam("projectId") Long projectId) { + return success(projectProfitService.lockAccountingSnapshot(projectId)); + } + + @PutMapping("/save-settlement") + @Operation(summary = "保存项目成本结算测算") + @PreAuthorize("@ss.hasPermission('tjt:profit:query')") + public CommonResult saveSettlementSnapshot(@Valid @RequestBody ProjectProfitSettlementSaveReqVO saveReqVO) { + return success(projectProfitService.saveSettlementSnapshot(saveReqVO)); + } + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java index 441deb8..38f6220 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java @@ -17,7 +17,7 @@ public class ProjectProfitRespVO { private String projectName; @Schema(description = "排序") - private Integer sortNo; + private String sortNo; @Schema(description = "是否签订合同") private Boolean contractSignedFlag; @@ -28,7 +28,7 @@ public class ProjectProfitRespVO { @Schema(description = "结算合同总产值") private BigDecimal finalSettlementAmount; - @Schema(description = "项目预算产值总计,结算合同总产值大于 0 时取结算合同总产值,否则取合同总产值") + @Schema(description = "项目预算产值总计,项目下所有合约规划的项目预算产值合计") private BigDecimal effectiveSettlementAmount; @Schema(description = "综合所人工成本") @@ -73,4 +73,13 @@ public class ProjectProfitRespVO { @Schema(description = "创建时间") private LocalDateTime createTime; + @Schema(description = "项目成本预算测算快照") + private ProjectProfitSnapshotRespVO budgetSnapshot; + + @Schema(description = "项目成本核算测算快照") + private ProjectProfitSnapshotRespVO accountingSnapshot; + + @Schema(description = "项目成本结算测算快照") + private ProjectProfitSnapshotRespVO settlementSnapshot; + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitSettlementSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitSettlementSaveReqVO.java new file mode 100644 index 0000000..670fd2e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitSettlementSaveReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Schema(description = "管理后台 - 项目成本结算测算保存 Request VO") +@Data +public class ProjectProfitSettlementSaveReqVO { + + @Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "项目 ID 不能为空") + private Long projectId; + + @Schema(description = "考核结果") + @Size(max = 64, message = "考核结果长度不能超过 64 个字符") + private String assessmentResult; + + @Schema(description = "备注") + @Size(max = 500, message = "备注长度不能超过 500 个字符") + private String remark; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitSnapshotRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitSnapshotRespVO.java new file mode 100644 index 0000000..7f38fdd --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitSnapshotRespVO.java @@ -0,0 +1,100 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.profit.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 ProjectProfitSnapshotRespVO { + + @Schema(description = "快照 ID") + private Long id; + + @Schema(description = "项目 ID") + private Long projectId; + + @Schema(description = "快照类型:budget/accounting/settlement") + private String snapshotType; + + @Schema(description = "是否锁定") + private Boolean lockedFlag; + + @Schema(description = "操作人 ID") + private Long actionUserId; + + @Schema(description = "操作人姓名") + private String actionUserName; + + @Schema(description = "操作时间") + private LocalDateTime actionTime; + + @Schema(description = "合同总产值") + private BigDecimal contractAmount; + + @Schema(description = "结算合同总产值") + private BigDecimal finalSettlementAmount; + + @Schema(description = "项目预算产值总计,项目下所有合约规划的项目预算产值合计") + private BigDecimal effectiveSettlementAmount; + + @Schema(description = "综合所人工成本") + private BigDecimal comprehensivePlanningAmount; + + @Schema(description = "专项分包人工成本合计") + private BigDecimal subcontractPlanningAmount; + + @Schema(description = "专项分包-专业所人工成本") + private BigDecimal specialSubcontractPlanningAmount; + + @Schema(description = "专项分包-源头合作分包人工成本") + private BigDecimal sourceCoopSubcontractPlanningAmount; + + @Schema(description = "专项分包-综合所人工成本") + private BigDecimal comprehensiveSubcontractPlanningAmount; + + @Schema(description = "专业所考核产值") + private BigDecimal majorOutputValue; + + @Schema(description = "专业所人工成本") + private BigDecimal majorExpectedPerformance; + + @Schema(description = "科创产值比例") + private BigDecimal innovationOutputRate; + + @Schema(description = "科创产值") + private BigDecimal innovationOutputValue; + + @Schema(description = "其他成本") + private BigDecimal otherCost; + + @Schema(description = "预算盈亏值") + private BigDecimal profitLossValue; + + @Schema(description = "预算盈亏百分比") + private BigDecimal profitLossRate; + + @Schema(description = "考核结果") + private String assessmentResult; + + @Schema(description = "考核系数") + private BigDecimal assessmentCoefficient; + + @Schema(description = "综合所考核产值核算值") + private BigDecimal comprehensiveAccountingOutputValue; + + @Schema(description = "综合所考核产值结算值") + private BigDecimal comprehensiveSettlementOutputValue; + + @Schema(description = "专业所考核产值核算值") + private BigDecimal majorAccountingOutputValue; + + @Schema(description = "专业所考核产值结算值") + private BigDecimal majorSettlementOutputValue; + + @Schema(description = "备注") + private String remark; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java index c7fe3a2..b7f4784 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java @@ -28,7 +28,7 @@ public class ProjectRespVO { @Schema(description = "排序") @ExcelProperty("排序") - private Integer sortNo; + private String sortNo; @Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("是否签订合同") diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java index 60cea41..019054a 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java @@ -28,8 +28,9 @@ public class ProjectSaveReqVO { @Size(max = 200, message = "项目名称长度不能超过 200 个字符") private String projectName; - @Schema(description = "排序", example = "0") - private Integer sortNo; + @Schema(description = "排序", example = "A-01") + @Size(max = 50, message = "排序长度不能超过 50 个字符") + private String sortNo; @Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") @NotNull(message = "是否签订合同不能为空") diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java index 5f11f9f..920d82b 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java @@ -25,7 +25,7 @@ public class ProjectPlanningDO extends TenantBaseDO { private Long projectId; - private Integer sortNo; + private String sortNo; private String ownershipType; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planningquarter/ProjectPlanningQuarterDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planningquarter/ProjectPlanningQuarterDO.java index 0698c14..b635537 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planningquarter/ProjectPlanningQuarterDO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planningquarter/ProjectPlanningQuarterDO.java @@ -25,6 +25,10 @@ public class ProjectPlanningQuarterDO extends TenantBaseDO { private Long planningId; + private Long guideDetailId; + + private Integer guideDetailSortNo; + private Integer distributionYear; private Integer quarterNo; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/profit/ProjectCostMeasureSnapshotDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/profit/ProjectCostMeasureSnapshotDO.java new file mode 100644 index 0000000..4940c24 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/profit/ProjectCostMeasureSnapshotDO.java @@ -0,0 +1,83 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.profit; + +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; +import java.time.LocalDateTime; + +/** + * 项目成本测算快照 DO + * + * @author Codex + */ +@TableName("tjt_project_cost_measure_snapshot") +@KeySequence("tjt_project_cost_measure_snapshot_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectCostMeasureSnapshotDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long projectId; + + private String snapshotType; + + private Boolean lockedFlag; + + private Long actionUserId; + + private String actionUserName; + + private LocalDateTime actionTime; + + private BigDecimal contractAmount; + + private BigDecimal finalSettlementAmount; + + private BigDecimal effectiveSettlementAmount; + + private BigDecimal comprehensivePlanningAmount; + + private BigDecimal subcontractPlanningAmount; + + private BigDecimal specialSubcontractPlanningAmount; + + private BigDecimal sourceCoopSubcontractPlanningAmount; + + private BigDecimal comprehensiveSubcontractPlanningAmount; + + private BigDecimal majorOutputValue; + + private BigDecimal majorExpectedPerformance; + + private BigDecimal innovationOutputRate; + + private BigDecimal innovationOutputValue; + + private BigDecimal otherCost; + + private BigDecimal profitLossValue; + + private BigDecimal profitLossRate; + + private String assessmentResult; + + private BigDecimal assessmentCoefficient; + + private BigDecimal comprehensiveAccountingOutputValue; + + private BigDecimal comprehensiveSettlementOutputValue; + + private BigDecimal majorAccountingOutputValue; + + private BigDecimal majorSettlementOutputValue; + + private String remark; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java index 717a15c..9de6ba8 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java @@ -34,7 +34,7 @@ public class ProjectDO extends TenantBaseDO { /** * 排序 */ - private Integer sortNo; + private String sortNo; /** * 是否签订合同 */ diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planning/ProjectPlanningMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planning/ProjectPlanningMapper.java index d728434..5e0da9a 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planning/ProjectPlanningMapper.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planning/ProjectPlanningMapper.java @@ -25,8 +25,7 @@ public interface ProjectPlanningMapper extends BaseMapperX { .likeIfPresent(ProjectPlanningDO::getPlanningContent, reqVO.getPlanningContent()) .eqIfPresent(ProjectPlanningDO::getPlanningStartYear, reqVO.getPlanningStartYear()) .betweenIfPresent(ProjectPlanningDO::getCreateTime, reqVO.getCreateTime()) - .orderByAsc(ProjectPlanningDO::getSortNo) - .orderByAsc(ProjectPlanningDO::getId)); + .last("ORDER BY CASE WHEN sort_no IS NULL OR sort_no = N'' THEN 1 ELSE 0 END, sort_no ASC, id ASC")); } default List selectListByProjectId(Long projectId) { @@ -38,8 +37,7 @@ public interface ProjectPlanningMapper extends BaseMapperX { default List selectDisplayListByProjectId(Long projectId) { return selectList(new LambdaQueryWrapperX() .eq(ProjectPlanningDO::getProjectId, projectId) - .orderByAsc(ProjectPlanningDO::getSortNo) - .orderByAsc(ProjectPlanningDO::getId)); + .last("ORDER BY CASE WHEN sort_no IS NULL OR sort_no = N'' THEN 1 ELSE 0 END, sort_no ASC, id ASC")); } } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planningquarter/ProjectPlanningQuarterMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planningquarter/ProjectPlanningQuarterMapper.java index 526b8b8..5790749 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planningquarter/ProjectPlanningQuarterMapper.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planningquarter/ProjectPlanningQuarterMapper.java @@ -20,25 +20,102 @@ public interface ProjectPlanningQuarterMapper extends BaseMapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .eq(ProjectPlanningQuarterDO::getDistributionYear, distributionYear) + .eq(ProjectPlanningQuarterDO::getQuarterNo, quarterNo) + .isNull(ProjectPlanningQuarterDO::getGuideDetailId)); + } + + default ProjectPlanningQuarterDO selectByPlanningIdAndGuideDetailIdAndDistributionYearAndQuarter( + Long planningId, Long guideDetailId, Integer distributionYear, Integer quarterNo) { + return selectOne(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .eq(ProjectPlanningQuarterDO::getGuideDetailId, guideDetailId) + .eq(ProjectPlanningQuarterDO::getDistributionYear, distributionYear) + .eq(ProjectPlanningQuarterDO::getQuarterNo, quarterNo)); } default List selectListByPlanningId(Long planningId) { return selectList(new LambdaQueryWrapperX() .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId) .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) .orderByAsc(ProjectPlanningQuarterDO::getId)); } + default List selectParentListByPlanningId(Long planningId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .isNull(ProjectPlanningQuarterDO::getGuideDetailId) + .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) + .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) + .orderByAsc(ProjectPlanningQuarterDO::getId)); + } + + default List selectDetailListByPlanningId(Long planningId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .isNotNull(ProjectPlanningQuarterDO::getGuideDetailId) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId) + .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) + .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) + .orderByAsc(ProjectPlanningQuarterDO::getId)); + } + + default List selectListByGuideDetailId(Long guideDetailId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getGuideDetailId, guideDetailId) + .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) + .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) + .orderByAsc(ProjectPlanningQuarterDO::getId)); + } + + default List selectListByGuideDetailIds(Collection guideDetailIds) { + if (guideDetailIds == null || guideDetailIds.isEmpty()) { + return Collections.emptyList(); + } + return selectList(new LambdaQueryWrapperX() + .in(ProjectPlanningQuarterDO::getGuideDetailId, guideDetailIds) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId) + .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) + .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) + .orderByAsc(ProjectPlanningQuarterDO::getId)); + } + + default boolean existsParentQuarter(Long planningId, Long excludeId) { + return selectCount(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .isNull(ProjectPlanningQuarterDO::getGuideDetailId) + .ne(excludeId != null, ProjectPlanningQuarterDO::getId, excludeId)) > 0; + } + + default boolean existsGuideDetailQuarter(Long planningId, Long excludeId) { + return selectCount(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .isNotNull(ProjectPlanningQuarterDO::getGuideDetailId) + .ne(excludeId != null, ProjectPlanningQuarterDO::getId, excludeId)) > 0; + } + default List selectListByPlanningIds(Collection planningIds) { if (planningIds == null || planningIds.isEmpty()) { return Collections.emptyList(); } return selectList(new LambdaQueryWrapperX() .in(ProjectPlanningQuarterDO::getPlanningId, planningIds) + .orderByAsc(ProjectPlanningQuarterDO::getPlanningId) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo) + .orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId) .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) .orderByAsc(ProjectPlanningQuarterDO::getId)); diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/profit/ProjectCostMeasureSnapshotMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/profit/ProjectCostMeasureSnapshotMapper.java new file mode 100644 index 0000000..ed9c739 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/profit/ProjectCostMeasureSnapshotMapper.java @@ -0,0 +1,24 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.profit; + +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.profit.ProjectCostMeasureSnapshotDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ProjectCostMeasureSnapshotMapper extends BaseMapperX { + + default ProjectCostMeasureSnapshotDO selectByProjectIdAndType(Long projectId, String snapshotType) { + return selectOne(new LambdaQueryWrapperX() + .eq(ProjectCostMeasureSnapshotDO::getProjectId, projectId) + .eq(ProjectCostMeasureSnapshotDO::getSnapshotType, snapshotType)); + } + + default List selectListByProjectId(Long projectId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectCostMeasureSnapshotDO::getProjectId, projectId)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java index a316774..4609cfd 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java @@ -22,8 +22,7 @@ public interface ProjectMapper extends BaseMapperX { .eqIfPresent(ProjectDO::getProjectStartYear, reqVO.getProjectStartYear()) .eqIfPresent(ProjectDO::getProjectStatus, reqVO.getProjectStatus()) .betweenIfPresent(ProjectDO::getCreateTime, reqVO.getCreateTime()) - .orderByAsc(ProjectDO::getSortNo) - .orderByAsc(ProjectDO::getId)); + .last("ORDER BY CASE WHEN sort_no IS NULL OR sort_no = N'' THEN 1 ELSE 0 END, sort_no ASC, id ASC")); } } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java index 7bdc56e..bae3da3 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java @@ -32,6 +32,10 @@ public interface ErrorCodeConstants { ErrorCode PROJECT_PLANNING_QUARTER_NOT_EXISTS = new ErrorCode(1_020_003_000, "季度分配明细不存在"); ErrorCode PROJECT_PLANNING_QUARTER_DUPLICATE = new ErrorCode(1_020_003_001, "同一合约规划下该年度季度分配已存在"); ErrorCode PROJECT_PLANNING_QUARTER_PLANNING_NOT_EXISTS = new ErrorCode(1_020_003_002, "关联合约规划不存在"); + ErrorCode PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_REQUIRED = new ErrorCode(1_020_003_003, "专业所指导价法季度分配必须选择指导价法明细"); + ErrorCode PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_INVALID = new ErrorCode(1_020_003_004, "指导价法明细不属于当前合约规划"); + ErrorCode PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_NOT_ALLOWED = new ErrorCode(1_020_003_005, "非专业所指导价法季度分配不能选择指导价法明细"); + ErrorCode PROJECT_PLANNING_QUARTER_SCOPE_MIXED = new ErrorCode(1_020_003_006, "当前合约规划同时存在父级和明细季度分配,请先清理后再保存"); // ========== 页面 4 拆分管理 1-020-004-000 ========== ErrorCode PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS = new ErrorCode(1_020_004_000, "关联合约规划不存在"); @@ -67,4 +71,10 @@ public interface ErrorCodeConstants { ErrorCode EMPLOYEE_YEAR_LEADER_OUTPUT_NOT_EXISTS = new ErrorCode(1_020_010_000, "年度所长考核产值记录不存在"); ErrorCode EMPLOYEE_YEAR_LEADER_OUTPUT_DUPLICATE = new ErrorCode(1_020_010_001, "当前所长该年度考核产值已存在"); + // ========== 项目成本测算快照 1-020-011-000 ========== + ErrorCode PROJECT_COST_MEASURE_BUDGET_LOCKED = new ErrorCode(1_020_011_000, "目标责任书已下达,不能重复操作"); + ErrorCode PROJECT_COST_MEASURE_ACCOUNTING_LOCKED = new ErrorCode(1_020_011_001, "竣工验收已完成,不能重复操作"); + ErrorCode PROJECT_COST_MEASURE_ACCOUNTING_REQUIRED = new ErrorCode(1_020_011_002, "请先完成竣工验收,生成项目成本核算测算后再维护结算测算"); + ErrorCode PROJECT_COST_MEASURE_BUDGET_REQUIRED = new ErrorCode(1_020_011_003, "请先下达目标责任书,生成项目成本预算测算后再完成竣工验收"); + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ProjectCostMeasureSnapshotTypeEnum.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ProjectCostMeasureSnapshotTypeEnum.java new file mode 100644 index 0000000..080a904 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ProjectCostMeasureSnapshotTypeEnum.java @@ -0,0 +1,22 @@ +package cn.iocoder.lyzsys.module.tjt.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 项目成本测算快照类型 + * + * @author Codex + */ +@Getter +@AllArgsConstructor +public enum ProjectCostMeasureSnapshotTypeEnum { + + BUDGET("budget", "项目成本预算测算"), + ACCOUNTING("accounting", "项目成本核算测算"), + SETTLEMENT("settlement", "项目成本结算测算"); + + private final String code; + private final String label; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java index edbafd9..1247e1c 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java @@ -157,18 +157,54 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { if (planningIds == null || planningIds.isEmpty()) { return Collections.emptyMap(); } - List quarterList = projectPlanningQuarterMapper.selectListByPlanningIds(planningIds); + List validPlanningIds = planningIds.stream() + .filter(Objects::nonNull) + .collect(java.util.stream.Collectors.toList()); + if (validPlanningIds.isEmpty()) { + return Collections.emptyMap(); + } + Map planningMap = projectPlanningMapper.selectBatchIds(validPlanningIds).stream() + .collect(java.util.stream.Collectors.toMap(ProjectPlanningDO::getId, item -> item, (a, b) -> a)); + List quarterList = projectPlanningQuarterMapper.selectListByPlanningIds(validPlanningIds); + Map> quarterMap = quarterList.stream() + .collect(java.util.stream.Collectors.groupingBy(ProjectPlanningQuarterDO::getPlanningId)); Map allocatedAmountMap = new HashMap<>(); - for (Long planningId : planningIds) { - allocatedAmountMap.put(planningId, ZERO_RATIO); + for (Long planningId : validPlanningIds) { + allocatedAmountMap.put(planningId, calculateAllocatedRatio( + planningMap.get(planningId), + quarterMap.getOrDefault(planningId, Collections.emptyList()))); } - for (ProjectPlanningQuarterDO quarter : quarterList) { - allocatedAmountMap.merge(quarter.getPlanningId(), ratio(quarter.getDistributionRatio()), BigDecimal::add); - } - allocatedAmountMap.replaceAll((key, value) -> ratio(value)); return allocatedAmountMap; } + private BigDecimal calculateAllocatedRatio(ProjectPlanningDO planning, List quarterList) { + if (quarterList == null || quarterList.isEmpty()) { + return ZERO_RATIO; + } + boolean hasGuideDetailQuarter = quarterList.stream().anyMatch(item -> item.getGuideDetailId() != null); + if (!hasGuideDetailQuarter) { + BigDecimal allocatedRatio = ZERO_RATIO; + for (ProjectPlanningQuarterDO quarter : quarterList) { + allocatedRatio = allocatedRatio.add(ratio(quarter.getDistributionRatio())) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + return allocatedRatio; + } + if (planning == null) { + return ZERO_RATIO; + } + BigDecimal baseAmount = multiplyAmount(planning.getAssessmentOutputValue(), planning.getTotalDistributionAmount()); + if (baseAmount.compareTo(BigDecimal.ZERO) == 0) { + return ZERO_RATIO; + } + BigDecimal allocatedAmount = ZERO_AMOUNT; + for (ProjectPlanningQuarterDO quarter : quarterList) { + allocatedAmount = allocatedAmount.add(amount(quarter.getDistributionAmount())) + .setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + } + return allocatedAmount.divide(baseAmount, RATIO_SCALE, RoundingMode.HALF_UP); + } + private void validateProjectPlanningForSave(ProjectPlanningSaveReqVO reqVO, ProjectPlanningDO dbPlanning) { if (!ProjectPlanningBizTypeConstants.isValidOwnershipType(reqVO.getOwnershipType())) { throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID); @@ -203,9 +239,6 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { } private void prepareProjectPlanning(ProjectPlanningDO planning, ProjectDO project) { - if (planning.getSortNo() == null) { - planning.setSortNo(0); - } if (planning.getPlanningStartYear() == null) { planning.setPlanningStartYear(project.getProjectStartYear()); } @@ -250,6 +283,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { planning.setWorkingDayUnitPrice(amountNullable(planning.getWorkingDayUnitPrice())); planning.setGuidanceUnitPrice(amountNullable(planning.getGuidanceUnitPrice())); planning.setGuidanceTotalPrice(amountNullable(planning.getGuidanceTotalPrice())); + planning.setContractUnitPrice(amountNullable(planning.getContractUnitPrice())); } private void calculateProjectPlanning(ProjectPlanningDO planning) { @@ -258,7 +292,9 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { planning.setProjectBudgetOutputValue(amount(planning.getPlanningAmount() .subtract(planning.getManagementFee()) .subtract(planning.getVatAmount()))); - planning.setContractUnitPrice(divideAmount(planning.getPlanningAmount(), planning.getPlanningArea())); + if (planning.getContractUnitPrice() == null) { + planning.setContractUnitPrice(divideAmount(planning.getPlanningAmount(), planning.getPlanningArea())); + } planning.setTotalAdjustmentFactor(ZERO_RATIO); planning.setAssessmentArea(ZERO_AMOUNT); planning.setVirtualOutputValue(ZERO_AMOUNT); diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningguidedetail/ProjectPlanningGuideDetailServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningguidedetail/ProjectPlanningGuideDetailServiceImpl.java index 213318a..6a7c6ec 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningguidedetail/ProjectPlanningGuideDetailServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningguidedetail/ProjectPlanningGuideDetailServiceImpl.java @@ -84,6 +84,7 @@ public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGui .filter(id -> !retainedIds.contains(id)) .collect(Collectors.toList()); if (!deletedIds.isEmpty()) { + projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getGuideDetailId, deletedIds); projectPlanningGuideDetailMapper.deleteBatchIds(deletedIds); } @@ -96,6 +97,7 @@ public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGui if (detail == null) { throw exception(PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS); } + projectPlanningQuarterMapper.delete(ProjectPlanningQuarterDO::getGuideDetailId, id); projectPlanningGuideDetailMapper.deleteById(id); recalculatePlanningSummary(detail.getPlanningId()); } @@ -122,6 +124,7 @@ public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGui if (planningId == null) { return; } + projectPlanningQuarterMapper.delete(ProjectPlanningQuarterDO::getPlanningId, planningId); projectPlanningGuideDetailMapper.delete(ProjectPlanningGuideDetailDO::getPlanningId, planningId); } @@ -130,6 +133,7 @@ public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGui if (planningIds == null || planningIds.isEmpty()) { return; } + projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, planningIds); projectPlanningGuideDetailMapper.deleteBatch(ProjectPlanningGuideDetailDO::getPlanningId, planningIds); } @@ -175,7 +179,6 @@ public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGui 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); @@ -241,9 +244,22 @@ public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGui return; } List quarterList = projectPlanningQuarterMapper.selectListByPlanningId(planning.getId()); + Map guideDetailMap = Collections.emptyMap(); + if (isGuideDetailScene(planning)) { + guideDetailMap = CollectionUtils.convertMap( + projectPlanningGuideDetailMapper.selectListByPlanningId(planning.getId()), + ProjectPlanningGuideDetailDO::getId); + } for (ProjectPlanningQuarterDO quarter : quarterList) { + ProjectPlanningGuideDetailDO guideDetail = quarter.getGuideDetailId() == null + ? null : guideDetailMap.get(quarter.getGuideDetailId()); + BigDecimal assessmentOutputValue = guideDetail == null + ? planning.getAssessmentOutputValue() : guideDetail.getAssessmentOutputValue(); + if (guideDetail != null) { + quarter.setGuideDetailSortNo(guideDetail.getSortNo()); + } quarter.setDistributionAmount(multiplyAmount( - planning.getAssessmentOutputValue(), + assessmentOutputValue, planning.getTotalDistributionAmount(), ratio(quarter.getDistributionRatio()) )); diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterServiceImpl.java index 0d6189a..8c59958 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterServiceImpl.java @@ -3,9 +3,12 @@ package cn.iocoder.lyzsys.module.tjt.service.planningquarter; import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterSaveReqVO; import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO; import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO; import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningguidedetail.ProjectPlanningGuideDetailMapper; import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper; +import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -13,11 +16,16 @@ import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; +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_QUARTER_DUPLICATE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_NOT_ALLOWED; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_REQUIRED; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_NOT_EXISTS; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_PLANNING_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_SCOPE_MIXED; /** * 季度分配 Service 实现类 @@ -35,6 +43,8 @@ public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarter private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; @Resource private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectPlanningGuideDetailMapper projectPlanningGuideDetailMapper; @Override public Long createProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO createReqVO) { @@ -42,10 +52,12 @@ public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarter if (isZeroQuarter(createReqVO)) { return null; } - validateQuarterUnique(null, createReqVO.getPlanningId(), + ProjectPlanningGuideDetailDO guideDetail = validateGuideDetailScope(planning, createReqVO.getGuideDetailId()); + validateScopeNotMixed(null, createReqVO.getPlanningId(), guideDetail); + validateQuarterUnique(null, createReqVO.getPlanningId(), createReqVO.getGuideDetailId(), createReqVO.getDistributionYear(), createReqVO.getQuarterNo()); ProjectPlanningQuarterDO quarter = BeanUtils.toBean(createReqVO, ProjectPlanningQuarterDO.class); - prepareQuarter(quarter, planning); + prepareQuarter(quarter, planning, guideDetail); projectPlanningQuarterMapper.insert(quarter); return quarter.getId(); } @@ -58,10 +70,12 @@ public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarter projectPlanningQuarterMapper.deleteById(dbQuarter.getId()); return; } - validateQuarterUnique(updateReqVO.getId(), updateReqVO.getPlanningId(), + ProjectPlanningGuideDetailDO guideDetail = validateGuideDetailScope(planning, updateReqVO.getGuideDetailId()); + validateScopeNotMixed(updateReqVO.getId(), updateReqVO.getPlanningId(), guideDetail); + validateQuarterUnique(updateReqVO.getId(), updateReqVO.getPlanningId(), updateReqVO.getGuideDetailId(), updateReqVO.getDistributionYear(), updateReqVO.getQuarterNo()); ProjectPlanningQuarterDO updateObj = BeanUtils.toBean(updateReqVO, ProjectPlanningQuarterDO.class); - prepareQuarter(updateObj, planning); + prepareQuarter(updateObj, planning, guideDetail); projectPlanningQuarterMapper.updateById(updateObj); } @@ -88,9 +102,13 @@ public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarter return projectPlanningQuarterMapper.selectListByPlanningId(planningId); } - private void validateQuarterUnique(Long id, Long planningId, Integer distributionYear, Integer quarterNo) { - ProjectPlanningQuarterDO quarter = projectPlanningQuarterMapper.selectByPlanningIdAndDistributionYearAndQuarter( - planningId, distributionYear, quarterNo); + private void validateQuarterUnique(Long id, Long planningId, Long guideDetailId, + Integer distributionYear, Integer quarterNo) { + ProjectPlanningQuarterDO quarter = guideDetailId == null + ? projectPlanningQuarterMapper.selectParentByPlanningIdAndDistributionYearAndQuarter( + planningId, distributionYear, quarterNo) + : projectPlanningQuarterMapper.selectByPlanningIdAndGuideDetailIdAndDistributionYearAndQuarter( + planningId, guideDetailId, distributionYear, quarterNo); if (quarter == null) { return; } @@ -115,14 +133,53 @@ public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarter return quarter; } + private ProjectPlanningGuideDetailDO validateGuideDetailScope(ProjectPlanningDO planning, Long guideDetailId) { + boolean guideDetailMode = ProjectPlanningBizTypeConstants.isMajorGuidanceScene( + planning.getOwnershipType(), planning.getCalculationMethod()); + if (!guideDetailMode) { + if (guideDetailId != null) { + throw exception(PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_NOT_ALLOWED); + } + return null; + } + if (guideDetailId == null) { + throw exception(PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_REQUIRED); + } + ProjectPlanningGuideDetailDO guideDetail = projectPlanningGuideDetailMapper.selectById(guideDetailId); + if (guideDetail == null || !Objects.equals(guideDetail.getPlanningId(), planning.getId())) { + throw exception(PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_INVALID); + } + return guideDetail; + } + + private void validateScopeNotMixed(Long id, Long planningId, ProjectPlanningGuideDetailDO guideDetail) { + boolean savingParentQuarter = guideDetail == null; + boolean mixed = savingParentQuarter + ? projectPlanningQuarterMapper.existsGuideDetailQuarter(planningId, id) + : projectPlanningQuarterMapper.existsParentQuarter(planningId, id); + if (mixed) { + throw exception(PROJECT_PLANNING_QUARTER_SCOPE_MIXED); + } + } + private boolean isZeroQuarter(ProjectPlanningQuarterSaveReqVO reqVO) { return ratio(reqVO.getDistributionRatio()).compareTo(BigDecimal.ZERO) == 0; } - private void prepareQuarter(ProjectPlanningQuarterDO quarter, ProjectPlanningDO planning) { + private void prepareQuarter(ProjectPlanningQuarterDO quarter, ProjectPlanningDO planning, + ProjectPlanningGuideDetailDO guideDetail) { quarter.setDistributionRatio(ratio(quarter.getDistributionRatio())); + if (guideDetail == null) { + quarter.setGuideDetailId(null); + quarter.setGuideDetailSortNo(null); + } else { + quarter.setGuideDetailId(guideDetail.getId()); + quarter.setGuideDetailSortNo(guideDetail.getSortNo()); + } + BigDecimal assessmentOutputValue = guideDetail == null + ? planning.getAssessmentOutputValue() : guideDetail.getAssessmentOutputValue(); quarter.setDistributionAmount(multiplyAmount( - planning.getAssessmentOutputValue(), + assessmentOutputValue, planning.getTotalDistributionAmount(), quarter.getDistributionRatio())); } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitService.java index df9737c..5f95200 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitService.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitService.java @@ -3,6 +3,7 @@ package cn.iocoder.lyzsys.module.tjt.service.profit; import cn.iocoder.lyzsys.framework.common.pojo.PageResult; import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitPageReqVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitSettlementSaveReqVO; /** * 项目盈亏 Service 接口 @@ -15,4 +16,10 @@ public interface ProjectProfitService { PageResult getProjectProfitPage(ProjectProfitPageReqVO pageReqVO); + ProjectProfitRespVO lockBudgetSnapshot(Long projectId); + + ProjectProfitRespVO lockAccountingSnapshot(Long projectId); + + ProjectProfitRespVO saveSettlementSnapshot(ProjectProfitSettlementSaveReqVO saveReqVO); + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java index ce25fbf..16837d6 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java @@ -2,17 +2,20 @@ package cn.iocoder.lyzsys.module.tjt.service.profit; import cn.iocoder.lyzsys.framework.common.pojo.PageResult; import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils; +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.lyzsys.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitPageReqVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitSettlementSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitSnapshotRespVO; 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.profit.ProjectCostMeasureSnapshotDO; import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; -import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO; 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.profit.ProjectCostMeasureSnapshotMapper; import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; -import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper; +import cn.iocoder.lyzsys.module.tjt.enums.ProjectCostMeasureSnapshotTypeEnum; import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -20,7 +23,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.Collection; +import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Map; @@ -28,6 +31,10 @@ 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_COST_MEASURE_ACCOUNTING_LOCKED; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_COST_MEASURE_ACCOUNTING_REQUIRED; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_COST_MEASURE_BUDGET_LOCKED; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_COST_MEASURE_BUDGET_REQUIRED; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; @Service @@ -36,25 +43,23 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP); - private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(4, RoundingMode.HALF_UP); private static final BigDecimal DEFAULT_INNOVATION_OUTPUT_RATE = new BigDecimal("0.0100"); - private static final BigDecimal DEFAULT_YEAR_K_VALUE = new BigDecimal("0.4000"); + private static final BigDecimal ASSESSMENT_COEFFICIENT_EXCELLENT = new BigDecimal("1.0200"); + private static final BigDecimal ASSESSMENT_COEFFICIENT_QUALIFIED = new BigDecimal("1.0000"); + private static final BigDecimal ASSESSMENT_COEFFICIENT_NEEDS_IMPROVEMENT = new BigDecimal("0.9500"); @Resource private ProjectMapper projectMapper; @Resource private ProjectPlanningMapper projectPlanningMapper; @Resource - private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; - @Resource - private YearKValueMapper yearKValueMapper; + private ProjectCostMeasureSnapshotMapper projectCostMeasureSnapshotMapper; @Override public ProjectProfitRespVO getProjectProfit(Long projectId) { - ProjectDO project = validateProjectExists(projectId); - List planningList = projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, - Collections.singleton(projectId)); - return buildProjectProfit(project, planningList); + ProjectProfitRespVO respVO = buildCurrentProjectProfit(projectId); + fillSnapshots(respVO); + return respVO; } @Override @@ -78,44 +83,129 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { return new PageResult<>(list, pageResult.getTotal()); } + @Override + public ProjectProfitRespVO lockBudgetSnapshot(Long projectId) { + ProjectProfitRespVO currentProfit = buildCurrentProjectProfit(projectId); + if (projectCostMeasureSnapshotMapper.selectByProjectIdAndType(projectId, + ProjectCostMeasureSnapshotTypeEnum.BUDGET.getCode()) != null) { + throw exception(PROJECT_COST_MEASURE_BUDGET_LOCKED); + } + ProjectCostMeasureSnapshotDO snapshot = buildSnapshot(currentProfit, ProjectCostMeasureSnapshotTypeEnum.BUDGET.getCode()); + snapshot.setLockedFlag(Boolean.TRUE); + fillActionInfo(snapshot); + projectCostMeasureSnapshotMapper.insert(snapshot); + return getProjectProfit(projectId); + } + + @Override + public ProjectProfitRespVO lockAccountingSnapshot(Long projectId) { + ProjectProfitRespVO currentProfit = buildCurrentProjectProfit(projectId); + ProjectCostMeasureSnapshotDO budgetSnapshot = projectCostMeasureSnapshotMapper.selectByProjectIdAndType(projectId, + ProjectCostMeasureSnapshotTypeEnum.BUDGET.getCode()); + if (budgetSnapshot == null || !Boolean.TRUE.equals(budgetSnapshot.getLockedFlag())) { + throw exception(PROJECT_COST_MEASURE_BUDGET_REQUIRED); + } + if (projectCostMeasureSnapshotMapper.selectByProjectIdAndType(projectId, + ProjectCostMeasureSnapshotTypeEnum.ACCOUNTING.getCode()) != null) { + throw exception(PROJECT_COST_MEASURE_ACCOUNTING_LOCKED); + } + ProjectCostMeasureSnapshotDO snapshot = buildSnapshot(currentProfit, ProjectCostMeasureSnapshotTypeEnum.ACCOUNTING.getCode()); + snapshot.setLockedFlag(Boolean.TRUE); + fillActionInfo(snapshot); + projectCostMeasureSnapshotMapper.insert(snapshot); + return getProjectProfit(projectId); + } + + @Override + public ProjectProfitRespVO saveSettlementSnapshot(ProjectProfitSettlementSaveReqVO saveReqVO) { + validateProjectExists(saveReqVO.getProjectId()); + ProjectCostMeasureSnapshotDO accountingSnapshot = projectCostMeasureSnapshotMapper.selectByProjectIdAndType( + saveReqVO.getProjectId(), ProjectCostMeasureSnapshotTypeEnum.ACCOUNTING.getCode()); + if (accountingSnapshot == null || !Boolean.TRUE.equals(accountingSnapshot.getLockedFlag())) { + throw exception(PROJECT_COST_MEASURE_ACCOUNTING_REQUIRED); + } + + ProjectCostMeasureSnapshotDO settlementSnapshot = projectCostMeasureSnapshotMapper.selectByProjectIdAndType( + saveReqVO.getProjectId(), ProjectCostMeasureSnapshotTypeEnum.SETTLEMENT.getCode()); + boolean create = settlementSnapshot == null; + if (create) { + settlementSnapshot = new ProjectCostMeasureSnapshotDO(); + } + copySnapshotValues(accountingSnapshot, settlementSnapshot); + settlementSnapshot.setProjectId(saveReqVO.getProjectId()); + settlementSnapshot.setSnapshotType(ProjectCostMeasureSnapshotTypeEnum.SETTLEMENT.getCode()); + settlementSnapshot.setLockedFlag(Boolean.FALSE); + settlementSnapshot.setAssessmentResult(saveReqVO.getAssessmentResult()); + settlementSnapshot.setAssessmentCoefficient(getAssessmentCoefficient(saveReqVO.getAssessmentResult())); + settlementSnapshot.setRemark(saveReqVO.getRemark()); + + BigDecimal comprehensiveAccountingOutputValue = amount(accountingSnapshot.getComprehensivePlanningAmount()); + BigDecimal majorAccountingOutputValue = amount(accountingSnapshot.getMajorOutputValue()); + settlementSnapshot.setComprehensiveAccountingOutputValue(comprehensiveAccountingOutputValue); + settlementSnapshot.setMajorAccountingOutputValue(majorAccountingOutputValue); + settlementSnapshot.setComprehensiveSettlementOutputValue( + multiplyAmount(comprehensiveAccountingOutputValue, settlementSnapshot.getAssessmentCoefficient())); + settlementSnapshot.setMajorSettlementOutputValue( + multiplyAmount(majorAccountingOutputValue, settlementSnapshot.getAssessmentCoefficient())); + fillActionInfo(settlementSnapshot); + + if (create) { + projectCostMeasureSnapshotMapper.insert(settlementSnapshot); + } else { + projectCostMeasureSnapshotMapper.updateById(settlementSnapshot); + } + return getProjectProfit(saveReqVO.getProjectId()); + } + + private ProjectProfitRespVO buildCurrentProjectProfit(Long projectId) { + ProjectDO project = validateProjectExists(projectId); + List planningList = projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, + Collections.singleton(projectId)); + return buildProjectProfit(project, planningList); + } + private ProjectProfitRespVO buildProjectProfit(ProjectDO project, List planningList) { List safePlanningList = planningList == null ? Collections.emptyList() : planningList; + BigDecimal projectBudgetOutputValue = ZERO_AMOUNT; BigDecimal comprehensivePlanningAmount = ZERO_AMOUNT; BigDecimal specialSubcontractPlanningAmount = ZERO_AMOUNT; BigDecimal sourceCoopSubcontractPlanningAmount = ZERO_AMOUNT; BigDecimal comprehensiveSubcontractPlanningAmount = ZERO_AMOUNT; + BigDecimal majorExpectedPerformance = ZERO_AMOUNT; BigDecimal majorOutputValue = ZERO_AMOUNT; - List majorPlanningList = safePlanningList.stream() - .filter(planning -> ProjectPlanningBizTypeConstants.isMajor(planning.getOwnershipType())) - .collect(Collectors.toList()); for (ProjectPlanningDO planning : safePlanningList) { + BigDecimal planningBudgetOutputValue = amount(planning.getProjectBudgetOutputValue()); + projectBudgetOutputValue = projectBudgetOutputValue.add(planningBudgetOutputValue); if (ProjectPlanningBizTypeConstants.isComprehensive(planning.getOwnershipType())) { - comprehensivePlanningAmount = comprehensivePlanningAmount.add(amount(planning.getPlanningAmount())); + comprehensivePlanningAmount = comprehensivePlanningAmount.add(planningBudgetOutputValue); continue; } if (ProjectPlanningBizTypeConstants.isSpecialSubcontract(planning.getOwnershipType())) { - specialSubcontractPlanningAmount = specialSubcontractPlanningAmount.add(amount(planning.getPlanningAmount())); + specialSubcontractPlanningAmount = specialSubcontractPlanningAmount.add(planningBudgetOutputValue); continue; } if (ProjectPlanningBizTypeConstants.isSourceCoopSubcontract(planning.getOwnershipType())) { - sourceCoopSubcontractPlanningAmount = sourceCoopSubcontractPlanningAmount.add(amount(planning.getPlanningAmount())); + sourceCoopSubcontractPlanningAmount = sourceCoopSubcontractPlanningAmount.add(planningBudgetOutputValue); continue; } if (ProjectPlanningBizTypeConstants.isComprehensiveSubcontract(planning.getOwnershipType())) { - comprehensiveSubcontractPlanningAmount = comprehensiveSubcontractPlanningAmount.add(amount(planning.getPlanningAmount())); + comprehensiveSubcontractPlanningAmount = comprehensiveSubcontractPlanningAmount.add(planningBudgetOutputValue); continue; } if (ProjectPlanningBizTypeConstants.isMajor(planning.getOwnershipType())) { + majorExpectedPerformance = majorExpectedPerformance.add(planningBudgetOutputValue); majorOutputValue = majorOutputValue.add(amount(planning.getAssessmentOutputValue())); } } - BigDecimal majorExpectedPerformance = calculateMajorExpectedPerformance(majorPlanningList); BigDecimal contractAmount = amount(project.getContractAmount()); BigDecimal finalSettlementAmount = amount(project.getFinalSettlementAmount()); - BigDecimal effectiveSettlementAmount = finalSettlementAmount.compareTo(BigDecimal.ZERO) > 0 + BigDecimal effectiveContractSettlementAmount = finalSettlementAmount.compareTo(BigDecimal.ZERO) > 0 ? finalSettlementAmount : contractAmount; + BigDecimal taxExcludedSettlementAmount = effectiveContractSettlementAmount.compareTo(BigDecimal.ZERO) == 0 + ? ZERO_AMOUNT + : effectiveContractSettlementAmount.divide(new BigDecimal("1.06"), 2, RoundingMode.HALF_UP); BigDecimal innovationOutputRate = project.getInnovationOutputRate() == null ? DEFAULT_INNOVATION_OUTPUT_RATE : ratio(project.getInnovationOutputRate()); BigDecimal innovationOutputValue = contractAmount.compareTo(BigDecimal.ZERO) <= 0 @@ -125,16 +215,16 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { BigDecimal subcontractPlanningAmount = specialSubcontractPlanningAmount .add(sourceCoopSubcontractPlanningAmount) .add(comprehensiveSubcontractPlanningAmount); - BigDecimal profitLossValue = effectiveSettlementAmount + BigDecimal profitLossValue = taxExcludedSettlementAmount .subtract(comprehensivePlanningAmount) .subtract(subcontractPlanningAmount) .subtract(majorExpectedPerformance) .subtract(innovationOutputValue) .subtract(otherCost) .setScale(2, RoundingMode.HALF_UP); - BigDecimal profitLossRate = effectiveSettlementAmount.compareTo(BigDecimal.ZERO) == 0 + BigDecimal profitLossRate = taxExcludedSettlementAmount.compareTo(BigDecimal.ZERO) == 0 ? ZERO_RATIO - : profitLossValue.divide(effectiveSettlementAmount, 4, RoundingMode.HALF_UP); + : profitLossValue.divide(taxExcludedSettlementAmount, 4, RoundingMode.HALF_UP); ProjectProfitRespVO respVO = new ProjectProfitRespVO(); respVO.setProjectId(project.getId()); @@ -143,14 +233,15 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { respVO.setContractSignedFlag(project.getContractSignedFlag()); respVO.setContractAmount(contractAmount); respVO.setFinalSettlementAmount(finalSettlementAmount); - respVO.setEffectiveSettlementAmount(effectiveSettlementAmount); + // 历史字段名沿用 effectiveSettlementAmount,当前业务含义为“项目预算产值总计”。 + respVO.setEffectiveSettlementAmount(projectBudgetOutputValue.setScale(2, RoundingMode.HALF_UP)); respVO.setComprehensivePlanningAmount(comprehensivePlanningAmount.setScale(2, RoundingMode.HALF_UP)); respVO.setSubcontractPlanningAmount(subcontractPlanningAmount.setScale(2, RoundingMode.HALF_UP)); respVO.setSpecialSubcontractPlanningAmount(specialSubcontractPlanningAmount.setScale(2, RoundingMode.HALF_UP)); respVO.setSourceCoopSubcontractPlanningAmount(sourceCoopSubcontractPlanningAmount.setScale(2, RoundingMode.HALF_UP)); respVO.setComprehensiveSubcontractPlanningAmount(comprehensiveSubcontractPlanningAmount.setScale(2, RoundingMode.HALF_UP)); respVO.setMajorOutputValue(majorOutputValue.setScale(2, RoundingMode.HALF_UP)); - respVO.setMajorExpectedPerformance(majorExpectedPerformance); + respVO.setMajorExpectedPerformance(majorExpectedPerformance.setScale(2, RoundingMode.HALF_UP)); respVO.setInnovationOutputRate(innovationOutputRate); respVO.setInnovationOutputValue(innovationOutputValue); respVO.setOtherCost(otherCost); @@ -161,41 +252,66 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { return respVO; } - private BigDecimal calculateMajorExpectedPerformance(List majorPlanningList) { - if (majorPlanningList == null || majorPlanningList.isEmpty()) { - return ZERO_AMOUNT; - } - List planningIds = majorPlanningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList()); - Map> quarterMap = CollectionUtils.convertMultiMap( - projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId); - Set years = quarterMap.values().stream() - .flatMap(Collection::stream) - .map(ProjectPlanningQuarterDO::getDistributionYear) - .collect(Collectors.toSet()); - Map yearKValueMap = yearKValueMapper.selectEnabledListByYears(years).stream() - .collect(Collectors.toMap(YearKValueDO::getKYear, item -> ratio(item.getKValue()), (a, b) -> b)); + private void fillSnapshots(ProjectProfitRespVO respVO) { + Map snapshotMap = projectCostMeasureSnapshotMapper + .selectListByProjectId(respVO.getProjectId()) + .stream() + .collect(Collectors.toMap(ProjectCostMeasureSnapshotDO::getSnapshotType, item -> item, (a, b) -> b)); + respVO.setBudgetSnapshot(toSnapshotRespVO(snapshotMap.get(ProjectCostMeasureSnapshotTypeEnum.BUDGET.getCode()))); + respVO.setAccountingSnapshot(toSnapshotRespVO(snapshotMap.get(ProjectCostMeasureSnapshotTypeEnum.ACCOUNTING.getCode()))); + respVO.setSettlementSnapshot(toSnapshotRespVO(snapshotMap.get(ProjectCostMeasureSnapshotTypeEnum.SETTLEMENT.getCode()))); + } - BigDecimal total = ZERO_AMOUNT; - for (ProjectPlanningDO planning : majorPlanningList) { - BigDecimal assessmentOutputValue = amount(planning.getAssessmentOutputValue()); - Map yearRatioMap = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream() - .collect(Collectors.groupingBy(ProjectPlanningQuarterDO::getDistributionYear, - Collectors.reducing(ZERO_RATIO, - item -> ratio(item.getDistributionRatio()), - BigDecimal::add))); - BigDecimal allocatedRatio = ZERO_RATIO; - for (Map.Entry entry : yearRatioMap.entrySet()) { - BigDecimal yearRatio = ratio(entry.getValue()); - allocatedRatio = allocatedRatio.add(yearRatio).setScale(4, RoundingMode.HALF_UP); - BigDecimal kValue = yearKValueMap.getOrDefault(entry.getKey(), DEFAULT_YEAR_K_VALUE); - total = total.add(multiplyAmount(assessmentOutputValue, yearRatio, kValue)); - } - BigDecimal unallocatedRatio = ONE_RATIO.subtract(allocatedRatio).setScale(4, RoundingMode.HALF_UP); - if (unallocatedRatio.compareTo(ZERO_RATIO) > 0) { - total = total.add(multiplyAmount(assessmentOutputValue, unallocatedRatio, DEFAULT_YEAR_K_VALUE)); - } - } - return total.setScale(2, RoundingMode.HALF_UP); + private ProjectCostMeasureSnapshotDO buildSnapshot(ProjectProfitRespVO profit, String snapshotType) { + ProjectCostMeasureSnapshotDO snapshot = new ProjectCostMeasureSnapshotDO(); + snapshot.setProjectId(profit.getProjectId()); + snapshot.setSnapshotType(snapshotType); + snapshot.setContractAmount(amount(profit.getContractAmount())); + snapshot.setFinalSettlementAmount(amount(profit.getFinalSettlementAmount())); + snapshot.setEffectiveSettlementAmount(amount(profit.getEffectiveSettlementAmount())); + snapshot.setComprehensivePlanningAmount(amount(profit.getComprehensivePlanningAmount())); + snapshot.setSubcontractPlanningAmount(amount(profit.getSubcontractPlanningAmount())); + snapshot.setSpecialSubcontractPlanningAmount(amount(profit.getSpecialSubcontractPlanningAmount())); + snapshot.setSourceCoopSubcontractPlanningAmount(amount(profit.getSourceCoopSubcontractPlanningAmount())); + snapshot.setComprehensiveSubcontractPlanningAmount(amount(profit.getComprehensiveSubcontractPlanningAmount())); + snapshot.setMajorOutputValue(amount(profit.getMajorOutputValue())); + snapshot.setMajorExpectedPerformance(amount(profit.getMajorExpectedPerformance())); + snapshot.setInnovationOutputRate(ratio(profit.getInnovationOutputRate())); + snapshot.setInnovationOutputValue(amount(profit.getInnovationOutputValue())); + snapshot.setOtherCost(amount(profit.getOtherCost())); + snapshot.setProfitLossValue(amount(profit.getProfitLossValue())); + snapshot.setProfitLossRate(ratio(profit.getProfitLossRate())); + return snapshot; + } + + private void copySnapshotValues(ProjectCostMeasureSnapshotDO source, ProjectCostMeasureSnapshotDO target) { + target.setContractAmount(amount(source.getContractAmount())); + target.setFinalSettlementAmount(amount(source.getFinalSettlementAmount())); + target.setEffectiveSettlementAmount(amount(source.getEffectiveSettlementAmount())); + target.setComprehensivePlanningAmount(amount(source.getComprehensivePlanningAmount())); + target.setSubcontractPlanningAmount(amount(source.getSubcontractPlanningAmount())); + target.setSpecialSubcontractPlanningAmount(amount(source.getSpecialSubcontractPlanningAmount())); + target.setSourceCoopSubcontractPlanningAmount(amount(source.getSourceCoopSubcontractPlanningAmount())); + target.setComprehensiveSubcontractPlanningAmount(amount(source.getComprehensiveSubcontractPlanningAmount())); + target.setMajorOutputValue(amount(source.getMajorOutputValue())); + target.setMajorExpectedPerformance(amount(source.getMajorExpectedPerformance())); + target.setInnovationOutputRate(ratio(source.getInnovationOutputRate())); + target.setInnovationOutputValue(amount(source.getInnovationOutputValue())); + target.setOtherCost(amount(source.getOtherCost())); + target.setProfitLossValue(amount(source.getProfitLossValue())); + target.setProfitLossRate(ratio(source.getProfitLossRate())); + } + + private void fillActionInfo(ProjectCostMeasureSnapshotDO snapshot) { + Long userId = SecurityFrameworkUtils.getLoginUserId(); + String nickname = SecurityFrameworkUtils.getLoginUserNickname(); + snapshot.setActionUserId(userId); + snapshot.setActionUserName(nickname == null ? "" : nickname); + snapshot.setActionTime(LocalDateTime.now()); + } + + private ProjectProfitSnapshotRespVO toSnapshotRespVO(ProjectCostMeasureSnapshotDO snapshot) { + return snapshot == null ? null : BeanUtils.toBean(snapshot, ProjectProfitSnapshotRespVO.class); } private ProjectDO validateProjectExists(Long projectId) { @@ -214,6 +330,16 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP); } + private BigDecimal getAssessmentCoefficient(String assessmentResult) { + if ("优秀".equals(assessmentResult)) { + return ASSESSMENT_COEFFICIENT_EXCELLENT; + } + if ("待改进".equals(assessmentResult)) { + return ASSESSMENT_COEFFICIENT_NEEDS_IMPROVEMENT; + } + return ASSESSMENT_COEFFICIENT_QUALIFIED; + } + private BigDecimal multiplyAmount(BigDecimal... values) { BigDecimal result = BigDecimal.ONE; for (BigDecimal value : values) { diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java index ef85d67..6d648e9 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java @@ -151,8 +151,8 @@ public class ProjectServiceImpl implements ProjectService { if (project.getArchiveTime() == null && dbProject != null) { project.setArchiveTime(dbProject.getArchiveTime()); } - if (project.getSortNo() == null) { - project.setSortNo(dbProject == null || dbProject.getSortNo() == null ? 0 : dbProject.getSortNo()); + if (project.getSortNo() == null && dbProject != null) { + project.setSortNo(dbProject.getSortNo()); } } @@ -269,11 +269,8 @@ public class ProjectServiceImpl implements ProjectService { Map> quarterMap = CollectionUtils.convertMultiMap( projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId); for (ProjectPlanningDO planning : planningList) { - java.math.BigDecimal allocatedRatio = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream() - .map(ProjectPlanningQuarterDO::getDistributionRatio) - .filter(Objects::nonNull) - .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add) - .setScale(4, java.math.RoundingMode.HALF_UP); + java.math.BigDecimal allocatedRatio = resolveAllocatedRatio( + planning, quarterMap.getOrDefault(planning.getId(), Collections.emptyList())); java.math.BigDecimal totalDistributionAmount = planning.getTotalDistributionAmount() == null ? java.math.BigDecimal.ONE.setScale(4, java.math.RoundingMode.HALF_UP) : planning.getTotalDistributionAmount().setScale(4, java.math.RoundingMode.HALF_UP); @@ -284,6 +281,39 @@ public class ProjectServiceImpl implements ProjectService { return true; } + private java.math.BigDecimal resolveAllocatedRatio(ProjectPlanningDO planning, List quarterList) { + if (quarterList == null || quarterList.isEmpty()) { + return java.math.BigDecimal.ZERO.setScale(4, java.math.RoundingMode.HALF_UP); + } + boolean hasGuideDetailQuarter = quarterList.stream().anyMatch(item -> item.getGuideDetailId() != null); + if (!hasGuideDetailQuarter) { + return quarterList.stream() + .map(ProjectPlanningQuarterDO::getDistributionRatio) + .filter(Objects::nonNull) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add) + .setScale(4, java.math.RoundingMode.HALF_UP); + } + java.math.BigDecimal totalDistributionAmount = planning.getTotalDistributionAmount() == null + ? java.math.BigDecimal.ONE.setScale(4, java.math.RoundingMode.HALF_UP) + : planning.getTotalDistributionAmount().setScale(4, java.math.RoundingMode.HALF_UP); + java.math.BigDecimal baseAmount = amount(planning.getAssessmentOutputValue()) + .multiply(totalDistributionAmount) + .setScale(2, java.math.RoundingMode.HALF_UP); + if (baseAmount.compareTo(java.math.BigDecimal.ZERO) == 0) { + return java.math.BigDecimal.ZERO.setScale(4, java.math.RoundingMode.HALF_UP); + } + java.math.BigDecimal allocatedAmount = quarterList.stream() + .map(ProjectPlanningQuarterDO::getDistributionAmount) + .map(this::amount) + .reduce(java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP), java.math.BigDecimal::add); + return allocatedAmount.divide(baseAmount, 4, java.math.RoundingMode.HALF_UP); + } + + private java.math.BigDecimal amount(java.math.BigDecimal value) { + return value == null ? java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP) + : value.setScale(2, java.math.RoundingMode.HALF_UP); + } + private void deleteProjectDependencies(Collection projectIds) { if (projectIds == null || projectIds.isEmpty()) { return; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java index d65b877..7a444e9 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java @@ -180,11 +180,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic ProjectPlanningDO anchorPlanning = validatePlanningExists(reqVO.getPlanningId()); ProjectDO project = validateProjectExists(anchorPlanning.getProjectId()); Integer reportYear = resolveExportYear(reqVO.getYear()); - List planningList = projectPlanningMapper.selectListByProjectId(project.getId()).stream() - .sorted(Comparator - .comparingInt((ProjectPlanningDO item) -> projectLeadOutputTypeOrder(item.getOwnershipType())) - .thenComparing(ProjectPlanningDO::getId)) - .collect(Collectors.toList()); + List planningList = sortPlanningList(projectPlanningMapper.selectListByProjectId(project.getId())); List projectRolePersons = projectRolePersonMapper.selectListByProjectId(project.getId()); Map> quarterMap = getQuarterMap(planningList); Map outputSplitMap = getOutputSplitMap(planningList); @@ -1265,7 +1261,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic } List rows = new ArrayList<>(); planningList.stream() - .sorted(Comparator.comparing(ProjectPlanningDO::getId)) .forEach(planning -> { if (!ProjectPlanningBizTypeConstants.isMajorGuidanceScene( planning.getOwnershipType(), planning.getCalculationMethod())) { @@ -1552,11 +1547,37 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic } private List sortPlanningList(List planningList) { + if (planningList == null || planningList.isEmpty()) { + return Collections.emptyList(); + } + Map projectMap = getProjectMap(planningList); return planningList.stream() - .sorted(Comparator.comparing(ProjectPlanningDO::getId)) + .sorted(Comparator + .comparingInt((ProjectPlanningDO item) -> sortNoEmptyRank(projectSortNo(item, projectMap))) + .thenComparing(item -> sortNoValue(projectSortNo(item, projectMap))) + .thenComparing(ProjectPlanningDO::getProjectId, Comparator.nullsLast(Long::compareTo)) + .thenComparingInt(item -> sortNoEmptyRank(item.getSortNo())) + .thenComparing(item -> sortNoValue(item.getSortNo())) + .thenComparing(ProjectPlanningDO::getId, Comparator.nullsLast(Long::compareTo))) .collect(Collectors.toList()); } + private String projectSortNo(ProjectPlanningDO planning, Map projectMap) { + if (planning == null || projectMap == null) { + return null; + } + ProjectDO project = projectMap.get(planning.getProjectId()); + return project == null ? null : project.getSortNo(); + } + + private int sortNoEmptyRank(String sortNo) { + return sortNo == null || sortNo.trim().isEmpty() ? 1 : 0; + } + + private String sortNoValue(String sortNo) { + return sortNo == null ? "" : sortNo.trim(); + } + private ProjectOutputSplitDO resolveQuarterOutputSplit(ProjectPlanningDO planning, Map outputSplitMap) { if (planning == null) { @@ -1708,6 +1729,14 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic if (quarterList == null || quarterList.isEmpty()) { return summary; } + boolean hasParentQuarter = quarterList.stream().anyMatch(item -> item.getGuideDetailId() == null); + boolean hasGuideDetailQuarter = quarterList.stream().anyMatch(item -> item.getGuideDetailId() != null); + if (hasParentQuarter && hasGuideDetailQuarter) { + throw exception(PROJECT_PLANNING_QUARTER_SCOPE_MIXED); + } + if (hasGuideDetailQuarter) { + return buildGuideDetailQuarterSummary(planning, quarterList, reportYear, summary); + } BigDecimal allocatedRatio = ZERO_RATIO; for (ProjectPlanningQuarterDO quarter : quarterList) { @@ -1750,6 +1779,56 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic return summary; } + private QuarterSummary buildGuideDetailQuarterSummary(ProjectPlanningDO planning, + List quarterList, + Integer reportYear, + QuarterSummary summary) { + BigDecimal baseAmount = multiplyAmount(planning == null ? null : planning.getAssessmentOutputValue(), + defaultDistributionRatio(planning)); + BigDecimal allocatedAmount = ZERO_AMOUNT; + BigDecimal historicalAmount = ZERO_AMOUNT; + for (ProjectPlanningQuarterDO quarter : quarterList) { + BigDecimal distributionAmount = amount(quarter.getDistributionAmount()); + allocatedAmount = allocatedAmount.add(distributionAmount).setScale(2, RoundingMode.HALF_UP); + if (quarter.getDistributionYear() != null && quarter.getDistributionYear() < reportYear) { + historicalAmount = historicalAmount.add(distributionAmount).setScale(2, RoundingMode.HALF_UP); + continue; + } + if (!Objects.equals(quarter.getDistributionYear(), reportYear)) { + continue; + } + if (Objects.equals(quarter.getQuarterNo(), 1)) { + summary.setQuarterOneAmount(summary.getQuarterOneAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } else if (Objects.equals(quarter.getQuarterNo(), 2)) { + summary.setQuarterTwoAmount(summary.getQuarterTwoAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } else if (Objects.equals(quarter.getQuarterNo(), 3)) { + summary.setQuarterThreeAmount(summary.getQuarterThreeAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } else if (Objects.equals(quarter.getQuarterNo(), 4)) { + summary.setQuarterFourAmount(summary.getQuarterFourAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } + summary.setYearTotalAmount(summary.getYearTotalAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } + + summary.setHistoricalIssuedRatio(amountToDistributionRatio(historicalAmount, baseAmount)); + summary.setQuarterOneRatio(amountToDistributionRatio(summary.getQuarterOneAmount(), baseAmount)); + summary.setQuarterTwoRatio(amountToDistributionRatio(summary.getQuarterTwoAmount(), baseAmount)); + summary.setQuarterThreeRatio(amountToDistributionRatio(summary.getQuarterThreeAmount(), baseAmount)); + summary.setQuarterFourRatio(amountToDistributionRatio(summary.getQuarterFourAmount(), baseAmount)); + summary.setCurrentYearRatio(amountToDistributionRatio(summary.getYearTotalAmount(), baseAmount)); + BigDecimal allocatedRatio = amountToDistributionRatio(allocatedAmount, baseAmount); + BigDecimal pendingRatio = defaultDistributionRatio(planning).subtract(allocatedRatio) + .setScale(4, RoundingMode.HALF_UP); + summary.setPendingRatio(pendingRatio.compareTo(BigDecimal.ZERO) < 0 ? ZERO_RATIO : pendingRatio); + return summary; + } + + private BigDecimal amountToDistributionRatio(BigDecimal amount, BigDecimal baseAmount) { + if (baseAmount == null || baseAmount.compareTo(BigDecimal.ZERO) == 0) { + return ZERO_RATIO; + } + return amount(amount).divide(baseAmount, 4, RoundingMode.HALF_UP); + } + private BigDecimal getCategoryRatio(ProjectOutputSplitDO outputSplit, String specialtyCode) { if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { return ratio(outputSplit.getProjectLeadRatio()); @@ -1823,26 +1902,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic return "其他产值"; } - private int projectLeadOutputTypeOrder(String ownershipType) { - String outputType = resolveBudgetCategoryLabel(ownershipType); - if (Objects.equals(outputType, "六大专业考核产值")) { - return 1; - } - if (Objects.equals(outputType, "专项分包-专业所产值")) { - return 2; - } - if (Objects.equals(outputType, "专项分包-源头合作分包产值")) { - return 3; - } - if (Objects.equals(outputType, "专项分包-综合所产值")) { - return 4; - } - if (Objects.equals(outputType, "内部协作产值")) { - return 5; - } - return 6; - } - private String buildPlanningDisplayName(ProjectPlanningDO planning) { if (planning == null) { return "";