diff --git a/lyzsys-module-tjt/pom.xml b/lyzsys-module-tjt/pom.xml new file mode 100644 index 0000000..6443bd3 --- /dev/null +++ b/lyzsys-module-tjt/pom.xml @@ -0,0 +1,64 @@ + + + + cn.iocoder.boot + lyzsys + ${revision} + + 4.0.0 + lyzsys-module-tjt + jar + + ${project.artifactId} + + tjt 模块,承载特建投设计产值统计业务。 + + + + + cn.iocoder.boot + lyzsys-module-infra + ${revision} + + + + cn.iocoder.boot + lyzsys-spring-boot-starter-biz-tenant + + + + cn.iocoder.boot + lyzsys-spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-validation + + + + cn.iocoder.boot + lyzsys-spring-boot-starter-mybatis + + + + cn.iocoder.boot + lyzsys-spring-boot-starter-redis + + + + cn.iocoder.boot + lyzsys-spring-boot-starter-test + test + + + + cn.iocoder.boot + lyzsys-spring-boot-starter-excel + + + + + 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 new file mode 100644 index 0000000..85d6251 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/ProjectOutputSplitController.java @@ -0,0 +1,43 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 特建投页面4拆分比例") +@RestController +@RequestMapping("/tjt/output-split") +@Validated +public class ProjectOutputSplitController { + + @Resource + private ProjectOutputSplitService projectOutputSplitService; + + @GetMapping("/get-by-planning") + @Operation(summary = "根据合约规划获得页面4拆分比例") + @Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:output-split:query')") + public CommonResult getProjectOutputSplitByPlanningId(@RequestParam("planningId") Long planningId) { + return success(projectOutputSplitService.getProjectOutputSplit(planningId)); + } + + @PutMapping("/save") + @Operation(summary = "保存页面4拆分比例") + @PreAuthorize("@ss.hasPermission('tjt:output-split:update')") + public CommonResult saveProjectOutputSplit(@Valid @RequestBody ProjectOutputSplitSaveReqVO reqVO) { + return success(projectOutputSplitService.saveProjectOutputSplit(reqVO)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/vo/ProjectOutputSplitRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/vo/ProjectOutputSplitRespVO.java new file mode 100644 index 0000000..ab97a35 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/vo/ProjectOutputSplitRespVO.java @@ -0,0 +1,99 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 页面4拆分比例 Response VO") +@Data +public class ProjectOutputSplitRespVO { + + @Schema(description = "页面4拆分 ID", example = "1") + private Long id; + + @Schema(description = "项目 ID", example = "1") + private Long projectId; + + @Schema(description = "合约规划 ID", example = "1") + private Long planningId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "规划内容") + private String planningContent; + + @Schema(description = "年度") + private Integer year; + + @Schema(description = "考核产值") + private BigDecimal assessmentOutputValue; + + @Schema(description = "项目经理") + private String projectManagerName; + + @Schema(description = "项目负责人") + private String engineeringLeaderName; + + @Schema(description = "项目经理比例") + private BigDecimal projectManagerRatio; + + @Schema(description = "项目经理金额") + private BigDecimal projectManagerAmount; + + @Schema(description = "项目负责人比例") + private BigDecimal engineeringLeaderRatio; + + @Schema(description = "项目负责人金额") + private BigDecimal engineeringLeaderAmount; + + @Schema(description = "专业所比例") + private BigDecimal officeRatio; + + @Schema(description = "专业所金额") + private BigDecimal officeAmount; + + @Schema(description = "建筑专业比例") + private BigDecimal archRatio; + + @Schema(description = "建筑专业金额") + private BigDecimal archAmount; + + @Schema(description = "装修专业比例") + private BigDecimal decorRatio; + + @Schema(description = "装修专业金额") + private BigDecimal decorAmount; + + @Schema(description = "结构专业比例") + private BigDecimal structRatio; + + @Schema(description = "结构专业金额") + private BigDecimal structAmount; + + @Schema(description = "水专业比例") + private BigDecimal waterRatio; + + @Schema(description = "水专业金额") + private BigDecimal waterAmount; + + @Schema(description = "电气专业比例") + private BigDecimal elecRatio; + + @Schema(description = "电气专业金额") + private BigDecimal elecAmount; + + @Schema(description = "暖通专业比例") + private BigDecimal hvacRatio; + + @Schema(description = "暖通专业金额") + private BigDecimal hvacAmount; + + @Schema(description = "数字化设计专业比例") + private BigDecimal digitalRatio; + + @Schema(description = "数字化设计专业金额") + private BigDecimal digitalAmount; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/vo/ProjectOutputSplitSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/vo/ProjectOutputSplitSaveReqVO.java new file mode 100644 index 0000000..33f4ab3 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/outputsplit/vo/ProjectOutputSplitSaveReqVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 页面4拆分比例新增/修改 Request VO") +@Data +public class ProjectOutputSplitSaveReqVO { + + @Schema(description = "合同规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "合同规划 ID 不能为空") + private Long planningId; + + @Schema(description = "项目经理比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0200") + @NotNull(message = "项目经理比例不能为空") + private BigDecimal projectManagerRatio; + + @Schema(description = "项目负责人比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0300") + @NotNull(message = "项目负责人比例不能为空") + private BigDecimal engineeringLeaderRatio; + + @Schema(description = "专业所比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.9500") + @NotNull(message = "专业所比例不能为空") + private BigDecimal officeRatio; + + @Schema(description = "建筑专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000") + @NotNull(message = "建筑专业比例不能为空") + private BigDecimal archRatio; + + @Schema(description = "装修专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0000") + @NotNull(message = "装修专业比例不能为空") + private BigDecimal decorRatio; + + @Schema(description = "结构专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.2000") + @NotNull(message = "结构专业比例不能为空") + private BigDecimal structRatio; + + @Schema(description = "水专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000") + @NotNull(message = "水专业比例不能为空") + private BigDecimal waterRatio; + + @Schema(description = "电气专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000") + @NotNull(message = "电气专业比例不能为空") + private BigDecimal elecRatio; + + @Schema(description = "暖通专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500") + @NotNull(message = "暖通专业比例不能为空") + private BigDecimal hvacRatio; + + @Schema(description = "数字化设计专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500") + @NotNull(message = "数字化设计专业比例不能为空") + private BigDecimal digitalRatio; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/ProjectPlanningController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/ProjectPlanningController.java new file mode 100644 index 0000000..f6be548 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/ProjectPlanningController.java @@ -0,0 +1,123 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planning; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +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.module.tjt.controller.admin.planning.vo.ProjectPlanningPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.service.planning.ProjectPlanningService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 特建投合约规划") +@RestController +@RequestMapping("/tjt/planning") +@Validated +public class ProjectPlanningController { + + private static final int RATIO_SCALE = 4; + private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + + @Resource + private ProjectPlanningService projectPlanningService; + + @PostMapping("/create") + @Operation(summary = "创建合约规划") + @PreAuthorize("@ss.hasPermission('tjt:planning:create')") + public CommonResult createProjectPlanning(@Valid @RequestBody ProjectPlanningSaveReqVO createReqVO) { + return success(projectPlanningService.createProjectPlanning(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改合约规划") + @PreAuthorize("@ss.hasPermission('tjt:planning:update')") + public CommonResult updateProjectPlanning(@Valid @RequestBody ProjectPlanningSaveReqVO updateReqVO) { + projectPlanningService.updateProjectPlanning(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除合约规划") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:planning:delete')") + public CommonResult deleteProjectPlanning(@RequestParam("id") Long id) { + projectPlanningService.deleteProjectPlanning(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除合约规划") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('tjt:planning:delete')") + public CommonResult deleteProjectPlanningList(@RequestParam("ids") List ids) { + projectPlanningService.deleteProjectPlanningList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得合约规划分页") + @PreAuthorize("@ss.hasPermission('tjt:planning:query')") + public CommonResult> getProjectPlanningPage(@Valid ProjectPlanningPageReqVO pageReqVO) { + PageResult pageResult = projectPlanningService.getProjectPlanningPage(pageReqVO); + Map allocatedAmountMap = projectPlanningService.getAllocatedAmountMap( + CollectionUtils.convertSet(pageResult.getList(), ProjectPlanningDO::getId)); + return success(BeanUtils.toBean(pageResult, ProjectPlanningRespVO.class, + respVO -> fillDistributionSummary(respVO, allocatedAmountMap))); + } + + @GetMapping("/get") + @Operation(summary = "获得合约规划详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:planning:query')") + public CommonResult getProjectPlanning(@RequestParam("id") Long id) { + ProjectPlanningDO planning = projectPlanningService.getProjectPlanning(id); + if (planning == null) { + return success(null); + } + Map allocatedAmountMap = projectPlanningService.getAllocatedAmountMap( + Collections.singleton(planning.getId())); + return success(BeanUtils.toBean(planning, ProjectPlanningRespVO.class, + respVO -> fillDistributionSummary(respVO, allocatedAmountMap))); + } + + @GetMapping("/list-by-project") + @Operation(summary = "根据项目获得合约规划列表") + @Parameter(name = "projectId", description = "项目 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:planning:query')") + public CommonResult> getProjectPlanningListByProjectId(@RequestParam("projectId") Long projectId) { + List list = projectPlanningService.getProjectPlanningListByProjectId(projectId); + Map allocatedAmountMap = projectPlanningService.getAllocatedAmountMap( + CollectionUtils.convertSet(list, ProjectPlanningDO::getId)); + return success(BeanUtils.toBean(list, ProjectPlanningRespVO.class, + respVO -> fillDistributionSummary(respVO, allocatedAmountMap))); + } + + private void fillDistributionSummary(ProjectPlanningRespVO respVO, Map allocatedAmountMap) { + BigDecimal allocatedRatio = allocatedAmountMap.getOrDefault(respVO.getId(), ZERO_RATIO); + BigDecimal totalDistributionAmount = respVO.getTotalDistributionAmount() == null + ? ZERO_RATIO : respVO.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) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningPageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningPageReqVO.java new file mode 100644 index 0000000..c11340e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningPageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 合约规划分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectPlanningPageReqVO extends PageParam { + + @Schema(description = "项目 ID", example = "1") + private Long projectId; + + @Schema(description = "归属类型", example = "专业所") + private String ownershipType; + + @Schema(description = "产值计算方式", example = "指导价法") + private String calculationMethod; + + @Schema(description = "规划内容,模糊匹配", example = "建筑") + private String planningContent; + + @Schema(description = "开始年度", example = "2026") + private Integer planningStartYear; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} 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 new file mode 100644 index 0000000..5402698 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java @@ -0,0 +1,127 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.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 ProjectPlanningRespVO { + + @Schema(description = "合约规划 ID", example = "1") + private Long id; + + @Schema(description = "项目 ID", example = "1") + private Long projectId; + + @Schema(description = "归属类型") + private String ownershipType; + + @Schema(description = "产值计算方式") + private String calculationMethod; + + @Schema(description = "规划内容") + private String planningContent; + + @Schema(description = "规划金额") + private BigDecimal planningAmount; + + @Schema(description = "管理费费率") + private BigDecimal managementFeeRate; + + @Schema(description = "管理费") + private BigDecimal managementFee; + + @Schema(description = "实施团队") + private String implementationTeam; + + @Schema(description = "开始年度") + private Integer planningStartYear; + + @Schema(description = "面积") + private BigDecimal planningArea; + + @Schema(description = "设计阶段") + private String designStage; + + @Schema(description = "本次设计阶段比例") + private BigDecimal currentDesignStageRatio; + + @Schema(description = "审核审定是否外包") + private Boolean reviewOutsourceFlag; + + @Schema(description = "审核审定占比") + private BigDecimal reviewOutsourceRatio; + + @Schema(description = "总分配比例") + private BigDecimal totalDistributionAmount; + + @Schema(description = "已分配比例") + private BigDecimal allocatedAmount; + + @Schema(description = "待分配比例") + private BigDecimal pendingAmount; + + @Schema(description = "Planning progress remark") + private String progressRemark; + + @Schema(description = "楼栋数或户型数") + private Integer buildingOrUnitCount; + + @Schema(description = "套图系数,保留两位小数") + private BigDecimal drawingSetFactor; + + @Schema(description = "规模系数,保留两位小数") + private BigDecimal scaleFactor; + + @Schema(description = "修改系数,保留两位小数") + private BigDecimal modificationFactor; + + @Schema(description = "复杂系数/复杂等级,按比例值返回(100%=1.0000)") + private BigDecimal complexityFactor; + + @Schema(description = "内部指导单价(元/㎡)") + private BigDecimal internalGuidanceUnitPrice; + + @Schema(description = "虚拟产值计算方式") + private String virtualCalculationMethod; + + @Schema(description = "工日") + private BigDecimal workingDayCount; + + @Schema(description = "工日单价") + private BigDecimal workingDayUnitPrice; + + @Schema(description = "指导单价") + private BigDecimal guidanceUnitPrice; + + @Schema(description = "指导总价") + private BigDecimal guidanceTotalPrice; + + @Schema(description = "虚拟总价") + private BigDecimal virtualTotalPrice; + + @Schema(description = "产值计算比例") + private BigDecimal calculationRatio; + + @Schema(description = "合同单价(元/㎡)") + private BigDecimal contractUnitPrice; + + @Schema(description = "合计调整系数") + private BigDecimal totalAdjustmentFactor; + + @Schema(description = "考核面积") + private BigDecimal assessmentArea; + + @Schema(description = "虚拟产值") + private BigDecimal virtualOutputValue; + + @Schema(description = "考核产值") + private BigDecimal assessmentOutputValue; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} 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 new file mode 100644 index 0000000..481c442 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java @@ -0,0 +1,115 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 合约规划新增/修改 Request VO") +@Data +public class ProjectPlanningSaveReqVO { + + @Schema(description = "合约规划 ID", example = "1") + private Long id; + + @Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "项目 ID 不能为空") + private Long projectId; + + @Schema(description = "归属类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "专业所") + @NotBlank(message = "归属类型不能为空") + @Size(max = 20, message = "归属类型长度不能超过 20 个字符") + private String ownershipType; + + @Schema(description = "产值计算方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "指导价法") + @NotBlank(message = "产值计算方式不能为空") + @Size(max = 30, message = "产值计算方式长度不能超过 30 个字符") + private String calculationMethod; + + @Schema(description = "规划内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑设计") + @NotBlank(message = "规划内容不能为空") + @Size(max = 255, message = "规划内容长度不能超过 255 个字符") + private String planningContent; + + @Schema(description = "规划金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "500000") + @NotNull(message = "规划金额不能为空") + private BigDecimal planningAmount; + + @Schema(description = "管理费费率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500") + @NotNull(message = "管理费费率不能为空") + private BigDecimal managementFeeRate; + + @Schema(description = "实施团队", example = "建筑一所") + @Size(max = 100, message = "实施团队长度不能超过 100 个字符") + private String implementationTeam; + + @Schema(description = "开始年度", example = "2026") + private Integer planningStartYear; + + @Schema(description = "面积", example = "30000") + private BigDecimal planningArea; + + @Schema(description = "设计阶段", example = "方案设计") + @Size(max = 50, message = "设计阶段长度不能超过 50 个字符") + private String designStage; + + @Schema(description = "本次设计阶段比例", example = "0.3000") + private BigDecimal currentDesignStageRatio; + + @Schema(description = "审核审定是否外包", example = "false") + private Boolean reviewOutsourceFlag; + + @Schema(description = "审核审定占比", example = "0.0600") + private BigDecimal reviewOutsourceRatio; + + @Schema(description = "总分配比例", example = "1.0000") + private BigDecimal totalDistributionAmount; + + @Schema(description = "Planning progress remark", example = "Q1 extraction arrangement") + @Size(max = 500, message = "Progress remark length must be within 500 characters") + private String progressRemark; + + @Schema(description = "楼栋数或户型数", example = "10") + private Integer buildingOrUnitCount; + + @Schema(description = "套图系数,保留两位小数", example = "1.00") + private BigDecimal drawingSetFactor; + + @Schema(description = "规模系数,保留两位小数", example = "1.00") + private BigDecimal scaleFactor; + + @Schema(description = "修改系数,保留两位小数", example = "1.00") + private BigDecimal modificationFactor; + + @Schema(description = "复杂系数/复杂等级,按比例值存储(100%=1.0000)", example = "1.0000") + private BigDecimal complexityFactor; + + @Schema(description = "内部指导单价(元/㎡)", example = "80.00") + private BigDecimal internalGuidanceUnitPrice; + + @Schema(description = "虚拟产值计算方式", example = "工日法") + @Size(max = 30, message = "虚拟产值计算方式长度不能超过 30 个字符") + private String virtualCalculationMethod; + + @Schema(description = "工日", example = "100") + private BigDecimal workingDayCount; + + @Schema(description = "工日单价", example = "1000.00") + private BigDecimal workingDayUnitPrice; + + @Schema(description = "指导单价", example = "88.00") + private BigDecimal guidanceUnitPrice; + + @Schema(description = "指导总价", example = "880000.00") + private BigDecimal guidanceTotalPrice; + + @Schema(description = "虚拟总价", example = "600000.00") + private BigDecimal virtualTotalPrice; + + @Schema(description = "产值计算比例", example = "0.0800") + private BigDecimal calculationRatio; + +} 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 new file mode 100644 index 0000000..94f2410 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/ProjectPlanningQuarterController.java @@ -0,0 +1,83 @@ +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.planningquarter.vo.ProjectPlanningQuarterRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO; +import cn.iocoder.lyzsys.module.tjt.service.planningquarter.ProjectPlanningQuarterService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 特建投季度分配") +@RestController +@RequestMapping("/tjt/planning-quarter") +@Validated +public class ProjectPlanningQuarterController { + + @Resource + private ProjectPlanningQuarterService projectPlanningQuarterService; + + @PostMapping("/create") + @Operation(summary = "创建季度分配") + @PreAuthorize("@ss.hasPermission('tjt:planning-quarter:create')") + public CommonResult createProjectPlanningQuarter(@Valid @RequestBody ProjectPlanningQuarterSaveReqVO createReqVO) { + return success(projectPlanningQuarterService.createProjectPlanningQuarter(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改季度分配") + @PreAuthorize("@ss.hasPermission('tjt:planning-quarter:update')") + public CommonResult updateProjectPlanningQuarter(@Valid @RequestBody ProjectPlanningQuarterSaveReqVO updateReqVO) { + projectPlanningQuarterService.updateProjectPlanningQuarter(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除季度分配") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:planning-quarter:delete')") + public CommonResult deleteProjectPlanningQuarter(@RequestParam("id") Long id) { + projectPlanningQuarterService.deleteProjectPlanningQuarter(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除季度分配") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('tjt:planning-quarter:delete')") + public CommonResult deleteProjectPlanningQuarterList(@RequestParam("ids") List ids) { + projectPlanningQuarterService.deleteProjectPlanningQuarterList(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得季度分配详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:planning-quarter:query')") + public CommonResult getProjectPlanningQuarter(@RequestParam("id") Long id) { + ProjectPlanningQuarterDO quarter = projectPlanningQuarterService.getProjectPlanningQuarter(id); + return success(BeanUtils.toBean(quarter, ProjectPlanningQuarterRespVO.class)); + } + + @GetMapping("/list-by-planning") + @Operation(summary = "根据合约规划获得季度分配列表") + @Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:planning-quarter:query')") + public CommonResult> getProjectPlanningQuarterListByPlanningId( + @RequestParam("planningId") Long planningId) { + List list = projectPlanningQuarterService.getProjectPlanningQuarterListByPlanningId(planningId); + return success(BeanUtils.toBean(list, ProjectPlanningQuarterRespVO.class)); + } + +} 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 new file mode 100644 index 0000000..2243d68 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.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 ProjectPlanningQuarterRespVO { + + @Schema(description = "季度分配 ID", example = "1") + private Long id; + + @Schema(description = "合约规划 ID", example = "1") + private Long planningId; + + @Schema(description = "分配年度") + private Integer distributionYear; + + @Schema(description = "季度") + private Integer quarterNo; + + @Schema(description = "分配比例") + private BigDecimal distributionRatio; + + @Schema(description = "分配金额") + private BigDecimal distributionAmount; + + @Schema(description = "提取进度备注") + private String progressRemark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} 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 new file mode 100644 index 0000000..8cfc307 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 季度分配新增/修改 Request VO") +@Data +public class ProjectPlanningQuarterSaveReqVO { + + @Schema(description = "季度分配 ID", example = "1") + private Long id; + + @Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "合约规划 ID 不能为空") + private Long planningId; + + @Schema(description = "分配年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "分配年度不能为空") + private Integer distributionYear; + + @Schema(description = "季度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "季度不能为空") + @Min(value = 1, message = "季度必须在 1 到 4 之间") + @Max(value = 4, message = "季度必须在 1 到 4 之间") + private Integer quarterNo; + + @Schema(description = "分配比例", example = "0.2500") + private BigDecimal distributionRatio; + + @Schema(description = "提取进度备注", example = "Q1 提取") + @Size(max = 500, message = "提取进度备注长度不能超过 500 个字符") + private String progressRemark; + +} 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 new file mode 100644 index 0000000..1404bb6 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/ProjectProfitController.java @@ -0,0 +1,47 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.profit; + +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.service.profit.ProjectProfitService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 特建投项目盈亏") +@RestController +@RequestMapping("/tjt/profit") +@Validated +public class ProjectProfitController { + + @Resource + private ProjectProfitService projectProfitService; + + @GetMapping("/get") + @Operation(summary = "获得项目盈亏详情") + @Parameter(name = "projectId", description = "项目 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:profit:query')") + public CommonResult getProjectProfit(@RequestParam("projectId") Long projectId) { + return success(projectProfitService.getProjectProfit(projectId)); + } + + @GetMapping("/page") + @Operation(summary = "获得项目盈亏分页") + @PreAuthorize("@ss.hasPermission('tjt:profit:query')") + public CommonResult> getProjectProfitPage(@Valid ProjectProfitPageReqVO pageReqVO) { + return success(projectProfitService.getProjectProfitPage(pageReqVO)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitPageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitPageReqVO.java new file mode 100644 index 0000000..52d619b --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 项目盈亏分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectProfitPageReqVO extends PageParam { + + @Schema(description = "工程名称,模糊匹配", example = "设计") + private String projectName; + + @Schema(description = "是否签订合同", example = "true") + private Boolean contractSignedFlag; + + @Schema(description = "项目开始年度", example = "2026") + private Integer projectStartYear; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} 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 new file mode 100644 index 0000000..f0a8ff2 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java @@ -0,0 +1,55 @@ +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 ProjectProfitRespVO { + + @Schema(description = "项目 ID", example = "1") + private Long projectId; + + @Schema(description = "工程名称") + private String projectName; + + @Schema(description = "是否签订合同") + private Boolean contractSignedFlag; + + @Schema(description = "合同金额") + private BigDecimal contractAmount; + + @Schema(description = "最终结算金额") + private BigDecimal finalSettlementAmount; + + @Schema(description = "综合所协作金额") + private BigDecimal comprehensivePlanningAmount; + + @Schema(description = "专业分包金额") + private BigDecimal subcontractPlanningAmount; + + @Schema(description = "专业所产值") + private BigDecimal majorOutputValue; + + @Schema(description = "预计 K 值") + private BigDecimal expectedKValue; + + @Schema(description = "专业所预计绩效") + private BigDecimal majorExpectedPerformance; + + @Schema(description = "盈亏值") + private BigDecimal profitLossValue; + + @Schema(description = "盈亏百分比") + private BigDecimal profitLossRate; + + @Schema(description = "项目开始年度") + private Integer projectStartYear; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/ProjectController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/ProjectController.java new file mode 100644 index 0000000..f751d12 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/ProjectController.java @@ -0,0 +1,82 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.project; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.service.project.ProjectService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 特建投项目") +@RestController +@RequestMapping("/tjt/project") +@Validated +public class ProjectController { + + @Resource + private ProjectService projectService; + + @PostMapping("/create") + @Operation(summary = "创建项目") + @PreAuthorize("@ss.hasPermission('tjt:project:create')") + public CommonResult createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) { + return success(projectService.createProject(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改项目") + @PreAuthorize("@ss.hasPermission('tjt:project:update')") + public CommonResult updateProject(@Valid @RequestBody ProjectSaveReqVO updateReqVO) { + projectService.updateProject(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除项目") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:project:delete')") + public CommonResult deleteProject(@RequestParam("id") Long id) { + projectService.deleteProject(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除项目") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('tjt:project:delete')") + public CommonResult deleteProjectList(@RequestParam("ids") List ids) { + projectService.deleteProjectList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得项目分页") + @PreAuthorize("@ss.hasPermission('tjt:project:query')") + public CommonResult> getProjectPage(@Valid ProjectPageReqVO pageReqVO) { + PageResult pageResult = projectService.getProjectPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProjectRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得项目详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:project:query')") + public CommonResult getProject(@RequestParam("id") Long id) { + return success(BeanUtils.toBean(projectService.getProject(id), ProjectRespVO.class)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectPageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectPageReqVO.java new file mode 100644 index 0000000..94a1ed0 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 项目分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectPageReqVO extends PageParam { + + @Schema(description = "工程名称,模糊匹配", example = "设计") + private String projectName; + + @Schema(description = "是否签订合同", example = "true") + private Boolean contractSignedFlag; + + @Schema(description = "项目开始年度", example = "2026") + private Integer projectStartYear; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} 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 new file mode 100644 index 0000000..074262e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java @@ -0,0 +1,85 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 项目 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProjectRespVO { + + private static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd"; + + @Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("项目ID") + private Long id; + + @Schema(description = "工程名称", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("工程名称") + private String projectName; + + @Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否签订合同") + private Boolean contractSignedFlag; + + @Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("合同金额") + private BigDecimal contractAmount; + + @Schema(description = "工程总面积", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("工程总面积") + private BigDecimal totalConstructionArea; + + @Schema(description = "建设单位") + @ExcelProperty("建设单位") + private String constructionUnitName; + + @Schema(description = "联系人") + @ExcelProperty("联系人") + private String contactName; + + @Schema(description = "联系方式") + @ExcelProperty("联系方式") + private String contactPhone; + + @Schema(description = "合同签订日期") + @ExcelProperty("合同签订日期") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate contractSigningDate; + + @Schema(description = "项目经理") + @ExcelProperty("项目经理") + private String projectManagerName; + + @Schema(description = "工程负责人") + @ExcelProperty("工程负责人") + private String engineeringPrincipalName; + + @Schema(description = "工程类型") + @ExcelProperty("工程类型") + private String projectType; + + @Schema(description = "项目开始年度") + @ExcelProperty("项目开始年度") + private Integer projectStartYear; + + @Schema(description = "最终结算金额") + @ExcelProperty("最终结算金额") + private BigDecimal finalSettlementAmount; + + @Schema(description = "预计 K 值") + @ExcelProperty("预计 K 值") + private BigDecimal expectedKValue; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} 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 new file mode 100644 index 0000000..0ae08ad --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java @@ -0,0 +1,79 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; + +import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 项目新增/修改 Request VO") +@Data +public class ProjectSaveReqVO { + + @Schema(description = "项目 ID", example = "1") + private Long id; + + @Schema(description = "工程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX 设计项目") + @NotBlank(message = "工程名称不能为空") + @Size(max = 200, message = "工程名称长度不能超过 200 个字符") + private String projectName; + + @Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否签订合同不能为空") + private Boolean contractSignedFlag; + + @Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000000") + @NotNull(message = "合同金额不能为空") + private BigDecimal contractAmount; + + @Schema(description = "工程总面积", requiredMode = Schema.RequiredMode.REQUIRED, example = "30000") + @NotNull(message = "工程总面积不能为空") + private BigDecimal totalConstructionArea; + + @Schema(description = "建设单位", example = "XX 建设单位") + @Size(max = 200, message = "建设单位长度不能超过 200 个字符") + private String constructionUnitName; + + @Schema(description = "联系人", example = "张三") + @Size(max = 64, message = "联系人长度不能超过 64 个字符") + private String contactName; + + @Schema(description = "联系方式", example = "13800000000") + @Size(max = 32, message = "联系方式长度不能超过 32 个字符") + private String contactPhone; + + @Schema(description = "合同签订日期", example = "2026-04-14") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate contractSigningDate; + + @Schema(description = "项目经理", example = "李四") + @Size(max = 64, message = "项目经理长度不能超过 64 个字符") + private String projectManagerName; + + @Schema(description = "工程负责人", example = "王五") + @Size(max = 64, message = "工程负责人长度不能超过 64 个字符") + private String engineeringPrincipalName; + + @Schema(description = "工程类型", example = "住宅") + @Size(max = 50, message = "工程类型长度不能超过 50 个字符") + private String projectType; + + @Schema(description = "项目开始年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "项目开始年度不能为空") + private Integer projectStartYear; + + @Schema(description = "最终结算金额", example = "1200000") + private BigDecimal finalSettlementAmount; + + @Schema(description = "预计 K 值", example = "0.8500") + private BigDecimal expectedKValue; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/SpecialtyRoleSplitController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/SpecialtyRoleSplitController.java new file mode 100644 index 0000000..a657e81 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/SpecialtyRoleSplitController.java @@ -0,0 +1,45 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitBatchSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitRespVO; +import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 特建投页面5角色比例") +@RestController +@RequestMapping("/tjt/specialty-role-split") +@Validated +public class SpecialtyRoleSplitController { + + @Resource + private SpecialtyRoleSplitService specialtyRoleSplitService; + + @GetMapping("/list-by-planning") + @Operation(summary = "根据合约规划获得页面5角色比例列表") + @Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:specialty-role-split:query')") + public CommonResult> getSpecialtyRoleSplitListByPlanningId(@RequestParam("planningId") Long planningId) { + return success(specialtyRoleSplitService.getSpecialtyRoleSplitListByPlanningId(planningId)); + } + + @PutMapping("/save-batch") + @Operation(summary = "批量保存页面5角色比例") + @PreAuthorize("@ss.hasPermission('tjt:specialty-role-split:update')") + public CommonResult saveSpecialtyRoleSplitBatch(@Valid @RequestBody SpecialtyRoleSplitBatchSaveReqVO reqVO) { + specialtyRoleSplitService.saveSpecialtyRoleSplitBatch(reqVO); + return success(true); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRolePersonRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRolePersonRespVO.java new file mode 100644 index 0000000..1e02e7f --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRolePersonRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 页面5角色人员 Response VO") +@Data +public class SpecialtyRolePersonRespVO { + + @Schema(description = "人员名称") + private String personName; + + @Schema(description = "人员比例") + private BigDecimal personRatio; + + @Schema(description = "人员金额") + private BigDecimal personAmount; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRolePersonSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRolePersonSaveReqVO.java new file mode 100644 index 0000000..ca6104a --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRolePersonSaveReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 页面5角色人员保存 Item Request VO") +@Data +public class SpecialtyRolePersonSaveReqVO { + + @Schema(description = "人员名称", example = "张三") + private String personName; + + @Schema(description = "人员比例", example = "0.1000") + private BigDecimal personRatio; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitBatchSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitBatchSaveReqVO.java new file mode 100644 index 0000000..bb3f8c5 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitBatchSaveReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 页面5角色比例批量保存 Request VO") +@Data +public class SpecialtyRoleSplitBatchSaveReqVO { + + @Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "合约规划 ID 不能为空") + private Long planningId; + + @Schema(description = "角色配置列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "角色配置列表不能为空") + @Valid + private List items; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitRespVO.java new file mode 100644 index 0000000..dee9f2a --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitRespVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 页面5角色比例 Response VO") +@Data +public class SpecialtyRoleSplitRespVO { + + @Schema(description = "角色比例 ID", example = "1") + private Long id; + + @Schema(description = "页面4拆分 ID", example = "1") + private Long outputSplitId; + + @Schema(description = "合同规划 ID", example = "1") + private Long planningId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "规划内容") + private String planningContent; + + @Schema(description = "专业编码") + private String specialtyCode; + + @Schema(description = "专业名称") + private String specialtyName; + + @Schema(description = "专业金额") + private BigDecimal specialtyAmount; + + @Schema(description = "角色编码") + private String roleCode; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "角色比例") + private BigDecimal roleRatio; + + @Schema(description = "角色金额") + private BigDecimal roleAmount; + + @Schema(description = "人员配置列表") + private List persons; + + @Schema(description = "排序号") + private Integer sortNo; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitSaveItemReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitSaveItemReqVO.java new file mode 100644 index 0000000..e9930f4 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitSaveItemReqVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 页面5角色比例保存 Item Request VO") +@Data +public class SpecialtyRoleSplitSaveItemReqVO { + + @Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "arch") + @NotBlank(message = "专业编码不能为空") + private String specialtyCode; + + @Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "director") + @NotBlank(message = "角色编码不能为空") + private String roleCode; + + @Schema(description = "角色比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000") + @NotNull(message = "角色比例不能为空") + private BigDecimal roleRatio; + + @Schema(description = "人员配置列表") + @Valid + private List persons; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/outputsplit/ProjectOutputSplitDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/outputsplit/ProjectOutputSplitDO.java new file mode 100644 index 0000000..f139657 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/outputsplit/ProjectOutputSplitDO.java @@ -0,0 +1,52 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit; + +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; + +/** + * 页面 4 拆分比例 DO + * + * @author Codex + */ +@TableName("tjt_project_output_split") +@KeySequence("tjt_project_output_split_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectOutputSplitDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long projectId; + + private Long planningId; + + private Integer year; + + private BigDecimal projectManagerRatio; + + private BigDecimal engineeringLeaderRatio; + + private BigDecimal officeRatio; + + private BigDecimal archRatio; + + private BigDecimal decorRatio; + + private BigDecimal structRatio; + + private BigDecimal waterRatio; + + private BigDecimal elecRatio; + + private BigDecimal hvacRatio; + + private BigDecimal digitalRatio; + +} 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 new file mode 100644 index 0000000..1c8771c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java @@ -0,0 +1,94 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning; + +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; + +/** + * 合约规划 DO + * + * @author Codex + */ +@TableName("tjt_project_planning") +@KeySequence("tjt_project_planning_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectPlanningDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long projectId; + + private String ownershipType; + + private String calculationMethod; + + private String planningContent; + + private BigDecimal planningAmount; + + private BigDecimal managementFeeRate; + + private BigDecimal managementFee; + + private String implementationTeam; + + private Integer planningStartYear; + + private BigDecimal planningArea; + + private String designStage; + + private BigDecimal currentDesignStageRatio; + + private Boolean reviewOutsourceFlag; + + private BigDecimal reviewOutsourceRatio; + + private BigDecimal totalDistributionAmount; + + private String progressRemark; + + private Integer buildingOrUnitCount; + + private BigDecimal drawingSetFactor; + + private BigDecimal scaleFactor; + + private BigDecimal modificationFactor; + + private BigDecimal complexityFactor; + + private BigDecimal internalGuidanceUnitPrice; + + private String virtualCalculationMethod; + + private BigDecimal workingDayCount; + + private BigDecimal workingDayUnitPrice; + + private BigDecimal guidanceUnitPrice; + + private BigDecimal guidanceTotalPrice; + + private BigDecimal virtualTotalPrice; + + private BigDecimal calculationRatio; + + private BigDecimal contractUnitPrice; + + private BigDecimal totalAdjustmentFactor; + + private BigDecimal assessmentArea; + + private BigDecimal virtualOutputValue; + + private BigDecimal assessmentOutputValue; + +} 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 new file mode 100644 index 0000000..0698c14 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planningquarter/ProjectPlanningQuarterDO.java @@ -0,0 +1,36 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter; + +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; + +/** + * 合约规划季度分配 DO + * + * @author Codex + */ +@TableName("tjt_project_planning_quarter") +@KeySequence("tjt_project_planning_quarter_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectPlanningQuarterDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long planningId; + + private Integer distributionYear; + + private Integer quarterNo; + + private BigDecimal distributionRatio; + + private BigDecimal distributionAmount; + +} 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 new file mode 100644 index 0000000..b0cf649 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java @@ -0,0 +1,86 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.project; + +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.LocalDate; + +/** + * 项目 DO + * + * @author Codex + */ +@TableName("tjt_project") +@KeySequence("tjt_project_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectDO extends TenantBaseDO { + + /** + * 主键 ID + */ + @TableId + private Long id; + /** + * 工程名称 + */ + private String projectName; + /** + * 是否签订合同 + */ + private Boolean contractSignedFlag; + /** + * 合同金额 + */ + private BigDecimal contractAmount; + /** + * 工程总面积 + */ + private BigDecimal totalConstructionArea; + /** + * 建设单位 + */ + private String constructionUnitName; + /** + * 联系人 + */ + private String contactName; + /** + * 联系方式 + */ + private String contactPhone; + /** + * 合同签订日期 + */ + private LocalDate contractSigningDate; + /** + * 项目经理 + */ + private String projectManagerName; + /** + * 工程负责人 + */ + private String engineeringPrincipalName; + /** + * 工程类型 + */ + private String projectType; + /** + * 项目开始年度 + */ + private Integer projectStartYear; + /** + * 最终结算金额 + */ + private BigDecimal finalSettlementAmount; + /** + * 预计 K 值 + */ + private BigDecimal expectedKValue; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/specialtyrolesplit/SpecialtyRoleSplitDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/specialtyrolesplit/SpecialtyRoleSplitDO.java new file mode 100644 index 0000000..d1c1f5e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/specialtyrolesplit/SpecialtyRoleSplitDO.java @@ -0,0 +1,42 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.specialtyrolesplit; + +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; + +/** + * 页面 5 角色比例 DO + * + * @author Codex + */ +@TableName("tjt_specialty_role_split") +@KeySequence("tjt_specialty_role_split_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class SpecialtyRoleSplitDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long outputSplitId; + + private String specialtyCode; + + private String specialtyName; + + private String roleCode; + + private String roleName; + + private BigDecimal roleRatio; + + private String personNames; + + private Integer sortNo; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/outputsplit/ProjectOutputSplitMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/outputsplit/ProjectOutputSplitMapper.java new file mode 100644 index 0000000..419f8ad --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/outputsplit/ProjectOutputSplitMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.outputsplit; + +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.outputsplit.ProjectOutputSplitDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 页面 4 拆分比例 Mapper + * + * @author Codex + */ +@Mapper +public interface ProjectOutputSplitMapper extends BaseMapperX { + + default ProjectOutputSplitDO selectByPlanningId(Long planningId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ProjectOutputSplitDO::getPlanningId, planningId)); + } + + default List selectListByPlanningIds(Collection planningIds) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(ProjectOutputSplitDO::getPlanningId, planningIds)); + } + +} 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 new file mode 100644 index 0000000..7718c89 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planning/ProjectPlanningMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.planning; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningPageReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 合约规划 Mapper + * + * @author Codex + */ +@Mapper +public interface ProjectPlanningMapper extends BaseMapperX { + + default PageResult selectPage(ProjectPlanningPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ProjectPlanningDO::getProjectId, reqVO.getProjectId()) + .eqIfPresent(ProjectPlanningDO::getOwnershipType, reqVO.getOwnershipType()) + .eqIfPresent(ProjectPlanningDO::getCalculationMethod, reqVO.getCalculationMethod()) + .likeIfPresent(ProjectPlanningDO::getPlanningContent, reqVO.getPlanningContent()) + .eqIfPresent(ProjectPlanningDO::getPlanningStartYear, reqVO.getPlanningStartYear()) + .betweenIfPresent(ProjectPlanningDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ProjectPlanningDO::getId)); + } + + default List selectListByProjectId(Long projectId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectPlanningDO::getProjectId, projectId) + .orderByDesc(ProjectPlanningDO::getId)); + } + +} 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 new file mode 100644 index 0000000..526b8b8 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/planningquarter/ProjectPlanningQuarterMapper.java @@ -0,0 +1,47 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter; + +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.planningquarter.ProjectPlanningQuarterDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 合约规划季度分配 Mapper + * + * @author Codex + */ +@Mapper +public interface ProjectPlanningQuarterMapper extends BaseMapperX { + + default ProjectPlanningQuarterDO selectByPlanningIdAndDistributionYearAndQuarter(Long planningId, + Integer distributionYear, + Integer quarterNo) { + return selectOne(ProjectPlanningQuarterDO::getPlanningId, planningId, + ProjectPlanningQuarterDO::getDistributionYear, distributionYear, + ProjectPlanningQuarterDO::getQuarterNo, quarterNo); + } + + default List selectListByPlanningId(Long planningId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectPlanningQuarterDO::getPlanningId, planningId) + .orderByAsc(ProjectPlanningQuarterDO::getDistributionYear) + .orderByAsc(ProjectPlanningQuarterDO::getQuarterNo) + .orderByAsc(ProjectPlanningQuarterDO::getId)); + } + + default List selectListByPlanningIds(Collection planningIds) { + if (planningIds == null || planningIds.isEmpty()) { + return Collections.emptyList(); + } + return selectList(new LambdaQueryWrapperX() + .in(ProjectPlanningQuarterDO::getPlanningId, planningIds) + .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/project/ProjectMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java new file mode 100644 index 0000000..5eac064 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java @@ -0,0 +1,27 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.project; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 项目 Mapper + * + * @author Codex + */ +@Mapper +public interface ProjectMapper extends BaseMapperX { + + default PageResult selectPage(ProjectPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ProjectDO::getProjectName, reqVO.getProjectName()) + .eqIfPresent(ProjectDO::getContractSignedFlag, reqVO.getContractSignedFlag()) + .eqIfPresent(ProjectDO::getProjectStartYear, reqVO.getProjectStartYear()) + .betweenIfPresent(ProjectDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ProjectDO::getId)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/specialtyrolesplit/SpecialtyRoleSplitMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/specialtyrolesplit/SpecialtyRoleSplitMapper.java new file mode 100644 index 0000000..1a12a9f --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/specialtyrolesplit/SpecialtyRoleSplitMapper.java @@ -0,0 +1,25 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.specialtyrolesplit; + +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.specialtyrolesplit.SpecialtyRoleSplitDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 页面 5 角色比例 Mapper + * + * @author Codex + */ +@Mapper +public interface SpecialtyRoleSplitMapper extends BaseMapperX { + + default List selectListByOutputSplitId(Long outputSplitId) { + return selectList(new LambdaQueryWrapperX() + .eq(SpecialtyRoleSplitDO::getOutputSplitId, outputSplitId) + .orderByAsc(SpecialtyRoleSplitDO::getSortNo) + .orderByAsc(SpecialtyRoleSplitDO::getId)); + } + +} 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 new file mode 100644 index 0000000..0d085fe --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java @@ -0,0 +1,43 @@ +package cn.iocoder.lyzsys.module.tjt.enums; + +import cn.iocoder.lyzsys.framework.common.exception.ErrorCode; + +/** + * 特建投模块错误码 + * + * @author Codex + */ +public interface ErrorCodeConstants { + + // ========== 项目管理 1-020-001-000 ========== + ErrorCode PROJECT_NOT_EXISTS = new ErrorCode(1_020_001_000, "项目不存在"); + + // ========== 合约规划管理 1-020-002-000 ========== + ErrorCode PROJECT_PLANNING_NOT_EXISTS = new ErrorCode(1_020_002_000, "合约规划不存在"); + ErrorCode PROJECT_PLANNING_PROJECT_NOT_EXISTS = new ErrorCode(1_020_002_001, "关联项目不存在"); + ErrorCode PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID = new ErrorCode(1_020_002_002, "归属类型不正确"); + ErrorCode PROJECT_PLANNING_CALCULATION_METHOD_INVALID = new ErrorCode(1_020_002_003, "产值计算方式不正确"); + ErrorCode PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE = new ErrorCode(1_020_002_004, "归属类型保存后不允许修改"); + ErrorCode PROJECT_PLANNING_CALCULATION_METHOD_IMMUTABLE = new ErrorCode(1_020_002_005, "产值计算方式保存后不允许修改"); + ErrorCode PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID = new ErrorCode(1_020_002_006, "虚拟产值计算方式不正确"); + + // ========== 季度分配管理 1-020-003-000 ========== + 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, "关联合约规划不存在"); + + // ========== 页面 4 拆分管理 1-020-004-000 ========== + ErrorCode PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS = new ErrorCode(1_020_004_000, "关联合约规划不存在"); + ErrorCode PROJECT_OUTPUT_SPLIT_NOT_MAJOR = new ErrorCode(1_020_004_001, "仅专业所记录允许进行页面4拆分"); + ErrorCode PROJECT_OUTPUT_SPLIT_RATIO_INVALID = new ErrorCode(1_020_004_002, "页面4比例合计必须等于 100%"); + + // ========== 页面 5 角色拆分管理 1-020-005-000 ========== + ErrorCode SPECIALTY_ROLE_SPLIT_OUTPUT_SPLIT_NOT_EXISTS = new ErrorCode(1_020_005_000, "页面4拆分记录不存在"); + ErrorCode SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID = new ErrorCode(1_020_005_001, "页面5五类角色比例合计必须等于 100%"); + ErrorCode SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID = new ErrorCode(1_020_005_002, "专业编码不正确"); + ErrorCode SPECIALTY_ROLE_SPLIT_ROLE_INVALID = new ErrorCode(1_020_005_003, "角色编码不正确"); + ErrorCode SPECIALTY_ROLE_SPLIT_PERSON_INVALID = new ErrorCode(1_020_005_004, "页面5人员名称和比例必须同时填写"); + ErrorCode SPECIALTY_ROLE_SPLIT_DESIGN_PERSON_REQUIRED = new ErrorCode(1_020_005_005, "页面5设计角色金额大于 0 时必须配置人员"); + ErrorCode SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID = new ErrorCode(1_020_005_007, "页面5同一角色下人员比例之和不能大于 100%"); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/OutputSplitBizConstants.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/OutputSplitBizConstants.java new file mode 100644 index 0000000..e95c97b --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/OutputSplitBizConstants.java @@ -0,0 +1,125 @@ +package cn.iocoder.lyzsys.module.tjt.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 页面 4 / 页面 5 业务常量 + * + * @author Codex + */ +public final class OutputSplitBizConstants { + + public static final String OWNERSHIP_TYPE_MAJOR = "专业所"; + + public static final String SPECIALTY_ARCH = "arch"; + public static final String SPECIALTY_DECOR = "decor"; + public static final String SPECIALTY_STRUCT = "struct"; + public static final String SPECIALTY_WATER = "water"; + public static final String SPECIALTY_ELEC = "elec"; + public static final String SPECIALTY_HVAC = "hvac"; + public static final String SPECIALTY_DIGITAL = "digital"; + + public static final String ROLE_DIRECTOR = "director"; + public static final String ROLE_CHECK = "check"; + public static final String ROLE_REVIEW = "review"; + public static final String ROLE_APPROVE = "approve"; + public static final String ROLE_DESIGN = "design"; + + public static final List SPECIALTY_ITEMS = Arrays.asList( + new SpecialtyItem(SPECIALTY_ARCH, "建筑", 1), + new SpecialtyItem(SPECIALTY_DECOR, "装修", 2), + new SpecialtyItem(SPECIALTY_STRUCT, "结构", 3), + new SpecialtyItem(SPECIALTY_WATER, "水", 4), + new SpecialtyItem(SPECIALTY_ELEC, "电气", 5), + new SpecialtyItem(SPECIALTY_HVAC, "暖通", 6), + new SpecialtyItem(SPECIALTY_DIGITAL, "数字化设计", 7) + ); + + public static final List ROLE_ITEMS = Arrays.asList( + new RoleItem(ROLE_DIRECTOR, "专业负责人", 1), + new RoleItem(ROLE_CHECK, "校对", 2), + new RoleItem(ROLE_REVIEW, "审核", 3), + new RoleItem(ROLE_APPROVE, "审定", 4), + new RoleItem(ROLE_DESIGN, "设计", 5) + ); + + private OutputSplitBizConstants() { + } + + public static boolean isMajorOwnershipType(String value) { + return OWNERSHIP_TYPE_MAJOR.equals(value); + } + + public static boolean isValidSpecialtyCode(String code) { + return SPECIALTY_ITEMS.stream().anyMatch(item -> item.getCode().equals(code)); + } + + public static boolean isValidRoleCode(String code) { + return ROLE_ITEMS.stream().anyMatch(item -> item.getCode().equals(code)); + } + + public static String getSpecialtyName(String code) { + return SPECIALTY_ITEMS.stream() + .filter(item -> item.getCode().equals(code)) + .findFirst() + .map(SpecialtyItem::getName) + .orElse(code); + } + + public static String getRoleName(String code) { + return ROLE_ITEMS.stream() + .filter(item -> item.getCode().equals(code)) + .findFirst() + .map(RoleItem::getName) + .orElse(code); + } + + public static int getRoleSortNo(String code) { + return ROLE_ITEMS.stream() + .filter(item -> item.getCode().equals(code)) + .findFirst() + .map(RoleItem::getSortNo) + .orElse(0); + } + + public static int getSpecialtySortNo(String code) { + return SPECIALTY_ITEMS.stream() + .filter(item -> item.getCode().equals(code)) + .findFirst() + .map(SpecialtyItem::getSortNo) + .orElse(0); + } + + public static Map buildSpecialtyMap() { + Map map = new LinkedHashMap<>(); + for (SpecialtyItem item : SPECIALTY_ITEMS) { + map.put(item.getCode(), item.getName()); + } + return map; + } + + @Getter + @AllArgsConstructor + public static class SpecialtyItem { + + private final String code; + private final String name; + private final Integer sortNo; + } + + @Getter + @AllArgsConstructor + public static class RoleItem { + + private final String code; + private final String name; + private final Integer sortNo; + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ProjectPlanningBizTypeConstants.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ProjectPlanningBizTypeConstants.java new file mode 100644 index 0000000..f77e8cd --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ProjectPlanningBizTypeConstants.java @@ -0,0 +1,83 @@ +package cn.iocoder.lyzsys.module.tjt.enums; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * 合约规划业务类型常量 + * + * @author Codex + */ +public final class ProjectPlanningBizTypeConstants { + + public static final String OWNERSHIP_TYPE_MAJOR = "专业所"; + public static final String OWNERSHIP_TYPE_COMPREHENSIVE = "综合所"; + public static final String OWNERSHIP_TYPE_SUBCONTRACT = "专业分包"; + + public static final String CALCULATION_METHOD_GUIDANCE_PRICE = "指导价法"; + public static final String CALCULATION_METHOD_CONTRACT_PRICE = "合同价法"; + public static final String CALCULATION_METHOD_VIRTUAL_OUTPUT = "虚拟产值法"; + + public static final String VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE = "指导单价法"; + public static final String VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE = "虚拟总价法"; + public static final String VIRTUAL_CALCULATION_METHOD_WORKING_DAY = "工日法"; + + private static final Set OWNERSHIP_TYPES = new HashSet<>(Arrays.asList( + OWNERSHIP_TYPE_MAJOR, + OWNERSHIP_TYPE_COMPREHENSIVE, + OWNERSHIP_TYPE_SUBCONTRACT + )); + + private static final Set CALCULATION_METHODS = new HashSet<>(Arrays.asList( + CALCULATION_METHOD_GUIDANCE_PRICE, + CALCULATION_METHOD_CONTRACT_PRICE, + CALCULATION_METHOD_VIRTUAL_OUTPUT + )); + + private static final Set VIRTUAL_CALCULATION_METHODS = new HashSet<>(Arrays.asList( + VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE, + VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE, + VIRTUAL_CALCULATION_METHOD_WORKING_DAY + )); + + private ProjectPlanningBizTypeConstants() { + } + + public static boolean isValidOwnershipType(String value) { + return OWNERSHIP_TYPES.contains(value); + } + + public static boolean isValidCalculationMethod(String value) { + return CALCULATION_METHODS.contains(value); + } + + public static boolean isValidVirtualCalculationMethod(String value) { + return VIRTUAL_CALCULATION_METHODS.contains(value); + } + + public static boolean isMajor(String value) { + return OWNERSHIP_TYPE_MAJOR.equals(value); + } + + public static boolean isComprehensive(String value) { + return OWNERSHIP_TYPE_COMPREHENSIVE.equals(value); + } + + public static boolean isSubcontract(String value) { + return OWNERSHIP_TYPE_SUBCONTRACT.equals(value); + } + + public static boolean isGuidancePrice(String value) { + return CALCULATION_METHOD_GUIDANCE_PRICE.equals(value); + } + + public static boolean isContractPrice(String value) { + return CALCULATION_METHOD_CONTRACT_PRICE.equals(value); + } + + public static boolean isVirtualOutput(String value) { + return CALCULATION_METHOD_VIRTUAL_OUTPUT.equals(value); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/outputsplit/ProjectOutputSplitService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/outputsplit/ProjectOutputSplitService.java new file mode 100644 index 0000000..cd30027 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/outputsplit/ProjectOutputSplitService.java @@ -0,0 +1,32 @@ +package cn.iocoder.lyzsys.module.tjt.service.outputsplit; + +import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Map; + +/** + * 页面4拆分比例 Service 接口 + * + * @author Codex + */ +public interface ProjectOutputSplitService { + + ProjectOutputSplitRespVO getProjectOutputSplit(Long planningId); + + Long saveProjectOutputSplit(ProjectOutputSplitSaveReqVO reqVO); + + ProjectOutputSplitDO getOrCreateProjectOutputSplit(Long planningId); + + Map getProjectOutputSplitMap(Collection planningIds); + + BigDecimal getSpecialtyAmount(ProjectOutputSplitDO outputSplit, BigDecimal assessmentOutputValue, String specialtyCode); + + void deleteByPlanningId(Long planningId); + + void deleteByPlanningIds(Collection planningIds); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/outputsplit/ProjectOutputSplitServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/outputsplit/ProjectOutputSplitServiceImpl.java new file mode 100644 index 0000000..0cc1654 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/outputsplit/ProjectOutputSplitServiceImpl.java @@ -0,0 +1,269 @@ +package cn.iocoder.lyzsys.module.tjt.service.outputsplit; + +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; +import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.outputsplit.ProjectOutputSplitMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; +import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.function.Consumer; +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_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_NOT_MAJOR; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_RATIO_INVALID; + +/** + * 页面4拆分比例 Service 实现类 + * + * @author Codex + */ +@Service +@Validated +public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService { + + private static final int RATIO_SCALE = 4; + private static final int AMOUNT_SCALE = 2; + private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + + @Resource + private ProjectOutputSplitMapper projectOutputSplitMapper; + @Resource + private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectMapper projectMapper; + + @Override + public ProjectOutputSplitRespVO getProjectOutputSplit(Long planningId) { + ProjectPlanningDO planning = validateMajorPlanning(planningId); + ProjectDO project = validateProjectExists(planning.getProjectId()); + ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId); + if (outputSplit == null) { + outputSplit = buildDefaultOutputSplit(planning); + } + return buildRespVO(outputSplit, planning, project); + } + + @Override + public Long saveProjectOutputSplit(ProjectOutputSplitSaveReqVO reqVO) { + ProjectPlanningDO planning = validateMajorPlanning(reqVO.getPlanningId()); + validateOutputSplitRatios(reqVO); + ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(reqVO.getPlanningId()); + if (outputSplit == null) { + outputSplit = BeanUtils.toBean(reqVO, ProjectOutputSplitDO.class); + outputSplit.setProjectId(planning.getProjectId()); + outputSplit.setYear(getPlanningYear(planning)); + normalizeRatios(outputSplit); + projectOutputSplitMapper.insert(outputSplit); + } else { + ProjectOutputSplitDO updateObj = BeanUtils.toBean(reqVO, ProjectOutputSplitDO.class); + updateObj.setId(outputSplit.getId()); + updateObj.setProjectId(planning.getProjectId()); + updateObj.setYear(getPlanningYear(planning)); + normalizeRatios(updateObj); + projectOutputSplitMapper.updateById(updateObj); + outputSplit = updateObj; + } + return outputSplit.getId(); + } + + @Override + public ProjectOutputSplitDO getOrCreateProjectOutputSplit(Long planningId) { + ProjectPlanningDO planning = validateMajorPlanning(planningId); + ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId); + if (outputSplit != null) { + return outputSplit; + } + ProjectOutputSplitDO createObj = buildDefaultOutputSplit(planning); + projectOutputSplitMapper.insert(createObj); + return createObj; + } + + @Override + public Map getProjectOutputSplitMap(Collection planningIds) { + if (planningIds == null || planningIds.isEmpty()) { + return Collections.emptyMap(); + } + return projectOutputSplitMapper.selectListByPlanningIds(planningIds).stream() + .collect(Collectors.toMap(ProjectOutputSplitDO::getPlanningId, item -> item, (a, b) -> b)); + } + + @Override + public BigDecimal getSpecialtyAmount(ProjectOutputSplitDO outputSplit, BigDecimal assessmentOutputValue, String specialtyCode) { + if (outputSplit == null) { + return ZERO_AMOUNT; + } + BigDecimal officeAmount = multiplyAmount(amount(assessmentOutputValue), ratio(outputSplit.getOfficeRatio())); + return multiplyAmount(officeAmount, getSpecialtyRatio(outputSplit, specialtyCode)); + } + + @Override + public void deleteByPlanningId(Long planningId) { + ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId); + if (outputSplit != null) { + projectOutputSplitMapper.deleteById(outputSplit.getId()); + } + } + + @Override + public void deleteByPlanningIds(Collection planningIds) { + if (planningIds == null || planningIds.isEmpty()) { + return; + } + projectOutputSplitMapper.delete(new cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX() + .in(ProjectOutputSplitDO::getPlanningId, planningIds)); + } + + private ProjectOutputSplitRespVO buildRespVO(ProjectOutputSplitDO outputSplit, ProjectPlanningDO planning, ProjectDO project) { + ProjectOutputSplitRespVO respVO = BeanUtils.toBean(outputSplit, ProjectOutputSplitRespVO.class); + BigDecimal assessmentOutputValue = amount(planning.getAssessmentOutputValue()); + BigDecimal projectManagerAmount = multiplyAmount(assessmentOutputValue, outputSplit.getProjectManagerRatio()); + BigDecimal engineeringLeaderAmount = multiplyAmount(assessmentOutputValue, outputSplit.getEngineeringLeaderRatio()); + BigDecimal officeAmount = multiplyAmount(assessmentOutputValue, outputSplit.getOfficeRatio()); + + respVO.setProjectName(project.getProjectName()); + respVO.setPlanningContent(planning.getPlanningContent()); + respVO.setYear(getPlanningYear(planning)); + respVO.setAssessmentOutputValue(assessmentOutputValue); + respVO.setProjectManagerName(project.getProjectManagerName()); + respVO.setEngineeringLeaderName(project.getEngineeringPrincipalName()); + respVO.setProjectManagerAmount(projectManagerAmount); + respVO.setEngineeringLeaderAmount(engineeringLeaderAmount); + respVO.setOfficeAmount(officeAmount); + respVO.setArchAmount(multiplyAmount(officeAmount, outputSplit.getArchRatio())); + respVO.setDecorAmount(multiplyAmount(officeAmount, outputSplit.getDecorRatio())); + respVO.setStructAmount(multiplyAmount(officeAmount, outputSplit.getStructRatio())); + respVO.setWaterAmount(multiplyAmount(officeAmount, outputSplit.getWaterRatio())); + respVO.setElecAmount(multiplyAmount(officeAmount, outputSplit.getElecRatio())); + respVO.setHvacAmount(multiplyAmount(officeAmount, outputSplit.getHvacRatio())); + respVO.setDigitalAmount(multiplyAmount(officeAmount, outputSplit.getDigitalRatio())); + return respVO; + } + + private ProjectPlanningDO validateMajorPlanning(Long planningId) { + ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId); + if (planning == null) { + throw exception(PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS); + } + if (!OutputSplitBizConstants.isMajorOwnershipType(planning.getOwnershipType())) { + throw exception(PROJECT_OUTPUT_SPLIT_NOT_MAJOR); + } + return planning; + } + + private ProjectDO validateProjectExists(Long projectId) { + ProjectDO project = projectMapper.selectById(projectId); + if (project == null) { + throw exception(PROJECT_NOT_EXISTS); + } + return project; + } + + private ProjectOutputSplitDO buildDefaultOutputSplit(ProjectPlanningDO planning) { + ProjectOutputSplitDO outputSplit = new ProjectOutputSplitDO(); + outputSplit.setProjectId(planning.getProjectId()); + outputSplit.setPlanningId(planning.getId()); + outputSplit.setYear(getPlanningYear(planning)); + outputSplit.setProjectManagerRatio(ZERO_RATIO); + outputSplit.setEngineeringLeaderRatio(ZERO_RATIO); + outputSplit.setOfficeRatio(ONE_RATIO); + outputSplit.setArchRatio(ONE_RATIO); + outputSplit.setDecorRatio(ZERO_RATIO); + outputSplit.setStructRatio(ZERO_RATIO); + outputSplit.setWaterRatio(ZERO_RATIO); + outputSplit.setElecRatio(ZERO_RATIO); + outputSplit.setHvacRatio(ZERO_RATIO); + outputSplit.setDigitalRatio(ZERO_RATIO); + return outputSplit; + } + + private Integer getPlanningYear(ProjectPlanningDO planning) { + return planning.getPlanningStartYear() == null ? 0 : planning.getPlanningStartYear(); + } + + private void validateOutputSplitRatios(ProjectOutputSplitSaveReqVO reqVO) { + BigDecimal projectTotal = ratio(reqVO.getProjectManagerRatio()) + .add(ratio(reqVO.getEngineeringLeaderRatio())) + .add(ratio(reqVO.getOfficeRatio())) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP); + BigDecimal specialtyTotal = ratio(reqVO.getArchRatio()) + .add(ratio(reqVO.getDecorRatio())) + .add(ratio(reqVO.getStructRatio())) + .add(ratio(reqVO.getWaterRatio())) + .add(ratio(reqVO.getElecRatio())) + .add(ratio(reqVO.getHvacRatio())) + .add(ratio(reqVO.getDigitalRatio())) + .setScale(RATIO_SCALE, RoundingMode.HALF_UP); + if (projectTotal.compareTo(ONE_RATIO) > 0 || specialtyTotal.compareTo(ONE_RATIO) != 0) { + throw exception(PROJECT_OUTPUT_SPLIT_RATIO_INVALID); + } + } + + private void normalizeRatios(ProjectOutputSplitDO outputSplit) { + normalize(outputSplit::setProjectManagerRatio, outputSplit.getProjectManagerRatio()); + normalize(outputSplit::setEngineeringLeaderRatio, outputSplit.getEngineeringLeaderRatio()); + normalize(outputSplit::setOfficeRatio, outputSplit.getOfficeRatio()); + normalize(outputSplit::setArchRatio, outputSplit.getArchRatio()); + normalize(outputSplit::setDecorRatio, outputSplit.getDecorRatio()); + normalize(outputSplit::setStructRatio, outputSplit.getStructRatio()); + normalize(outputSplit::setWaterRatio, outputSplit.getWaterRatio()); + normalize(outputSplit::setElecRatio, outputSplit.getElecRatio()); + normalize(outputSplit::setHvacRatio, outputSplit.getHvacRatio()); + normalize(outputSplit::setDigitalRatio, outputSplit.getDigitalRatio()); + } + + private void normalize(Consumer consumer, BigDecimal value) { + consumer.accept(ratio(value)); + } + + private BigDecimal getSpecialtyRatio(ProjectOutputSplitDO outputSplit, String specialtyCode) { + switch (specialtyCode) { + case OutputSplitBizConstants.SPECIALTY_ARCH: + return ratio(outputSplit.getArchRatio()); + case OutputSplitBizConstants.SPECIALTY_DECOR: + return ratio(outputSplit.getDecorRatio()); + case OutputSplitBizConstants.SPECIALTY_STRUCT: + return ratio(outputSplit.getStructRatio()); + case OutputSplitBizConstants.SPECIALTY_WATER: + return ratio(outputSplit.getWaterRatio()); + case OutputSplitBizConstants.SPECIALTY_ELEC: + return ratio(outputSplit.getElecRatio()); + case OutputSplitBizConstants.SPECIALTY_HVAC: + return ratio(outputSplit.getHvacRatio()); + case OutputSplitBizConstants.SPECIALTY_DIGITAL: + return ratio(outputSplit.getDigitalRatio()); + default: + return ZERO_RATIO; + } + } + + private BigDecimal multiplyAmount(BigDecimal left, BigDecimal right) { + return amount(left).multiply(ratio(right)).setScale(AMOUNT_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/service/planning/ProjectPlanningService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningService.java new file mode 100644 index 0000000..77a6f6c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningService.java @@ -0,0 +1,36 @@ +package cn.iocoder.lyzsys.module.tjt.service.planning; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 合约规划 Service 接口 + * + * @author Codex + */ +public interface ProjectPlanningService { + + Long createProjectPlanning(ProjectPlanningSaveReqVO createReqVO); + + void updateProjectPlanning(ProjectPlanningSaveReqVO updateReqVO); + + void deleteProjectPlanning(Long id); + + void deleteProjectPlanningList(List ids); + + PageResult getProjectPlanningPage(ProjectPlanningPageReqVO pageReqVO); + + List getProjectPlanningListByProjectId(Long projectId); + + ProjectPlanningDO getProjectPlanning(Long id); + + Map getAllocatedAmountMap(Collection planningIds); + +} 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 new file mode 100644 index 0000000..fb3a85d --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java @@ -0,0 +1,438 @@ +package cn.iocoder.lyzsys.module.tjt.service.planning; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; +import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService; +import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService; +import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_CALCULATION_METHOD_IMMUTABLE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_CALCULATION_METHOD_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_PROJECT_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID; + +/** + * 合约规划 Service 实现类 + * + * @author Codex + */ +@Service +@Validated +public class ProjectPlanningServiceImpl implements ProjectPlanningService { + + private static final int AMOUNT_SCALE = 2; + private static final int RATIO_SCALE = 4; + private static final int FACTOR_SCALE = 2; + + 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); + private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + private static final BigDecimal DEFAULT_COMPREHENSIVE_RATIO = new BigDecimal("0.0800"); + private static final BigDecimal DEFAULT_SUBCONTRACT_RATIO = new BigDecimal("0.0400"); + private static final BigDecimal DEFAULT_MAJOR_OUTSOURCE_RATIO = new BigDecimal("0.0600"); + private static final BigDecimal DEFAULT_COMMON_OUTSOURCE_RATIO = new BigDecimal("0.2500"); + private static final BigDecimal DEFAULT_WORKING_DAY_UNIT_PRICE = new BigDecimal("1000.00"); + private static final BigDecimal MIN_GUIDANCE_PRICE_RATIO = new BigDecimal("0.6000"); + + @Resource + private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectMapper projectMapper; + @Resource + private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; + @Resource + private ProjectOutputSplitService projectOutputSplitService; + @Resource + private SpecialtyRoleSplitService specialtyRoleSplitService; + + @Override + public Long createProjectPlanning(ProjectPlanningSaveReqVO createReqVO) { + ProjectDO project = validateProjectExists(createReqVO.getProjectId()); + validateProjectPlanningForSave(createReqVO, null); + ProjectPlanningDO planning = BeanUtils.toBean(createReqVO, ProjectPlanningDO.class); + prepareProjectPlanning(planning, project); + calculateProjectPlanning(planning); + projectPlanningMapper.insert(planning); + return planning.getId(); + } + + @Override + public void updateProjectPlanning(ProjectPlanningSaveReqVO updateReqVO) { + ProjectPlanningDO dbPlanning = validateProjectPlanningExists(updateReqVO.getId()); + ProjectDO project = validateProjectExists(updateReqVO.getProjectId()); + validateProjectPlanningForSave(updateReqVO, dbPlanning); + ProjectPlanningDO updateObj = BeanUtils.toBean(updateReqVO, ProjectPlanningDO.class); + prepareProjectPlanning(updateObj, project); + calculateProjectPlanning(updateObj); + projectPlanningMapper.updateById(updateObj); + refreshQuarterDistributionAmounts(updateObj); + } + + @Override + public void deleteProjectPlanning(Long id) { + validateProjectPlanningExists(id); + ProjectOutputSplitDO outputSplit = projectOutputSplitService.getProjectOutputSplitMap(Collections.singleton(id)).get(id); + if (outputSplit != null) { + specialtyRoleSplitService.deleteByOutputSplitId(outputSplit.getId()); + projectOutputSplitService.deleteByPlanningId(id); + } + projectPlanningQuarterMapper.delete(ProjectPlanningQuarterDO::getPlanningId, id); + projectPlanningMapper.deleteById(id); + } + + @Override + public void deleteProjectPlanningList(List ids) { + ids.forEach(this::validateProjectPlanningExists); + Map outputSplitMap = projectOutputSplitService.getProjectOutputSplitMap(ids); + if (!outputSplitMap.isEmpty()) { + specialtyRoleSplitService.deleteByOutputSplitIds(outputSplitMap.values().stream() + .map(ProjectOutputSplitDO::getId) + .collect(java.util.stream.Collectors.toList())); + projectOutputSplitService.deleteByPlanningIds(ids); + } + projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, ids); + projectPlanningMapper.deleteBatchIds(ids); + } + + @Override + public PageResult getProjectPlanningPage(ProjectPlanningPageReqVO pageReqVO) { + if (pageReqVO.getProjectId() != null) { + validateProjectExists(pageReqVO.getProjectId()); + } + return projectPlanningMapper.selectPage(pageReqVO); + } + + @Override + public List getProjectPlanningListByProjectId(Long projectId) { + validateProjectExists(projectId); + return projectPlanningMapper.selectListByProjectId(projectId); + } + + @Override + public ProjectPlanningDO getProjectPlanning(Long id) { + return projectPlanningMapper.selectById(id); + } + + @Override + public Map getAllocatedAmountMap(Collection planningIds) { + if (planningIds == null || planningIds.isEmpty()) { + return Collections.emptyMap(); + } + List quarterList = projectPlanningQuarterMapper.selectListByPlanningIds(planningIds); + Map allocatedAmountMap = new HashMap<>(); + for (Long planningId : planningIds) { + allocatedAmountMap.put(planningId, ZERO_RATIO); + } + for (ProjectPlanningQuarterDO quarter : quarterList) { + allocatedAmountMap.merge(quarter.getPlanningId(), ratio(quarter.getDistributionRatio()), BigDecimal::add); + } + allocatedAmountMap.replaceAll((key, value) -> ratio(value)); + return allocatedAmountMap; + } + + private void validateProjectPlanningForSave(ProjectPlanningSaveReqVO reqVO, ProjectPlanningDO dbPlanning) { + if (!ProjectPlanningBizTypeConstants.isValidOwnershipType(reqVO.getOwnershipType())) { + throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID); + } + if (!ProjectPlanningBizTypeConstants.isValidCalculationMethod(reqVO.getCalculationMethod())) { + throw exception(PROJECT_PLANNING_CALCULATION_METHOD_INVALID); + } + if (ProjectPlanningBizTypeConstants.isVirtualOutput(reqVO.getCalculationMethod()) + && StrUtil.isNotBlank(reqVO.getVirtualCalculationMethod()) + && !ProjectPlanningBizTypeConstants.isValidVirtualCalculationMethod(reqVO.getVirtualCalculationMethod())) { + throw exception(PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID); + } + if (dbPlanning == null) { + return; + } + if (!Objects.equals(dbPlanning.getOwnershipType(), reqVO.getOwnershipType())) { + throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE); + } + if (!Objects.equals(dbPlanning.getCalculationMethod(), reqVO.getCalculationMethod())) { + throw exception(PROJECT_PLANNING_CALCULATION_METHOD_IMMUTABLE); + } + } + + private void prepareProjectPlanning(ProjectPlanningDO planning, ProjectDO project) { + if (planning.getPlanningStartYear() == null) { + planning.setPlanningStartYear(project.getProjectStartYear()); + } + if (planning.getReviewOutsourceFlag() == null) { + planning.setReviewOutsourceFlag(Boolean.FALSE); + } + planning.setPlanningAmount(amount(planning.getPlanningAmount())); + planning.setManagementFeeRate(ratio(planning.getManagementFeeRate())); + if (planning.getPlanningArea() == null) { + planning.setPlanningArea(project.getTotalConstructionArea()); + } + planning.setPlanningArea(amount(planning.getPlanningArea())); + planning.setCurrentDesignStageRatio(ratio(planning.getCurrentDesignStageRatio())); + planning.setTotalDistributionAmount(planning.getTotalDistributionAmount() == null + ? ONE_RATIO : ratio(planning.getTotalDistributionAmount())); + + if (planning.getReviewOutsourceRatio() == null) { + planning.setReviewOutsourceRatio(defaultReviewOutsourceRatio(planning.getOwnershipType(), + Boolean.TRUE.equals(planning.getReviewOutsourceFlag()))); + } else { + planning.setReviewOutsourceRatio(ratio(planning.getReviewOutsourceRatio())); + } + if ((ProjectPlanningBizTypeConstants.isComprehensive(planning.getOwnershipType()) + || ProjectPlanningBizTypeConstants.isSubcontract(planning.getOwnershipType())) + && planning.getCalculationRatio() == null) { + planning.setCalculationRatio(defaultCalculationRatio(planning.getOwnershipType())); + } else if (planning.getCalculationRatio() != null) { + planning.setCalculationRatio(ratio(planning.getCalculationRatio())); + } + + planning.setDrawingSetFactor(factorNullable(planning.getDrawingSetFactor())); + planning.setScaleFactor(factorNullable(planning.getScaleFactor())); + planning.setModificationFactor(factorNullable(planning.getModificationFactor())); + planning.setComplexityFactor(ratioNullable(planning.getComplexityFactor())); + planning.setInternalGuidanceUnitPrice(amountNullable(planning.getInternalGuidanceUnitPrice())); + planning.setWorkingDayCount(amountNullable(planning.getWorkingDayCount())); + planning.setWorkingDayUnitPrice(amountNullable(planning.getWorkingDayUnitPrice())); + planning.setGuidanceUnitPrice(amountNullable(planning.getGuidanceUnitPrice())); + planning.setGuidanceTotalPrice(amountNullable(planning.getGuidanceTotalPrice())); + planning.setVirtualTotalPrice(amountNullable(planning.getVirtualTotalPrice())); + + if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(planning.getVirtualCalculationMethod()) + && planning.getWorkingDayUnitPrice() == null) { + planning.setWorkingDayUnitPrice(DEFAULT_WORKING_DAY_UNIT_PRICE); + } + } + + private void calculateProjectPlanning(ProjectPlanningDO planning) { + planning.setManagementFee(multiplyAmount(planning.getPlanningAmount(), planning.getManagementFeeRate())); + planning.setContractUnitPrice(divideAmount(planning.getPlanningAmount(), planning.getPlanningArea())); + planning.setTotalAdjustmentFactor(ZERO_RATIO); + planning.setAssessmentArea(ZERO_AMOUNT); + planning.setVirtualOutputValue(ZERO_AMOUNT); + planning.setAssessmentOutputValue(ZERO_AMOUNT); + + if (ProjectPlanningBizTypeConstants.isMajor(planning.getOwnershipType())) { + calculateMajorPlanning(planning); + return; + } + if (ProjectPlanningBizTypeConstants.isComprehensive(planning.getOwnershipType()) + || ProjectPlanningBizTypeConstants.isSubcontract(planning.getOwnershipType())) { + planning.setAssessmentOutputValue(multiplyAmount( + planning.getPlanningAmount(), + ratio(planning.getCalculationRatio()), + planning.getCurrentDesignStageRatio(), + oneMinus(planning.getReviewOutsourceRatio()))); + } + } + + private void calculateMajorPlanning(ProjectPlanningDO planning) { + if (ProjectPlanningBizTypeConstants.isGuidancePrice(planning.getCalculationMethod())) { + BigDecimal totalAdjustmentFactor = calculateTotalAdjustmentFactor(planning); + planning.setTotalAdjustmentFactor(totalAdjustmentFactor); + planning.setAssessmentArea(multiplyAmount(planning.getPlanningArea(), totalAdjustmentFactor)); + BigDecimal applicableUnitPrice = calculateApplicableUnitPrice( + planning.getContractUnitPrice(), planning.getInternalGuidanceUnitPrice()); + planning.setAssessmentOutputValue(multiplyAmount( + applicableUnitPrice, + planning.getAssessmentArea(), + planning.getCurrentDesignStageRatio(), + oneMinus(planning.getReviewOutsourceRatio()))); + return; + } + if (ProjectPlanningBizTypeConstants.isContractPrice(planning.getCalculationMethod())) { + BigDecimal totalAdjustmentFactor = calculateTotalAdjustmentFactor(planning); + planning.setTotalAdjustmentFactor(totalAdjustmentFactor); + planning.setAssessmentArea(multiplyAmount(planning.getPlanningArea(), totalAdjustmentFactor)); + planning.setAssessmentOutputValue(multiplyAmount( + planning.getPlanningAmount(), + totalAdjustmentFactor, + planning.getCurrentDesignStageRatio(), + oneMinus(planning.getReviewOutsourceRatio()))); + return; + } + if (ProjectPlanningBizTypeConstants.isVirtualOutput(planning.getCalculationMethod())) { + planning.setTotalAdjustmentFactor(ONE_RATIO); + planning.setVirtualOutputValue(calculateVirtualOutputValue(planning)); + planning.setAssessmentOutputValue(multiplyAmount( + planning.getVirtualOutputValue(), + planning.getCurrentDesignStageRatio(), + oneMinus(planning.getReviewOutsourceRatio()))); + } + } + + private BigDecimal calculateTotalAdjustmentFactor(ProjectPlanningDO planning) { + return productRatio( + defaultFactor(planning.getDrawingSetFactor()), + defaultFactor(planning.getScaleFactor()), + defaultFactor(planning.getModificationFactor()), + defaultFactor(planning.getComplexityFactor())); + } + + private BigDecimal calculateApplicableUnitPrice(BigDecimal contractUnitPrice, BigDecimal internalGuidanceUnitPrice) { + BigDecimal guidanceUnitPrice = amount(internalGuidanceUnitPrice); + if (guidanceUnitPrice.compareTo(BigDecimal.ZERO) <= 0) { + return ZERO_AMOUNT; + } + BigDecimal unitPrice = amount(contractUnitPrice); + BigDecimal minimumGuidancePrice = multiplyAmount(guidanceUnitPrice, MIN_GUIDANCE_PRICE_RATIO); + if (unitPrice.compareTo(guidanceUnitPrice) > 0) { + return guidanceUnitPrice; + } + if (unitPrice.compareTo(minimumGuidancePrice) >= 0) { + return unitPrice; + } + return minimumGuidancePrice; + } + + private BigDecimal calculateVirtualOutputValue(ProjectPlanningDO planning) { + if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(planning.getVirtualCalculationMethod())) { + return multiplyAmount(planning.getWorkingDayCount(), planning.getWorkingDayUnitPrice()); + } + if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE.equals(planning.getVirtualCalculationMethod())) { + if (isPositive(planning.getGuidanceTotalPrice())) { + return amount(planning.getGuidanceTotalPrice()); + } + return multiplyAmount(planning.getGuidanceUnitPrice(), planning.getPlanningArea()); + } + if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE.equals(planning.getVirtualCalculationMethod())) { + return amount(planning.getVirtualTotalPrice()); + } + return ZERO_AMOUNT; + } + + private BigDecimal defaultReviewOutsourceRatio(String ownershipType, boolean outsourceFlag) { + if (!outsourceFlag) { + return ZERO_RATIO; + } + if (ProjectPlanningBizTypeConstants.isMajor(ownershipType)) { + return DEFAULT_MAJOR_OUTSOURCE_RATIO; + } + return DEFAULT_COMMON_OUTSOURCE_RATIO; + } + + private BigDecimal defaultCalculationRatio(String ownershipType) { + if (ProjectPlanningBizTypeConstants.isComprehensive(ownershipType)) { + return DEFAULT_COMPREHENSIVE_RATIO; + } + if (ProjectPlanningBizTypeConstants.isSubcontract(ownershipType)) { + return DEFAULT_SUBCONTRACT_RATIO; + } + return null; + } + + private ProjectDO validateProjectExists(Long projectId) { + ProjectDO project = projectMapper.selectById(projectId); + if (project == null) { + throw exception(PROJECT_PLANNING_PROJECT_NOT_EXISTS); + } + return project; + } + + private ProjectPlanningDO validateProjectPlanningExists(Long id) { + ProjectPlanningDO planning = projectPlanningMapper.selectById(id); + if (planning == null) { + throw exception(PROJECT_PLANNING_NOT_EXISTS); + } + return planning; + } + + private BigDecimal multiplyAmount(BigDecimal... values) { + BigDecimal result = BigDecimal.ONE; + for (BigDecimal value : values) { + result = result.multiply(value == null ? BigDecimal.ZERO : value); + } + return result.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal productRatio(BigDecimal... values) { + BigDecimal result = BigDecimal.ONE; + for (BigDecimal value : values) { + result = result.multiply(value == null ? BigDecimal.ONE : value); + } + return result.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal divideAmount(BigDecimal dividend, BigDecimal divisor) { + if (dividend == null || divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) { + return ZERO_AMOUNT; + } + return dividend.divide(divisor, AMOUNT_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal oneMinus(BigDecimal value) { + return BigDecimal.ONE.subtract(ratio(value)).setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal defaultFactor(BigDecimal value) { + return value == null ? ONE_RATIO : ratio(value); + } + + private BigDecimal amount(BigDecimal value) { + return value == null ? ZERO_AMOUNT : value.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal amountNullable(BigDecimal value) { + return value == null ? null : amount(value); + } + + private BigDecimal factor(BigDecimal value) { + return value == null ? BigDecimal.ZERO.setScale(FACTOR_SCALE, RoundingMode.HALF_UP) + : value.setScale(FACTOR_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal factorNullable(BigDecimal value) { + return value == null ? null : factor(value); + } + + private BigDecimal ratio(BigDecimal value) { + return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + + private BigDecimal ratioNullable(BigDecimal value) { + return value == null ? null : ratio(value); + } + + private boolean isPositive(BigDecimal value) { + return value != null && value.compareTo(BigDecimal.ZERO) > 0; + } + + private void refreshQuarterDistributionAmounts(ProjectPlanningDO planning) { + List quarterList = projectPlanningQuarterMapper.selectListByPlanningId(planning.getId()); + for (ProjectPlanningQuarterDO quarter : quarterList) { + quarter.setDistributionAmount(calculateQuarterDistributionAmount(planning, quarter.getDistributionRatio())); + projectPlanningQuarterMapper.updateById(quarter); + } + } + + private BigDecimal calculateQuarterDistributionAmount(ProjectPlanningDO planning, BigDecimal distributionRatio) { + return multiplyAmount( + planning.getAssessmentOutputValue(), + planning.getTotalDistributionAmount(), + ratio(distributionRatio)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterService.java new file mode 100644 index 0000000..e3090b3 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterService.java @@ -0,0 +1,27 @@ +package cn.iocoder.lyzsys.module.tjt.service.planningquarter; + +import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO; + +import java.util.List; + +/** + * 季度分配 Service 接口 + * + * @author Codex + */ +public interface ProjectPlanningQuarterService { + + Long createProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO createReqVO); + + void updateProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO updateReqVO); + + void deleteProjectPlanningQuarter(Long id); + + void deleteProjectPlanningQuarterList(List ids); + + ProjectPlanningQuarterDO getProjectPlanningQuarter(Long id); + + List getProjectPlanningQuarterListByPlanningId(Long planningId); + +} 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 new file mode 100644 index 0000000..0d6189a --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planningquarter/ProjectPlanningQuarterServiceImpl.java @@ -0,0 +1,146 @@ +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.planningquarter.ProjectPlanningQuarterDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +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_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_PLANNING_NOT_EXISTS; + +/** + * 季度分配 Service 实现类 + * + * @author Codex + */ +@Service +@Validated +public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarterService { + + 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); + + @Resource + private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; + @Resource + private ProjectPlanningMapper projectPlanningMapper; + + @Override + public Long createProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO createReqVO) { + ProjectPlanningDO planning = validatePlanningExists(createReqVO.getPlanningId()); + if (isZeroQuarter(createReqVO)) { + return null; + } + validateQuarterUnique(null, createReqVO.getPlanningId(), + createReqVO.getDistributionYear(), createReqVO.getQuarterNo()); + ProjectPlanningQuarterDO quarter = BeanUtils.toBean(createReqVO, ProjectPlanningQuarterDO.class); + prepareQuarter(quarter, planning); + projectPlanningQuarterMapper.insert(quarter); + return quarter.getId(); + } + + @Override + public void updateProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO updateReqVO) { + ProjectPlanningQuarterDO dbQuarter = validateQuarterExists(updateReqVO.getId()); + ProjectPlanningDO planning = validatePlanningExists(updateReqVO.getPlanningId()); + if (isZeroQuarter(updateReqVO)) { + projectPlanningQuarterMapper.deleteById(dbQuarter.getId()); + return; + } + validateQuarterUnique(updateReqVO.getId(), updateReqVO.getPlanningId(), + updateReqVO.getDistributionYear(), updateReqVO.getQuarterNo()); + ProjectPlanningQuarterDO updateObj = BeanUtils.toBean(updateReqVO, ProjectPlanningQuarterDO.class); + prepareQuarter(updateObj, planning); + projectPlanningQuarterMapper.updateById(updateObj); + } + + @Override + public void deleteProjectPlanningQuarter(Long id) { + validateQuarterExists(id); + projectPlanningQuarterMapper.deleteById(id); + } + + @Override + public void deleteProjectPlanningQuarterList(List ids) { + ids.forEach(this::validateQuarterExists); + projectPlanningQuarterMapper.deleteBatchIds(ids); + } + + @Override + public ProjectPlanningQuarterDO getProjectPlanningQuarter(Long id) { + return projectPlanningQuarterMapper.selectById(id); + } + + @Override + public List getProjectPlanningQuarterListByPlanningId(Long planningId) { + validatePlanningExists(planningId); + return projectPlanningQuarterMapper.selectListByPlanningId(planningId); + } + + private void validateQuarterUnique(Long id, Long planningId, Integer distributionYear, Integer quarterNo) { + ProjectPlanningQuarterDO quarter = projectPlanningQuarterMapper.selectByPlanningIdAndDistributionYearAndQuarter( + planningId, distributionYear, quarterNo); + if (quarter == null) { + return; + } + if (id == null || !quarter.getId().equals(id)) { + throw exception(PROJECT_PLANNING_QUARTER_DUPLICATE); + } + } + + private ProjectPlanningDO validatePlanningExists(Long planningId) { + ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId); + if (planning == null) { + throw exception(PROJECT_PLANNING_QUARTER_PLANNING_NOT_EXISTS); + } + return planning; + } + + private ProjectPlanningQuarterDO validateQuarterExists(Long id) { + ProjectPlanningQuarterDO quarter = projectPlanningQuarterMapper.selectById(id); + if (quarter == null) { + throw exception(PROJECT_PLANNING_QUARTER_NOT_EXISTS); + } + return quarter; + } + + private boolean isZeroQuarter(ProjectPlanningQuarterSaveReqVO reqVO) { + return ratio(reqVO.getDistributionRatio()).compareTo(BigDecimal.ZERO) == 0; + } + + private void prepareQuarter(ProjectPlanningQuarterDO quarter, ProjectPlanningDO planning) { + quarter.setDistributionRatio(ratio(quarter.getDistributionRatio())); + quarter.setDistributionAmount(multiplyAmount( + planning.getAssessmentOutputValue(), + planning.getTotalDistributionAmount(), + quarter.getDistributionRatio())); + } + + private BigDecimal amount(BigDecimal value) { + return value == null ? ZERO_AMOUNT : value.setScale(2, RoundingMode.HALF_UP); + } + + private BigDecimal ratio(BigDecimal value) { + return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP); + } + + private BigDecimal multiplyAmount(BigDecimal... values) { + BigDecimal result = BigDecimal.ONE; + for (BigDecimal value : values) { + result = result.multiply(value == null ? BigDecimal.ZERO : value); + } + return result.setScale(2, RoundingMode.HALF_UP); + } + +} 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 new file mode 100644 index 0000000..df9737c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitService.java @@ -0,0 +1,18 @@ +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; + +/** + * 项目盈亏 Service 接口 + * + * @author Codex + */ +public interface ProjectProfitService { + + ProjectProfitRespVO getProjectProfit(Long projectId); + + PageResult getProjectProfitPage(ProjectProfitPageReqVO pageReqVO); + +} 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 new file mode 100644 index 0000000..202e01b --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java @@ -0,0 +1,135 @@ +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.mybatis.core.query.LambdaQueryWrapperX; +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.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; +import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; + +/** + * 项目盈亏 Service 实现类 + * + * @author Codex + */ +@Service +@Validated +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); + + @Resource + private ProjectMapper projectMapper; + @Resource + private ProjectPlanningMapper projectPlanningMapper; + + @Override + public ProjectProfitRespVO getProjectProfit(Long projectId) { + ProjectDO project = validateProjectExists(projectId); + List planningList = projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, + Collections.singleton(projectId)); + return buildProjectProfit(project, planningList); + } + + @Override + public PageResult getProjectProfitPage(ProjectProfitPageReqVO pageReqVO) { + PageResult pageResult = projectMapper.selectPage(pageReqVO, new LambdaQueryWrapperX() + .likeIfPresent(ProjectDO::getProjectName, pageReqVO.getProjectName()) + .eqIfPresent(ProjectDO::getContractSignedFlag, pageReqVO.getContractSignedFlag()) + .eqIfPresent(ProjectDO::getProjectStartYear, pageReqVO.getProjectStartYear()) + .betweenIfPresent(ProjectDO::getCreateTime, pageReqVO.getCreateTime()) + .orderByDesc(ProjectDO::getId)); + if (pageResult.getList().isEmpty()) { + return new PageResult<>(Collections.emptyList(), pageResult.getTotal()); + } + Map> planningMap = CollectionUtils.convertMultiMap( + projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, + CollectionUtils.convertSet(pageResult.getList(), ProjectDO::getId)), + ProjectPlanningDO::getProjectId); + List list = CollectionUtils.convertList(pageResult.getList(), + project -> buildProjectProfit(project, planningMap.get(project.getId()))); + return new PageResult<>(list, pageResult.getTotal()); + } + + private ProjectProfitRespVO buildProjectProfit(ProjectDO project, List planningList) { + List safePlanningList = planningList == null ? Collections.emptyList() : planningList; + BigDecimal comprehensivePlanningAmount = ZERO_AMOUNT; + BigDecimal subcontractPlanningAmount = ZERO_AMOUNT; + BigDecimal majorOutputValue = ZERO_AMOUNT; + for (ProjectPlanningDO planning : safePlanningList) { + if (ProjectPlanningBizTypeConstants.isComprehensive(planning.getOwnershipType())) { + comprehensivePlanningAmount = comprehensivePlanningAmount.add(amount(planning.getPlanningAmount())); + continue; + } + if (ProjectPlanningBizTypeConstants.isSubcontract(planning.getOwnershipType())) { + subcontractPlanningAmount = subcontractPlanningAmount.add(amount(planning.getPlanningAmount())); + continue; + } + if (ProjectPlanningBizTypeConstants.isMajor(planning.getOwnershipType())) { + majorOutputValue = majorOutputValue.add(amount(planning.getAssessmentOutputValue())); + } + } + BigDecimal expectedKValue = ratio(project.getExpectedKValue()); + BigDecimal majorExpectedPerformance = majorOutputValue.multiply(expectedKValue).setScale(2, RoundingMode.HALF_UP); + BigDecimal finalSettlementAmount = amount(project.getFinalSettlementAmount()); + BigDecimal profitLossValue = finalSettlementAmount + .subtract(comprehensivePlanningAmount) + .subtract(subcontractPlanningAmount) + .subtract(majorExpectedPerformance) + .setScale(2, RoundingMode.HALF_UP); + BigDecimal profitLossRate = finalSettlementAmount.compareTo(BigDecimal.ZERO) == 0 + ? ZERO_RATIO + : profitLossValue.divide(finalSettlementAmount, 4, RoundingMode.HALF_UP); + + ProjectProfitRespVO respVO = new ProjectProfitRespVO(); + respVO.setProjectId(project.getId()); + respVO.setProjectName(project.getProjectName()); + respVO.setContractSignedFlag(project.getContractSignedFlag()); + respVO.setContractAmount(amount(project.getContractAmount())); + respVO.setFinalSettlementAmount(finalSettlementAmount); + respVO.setComprehensivePlanningAmount(comprehensivePlanningAmount.setScale(2, RoundingMode.HALF_UP)); + respVO.setSubcontractPlanningAmount(subcontractPlanningAmount.setScale(2, RoundingMode.HALF_UP)); + respVO.setMajorOutputValue(majorOutputValue.setScale(2, RoundingMode.HALF_UP)); + respVO.setExpectedKValue(expectedKValue); + respVO.setMajorExpectedPerformance(majorExpectedPerformance); + respVO.setProfitLossValue(profitLossValue); + respVO.setProfitLossRate(profitLossRate); + respVO.setProjectStartYear(project.getProjectStartYear()); + respVO.setCreateTime(project.getCreateTime()); + return respVO; + } + + private ProjectDO validateProjectExists(Long projectId) { + ProjectDO project = projectMapper.selectById(projectId); + if (project == null) { + throw exception(PROJECT_NOT_EXISTS); + } + return project; + } + + private BigDecimal amount(BigDecimal value) { + return value == null ? ZERO_AMOUNT : value.setScale(2, RoundingMode.HALF_UP); + } + + private BigDecimal ratio(BigDecimal value) { + return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectService.java new file mode 100644 index 0000000..009e057 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectService.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.service.project; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; + +import java.util.List; + +/** + * 项目 Service 接口 + * + * @author Codex + */ +public interface ProjectService { + + Long createProject(ProjectSaveReqVO createReqVO); + + void updateProject(ProjectSaveReqVO updateReqVO); + + void deleteProject(Long id); + + void deleteProjectList(List ids); + + PageResult getProjectPage(ProjectPageReqVO pageReqVO); + + ProjectDO getProject(Long id); + +} 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 new file mode 100644 index 0000000..804c01f --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java @@ -0,0 +1,72 @@ +package cn.iocoder.lyzsys.module.tjt.service.project; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; + +/** + * 项目 Service 实现类 + * + * @author Codex + */ +@Service +@Validated +public class ProjectServiceImpl implements ProjectService { + + @Resource + private ProjectMapper projectMapper; + + @Override + public Long createProject(ProjectSaveReqVO createReqVO) { + ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class); + projectMapper.insert(project); + return project.getId(); + } + + @Override + public void updateProject(ProjectSaveReqVO updateReqVO) { + validateProjectExists(updateReqVO.getId()); + ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class); + projectMapper.updateById(updateObj); + } + + @Override + public void deleteProject(Long id) { + validateProjectExists(id); + projectMapper.deleteById(id); + } + + @Override + public void deleteProjectList(List ids) { + ids.forEach(this::validateProjectExists); + projectMapper.deleteBatchIds(ids); + } + + @Override + public PageResult getProjectPage(ProjectPageReqVO pageReqVO) { + return projectMapper.selectPage(pageReqVO); + } + + @Override + public ProjectDO getProject(Long id) { + return projectMapper.selectById(id); + } + + private void validateProjectExists(Long id) { + if (projectMapper.selectById(id) == null) { + throw exception(PROJECT_NOT_EXISTS); + } + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/specialtyrolesplit/SpecialtyRoleSplitService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/specialtyrolesplit/SpecialtyRoleSplitService.java new file mode 100644 index 0000000..8db4a8d --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/specialtyrolesplit/SpecialtyRoleSplitService.java @@ -0,0 +1,23 @@ +package cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit; + +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitBatchSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitRespVO; + +import java.util.List; + +/** + * 页面5角色比例 Service 接口 + * + * @author Codex + */ +public interface SpecialtyRoleSplitService { + + List getSpecialtyRoleSplitListByPlanningId(Long planningId); + + void saveSpecialtyRoleSplitBatch(SpecialtyRoleSplitBatchSaveReqVO reqVO); + + void deleteByOutputSplitId(Long outputSplitId); + + void deleteByOutputSplitIds(List outputSplitIds); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/specialtyrolesplit/SpecialtyRoleSplitServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/specialtyrolesplit/SpecialtyRoleSplitServiceImpl.java new file mode 100644 index 0000000..d2d5e20 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/specialtyrolesplit/SpecialtyRoleSplitServiceImpl.java @@ -0,0 +1,402 @@ +package cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.lyzsys.framework.common.util.json.JsonUtils; +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRolePersonRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRolePersonSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitBatchSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitSaveItemReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.specialtyrolesplit.SpecialtyRoleSplitDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.specialtyrolesplit.SpecialtyRoleSplitMapper; +import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants; +import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService; +import com.fasterxml.jackson.core.type.TypeReference; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +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_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_DESIGN_PERSON_REQUIRED; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_PERSON_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_ROLE_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID; + +/** + * 页面5角色比例 Service 实现类 + * + * @author Codex + */ +@Service +@Validated +public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService { + + private static final int RATIO_SCALE = 4; + private static final int AMOUNT_SCALE = 2; + private static final TypeReference> PERSON_LIST_TYPE = + new TypeReference>() {}; + private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(RATIO_SCALE, RoundingMode.HALF_UP); + private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); + + @Resource + private SpecialtyRoleSplitMapper specialtyRoleSplitMapper; + @Resource + private ProjectOutputSplitService projectOutputSplitService; + @Resource + private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectMapper projectMapper; + + @Override + public List getSpecialtyRoleSplitListByPlanningId(Long planningId) { + ProjectPlanningDO planning = validatePlanningExists(planningId); + ProjectDO project = validateProjectExists(planning.getProjectId()); + ProjectOutputSplitDO outputSplit = projectOutputSplitService.getOrCreateProjectOutputSplit(planningId); + List dbList = specialtyRoleSplitMapper.selectListByOutputSplitId(outputSplit.getId()); + Map dbMap = dbList.stream().collect(Collectors.toMap( + item -> item.getSpecialtyCode() + ":" + item.getRoleCode(), item -> item, (a, b) -> b)); + List result = new ArrayList<>(); + BigDecimal assessmentOutputValue = planning.getAssessmentOutputValue(); + for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.SPECIALTY_ITEMS) { + BigDecimal specialtyAmount = projectOutputSplitService.getSpecialtyAmount( + outputSplit, assessmentOutputValue, specialtyItem.getCode()); + for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.ROLE_ITEMS) { + SpecialtyRoleSplitDO dbItem = dbMap.get(specialtyItem.getCode() + ":" + roleItem.getCode()); + BigDecimal roleRatio = getStoredRoleRatio(dbItem, roleItem.getCode()); + BigDecimal roleAmount = multiplyAmount(specialtyAmount, roleRatio); + List persons = buildRespPersons(dbItem, roleAmount, roleRatio); + SpecialtyRoleSplitRespVO respVO = dbItem == null + ? new SpecialtyRoleSplitRespVO() + : BeanUtils.toBean(dbItem, SpecialtyRoleSplitRespVO.class); + if (respVO.getId() == null) { + respVO.setOutputSplitId(outputSplit.getId()); + respVO.setSortNo(roleItem.getSortNo()); + } + respVO.setPlanningId(planning.getId()); + respVO.setProjectName(project.getProjectName()); + respVO.setPlanningContent(planning.getPlanningContent()); + respVO.setSpecialtyCode(specialtyItem.getCode()); + respVO.setSpecialtyName(specialtyItem.getName()); + respVO.setSpecialtyAmount(specialtyAmount); + respVO.setRoleCode(roleItem.getCode()); + respVO.setRoleName(roleItem.getName()); + respVO.setRoleRatio(roleRatio); + respVO.setRoleAmount(roleAmount); + respVO.setPersons(persons); + result.add(respVO); + } + } + return result; + } + + @Override + public void saveSpecialtyRoleSplitBatch(SpecialtyRoleSplitBatchSaveReqVO reqVO) { + ProjectPlanningDO planning = validatePlanningExists(reqVO.getPlanningId()); + ProjectOutputSplitDO outputSplit = projectOutputSplitService.getOrCreateProjectOutputSplit(reqVO.getPlanningId()); + List dbList = specialtyRoleSplitMapper.selectListByOutputSplitId(outputSplit.getId()); + Map dbMap = dbList.stream().collect(Collectors.toMap( + item -> item.getSpecialtyCode() + ":" + item.getRoleCode(), item -> item, (a, b) -> b)); + Map> groupedMap = buildGroupedInput(reqVO.getItems()); + BigDecimal assessmentOutputValue = planning.getAssessmentOutputValue(); + for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.SPECIALTY_ITEMS) { + Map roleMap = groupedMap.getOrDefault( + specialtyItem.getCode(), new LinkedHashMap<>()); + Map ratioMap = new LinkedHashMap<>(); + Map> personMap = new LinkedHashMap<>(); + BigDecimal roleTotal = ZERO_RATIO; + BigDecimal specialtyAmount = projectOutputSplitService.getSpecialtyAmount( + outputSplit, assessmentOutputValue, specialtyItem.getCode()); + for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.ROLE_ITEMS) { + BigDecimal roleRatio = getSaveRoleRatio(roleMap, roleItem.getCode()); + BigDecimal roleAmount = multiplyAmount(specialtyAmount, roleRatio); + List persons = normalizePersons( + getSavePersons(roleMap, roleItem.getCode())); + validateRolePersons(roleItem.getCode(), roleAmount, persons); + ratioMap.put(roleItem.getCode(), roleRatio); + personMap.put(roleItem.getCode(), persons); + roleTotal = roleTotal.add(roleRatio).setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + validateRoleTotal(roleTotal); + for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.ROLE_ITEMS) { + upsertRole(dbMap, outputSplit.getId(), specialtyItem.getCode(), specialtyItem.getName(), + roleItem.getCode(), ratioMap.get(roleItem.getCode()), + toPersonJson(personMap.get(roleItem.getCode()))); + } + } + } + + @Override + public void deleteByOutputSplitId(Long outputSplitId) { + specialtyRoleSplitMapper.delete(SpecialtyRoleSplitDO::getOutputSplitId, outputSplitId); + } + + @Override + public void deleteByOutputSplitIds(List outputSplitIds) { + if (outputSplitIds == null || outputSplitIds.isEmpty()) { + return; + } + specialtyRoleSplitMapper.delete(new cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX() + .in(SpecialtyRoleSplitDO::getOutputSplitId, outputSplitIds)); + } + + private Map> buildGroupedInput(List items) { + Map> result = new LinkedHashMap<>(); + for (SpecialtyRoleSplitSaveItemReqVO item : items) { + if (!OutputSplitBizConstants.isValidSpecialtyCode(item.getSpecialtyCode())) { + throw exception(SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID); + } + if (!OutputSplitBizConstants.isValidRoleCode(item.getRoleCode())) { + throw exception(SPECIALTY_ROLE_SPLIT_ROLE_INVALID); + } + result.computeIfAbsent(item.getSpecialtyCode(), key -> new LinkedHashMap<>()) + .put(item.getRoleCode(), item); + } + return result; + } + + private void upsertRole(Map dbMap, Long outputSplitId, String specialtyCode, + String specialtyName, String roleCode, BigDecimal roleRatio, String personNames) { + String key = specialtyCode + ":" + roleCode; + SpecialtyRoleSplitDO dbItem = dbMap.get(key); + Integer sortNo = dbItem != null && dbItem.getSortNo() != null + ? dbItem.getSortNo() + : OutputSplitBizConstants.getSpecialtySortNo(specialtyCode) * 10 + + OutputSplitBizConstants.getRoleSortNo(roleCode); + if (dbItem == null) { + SpecialtyRoleSplitDO createObj = new SpecialtyRoleSplitDO(); + createObj.setOutputSplitId(outputSplitId); + createObj.setSpecialtyCode(specialtyCode); + createObj.setSpecialtyName(specialtyName); + createObj.setRoleCode(roleCode); + createObj.setRoleName(OutputSplitBizConstants.getRoleName(roleCode)); + createObj.setRoleRatio(ratio(roleRatio)); + createObj.setPersonNames(personNames); + createObj.setSortNo(sortNo); + specialtyRoleSplitMapper.insert(createObj); + return; + } + SpecialtyRoleSplitDO updateObj = new SpecialtyRoleSplitDO(); + updateObj.setId(dbItem.getId()); + updateObj.setOutputSplitId(outputSplitId); + updateObj.setSpecialtyCode(specialtyCode); + updateObj.setSpecialtyName(specialtyName); + updateObj.setRoleCode(roleCode); + updateObj.setRoleName(OutputSplitBizConstants.getRoleName(roleCode)); + updateObj.setRoleRatio(ratio(roleRatio)); + updateObj.setPersonNames(personNames); + updateObj.setSortNo(sortNo); + specialtyRoleSplitMapper.updateById(updateObj); + } + + private List getSavePersons(Map roleMap, + String roleCode) { + SpecialtyRoleSplitSaveItemReqVO item = roleMap.get(roleCode); + return item == null ? new ArrayList<>() : item.getPersons(); + } + + private BigDecimal getSaveRoleRatio(Map roleMap, String roleCode) { + SpecialtyRoleSplitSaveItemReqVO item = roleMap.get(roleCode); + BigDecimal roleRatio = item == null ? ZERO_RATIO : ratio(item.getRoleRatio()); + if (roleRatio.compareTo(ZERO_RATIO) < 0 || roleRatio.compareTo(ONE_RATIO) > 0) { + throw exception(SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID); + } + return roleRatio; + } + + private List normalizePersons(List persons) { + List result = new ArrayList<>(); + if (persons == null || persons.isEmpty()) { + return result; + } + for (SpecialtyRolePersonSaveReqVO person : persons) { + if (person == null) { + continue; + } + String personName = StrUtil.trim(person.getPersonName()); + BigDecimal personRatio = person.getPersonRatio(); + boolean hasName = StrUtil.isNotBlank(personName); + boolean hasRatio = personRatio != null; + if (!hasName && !hasRatio) { + continue; + } + if (!hasName || !hasRatio) { + throw exception(SPECIALTY_ROLE_SPLIT_PERSON_INVALID); + } + SpecialtyRolePersonSaveReqVO normalized = new SpecialtyRolePersonSaveReqVO(); + normalized.setPersonName(personName); + BigDecimal normalizedRatio = ratio(personRatio); + if (normalizedRatio.compareTo(ZERO_RATIO) < 0 || normalizedRatio.compareTo(ONE_RATIO) > 0) { + throw exception(SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID); + } + normalized.setPersonRatio(normalizedRatio); + result.add(normalized); + } + return result; + } + + private void validateRolePersons(String roleCode, BigDecimal roleAmount, List persons) { + BigDecimal personTotal = sumPersonRatios(persons); + if (personTotal.compareTo(ONE_RATIO) > 0) { + throw exception(SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID); + } + if (OutputSplitBizConstants.ROLE_DESIGN.equals(roleCode) + && roleAmount.compareTo(ZERO_AMOUNT) > 0 + && persons.isEmpty()) { + throw exception(SPECIALTY_ROLE_SPLIT_DESIGN_PERSON_REQUIRED); + } + } + + private void validateRoleTotal(BigDecimal roleTotal) { + if (ratio(roleTotal).compareTo(ONE_RATIO) != 0) { + throw exception(SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID); + } + } + + private BigDecimal getStoredRoleRatio(SpecialtyRoleSplitDO dbItem, String roleCode) { + if (dbItem == null) { + return OutputSplitBizConstants.ROLE_DESIGN.equals(roleCode) ? ONE_RATIO : ZERO_RATIO; + } + return ratio(dbItem.getRoleRatio()); + } + + private List buildRespPersons(SpecialtyRoleSplitDO dbItem, BigDecimal roleAmount, + BigDecimal roleRatio) { + List result = new ArrayList<>(); + for (SpecialtyRolePersonSaveReqVO person : parseStoredPersons(dbItem, roleRatio)) { + SpecialtyRolePersonRespVO respVO = new SpecialtyRolePersonRespVO(); + respVO.setPersonName(person.getPersonName()); + respVO.setPersonRatio(ratio(person.getPersonRatio())); + respVO.setPersonAmount(multiplyAmount(roleAmount, respVO.getPersonRatio())); + result.add(respVO); + } + return result; + } + + private List parseStoredPersons(SpecialtyRoleSplitDO dbItem, BigDecimal roleRatio) { + List result = new ArrayList<>(); + if (dbItem == null) { + return result; + } + String rawPersons = StrUtil.trimToEmpty(dbItem.getPersonNames()); + BigDecimal storedRoleRatio = ratio(roleRatio); + if (StrUtil.isNotBlank(rawPersons) && JsonUtils.isJson(rawPersons)) { + List jsonPersons = JsonUtils.parseObjectQuietly(rawPersons, PERSON_LIST_TYPE); + if (jsonPersons != null) { + for (SpecialtyRolePersonSaveReqVO jsonPerson : jsonPersons) { + if (jsonPerson == null) { + continue; + } + String personName = StrUtil.trimToEmpty(jsonPerson.getPersonName()); + BigDecimal personRatio = jsonPerson.getPersonRatio(); + if (StrUtil.isBlank(personName) && personRatio == null) { + continue; + } + SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO(); + person.setPersonName(personName); + person.setPersonRatio(ratio(personRatio)); + result.add(person); + } + if (!result.isEmpty()) { + if (storedRoleRatio.compareTo(ZERO_RATIO) > 0 + && sumPersonRatios(result).compareTo(storedRoleRatio) == 0) { + return convertLegacyPersons(result, storedRoleRatio); + } + return result; + } + } + } + if (StrUtil.isNotBlank(rawPersons)) { + result.add(buildStoredPerson(rawPersons, storedRoleRatio.compareTo(ZERO_RATIO) > 0 ? ONE_RATIO : ZERO_RATIO)); + return result; + } + return result; + } + + private List convertLegacyPersons(List persons, + BigDecimal roleRatio) { + List result = new ArrayList<>(); + if (roleRatio.compareTo(ZERO_RATIO) <= 0) { + return persons; + } + for (SpecialtyRolePersonSaveReqVO person : persons) { + SpecialtyRolePersonSaveReqVO converted = new SpecialtyRolePersonSaveReqVO(); + converted.setPersonName(person.getPersonName()); + converted.setPersonRatio(ratio(person.getPersonRatio().divide(roleRatio, RATIO_SCALE, RoundingMode.HALF_UP))); + result.add(converted); + } + return result; + } + + private SpecialtyRolePersonSaveReqVO buildStoredPerson(String personName, BigDecimal personRatio) { + SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO(); + person.setPersonName(personName); + person.setPersonRatio(ratio(personRatio)); + return person; + } + + private String toPersonJson(List persons) { + return persons == null || persons.isEmpty() ? null : JsonUtils.toJsonString(persons); + } + + private BigDecimal sumPersonRatios(List persons) { + BigDecimal sum = ZERO_RATIO; + if (persons == null || persons.isEmpty()) { + return sum; + } + for (SpecialtyRolePersonSaveReqVO person : persons) { + if (person == null) { + continue; + } + sum = sum.add(ratio(person.getPersonRatio())).setScale(RATIO_SCALE, RoundingMode.HALF_UP); + } + return sum; + } + + private ProjectPlanningDO validatePlanningExists(Long planningId) { + ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId); + if (planning == null) { + throw exception(PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS); + } + return planning; + } + + private ProjectDO validateProjectExists(Long projectId) { + ProjectDO project = projectMapper.selectById(projectId); + if (project == null) { + throw exception(PROJECT_NOT_EXISTS); + } + return project; + } + + private BigDecimal multiplyAmount(BigDecimal left, BigDecimal right) { + return amount(left).multiply(ratio(right)).setScale(AMOUNT_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-server/pom.xml b/lyzsys-server/pom.xml index 92ce35f..241f783 100644 --- a/lyzsys-server/pom.xml +++ b/lyzsys-server/pom.xml @@ -33,7 +33,7 @@ cn.iocoder.boot - lyzsys-module-demo + lyzsys-module-tjt ${revision} diff --git a/lyzsys-server/src/main/resources/application-local.yaml b/lyzsys-server/src/main/resources/application-local.yaml index e476b49..9b1eba0 100644 --- a/lyzsys-server/src/main/resources/application-local.yaml +++ b/lyzsys-server/src/main/resources/application-local.yaml @@ -39,7 +39,7 @@ spring: time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒(1 分钟) min-evictable-idle-time-millis: 600000 # 配置一个连接在池中最小生存的时间,单位:毫秒(10 分钟) max-evictable-idle-time-millis: 1800000 # 配置一个连接在池中最大生存的时间,单位:毫秒(30 分钟) - validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + validation-query: SELECT 1 # 配置检测连接是否有效 test-while-idle: true test-on-borrow: false test-on-return: false diff --git a/pom.xml b/pom.xml index 50791ef..47e3bfb 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,8 @@ lyzsys-module-infra - lyzsys-module-demo + + lyzsys-module-tjt