项目成本测算新增项目成本预算、核算、结算,季度分配调整可子合约规划分配

This commit is contained in:
lzm
2026-05-22 18:05:26 +08:00
parent 8898c95caa
commit 6c6c02ae59
31 changed files with 1050 additions and 139 deletions

View File

@@ -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<ProjectPlanningQuarterDO> 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<ProjectPlanningQuarterRespVO> buildPlanningQuarterRespList(
ProjectPlanningDO planning, List<ProjectPlanningQuarterDO> 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<String, ProjectPlanningQuarterRespVO> 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<ProjectPlanningQuarterRespVO> 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);
}
}

View File

@@ -17,7 +17,7 @@ public class ProjectPlanningRespVO {
private Long projectId;
@Schema(description = "排序")
private Integer sortNo;
private String sortNo;
@Schema(description = "归属类型")
private String ownershipType;

View File

@@ -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;
}

View File

@@ -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<ProjectPlanningQuarterDO> parentQuarterList = quarterList.stream()
.filter(item -> item.getGuideDetailId() == null)
.collect(Collectors.toList());
List<ProjectPlanningQuarterDO> 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<ProjectPlanningGuideDetailDO> 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<ProjectPlanningQuarterGuideDetailRespVO> buildGuideDetailRespList(
ProjectPlanningDO planning,
List<ProjectPlanningGuideDetailDO> guideDetailList,
List<ProjectPlanningQuarterDO> detailQuarterList) {
if (guideDetailList == null || guideDetailList.isEmpty()) {
return Collections.emptyList();
}
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = detailQuarterList == null
? Collections.emptyMap()
: detailQuarterList.stream()
.filter(item -> item.getGuideDetailId() != null)
.collect(Collectors.groupingBy(ProjectPlanningQuarterDO::getGuideDetailId));
List<ProjectPlanningQuarterGuideDetailRespVO> result = new ArrayList<>();
for (ProjectPlanningGuideDetailDO guideDetail : guideDetailList) {
ProjectPlanningQuarterGuideDetailRespVO respVO =
BeanUtils.toBean(guideDetail, ProjectPlanningQuarterGuideDetailRespVO.class);
List<ProjectPlanningQuarterDO> 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<ProjectPlanningQuarterDO> 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<Long, BigDecimal> 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);
}
}

View File

@@ -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<ProjectPlanningQuarterRespVO> quarters = Collections.emptyList();
}

View File

@@ -17,4 +17,19 @@ public class ProjectPlanningQuarterPlanningDetailRespVO {
@Schema(description = "季度分配列表")
private List<ProjectPlanningQuarterRespVO> 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<ProjectPlanningQuarterRespVO> parentQuarters = Collections.emptyList();
@Schema(description = "指导价法明细及各自季度分配列表")
private List<ProjectPlanningQuarterGuideDetailRespVO> guideDetails = Collections.emptyList();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<ProjectProfitRespVO> 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<ProjectProfitRespVO> lockAccountingSnapshot(@RequestParam("projectId") Long projectId) {
return success(projectProfitService.lockAccountingSnapshot(projectId));
}
@PutMapping("/save-settlement")
@Operation(summary = "保存项目成本结算测算")
@PreAuthorize("@ss.hasPermission('tjt:profit:query')")
public CommonResult<ProjectProfitRespVO> saveSettlementSnapshot(@Valid @RequestBody ProjectProfitSettlementSaveReqVO saveReqVO) {
return success(projectProfitService.saveSettlementSnapshot(saveReqVO));
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -28,7 +28,7 @@ public class ProjectRespVO {
@Schema(description = "排序")
@ExcelProperty("排序")
private Integer sortNo;
private String sortNo;
@Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("是否签订合同")

View File

@@ -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 = "是否签订合同不能为空")

View File

@@ -25,7 +25,7 @@ public class ProjectPlanningDO extends TenantBaseDO {
private Long projectId;
private Integer sortNo;
private String sortNo;
private String ownershipType;

View File

@@ -25,6 +25,10 @@ public class ProjectPlanningQuarterDO extends TenantBaseDO {
private Long planningId;
private Long guideDetailId;
private Integer guideDetailSortNo;
private Integer distributionYear;
private Integer quarterNo;

View File

@@ -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;
}

View File

@@ -34,7 +34,7 @@ public class ProjectDO extends TenantBaseDO {
/**
* 排序
*/
private Integer sortNo;
private String sortNo;
/**
* 是否签订合同
*/

View File

@@ -25,8 +25,7 @@ public interface ProjectPlanningMapper extends BaseMapperX<ProjectPlanningDO> {
.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<ProjectPlanningDO> selectListByProjectId(Long projectId) {
@@ -38,8 +37,7 @@ public interface ProjectPlanningMapper extends BaseMapperX<ProjectPlanningDO> {
default List<ProjectPlanningDO> selectDisplayListByProjectId(Long projectId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningDO>()
.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"));
}
}

View File

@@ -20,25 +20,102 @@ public interface ProjectPlanningQuarterMapper extends BaseMapperX<ProjectPlannin
default ProjectPlanningQuarterDO selectByPlanningIdAndDistributionYearAndQuarter(Long planningId,
Integer distributionYear,
Integer quarterNo) {
return selectOne(ProjectPlanningQuarterDO::getPlanningId, planningId,
ProjectPlanningQuarterDO::getDistributionYear, distributionYear,
ProjectPlanningQuarterDO::getQuarterNo, quarterNo);
return selectParentByPlanningIdAndDistributionYearAndQuarter(planningId, distributionYear, quarterNo);
}
default ProjectPlanningQuarterDO selectParentByPlanningIdAndDistributionYearAndQuarter(Long planningId,
Integer distributionYear,
Integer quarterNo) {
return selectOne(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.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<ProjectPlanningQuarterDO>()
.eq(ProjectPlanningQuarterDO::getPlanningId, planningId)
.eq(ProjectPlanningQuarterDO::getGuideDetailId, guideDetailId)
.eq(ProjectPlanningQuarterDO::getDistributionYear, distributionYear)
.eq(ProjectPlanningQuarterDO::getQuarterNo, quarterNo));
}
default List<ProjectPlanningQuarterDO> selectListByPlanningId(Long planningId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.eq(ProjectPlanningQuarterDO::getPlanningId, planningId)
.orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo)
.orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId)
.orderByAsc(ProjectPlanningQuarterDO::getDistributionYear)
.orderByAsc(ProjectPlanningQuarterDO::getQuarterNo)
.orderByAsc(ProjectPlanningQuarterDO::getId));
}
default List<ProjectPlanningQuarterDO> selectParentListByPlanningId(Long planningId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.eq(ProjectPlanningQuarterDO::getPlanningId, planningId)
.isNull(ProjectPlanningQuarterDO::getGuideDetailId)
.orderByAsc(ProjectPlanningQuarterDO::getDistributionYear)
.orderByAsc(ProjectPlanningQuarterDO::getQuarterNo)
.orderByAsc(ProjectPlanningQuarterDO::getId));
}
default List<ProjectPlanningQuarterDO> selectDetailListByPlanningId(Long planningId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.eq(ProjectPlanningQuarterDO::getPlanningId, planningId)
.isNotNull(ProjectPlanningQuarterDO::getGuideDetailId)
.orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo)
.orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId)
.orderByAsc(ProjectPlanningQuarterDO::getDistributionYear)
.orderByAsc(ProjectPlanningQuarterDO::getQuarterNo)
.orderByAsc(ProjectPlanningQuarterDO::getId));
}
default List<ProjectPlanningQuarterDO> selectListByGuideDetailId(Long guideDetailId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.eq(ProjectPlanningQuarterDO::getGuideDetailId, guideDetailId)
.orderByAsc(ProjectPlanningQuarterDO::getDistributionYear)
.orderByAsc(ProjectPlanningQuarterDO::getQuarterNo)
.orderByAsc(ProjectPlanningQuarterDO::getId));
}
default List<ProjectPlanningQuarterDO> selectListByGuideDetailIds(Collection<Long> guideDetailIds) {
if (guideDetailIds == null || guideDetailIds.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.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<ProjectPlanningQuarterDO>()
.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<ProjectPlanningQuarterDO>()
.eq(ProjectPlanningQuarterDO::getPlanningId, planningId)
.isNotNull(ProjectPlanningQuarterDO::getGuideDetailId)
.ne(excludeId != null, ProjectPlanningQuarterDO::getId, excludeId)) > 0;
}
default List<ProjectPlanningQuarterDO> selectListByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<ProjectPlanningQuarterDO>()
.in(ProjectPlanningQuarterDO::getPlanningId, planningIds)
.orderByAsc(ProjectPlanningQuarterDO::getPlanningId)
.orderByAsc(ProjectPlanningQuarterDO::getGuideDetailSortNo)
.orderByAsc(ProjectPlanningQuarterDO::getGuideDetailId)
.orderByAsc(ProjectPlanningQuarterDO::getDistributionYear)
.orderByAsc(ProjectPlanningQuarterDO::getQuarterNo)
.orderByAsc(ProjectPlanningQuarterDO::getId));

View File

@@ -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<ProjectCostMeasureSnapshotDO> {
default ProjectCostMeasureSnapshotDO selectByProjectIdAndType(Long projectId, String snapshotType) {
return selectOne(new LambdaQueryWrapperX<ProjectCostMeasureSnapshotDO>()
.eq(ProjectCostMeasureSnapshotDO::getProjectId, projectId)
.eq(ProjectCostMeasureSnapshotDO::getSnapshotType, snapshotType));
}
default List<ProjectCostMeasureSnapshotDO> selectListByProjectId(Long projectId) {
return selectList(new LambdaQueryWrapperX<ProjectCostMeasureSnapshotDO>()
.eq(ProjectCostMeasureSnapshotDO::getProjectId, projectId));
}
}

View File

@@ -22,8 +22,7 @@ public interface ProjectMapper extends BaseMapperX<ProjectDO> {
.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"));
}
}

View File

@@ -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, "请先下达目标责任书,生成项目成本预算测算后再完成竣工验收");
}

View File

@@ -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;
}

View File

@@ -157,18 +157,54 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
if (planningIds == null || planningIds.isEmpty()) {
return Collections.emptyMap();
}
List<ProjectPlanningQuarterDO> quarterList = projectPlanningQuarterMapper.selectListByPlanningIds(planningIds);
List<Long> validPlanningIds = planningIds.stream()
.filter(Objects::nonNull)
.collect(java.util.stream.Collectors.toList());
if (validPlanningIds.isEmpty()) {
return Collections.emptyMap();
}
Map<Long, ProjectPlanningDO> planningMap = projectPlanningMapper.selectBatchIds(validPlanningIds).stream()
.collect(java.util.stream.Collectors.toMap(ProjectPlanningDO::getId, item -> item, (a, b) -> a));
List<ProjectPlanningQuarterDO> quarterList = projectPlanningQuarterMapper.selectListByPlanningIds(validPlanningIds);
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = quarterList.stream()
.collect(java.util.stream.Collectors.groupingBy(ProjectPlanningQuarterDO::getPlanningId));
Map<Long, BigDecimal> 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<ProjectPlanningQuarterDO> 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);

View File

@@ -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<ProjectPlanningQuarterDO> quarterList = projectPlanningQuarterMapper.selectListByPlanningId(planning.getId());
Map<Long, ProjectPlanningGuideDetailDO> 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())
));

View File

@@ -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()));
}

View File

@@ -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<ProjectProfitRespVO> getProjectProfitPage(ProjectProfitPageReqVO pageReqVO);
ProjectProfitRespVO lockBudgetSnapshot(Long projectId);
ProjectProfitRespVO lockAccountingSnapshot(Long projectId);
ProjectProfitRespVO saveSettlementSnapshot(ProjectProfitSettlementSaveReqVO saveReqVO);
}

View File

@@ -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<ProjectPlanningDO> 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<ProjectPlanningDO> planningList = projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId,
Collections.singleton(projectId));
return buildProjectProfit(project, planningList);
}
private ProjectProfitRespVO buildProjectProfit(ProjectDO project, List<ProjectPlanningDO> planningList) {
List<ProjectPlanningDO> 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<ProjectPlanningDO> 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<ProjectPlanningDO> majorPlanningList) {
if (majorPlanningList == null || majorPlanningList.isEmpty()) {
return ZERO_AMOUNT;
}
List<Long> planningIds = majorPlanningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList());
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = CollectionUtils.convertMultiMap(
projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId);
Set<Integer> years = quarterMap.values().stream()
.flatMap(Collection::stream)
.map(ProjectPlanningQuarterDO::getDistributionYear)
.collect(Collectors.toSet());
Map<Integer, BigDecimal> yearKValueMap = yearKValueMapper.selectEnabledListByYears(years).stream()
.collect(Collectors.toMap(YearKValueDO::getKYear, item -> ratio(item.getKValue()), (a, b) -> b));
private void fillSnapshots(ProjectProfitRespVO respVO) {
Map<String, ProjectCostMeasureSnapshotDO> 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<Integer, BigDecimal> 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<Integer, BigDecimal> 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) {

View File

@@ -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<Long, List<ProjectPlanningQuarterDO>> 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<ProjectPlanningQuarterDO> 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<Long> projectIds) {
if (projectIds == null || projectIds.isEmpty()) {
return;

View File

@@ -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<ProjectPlanningDO> planningList = projectPlanningMapper.selectListByProjectId(project.getId()).stream()
.sorted(Comparator
.comparingInt((ProjectPlanningDO item) -> projectLeadOutputTypeOrder(item.getOwnershipType()))
.thenComparing(ProjectPlanningDO::getId))
.collect(Collectors.toList());
List<ProjectPlanningDO> planningList = sortPlanningList(projectPlanningMapper.selectListByProjectId(project.getId()));
List<ProjectRolePersonDO> projectRolePersons = projectRolePersonMapper.selectListByProjectId(project.getId());
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = getQuarterMap(planningList);
Map<Long, ProjectOutputSplitDO> outputSplitMap = getOutputSplitMap(planningList);
@@ -1265,7 +1261,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic
}
List<ProjectBudgetExcelBuilder.BudgetRow> 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<ProjectPlanningDO> sortPlanningList(List<ProjectPlanningDO> planningList) {
if (planningList == null || planningList.isEmpty()) {
return Collections.emptyList();
}
Map<Long, ProjectDO> 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<Long, ProjectDO> 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<Long, ProjectOutputSplitDO> 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<ProjectPlanningQuarterDO> 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 "";