diff --git a/README.md b/README.md index e06f621..f58f0da 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,6 @@ http://127.0.0.1:48080/swagger-ui - `doc/后端开发文档.md` -特建投框架调研补充文档: +特建投业务分析与计划文档: -- [`../doc/tejiantou_ai_requirements_20260410_v2_framework.md`](../doc/tejiantou_ai_requirements_20260410_v2_framework.md) +- [`../doc/特建投业务分析与计划.md`](../doc/特建投业务分析与计划.md) diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/EmployeeController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/EmployeeController.java new file mode 100644 index 0000000..6634cd4 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/EmployeeController.java @@ -0,0 +1,81 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employee; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO; +import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService; +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/employee") +@Validated +public class EmployeeController { + + @Resource + private EmployeeService employeeService; + + @PostMapping("/create") + @Operation(summary = "创建员工") + @PreAuthorize("@ss.hasPermission('tjt:employee:create')") + public CommonResult createEmployee(@Valid @RequestBody EmployeeSaveReqVO createReqVO) { + return success(employeeService.createEmployee(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改员工") + @PreAuthorize("@ss.hasPermission('tjt:employee:update')") + public CommonResult updateEmployee(@Valid @RequestBody EmployeeSaveReqVO updateReqVO) { + employeeService.updateEmployee(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除员工") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:employee:delete')") + public CommonResult deleteEmployee(@RequestParam("id") Long id) { + employeeService.deleteEmployee(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得员工详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:employee:query')") + public CommonResult getEmployee(@RequestParam("id") Long id) { + return success(employeeService.getEmployee(id)); + } + + @GetMapping("/page") + @Operation(summary = "获得员工分页") + @PreAuthorize("@ss.hasPermission('tjt:employee:query')") + public CommonResult> getEmployeePage(@Valid EmployeePageReqVO pageReqVO) { + return success(employeeService.getEmployeePage(pageReqVO)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得员工精简列表") + public CommonResult> getEmployeeSimpleList( + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "officeId", required = false) Long officeId, + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "enabledFlag", required = false) Boolean enabledFlag) { + return success(employeeService.getEmployeeSimpleList(keyword, officeId, status, enabledFlag)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeePageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeePageReqVO.java new file mode 100644 index 0000000..8f53e89 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeePageReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 员工分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class EmployeePageReqVO extends PageParam { + + @Schema(description = "员工姓名", example = "张") + private String employeeName; + + @Schema(description = "所属专业所 ID", example = "1") + private Long officeId; + + @Schema(description = "状态", example = "在职") + private String employeeStatus; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeRespVO.java new file mode 100644 index 0000000..35a0d1c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeRespVO.java @@ -0,0 +1,58 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 员工 Response VO") +@Data +public class EmployeeRespVO { + + @Schema(description = "员工 ID", example = "1") + private Long id; + + @Schema(description = "员工姓名") + private String employeeName; + + @Schema(description = "性别") + private String gender; + + @Schema(description = "所属专业所 ID") + private Long officeId; + + @Schema(description = "所属专业所名称") + private String officeName; + + @Schema(description = "注册类型及等级") + private String registrationType; + + @Schema(description = "职称") + private String jobTitle; + + @Schema(description = "注册章号") + private String registrationSealNo; + + @Schema(description = "入职时间") + private LocalDate entryDate; + + @Schema(description = "离职时间") + private LocalDate leaveDate; + + @Schema(description = "状态") + private String employeeStatus; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序号") + private Integer sortNo; + + @Schema(description = "是否启用") + private Boolean enabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeSaveReqVO.java new file mode 100644 index 0000000..b73ea28 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeSaveReqVO.java @@ -0,0 +1,73 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.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.time.LocalDate; + +import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 员工新增/修改 Request VO") +@Data +public class EmployeeSaveReqVO { + + @Schema(description = "员工 ID", example = "1") + private Long id; + + @Schema(description = "员工姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @NotBlank(message = "员工姓名不能为空") + @Size(max = 64, message = "员工姓名长度不能超过 64 个字符") + private String employeeName; + + @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "男") + @NotBlank(message = "性别不能为空") + @Size(max = 2, message = "性别长度不能超过 2 个字符") + private String gender; + + @Schema(description = "所属专业所 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "所属专业所不能为空") + private Long officeId; + + @Schema(description = "注册类型及等级", example = "一级注册建筑师") + @Size(max = 100, message = "注册类型及等级长度不能超过 100 个字符") + private String registrationType; + + @Schema(description = "职称", example = "正高级") + @Size(max = 50, message = "职称长度不能超过 50 个字符") + private String jobTitle; + + @Schema(description = "注册章号", example = "A123456") + @Size(max = 100, message = "注册章号长度不能超过 100 个字符") + private String registrationSealNo; + + @Schema(description = "入职时间", example = "2026-01-01") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate entryDate; + + @Schema(description = "离职时间", example = "2026-12-31") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate leaveDate; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "在职") + @NotBlank(message = "状态不能为空") + @Size(max = 20, message = "状态长度不能超过 20 个字符") + private String employeeStatus; + + @Schema(description = "备注") + @Size(max = 255, message = "备注长度不能超过 255 个字符") + private String remark; + + @Schema(description = "排序号", example = "1") + private Integer sortNo; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeSimpleRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeSimpleRespVO.java new file mode 100644 index 0000000..3438270 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employee/vo/EmployeeSimpleRespVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 员工精简 Response VO") +@Data +public class EmployeeSimpleRespVO { + + @Schema(description = "员工 ID", example = "1") + private Long id; + + @Schema(description = "员工姓名") + private String employeeName; + + @Schema(description = "所属专业所 ID") + private Long officeId; + + @Schema(description = "所属专业所名称") + private String officeName; + + @Schema(description = "员工状态") + private String employeeStatus; + + @Schema(description = "注册类型及等级") + private String registrationType; + + @Schema(description = "职称") + private String jobTitle; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/EmployeeYearCostBudgetController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/EmployeeYearCostBudgetController.java new file mode 100644 index 0000000..4a9a3a1 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/EmployeeYearCostBudgetController.java @@ -0,0 +1,70 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget.EmployeeYearCostBudgetService; +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 = "管理后台 - 员工年度成本预算") +@RestController +@RequestMapping("/tjt/employee-year-cost-budget") +@Validated +public class EmployeeYearCostBudgetController { + + @Resource + private EmployeeYearCostBudgetService employeeYearCostBudgetService; + + @PostMapping("/create") + @Operation(summary = "创建员工年度成本预算") + @PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:create')") + public CommonResult createEmployeeYearCostBudget(@Valid @RequestBody EmployeeYearCostBudgetSaveReqVO createReqVO) { + return success(employeeYearCostBudgetService.createEmployeeYearCostBudget(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改员工年度成本预算") + @PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:update')") + public CommonResult updateEmployeeYearCostBudget(@Valid @RequestBody EmployeeYearCostBudgetSaveReqVO updateReqVO) { + employeeYearCostBudgetService.updateEmployeeYearCostBudget(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除员工年度成本预算") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:delete')") + public CommonResult deleteEmployeeYearCostBudget(@RequestParam("id") Long id) { + employeeYearCostBudgetService.deleteEmployeeYearCostBudget(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得员工年度成本预算详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:query')") + public CommonResult getEmployeeYearCostBudget(@RequestParam("id") Long id) { + return success(employeeYearCostBudgetService.getEmployeeYearCostBudget(id)); + } + + @GetMapping("/page") + @Operation(summary = "获得员工年度成本预算分页") + @PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:query')") + public CommonResult> getEmployeeYearCostBudgetPage( + @Valid EmployeeYearCostBudgetPageReqVO pageReqVO) { + return success(employeeYearCostBudgetService.getEmployeeYearCostBudgetPage(pageReqVO)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetPageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetPageReqVO.java new file mode 100644 index 0000000..5ab19ae --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetPageReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 员工年度成本预算分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class EmployeeYearCostBudgetPageReqVO extends PageParam { + + @Schema(description = "员工 ID", example = "1") + private Long employeeId; + + @Schema(description = "员工姓名", example = "张") + private String employeeName; + + @Schema(description = "预算年度", example = "2026") + private Integer budgetYear; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetRespVO.java new file mode 100644 index 0000000..fcc996c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.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 EmployeeYearCostBudgetRespVO { + + @Schema(description = "记录 ID", example = "1") + private Long id; + + @Schema(description = "员工 ID") + private Long employeeId; + + @Schema(description = "员工姓名") + private String employeeName; + + @Schema(description = "预算年度") + private Integer budgetYear; + + @Schema(description = "预计发生成本") + private BigDecimal expectedCostAmount; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序号") + private Integer sortNo; + + @Schema(description = "是否启用") + private Boolean enabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetSaveReqVO.java new file mode 100644 index 0000000..c9fb52e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/employeeyearcostbudget/vo/EmployeeYearCostBudgetSaveReqVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 员工年度成本预算新增/修改 Request VO") +@Data +public class EmployeeYearCostBudgetSaveReqVO { + + @Schema(description = "记录 ID", example = "1") + private Long id; + + @Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "员工 ID 不能为空") + private Long employeeId; + + @Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三") + @Size(max = 64, message = "员工姓名长度不能超过 64 个字符") + private String employeeName; + + @Schema(description = "预算年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "预算年度不能为空") + private Integer budgetYear; + + @Schema(description = "预计发生成本", requiredMode = Schema.RequiredMode.REQUIRED, example = "100000") + @NotNull(message = "预计发生成本不能为空") + private BigDecimal expectedCostAmount; + + @Schema(description = "备注") + @Size(max = 255, message = "备注长度不能超过 255 个字符") + private String remark; + + @Schema(description = "排序号", example = "1") + private Integer sortNo; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/OfficeController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/OfficeController.java new file mode 100644 index 0000000..b6734ec --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/OfficeController.java @@ -0,0 +1,77 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.office; + +import cn.iocoder.lyzsys.framework.common.pojo.CommonResult; +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO; +import cn.iocoder.lyzsys.module.tjt.service.office.OfficeService; +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/office") +@Validated +public class OfficeController { + + @Resource + private OfficeService officeService; + + @PostMapping("/create") + @Operation(summary = "创建专业所") + @PreAuthorize("@ss.hasPermission('tjt:office:create')") + public CommonResult createOffice(@Valid @RequestBody OfficeSaveReqVO createReqVO) { + return success(officeService.createOffice(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改专业所") + @PreAuthorize("@ss.hasPermission('tjt:office:update')") + public CommonResult updateOffice(@Valid @RequestBody OfficeSaveReqVO updateReqVO) { + officeService.updateOffice(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除专业所") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:office:delete')") + public CommonResult deleteOffice(@RequestParam("id") Long id) { + officeService.deleteOffice(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得专业所详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:office:query')") + public CommonResult getOffice(@RequestParam("id") Long id) { + return success(officeService.getOffice(id)); + } + + @GetMapping("/page") + @Operation(summary = "获得专业所分页") + @PreAuthorize("@ss.hasPermission('tjt:office:query')") + public CommonResult> getOfficePage(@Valid OfficePageReqVO pageReqVO) { + return success(officeService.getOfficePage(pageReqVO)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得专业所精简列表") + public CommonResult> getOfficeSimpleList() { + return success(officeService.getOfficeSimpleList()); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficePageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficePageReqVO.java new file mode 100644 index 0000000..e9f3422 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficePageReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 专业所分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class OfficePageReqVO extends PageParam { + + @Schema(description = "专业所名称", example = "建筑") + private String officeName; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeRespVO.java new file mode 100644 index 0000000..717bcb6 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeRespVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 专业所 Response VO") +@Data +public class OfficeRespVO { + + @Schema(description = "专业所 ID", example = "1") + private Long id; + + @Schema(description = "专业所名称", example = "建筑一所") + private String officeName; + + @Schema(description = "专业所编码", example = "JZY") + private String officeCode; + + @Schema(description = "排序号", example = "1") + private Integer sortNo; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeSaveReqVO.java new file mode 100644 index 0000000..b843605 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeSaveReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +@Schema(description = "管理后台 - 专业所新增/修改 Request VO") +@Data +public class OfficeSaveReqVO { + + @Schema(description = "专业所 ID", example = "1") + private Long id; + + @Schema(description = "专业所名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑一所") + @NotBlank(message = "专业所名称不能为空") + @Size(max = 100, message = "专业所名称长度不能超过 100 个字符") + private String officeName; + + @Schema(description = "专业所编码", example = "JZY") + @Size(max = 50, message = "专业所编码长度不能超过 50 个字符") + private String officeCode; + + @Schema(description = "排序号", example = "1") + private Integer sortNo; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + + @Schema(description = "备注", example = "默认专业所") + @Size(max = 255, message = "备注长度不能超过 255 个字符") + private String remark; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeSimpleRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeSimpleRespVO.java new file mode 100644 index 0000000..0eee735 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/office/vo/OfficeSimpleRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 专业所精简 Response VO") +@Data +public class OfficeSimpleRespVO { + + @Schema(description = "专业所 ID", example = "1") + private Long id; + + @Schema(description = "专业所名称", example = "建筑一所") + private String officeName; + +} 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 index ab97a35..d712c13 100644 --- 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 @@ -36,17 +36,14 @@ public class ProjectOutputSplitRespVO { @Schema(description = "项目负责人") private String engineeringLeaderName; - @Schema(description = "项目经理比例") - private BigDecimal projectManagerRatio; + @Schema(description = "项目经理/项目负责人") + private String projectLeadName; - @Schema(description = "项目经理金额") - private BigDecimal projectManagerAmount; + @Schema(description = "项目经理/项目负责人合并比例") + private BigDecimal projectLeadRatio; - @Schema(description = "项目负责人比例") - private BigDecimal engineeringLeaderRatio; - - @Schema(description = "项目负责人金额") - private BigDecimal engineeringLeaderAmount; + @Schema(description = "项目经理/项目负责人合并金额") + private BigDecimal projectLeadAmount; @Schema(description = "专业所比例") private BigDecimal officeRatio; 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 index 33f4ab3..3f6a8df 100644 --- 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 @@ -3,6 +3,8 @@ 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.DecimalMax; +import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotNull; import java.math.BigDecimal; @@ -10,48 +12,62 @@ import java.math.BigDecimal; @Data public class ProjectOutputSplitSaveReqVO { - @Schema(description = "合同规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "合同规划 ID 不能为空") + @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.0550") + @NotNull(message = "项目经理/项目负责人合并比例不能为空") + @DecimalMin(value = "0.0000", message = "projectLeadRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "projectLeadRatio must be <= 1") + private BigDecimal projectLeadRatio; @Schema(description = "专业所比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.9500") @NotNull(message = "专业所比例不能为空") + @DecimalMin(value = "0.0000", message = "officeRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "officeRatio must be <= 1") private BigDecimal officeRatio; @Schema(description = "建筑专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000") @NotNull(message = "建筑专业比例不能为空") + @DecimalMin(value = "0.0000", message = "archRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "archRatio must be <= 1") private BigDecimal archRatio; @Schema(description = "装修专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0000") @NotNull(message = "装修专业比例不能为空") + @DecimalMin(value = "0.0000", message = "decorRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "decorRatio must be <= 1") private BigDecimal decorRatio; @Schema(description = "结构专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.2000") @NotNull(message = "结构专业比例不能为空") + @DecimalMin(value = "0.0000", message = "structRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "structRatio must be <= 1") private BigDecimal structRatio; @Schema(description = "水专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000") @NotNull(message = "水专业比例不能为空") + @DecimalMin(value = "0.0000", message = "waterRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "waterRatio must be <= 1") private BigDecimal waterRatio; @Schema(description = "电气专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000") @NotNull(message = "电气专业比例不能为空") + @DecimalMin(value = "0.0000", message = "elecRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "elecRatio must be <= 1") private BigDecimal elecRatio; @Schema(description = "暖通专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500") @NotNull(message = "暖通专业比例不能为空") + @DecimalMin(value = "0.0000", message = "hvacRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "hvacRatio must be <= 1") private BigDecimal hvacRatio; @Schema(description = "数字化设计专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500") @NotNull(message = "数字化设计专业比例不能为空") + @DecimalMin(value = "0.0000", message = "digitalRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "digitalRatio must be <= 1") private BigDecimal digitalRatio; } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java index 5402698..e8280c6 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningRespVO.java @@ -19,6 +19,12 @@ public class ProjectPlanningRespVO { @Schema(description = "归属类型") private String ownershipType; + @Schema(description = "设计部位") + private String designPart; + + @Schema(description = "建筑类型") + private String buildingType; + @Schema(description = "产值计算方式") private String calculationMethod; @@ -64,7 +70,7 @@ public class ProjectPlanningRespVO { @Schema(description = "待分配比例") private BigDecimal pendingAmount; - @Schema(description = "Planning progress remark") + @Schema(description = "提取进度备注") private String progressRemark; @Schema(description = "楼栋数或户型数") @@ -100,9 +106,6 @@ public class ProjectPlanningRespVO { @Schema(description = "指导总价") private BigDecimal guidanceTotalPrice; - @Schema(description = "虚拟总价") - private BigDecimal virtualTotalPrice; - @Schema(description = "产值计算比例") private BigDecimal calculationRatio; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java index 481c442..a919af3 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planning/vo/ProjectPlanningSaveReqVO.java @@ -3,6 +3,8 @@ 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.DecimalMax; +import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -24,8 +26,15 @@ public class ProjectPlanningSaveReqVO { @Size(max = 20, message = "归属类型长度不能超过 20 个字符") private String ownershipType; - @Schema(description = "产值计算方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "指导价法") - @NotBlank(message = "产值计算方式不能为空") + @Schema(description = "设计部位", example = "地上部分") + @Size(max = 20, message = "设计部位长度不能超过 20 个字符") + private String designPart; + + @Schema(description = "建筑类型", example = "住宅") + @Size(max = 100, message = "建筑类型长度不能超过 100 个字符") + private String buildingType; + + @Schema(description = "产值计算方式,页面 2 维护", example = "指导价法") @Size(max = 30, message = "产值计算方式长度不能超过 30 个字符") private String calculationMethod; @@ -40,6 +49,8 @@ public class ProjectPlanningSaveReqVO { @Schema(description = "管理费费率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500") @NotNull(message = "管理费费率不能为空") + @DecimalMin(value = "0.0000", message = "managementFeeRate must be >= 0") + @DecimalMax(value = "1.0000", message = "managementFeeRate must be <= 1") private BigDecimal managementFeeRate; @Schema(description = "实施团队", example = "建筑一所") @@ -57,19 +68,25 @@ public class ProjectPlanningSaveReqVO { private String designStage; @Schema(description = "本次设计阶段比例", example = "0.3000") + @DecimalMin(value = "0.0000", message = "currentDesignStageRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "currentDesignStageRatio must be <= 1") private BigDecimal currentDesignStageRatio; @Schema(description = "审核审定是否外包", example = "false") private Boolean reviewOutsourceFlag; @Schema(description = "审核审定占比", example = "0.0600") + @DecimalMin(value = "0.0000", message = "reviewOutsourceRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "reviewOutsourceRatio must be <= 1") private BigDecimal reviewOutsourceRatio; @Schema(description = "总分配比例", example = "1.0000") + @DecimalMin(value = "0.0000", message = "totalDistributionAmount must be >= 0") + @DecimalMax(value = "1.0000", message = "totalDistributionAmount must be <= 1") private BigDecimal totalDistributionAmount; - @Schema(description = "Planning progress remark", example = "Q1 extraction arrangement") - @Size(max = 500, message = "Progress remark length must be within 500 characters") + @Schema(description = "提取进度备注", example = "Q1 提取安排") + @Size(max = 500, message = "提取进度备注长度不能超过 500 个字符") private String progressRemark; @Schema(description = "楼栋数或户型数", example = "10") @@ -85,12 +102,14 @@ public class ProjectPlanningSaveReqVO { private BigDecimal modificationFactor; @Schema(description = "复杂系数/复杂等级,按比例值存储(100%=1.0000)", example = "1.0000") + @DecimalMin(value = "0.0000", message = "complexityFactor must be >= 0") + @DecimalMax(value = "1.0000", message = "complexityFactor must be <= 1") private BigDecimal complexityFactor; @Schema(description = "内部指导单价(元/㎡)", example = "80.00") private BigDecimal internalGuidanceUnitPrice; - @Schema(description = "虚拟产值计算方式", example = "工日法") + @Schema(description = "虚拟产值计算方式:指导单价法 / 指导总价法 / 工日法", example = "工日法") @Size(max = 30, message = "虚拟产值计算方式长度不能超过 30 个字符") private String virtualCalculationMethod; @@ -106,10 +125,9 @@ public class ProjectPlanningSaveReqVO { @Schema(description = "指导总价", example = "880000.00") private BigDecimal guidanceTotalPrice; - @Schema(description = "虚拟总价", example = "600000.00") - private BigDecimal virtualTotalPrice; - @Schema(description = "产值计算比例", example = "0.0800") + @DecimalMin(value = "0.0000", message = "calculationRatio must be >= 0") + @DecimalMax(value = "1.0000", message = "calculationRatio must be <= 1") private BigDecimal calculationRatio; } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java index 2243d68..7a1092b 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterRespVO.java @@ -6,32 +6,29 @@ import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; -@Schema(description = "管理后台 - 季度分配 Response VO") +@Schema(description = "Admin - project planning quarter Response VO") @Data public class ProjectPlanningQuarterRespVO { - @Schema(description = "季度分配 ID", example = "1") + @Schema(description = "Quarter distribution ID", example = "1") private Long id; - @Schema(description = "合约规划 ID", example = "1") + @Schema(description = "Planning ID", example = "1") private Long planningId; - @Schema(description = "分配年度") + @Schema(description = "Distribution year") private Integer distributionYear; - @Schema(description = "季度") + @Schema(description = "Quarter number") private Integer quarterNo; - @Schema(description = "分配比例") + @Schema(description = "Distribution ratio") private BigDecimal distributionRatio; - @Schema(description = "分配金额") + @Schema(description = "Distribution amount") private BigDecimal distributionAmount; - @Schema(description = "提取进度备注") - private String progressRemark; - - @Schema(description = "创建时间") + @Schema(description = "Create time") 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 index 8cfc307..11a2a4b 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/planningquarter/vo/ProjectPlanningQuarterSaveReqVO.java @@ -6,35 +6,30 @@ 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") +@Schema(description = "Admin - project planning quarter save Request VO") @Data public class ProjectPlanningQuarterSaveReqVO { - @Schema(description = "季度分配 ID", example = "1") + @Schema(description = "Quarter distribution ID", example = "1") private Long id; - @Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "合约规划 ID 不能为空") + @Schema(description = "Planning ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "planningId cannot be null") private Long planningId; - @Schema(description = "分配年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") - @NotNull(message = "分配年度不能为空") + @Schema(description = "Distribution year", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "distributionYear cannot be null") 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 之间") + @Schema(description = "Quarter number", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "quarterNo cannot be null") + @Min(value = 1, message = "quarterNo must be between 1 and 4") + @Max(value = 4, message = "quarterNo must be between 1 and 4") private Integer quarterNo; - @Schema(description = "分配比例", example = "0.2500") + @Schema(description = "Distribution ratio", 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/vo/ProjectProfitRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java index f0a8ff2..75d8a89 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/profit/vo/ProjectProfitRespVO.java @@ -34,12 +34,18 @@ public class ProjectProfitRespVO { @Schema(description = "专业所产值") private BigDecimal majorOutputValue; - @Schema(description = "预计 K 值") - private BigDecimal expectedKValue; - @Schema(description = "专业所预计绩效") private BigDecimal majorExpectedPerformance; + @Schema(description = "科创产值比例") + private BigDecimal innovationOutputRate; + + @Schema(description = "科创产值") + private BigDecimal innovationOutputValue; + + @Schema(description = "其他成本") + private BigDecimal otherCost; + @Schema(description = "盈亏值") private BigDecimal profitLossValue; 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 index f751d12..302c014 100644 --- 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 @@ -2,11 +2,9 @@ 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; @@ -67,8 +65,7 @@ public class ProjectController { @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)); + return success(projectService.getProjectPage(pageReqVO)); } @GetMapping("/get") @@ -76,7 +73,7 @@ public class ProjectController { @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)); + return success(projectService.getProject(id)); } } 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 index 94a1ed0..773f0ef 100644 --- 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 @@ -24,6 +24,9 @@ public class ProjectPageReqVO extends PageParam { @Schema(description = "项目开始年度", example = "2026") private Integer projectStartYear; + @Schema(description = "项目状态", example = "进行中") + private String projectStatus; + @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 index 074262e..3378bbb 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRespVO.java @@ -9,6 +9,7 @@ import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; @Schema(description = "管理后台 - 项目 Response VO") @Data @@ -66,20 +67,47 @@ public class ProjectRespVO { @ExcelProperty("工程类型") private String projectType; + @Schema(description = "工程类别") + @ExcelProperty("工程类别") + private String projectCategory; + @Schema(description = "项目开始年度") @ExcelProperty("项目开始年度") private Integer projectStartYear; + @Schema(description = "项目状态") + @ExcelProperty("项目状态") + private String projectStatus; + + @Schema(description = "是否封档") + private Boolean archiveFlag; + + @Schema(description = "封档时间") + private LocalDateTime archiveTime; + + @Schema(description = "暂停原因") + private String pauseReason; + + @Schema(description = "中止原因") + private String terminateReason; + @Schema(description = "最终结算金额") @ExcelProperty("最终结算金额") private BigDecimal finalSettlementAmount; - @Schema(description = "预计 K 值") - @ExcelProperty("预计 K 值") - private BigDecimal expectedKValue; + @Schema(description = "科创产值比例") + @ExcelProperty("科创产值比例") + private BigDecimal innovationOutputRate; + + @Schema(description = "其他成本") + @ExcelProperty("其他成本") + private BigDecimal otherCost; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("创建时间") private LocalDateTime createTime; + @Schema(description = "项目角色人员列表") + private List rolePersons; + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRolePersonRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRolePersonRespVO.java new file mode 100644 index 0000000..aeb0507 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRolePersonRespVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 项目角色人员 Response VO") +@Data +public class ProjectRolePersonRespVO { + + @Schema(description = "主键 ID", example = "1") + private Long id; + + @Schema(description = "项目 ID", example = "1") + private Long projectId; + + @Schema(description = "角色编码", example = "project_manager") + private String roleCode; + + @Schema(description = "角色名称", example = "项目经理") + private String roleName; + + @Schema(description = "员工 ID", example = "1") + private Long employeeId; + + @Schema(description = "员工姓名", example = "张三") + private String employeeName; + + @Schema(description = "排序号", example = "1") + private Integer sortNo; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRolePersonSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRolePersonSaveReqVO.java new file mode 100644 index 0000000..c3f11e1 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectRolePersonSaveReqVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.project.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; + +@Schema(description = "管理后台 - 项目角色人员新增/修改 Request VO") +@Data +public class ProjectRolePersonSaveReqVO { + + @Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "project_manager") + @NotBlank(message = "角色编码不能为空") + private String roleCode; + + @Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "员工 ID 不能为空") + private Long employeeId; + + @Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三") + @Size(max = 64, message = "员工姓名长度不能超过 64 个字符") + private String employeeName; + + @Schema(description = "排序号", example = "1") + private Integer sortNo; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java index 0ae08ad..0b4971d 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/project/vo/ProjectSaveReqVO.java @@ -5,6 +5,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; +import javax.validation.Valid; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -54,26 +57,43 @@ public class ProjectSaveReqVO { @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 = "住宅") + @Schema(description = "工程类型", example = "建筑工程") @Size(max = 50, message = "工程类型长度不能超过 50 个字符") private String projectType; + @Schema(description = "工程类别", example = "住宅") + @Size(max = 50, message = "工程类别长度不能超过 50 个字符") + private String projectCategory; + @Schema(description = "项目开始年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") @NotNull(message = "项目开始年度不能为空") private Integer projectStartYear; + @Schema(description = "项目状态", example = "进行中") + @Size(max = 20, message = "项目状态长度不能超过 20 个字符") + private String projectStatus; + + @Schema(description = "暂停原因") + @Size(max = 255, message = "暂停原因长度不能超过 255 个字符") + private String pauseReason; + + @Schema(description = "中止原因") + @Size(max = 255, message = "中止原因长度不能超过 255 个字符") + private String terminateReason; + @Schema(description = "最终结算金额", example = "1200000") private BigDecimal finalSettlementAmount; - @Schema(description = "预计 K 值", example = "0.8500") - private BigDecimal expectedKValue; + @Schema(description = "科创产值比例", example = "0.0100") + @DecimalMin(value = "0.0000", message = "科创产值比例不能小于 0%") + @DecimalMax(value = "1.0000", message = "科创产值比例不能大于 100%") + private BigDecimal innovationOutputRate; + + @Schema(description = "其他成本", example = "20000.00") + private BigDecimal otherCost; + + @Schema(description = "项目角色人员列表") + @Valid + private java.util.List rolePersons; } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/ProjectOutputReportController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/ProjectOutputReportController.java new file mode 100644 index 0000000..c583200 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/ProjectOutputReportController.java @@ -0,0 +1,91 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report; + +import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectBudgetExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectLeadQuarterOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectOverviewExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectQuarterOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.SpecialtyPersonOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.service.report.ProjectOutputReportService; +import io.swagger.v3.oas.annotations.Operation; +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.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; + +import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 产值报表导出") +@RestController +@RequestMapping("/tjt/report") +@Validated +public class ProjectOutputReportController { + + @Resource + private ProjectOutputReportService projectOutputReportService; + + @GetMapping("/project-budget/export-excel") + @Operation(summary = "导出项目考核产值预算表") + @PreAuthorize("@ss.hasPermission('tjt:report-budget:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProjectBudgetExcel(HttpServletResponse response, + @Valid ProjectBudgetExportReqVO reqVO) throws IOException { + projectOutputReportService.exportProjectBudgetExcel(response, reqVO); + } + + @GetMapping("/project-quarter-output/export-excel") + @Operation(summary = "导出项目级年度季度计取表") + @PreAuthorize("@ss.hasPermission('tjt:report-project-quarter:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProjectQuarterOutputExcel(HttpServletResponse response, + @Valid ProjectQuarterOutputExportReqVO reqVO) throws IOException { + projectOutputReportService.exportProjectQuarterOutputExcel(response, reqVO); + } + + @GetMapping("/project-lead-quarter-output/export-excel") + @Operation(summary = "导出项目负责人年度季度计取表") + @PreAuthorize("@ss.hasPermission('tjt:report-project-quarter:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProjectLeadQuarterOutputExcel(HttpServletResponse response, + @Valid ProjectLeadQuarterOutputExportReqVO reqVO) + throws IOException { + projectOutputReportService.exportProjectLeadQuarterOutputExcel(response, reqVO); + } + + @GetMapping("/specialty-person-output/export-excel") + @Operation(summary = "导出专业内人员年度季度计取表") + @PreAuthorize("@ss.hasPermission('tjt:report-specialty-person:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSpecialtyPersonOutputExcel(HttpServletResponse response, + @Valid SpecialtyPersonOutputExportReqVO reqVO) throws IOException { + projectOutputReportService.exportSpecialtyPersonOutputExcel(response, reqVO); + } + + @GetMapping("/project-overview/export-excel") + @Operation(summary = "导出项目总览表") + @PreAuthorize("@ss.hasPermission('tjt:report-summary:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProjectOverviewExcel(HttpServletResponse response, + @Valid ProjectOverviewExportReqVO reqVO) throws IOException { + projectOutputReportService.exportProjectOverviewExcel(response, reqVO); + } + + @GetMapping("/employee-output-summary/export-excel") + @Operation(summary = "导出员工个人考核产值汇总表") + @PreAuthorize("@ss.hasPermission('tjt:report-summary:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportEmployeeOutputSummaryExcel(HttpServletResponse response, + @Valid EmployeeOutputSummaryExportReqVO reqVO) + throws IOException { + projectOutputReportService.exportEmployeeOutputSummaryExcel(response, reqVO); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/EmployeeOutputSummaryExcelRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/EmployeeOutputSummaryExcelRespVO.java new file mode 100644 index 0000000..867888c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/EmployeeOutputSummaryExcelRespVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 员工个人考核产值汇总 Excel 导出 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EmployeeOutputSummaryExcelRespVO { + + @ExcelProperty("序号") + private Integer serialNo; + + @ExcelProperty("姓名") + private String employeeName; + + @ExcelProperty("所属专业所") + private String officeName; + + @ExcelProperty("第一季度") + private BigDecimal quarterOneAmount; + + @ExcelProperty("第二季度") + private BigDecimal quarterTwoAmount; + + @ExcelProperty("第三季度") + private BigDecimal quarterThreeAmount; + + @ExcelProperty("第四季度") + private BigDecimal quarterFourAmount; + + @ExcelProperty("年度合计") + private BigDecimal annualTotalAmount; + + @ExcelProperty("所长/BIM考核产值") + private BigDecimal officeLeaderOrBimAmount; + + @ExcelProperty("年度考核产值合计") + private BigDecimal totalAssessmentOutputAmount; + + @ExcelProperty("1~12月份预计发生成本") + private BigDecimal expectedCostAmount; + + @ExcelProperty("基本考核产值") + private BigDecimal basicAssessmentOutputAmount; + + @ExcelProperty("剩余产值") + private BigDecimal remainingOutputAmount; + + @ExcelProperty("预估年底绩效") + private BigDecimal estimatedYearEndPerformanceAmount; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/EmployeeOutputSummaryExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/EmployeeOutputSummaryExportReqVO.java new file mode 100644 index 0000000..cf90a71 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/EmployeeOutputSummaryExportReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 员工个人考核产值汇总导出 Request VO") +@Data +public class EmployeeOutputSummaryExportReqVO { + + @Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "年度不能为空") + private Integer year; + + @Schema(description = "专业所 ID", example = "1") + private Long officeId; + + @Schema(description = "员工 ID", example = "1") + private Long employeeId; + + @Schema(description = "员工状态", example = "在职") + private String employeeStatus; + + @Schema(description = "排序方式", example = "annual_total_desc") + private String sortType; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectBudgetExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectBudgetExportReqVO.java new file mode 100644 index 0000000..2de2236 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectBudgetExportReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 项目考核产值预算表导出 Request VO") +@Data +public class ProjectBudgetExportReqVO { + + @Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "项目 ID 不能为空") + private Long projectId; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectLeadQuarterOutputExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectLeadQuarterOutputExportReqVO.java new file mode 100644 index 0000000..a81a437 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectLeadQuarterOutputExportReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 项目负责人年度季度计取表导出 Request VO") +@Data +public class ProjectLeadQuarterOutputExportReqVO { + + @Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "合约规划 ID 不能为空") + private Long planningId; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExcelRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExcelRespVO.java new file mode 100644 index 0000000..81a2daf --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExcelRespVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 项目总览表 Excel 导出 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProjectOverviewExcelRespVO { + + @ExcelProperty("序号") + private Integer serialNo; + + @ExcelProperty("项目名称") + private String projectName; + + @ExcelProperty("规划内容") + private String planningContent; + + @ExcelProperty("工程进度情况及其它说明") + private String progressText; + + @ExcelProperty("工作阶段") + private String designStage; + + @ExcelProperty("本专业+项总核算总产值") + private BigDecimal totalOutputAmount; + + @ExcelProperty("往年已发放比例") + private String historicalIssuedRatioText; + + @ExcelProperty("本期结算") + private BigDecimal currentSettlementAmount; + + @ExcelProperty("未结算比例") + private String pendingRatioText; + + @ExcelProperty("一季度") + private BigDecimal quarterOneAmount; + + @ExcelProperty("二季度") + private BigDecimal quarterTwoAmount; + + @ExcelProperty("三季度") + private BigDecimal quarterThreeAmount; + + @ExcelProperty("四季度") + private BigDecimal quarterFourAmount; + + @ExcelProperty("本年度小计") + private BigDecimal yearTotalAmount; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java new file mode 100644 index 0000000..f17392c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 项目总览表导出 Request VO") +@Data +public class ProjectOverviewExportReqVO { + + @Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "年度不能为空") + private Integer year; + + @Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "water") + @NotBlank(message = "专业编码不能为空") + private String specialtyCode; + + @Schema(description = "排序方式", example = "output_desc") + private String sortType; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectQuarterOutputExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectQuarterOutputExportReqVO.java new file mode 100644 index 0000000..a7e167c --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectQuarterOutputExportReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 项目级年度季度计取表导出 Request VO") +@Data +public class ProjectQuarterOutputExportReqVO { + + @Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "合约规划 ID 不能为空") + private Long planningId; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/SpecialtyPersonOutputExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/SpecialtyPersonOutputExportReqVO.java new file mode 100644 index 0000000..1250175 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/SpecialtyPersonOutputExportReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 专业内人员年度季度计取表导出 Request VO") +@Data +public class SpecialtyPersonOutputExportReqVO { + + @Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "合约规划 ID 不能为空") + private Long planningId; + + @Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "water") + @NotBlank(message = "专业编码不能为空") + private String specialtyCode; + +} 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 index 1e02e7f..84519bc 100644 --- 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 @@ -9,8 +9,11 @@ import java.math.BigDecimal; @Data public class SpecialtyRolePersonRespVO { - @Schema(description = "人员名称") - private String personName; + @Schema(description = "员工 ID") + private Long employeeId; + + @Schema(description = "员工姓名") + private String employeeName; @Schema(description = "人员比例") private BigDecimal personRatio; 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 index ca6104a..c906e56 100644 --- 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 @@ -9,8 +9,11 @@ import java.math.BigDecimal; @Data public class SpecialtyRolePersonSaveReqVO { - @Schema(description = "人员名称", example = "张三") - private String personName; + @Schema(description = "员工 ID", example = "1") + private Long employeeId; + + @Schema(description = "员工姓名", example = "张三") + private String employeeName; @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/SpecialtyRoleSplitRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/specialtyrolesplit/vo/SpecialtyRoleSplitRespVO.java index dee9f2a..d23c28d 100644 --- 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 @@ -16,7 +16,7 @@ public class SpecialtyRoleSplitRespVO { @Schema(description = "页面4拆分 ID", example = "1") private Long outputSplitId; - @Schema(description = "合同规划 ID", example = "1") + @Schema(description = "合约规划 ID", example = "1") private Long planningId; @Schema(description = "项目名称") diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/YearKValueController.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/YearKValueController.java new file mode 100644 index 0000000..c21e6a6 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/YearKValueController.java @@ -0,0 +1,104 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue; + +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.yearkvalue.vo.YearKValuePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.service.yearkvalue.YearKValueService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.math.BigDecimal; +import java.util.Map; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; +import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 年度 K 值") +@RestController +@RequestMapping("/tjt/year-k-value") +@Validated +public class YearKValueController { + + @Resource + private YearKValueService yearKValueService; + + @PostMapping("/create") + @Operation(summary = "创建年度 K 值") + @PreAuthorize("@ss.hasPermission('tjt:year-k-value:create')") + public CommonResult createYearKValue(@RequestBody Map requestBody) { + YearKValueSaveReqVO createReqVO = buildSaveReqVO(requestBody, false); + return success(yearKValueService.createYearKValue(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改年度 K 值") + @PreAuthorize("@ss.hasPermission('tjt:year-k-value:update')") + public CommonResult updateYearKValue(@RequestBody Map requestBody) { + YearKValueSaveReqVO updateReqVO = buildSaveReqVO(requestBody, true); + yearKValueService.updateYearKValue(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除年度 K 值") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:year-k-value:delete')") + public CommonResult deleteYearKValue(@RequestParam("id") Long id) { + yearKValueService.deleteYearKValue(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得年度 K 值详情") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('tjt:year-k-value:query')") + public CommonResult getYearKValue(@RequestParam("id") Long id) { + return success(yearKValueService.getYearKValue(id)); + } + + @GetMapping("/page") + @Operation(summary = "获得年度 K 值分页") + @PreAuthorize("@ss.hasPermission('tjt:year-k-value:query')") + public CommonResult> getYearKValuePage(@Valid YearKValuePageReqVO pageReqVO) { + return success(yearKValueService.getYearKValuePage(pageReqVO)); + } + + private YearKValueSaveReqVO buildSaveReqVO(Map requestBody, boolean requireId) { + YearKValueSaveReqVO reqVO = BeanUtils.toBean(requestBody, YearKValueSaveReqVO.class); + if (requireId && reqVO.getId() == null) { + throw invalidParamException("编号不能为空"); + } + if (reqVO.getKYear() == null) { + throw invalidParamException("年度不能为空"); + } + if (reqVO.getKValue() == null) { + throw invalidParamException("K值不能为空"); + } + if (reqVO.getKValue().compareTo(BigDecimal.ZERO) < 0 + || reqVO.getKValue().compareTo(BigDecimal.ONE) > 0) { + throw invalidParamException("K值必须在 0% 到 100% 之间"); + } + if (reqVO.getRemark() != null && reqVO.getRemark().length() > 255) { + throw invalidParamException("备注长度不能超过 255 个字符"); + } + return reqVO; + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValuePageReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValuePageReqVO.java new file mode 100644 index 0000000..23fa2f6 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValuePageReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo; + +import cn.iocoder.lyzsys.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 年度 K 值分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class YearKValuePageReqVO extends PageParam { + + @Schema(description = "年度", example = "2026") + private Integer kYear; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValueRespVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValueRespVO.java new file mode 100644 index 0000000..47114bd --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValueRespVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 年度 K 值 Response VO") +@Data +public class YearKValueRespVO { + + @Schema(description = "主键 ID", example = "1") + private Long id; + + @Schema(description = "年度", example = "2026") + private Integer kYear; + + @Schema(description = "K 值", example = "0.4000") + private BigDecimal kValue; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "是否启用") + private Boolean enabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValueSaveReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValueSaveReqVO.java new file mode 100644 index 0000000..e0c5319 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/yearkvalue/vo/YearKValueSaveReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 年度 K 值新增/修改 Request VO") +@Data +public class YearKValueSaveReqVO { + + @Schema(description = "主键 ID", example = "1") + private Long id; + + @Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") + @NotNull(message = "年度不能为空") + private Integer kYear; + + @Schema(description = "K 值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.4000") + @NotNull(message = "K 值不能为空") + @DecimalMin(value = "0.0000", message = "K 值不能小于 0%") + @DecimalMax(value = "1.0000", message = "K 值不能大于 100%") + private BigDecimal kValue; + + @Schema(description = "备注") + @Size(max = 255, message = "备注长度不能超过 255 个字符") + private String remark; + + @Schema(description = "是否启用", example = "true") + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/employee/EmployeeDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/employee/EmployeeDO.java new file mode 100644 index 0000000..6528e3e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/employee/EmployeeDO.java @@ -0,0 +1,45 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee; + +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.time.LocalDate; + +@TableName("tjt_employee") +@KeySequence("tjt_employee_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class EmployeeDO extends TenantBaseDO { + + @TableId + private Long id; + + private String employeeName; + + private String gender; + + private Long officeId; + + private String registrationType; + + private String jobTitle; + + private String registrationSealNo; + + private LocalDate entryDate; + + private LocalDate leaveDate; + + private String employeeStatus; + + private String remark; + + private Integer sortNo; + + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/employeeyearcostbudget/EmployeeYearCostBudgetDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/employeeyearcostbudget/EmployeeYearCostBudgetDO.java new file mode 100644 index 0000000..8b5099d --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/employeeyearcostbudget/EmployeeYearCostBudgetDO.java @@ -0,0 +1,35 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget; + +import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +@TableName("tjt_employee_year_cost_budget") +@KeySequence("tjt_employee_year_cost_budget_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class EmployeeYearCostBudgetDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long employeeId; + + private String employeeName; + + private Integer budgetYear; + + private BigDecimal expectedCostAmount; + + private String remark; + + private Integer sortNo; + + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/office/OfficeDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/office/OfficeDO.java new file mode 100644 index 0000000..8c7e2dd --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/office/OfficeDO.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.office; + +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; + +@TableName("tjt_office") +@KeySequence("tjt_office_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class OfficeDO extends TenantBaseDO { + + @TableId + private Long id; + + private String officeName; + + private String officeCode; + + private Integer sortNo; + + private Boolean enabledFlag; + + private String remark; + +} 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 index f139657..472ba99 100644 --- 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 @@ -29,9 +29,7 @@ public class ProjectOutputSplitDO extends TenantBaseDO { private Integer year; - private BigDecimal projectManagerRatio; - - private BigDecimal engineeringLeaderRatio; + private BigDecimal projectLeadRatio; private BigDecimal officeRatio; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java index 1c8771c..e71e488 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/planning/ProjectPlanningDO.java @@ -27,6 +27,10 @@ public class ProjectPlanningDO extends TenantBaseDO { private String ownershipType; + private String designPart; + + private String buildingType; + private String calculationMethod; private String planningContent; @@ -77,8 +81,6 @@ public class ProjectPlanningDO extends TenantBaseDO { private BigDecimal guidanceTotalPrice; - private BigDecimal virtualTotalPrice; - private BigDecimal calculationRatio; private BigDecimal contractUnitPrice; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java index b0cf649..fdb9cdf 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/project/ProjectDO.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; /** * 项目 DO @@ -58,29 +59,49 @@ public class ProjectDO extends TenantBaseDO { * 合同签订日期 */ private LocalDate contractSigningDate; - /** - * 项目经理 - */ - private String projectManagerName; - /** - * 工程负责人 - */ - private String engineeringPrincipalName; /** * 工程类型 */ private String projectType; + /** + * 工程类别 + */ + private String projectCategory; /** * 项目开始年度 */ private Integer projectStartYear; + /** + * 项目状态 + */ + private String projectStatus; + /** + * 是否封档 + */ + private Boolean archiveFlag; + /** + * 封档时间 + */ + private LocalDateTime archiveTime; + /** + * 暂停原因 + */ + private String pauseReason; + /** + * 中止原因 + */ + private String terminateReason; /** * 最终结算金额 */ private BigDecimal finalSettlementAmount; /** - * 预计 K 值 + * 科创产值比例 */ - private BigDecimal expectedKValue; + private BigDecimal innovationOutputRate; + /** + * 其他成本 + */ + private BigDecimal otherCost; } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/projectroleperson/ProjectRolePersonDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/projectroleperson/ProjectRolePersonDO.java new file mode 100644 index 0000000..fa79b38 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/projectroleperson/ProjectRolePersonDO.java @@ -0,0 +1,33 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson; + +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; + +@TableName("tjt_project_role_person") +@KeySequence("tjt_project_role_person_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectRolePersonDO extends TenantBaseDO { + + @TableId + private Long id; + + private Long projectId; + + private String roleCode; + + private String roleName; + + private Long employeeId; + + private String employeeName; + + private Integer sortNo; + + private Boolean enabledFlag; + +} 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 index d1c1f5e..cc15458 100644 --- 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 @@ -35,7 +35,7 @@ public class SpecialtyRoleSplitDO extends TenantBaseDO { private BigDecimal roleRatio; - private String personNames; + private String personsJson; private Integer sortNo; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/yearkvalue/YearKValueDO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/yearkvalue/YearKValueDO.java new file mode 100644 index 0000000..8336e63 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/dataobject/yearkvalue/YearKValueDO.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue; + +import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +@TableName("tjt_year_k_value") +@KeySequence("tjt_year_k_value_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class YearKValueDO extends TenantBaseDO { + + @TableId + private Long id; + + private Integer kYear; + + private BigDecimal kValue; + + private String remark; + + private Boolean enabledFlag; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/employee/EmployeeMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/employee/EmployeeMapper.java new file mode 100644 index 0000000..ecf9e51 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/employee/EmployeeMapper.java @@ -0,0 +1,40 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.employee; + +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.employee.vo.EmployeePageReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface EmployeeMapper extends BaseMapperX { + + default PageResult selectPage(EmployeePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(EmployeeDO::getEmployeeName, reqVO.getEmployeeName()) + .eqIfPresent(EmployeeDO::getOfficeId, reqVO.getOfficeId()) + .eqIfPresent(EmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus()) + .eqIfPresent(EmployeeDO::getEnabledFlag, reqVO.getEnabledFlag()) + .orderByAsc(EmployeeDO::getSortNo) + .orderByAsc(EmployeeDO::getId)); + } + + default List selectSimpleList(String keyword, Long officeId, String status, Boolean enabledFlag) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(EmployeeDO::getEmployeeName, keyword) + .eqIfPresent(EmployeeDO::getOfficeId, officeId) + .eqIfPresent(EmployeeDO::getEmployeeStatus, status) + .eqIfPresent(EmployeeDO::getEnabledFlag, enabledFlag) + .orderByAsc(EmployeeDO::getSortNo) + .orderByAsc(EmployeeDO::getId)); + } + + default Long selectCountByOfficeId(Long officeId) { + return selectCount(new LambdaQueryWrapperX() + .eq(EmployeeDO::getOfficeId, officeId)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/employeeyearcostbudget/EmployeeYearCostBudgetMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/employeeyearcostbudget/EmployeeYearCostBudgetMapper.java new file mode 100644 index 0000000..f7b1972 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/employeeyearcostbudget/EmployeeYearCostBudgetMapper.java @@ -0,0 +1,35 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget; + +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.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget.EmployeeYearCostBudgetDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface EmployeeYearCostBudgetMapper extends BaseMapperX { + + default PageResult selectPage(EmployeeYearCostBudgetPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(EmployeeYearCostBudgetDO::getEmployeeId, reqVO.getEmployeeId()) + .likeIfPresent(EmployeeYearCostBudgetDO::getEmployeeName, reqVO.getEmployeeName()) + .eqIfPresent(EmployeeYearCostBudgetDO::getBudgetYear, reqVO.getBudgetYear()) + .eqIfPresent(EmployeeYearCostBudgetDO::getEnabledFlag, reqVO.getEnabledFlag()) + .orderByDesc(EmployeeYearCostBudgetDO::getBudgetYear) + .orderByAsc(EmployeeYearCostBudgetDO::getSortNo) + .orderByAsc(EmployeeYearCostBudgetDO::getId)); + } + + default EmployeeYearCostBudgetDO selectByEmployeeIdAndBudgetYear(Long employeeId, Integer budgetYear) { + return selectOne(new LambdaQueryWrapperX() + .eq(EmployeeYearCostBudgetDO::getEmployeeId, employeeId) + .eq(EmployeeYearCostBudgetDO::getBudgetYear, budgetYear)); + } + + default Long selectCountByEmployeeId(Long employeeId) { + return selectCount(new LambdaQueryWrapperX() + .eq(EmployeeYearCostBudgetDO::getEmployeeId, employeeId)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/office/OfficeMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/office/OfficeMapper.java new file mode 100644 index 0000000..5281991 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/office/OfficeMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.office; + +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.office.vo.OfficePageReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface OfficeMapper extends BaseMapperX { + + default PageResult selectPage(OfficePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(OfficeDO::getOfficeName, reqVO.getOfficeName()) + .eqIfPresent(OfficeDO::getEnabledFlag, reqVO.getEnabledFlag()) + .orderByAsc(OfficeDO::getSortNo) + .orderByAsc(OfficeDO::getId)); + } + + default List selectSimpleList() { + return selectList(new LambdaQueryWrapperX() + .eq(OfficeDO::getEnabledFlag, Boolean.TRUE) + .orderByAsc(OfficeDO::getSortNo) + .orderByAsc(OfficeDO::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 index 5eac064..735ce1c 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/project/ProjectMapper.java @@ -20,6 +20,7 @@ public interface ProjectMapper extends BaseMapperX { .likeIfPresent(ProjectDO::getProjectName, reqVO.getProjectName()) .eqIfPresent(ProjectDO::getContractSignedFlag, reqVO.getContractSignedFlag()) .eqIfPresent(ProjectDO::getProjectStartYear, reqVO.getProjectStartYear()) + .eqIfPresent(ProjectDO::getProjectStatus, reqVO.getProjectStatus()) .betweenIfPresent(ProjectDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(ProjectDO::getId)); } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/projectroleperson/ProjectRolePersonMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/projectroleperson/ProjectRolePersonMapper.java new file mode 100644 index 0000000..e9ffac5 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/projectroleperson/ProjectRolePersonMapper.java @@ -0,0 +1,42 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson; + +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.projectroleperson.ProjectRolePersonDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@Mapper +public interface ProjectRolePersonMapper extends BaseMapperX { + + default List selectListByProjectId(Long projectId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectRolePersonDO::getProjectId, projectId) + .eq(ProjectRolePersonDO::getEnabledFlag, Boolean.TRUE) + .orderByAsc(ProjectRolePersonDO::getRoleCode) + .orderByAsc(ProjectRolePersonDO::getSortNo) + .orderByAsc(ProjectRolePersonDO::getId)); + } + + default List selectListByProjectIds(Collection projectIds) { + if (projectIds == null || projectIds.isEmpty()) { + return Collections.emptyList(); + } + return selectList(new LambdaQueryWrapperX() + .in(ProjectRolePersonDO::getProjectId, projectIds) + .eq(ProjectRolePersonDO::getEnabledFlag, Boolean.TRUE) + .orderByAsc(ProjectRolePersonDO::getProjectId) + .orderByAsc(ProjectRolePersonDO::getRoleCode) + .orderByAsc(ProjectRolePersonDO::getSortNo) + .orderByAsc(ProjectRolePersonDO::getId)); + } + + default Long selectCountByEmployeeId(Long employeeId) { + return selectCount(new LambdaQueryWrapperX() + .eq(ProjectRolePersonDO::getEmployeeId, employeeId)); + } + +} 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 index 1a12a9f..aca69ac 100644 --- 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 @@ -22,4 +22,9 @@ public interface SpecialtyRoleSplitMapper extends BaseMapperX selectListByPersonsJsonKeyword(String keyword) { + return selectList(new LambdaQueryWrapperX() + .like(SpecialtyRoleSplitDO::getPersonsJson, keyword)); + } + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/yearkvalue/YearKValueMapper.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/yearkvalue/YearKValueMapper.java new file mode 100644 index 0000000..0876720 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/dal/mysql/yearkvalue/YearKValueMapper.java @@ -0,0 +1,44 @@ +package cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue; + +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.yearkvalue.vo.YearKValuePageReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@Mapper +public interface YearKValueMapper extends BaseMapperX { + + default PageResult selectPage(YearKValuePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(YearKValueDO::getKYear, reqVO.getKYear()) + .eqIfPresent(YearKValueDO::getEnabledFlag, reqVO.getEnabledFlag()) + .orderByAsc(YearKValueDO::getKYear)); + } + + default YearKValueDO selectByKYear(Integer kYear) { + return selectOne(new LambdaQueryWrapperX() + .eq(YearKValueDO::getKYear, kYear)); + } + + default YearKValueDO selectEnabledByKYear(Integer kYear) { + return selectOne(new LambdaQueryWrapperX() + .eq(YearKValueDO::getKYear, kYear) + .eq(YearKValueDO::getEnabledFlag, Boolean.TRUE)); + } + + default List selectEnabledListByYears(Collection years) { + if (years == null || years.isEmpty()) { + return Collections.emptyList(); + } + return selectList(new LambdaQueryWrapperX() + .in(YearKValueDO::getKYear, years) + .eq(YearKValueDO::getEnabledFlag, Boolean.TRUE)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java index 0d085fe..5e07ab5 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/enums/ErrorCodeConstants.java @@ -20,6 +20,9 @@ public interface ErrorCodeConstants { 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, "虚拟产值计算方式不正确"); + ErrorCode PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED = new ErrorCode(1_020_002_007, "工日法下工日不能为空"); + ErrorCode PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED = new ErrorCode(1_020_002_008, "工日法下工日单价不能为空"); + ErrorCode PROJECT_PLANNING_DESIGN_PART_INVALID = new ErrorCode(1_020_002_009, "设计部位不正确"); // ========== 季度分配管理 1-020-003-000 ========== ErrorCode PROJECT_PLANNING_QUARTER_NOT_EXISTS = new ErrorCode(1_020_003_000, "季度分配明细不存在"); @@ -36,8 +39,24 @@ public interface ErrorCodeConstants { 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_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%"); + // ========== 年度 K 值管理 1-020-006-000 ========== + ErrorCode YEAR_K_VALUE_NOT_EXISTS = new ErrorCode(1_020_006_000, "年度 K 值记录不存在"); + ErrorCode YEAR_K_VALUE_DUPLICATE = new ErrorCode(1_020_006_001, "同一年度的 K 值记录已存在"); + + // ========== 专业所管理 1-020-007-000 ========== + ErrorCode OFFICE_NOT_EXISTS = new ErrorCode(1_020_007_000, "专业所记录不存在"); + ErrorCode OFFICE_IN_USE = new ErrorCode(1_020_007_001, "当前专业所已被员工引用,不能删除"); + + // ========== 员工管理 1-020-008-000 ========== + ErrorCode EMPLOYEE_NOT_EXISTS = new ErrorCode(1_020_008_000, "员工记录不存在"); + ErrorCode EMPLOYEE_IN_USE = new ErrorCode(1_020_008_001, "当前员工已被业务数据引用,不能删除"); + + // ========== 员工年度成本预算 1-020-009-000 ========== + ErrorCode EMPLOYEE_YEAR_COST_BUDGET_NOT_EXISTS = new ErrorCode(1_020_009_000, "员工年度成本预算记录不存在"); + ErrorCode EMPLOYEE_YEAR_COST_BUDGET_DUPLICATE = new ErrorCode(1_020_009_001, "当前员工该年度预计发生成本已存在"); + } 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 index e95c97b..21601cf 100644 --- 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 @@ -15,8 +15,7 @@ import java.util.Map; */ public final class OutputSplitBizConstants { - public static final String OWNERSHIP_TYPE_MAJOR = "专业所"; - + public static final String SPECIALTY_PROJECT_LEAD = "project_lead"; public static final String SPECIALTY_ARCH = "arch"; public static final String SPECIALTY_DECOR = "decor"; public static final String SPECIALTY_STRUCT = "struct"; @@ -30,18 +29,31 @@ public final class OutputSplitBizConstants { public static final String ROLE_REVIEW = "review"; public static final String ROLE_APPROVE = "approve"; public static final String ROLE_DESIGN = "design"; + public static final String ROLE_PROJECT_MANAGER = "project_manager"; + public static final String ROLE_ENGINEERING_PRINCIPAL = "engineering_principal"; - public static final List SPECIALTY_ITEMS = Arrays.asList( + public static final List ASSIGNMENT_SPECIALTY_ITEMS = Arrays.asList( + new SpecialtyItem(SPECIALTY_PROJECT_LEAD, "项目经理/项目负责人", 0), new SpecialtyItem(SPECIALTY_ARCH, "建筑", 1), - new SpecialtyItem(SPECIALTY_DECOR, "装修", 2), + new SpecialtyItem(SPECIALTY_DECOR, "装饰", 2), new SpecialtyItem(SPECIALTY_STRUCT, "结构", 3), - new SpecialtyItem(SPECIALTY_WATER, "水", 4), + 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( + 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 SPECIALTY_ROLE_ITEMS = Arrays.asList( new RoleItem(ROLE_DIRECTOR, "专业负责人", 1), new RoleItem(ROLE_CHECK, "校对", 2), new RoleItem(ROLE_REVIEW, "审核", 3), @@ -49,23 +61,33 @@ public final class OutputSplitBizConstants { new RoleItem(ROLE_DESIGN, "设计", 5) ); + public static final List PROJECT_LEAD_ROLE_ITEMS = Arrays.asList( + new RoleItem(ROLE_PROJECT_MANAGER, "项目经理", 1), + new RoleItem(ROLE_ENGINEERING_PRINCIPAL, "项目负责人", 2) + ); + private OutputSplitBizConstants() { } public static boolean isMajorOwnershipType(String value) { - return OWNERSHIP_TYPE_MAJOR.equals(value); + return ProjectPlanningBizTypeConstants.isMajor(value); } public static boolean isValidSpecialtyCode(String code) { - return SPECIALTY_ITEMS.stream().anyMatch(item -> item.getCode().equals(code)); + return ASSIGNMENT_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)); + return SPECIALTY_ROLE_ITEMS.stream().anyMatch(item -> item.getCode().equals(code)) + || PROJECT_LEAD_ROLE_ITEMS.stream().anyMatch(item -> item.getCode().equals(code)); + } + + public static boolean isValidRoleCode(String specialtyCode, String roleCode) { + return getRoleItems(specialtyCode).stream().anyMatch(item -> item.getCode().equals(roleCode)); } public static String getSpecialtyName(String code) { - return SPECIALTY_ITEMS.stream() + return ASSIGNMENT_SPECIALTY_ITEMS.stream() .filter(item -> item.getCode().equals(code)) .findFirst() .map(SpecialtyItem::getName) @@ -73,7 +95,7 @@ public final class OutputSplitBizConstants { } public static String getRoleName(String code) { - return ROLE_ITEMS.stream() + return getAllRoleItems().stream() .filter(item -> item.getCode().equals(code)) .findFirst() .map(RoleItem::getName) @@ -81,7 +103,7 @@ public final class OutputSplitBizConstants { } public static int getRoleSortNo(String code) { - return ROLE_ITEMS.stream() + return getAllRoleItems().stream() .filter(item -> item.getCode().equals(code)) .findFirst() .map(RoleItem::getSortNo) @@ -89,7 +111,7 @@ public final class OutputSplitBizConstants { } public static int getSpecialtySortNo(String code) { - return SPECIALTY_ITEMS.stream() + return ASSIGNMENT_SPECIALTY_ITEMS.stream() .filter(item -> item.getCode().equals(code)) .findFirst() .map(SpecialtyItem::getSortNo) @@ -104,6 +126,25 @@ public final class OutputSplitBizConstants { return map; } + public static List getRoleItems(String specialtyCode) { + if (SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + return PROJECT_LEAD_ROLE_ITEMS; + } + return SPECIALTY_ROLE_ITEMS; + } + + private static List getAllRoleItems() { + return Arrays.asList( + PROJECT_LEAD_ROLE_ITEMS.get(0), + PROJECT_LEAD_ROLE_ITEMS.get(1), + SPECIALTY_ROLE_ITEMS.get(0), + SPECIALTY_ROLE_ITEMS.get(1), + SPECIALTY_ROLE_ITEMS.get(2), + SPECIALTY_ROLE_ITEMS.get(3), + SPECIALTY_ROLE_ITEMS.get(4) + ); + } + @Getter @AllArgsConstructor public static class SpecialtyItem { 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 index f77e8cd..4192e73 100644 --- 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 @@ -14,13 +14,15 @@ 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 DESIGN_PART_REAL_ESTATE = "地上部分"; + public static final String DESIGN_PART_UNDERGROUND = "地下部分"; 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_GUIDANCE_TOTAL_PRICE = "指导总价法"; public static final String VIRTUAL_CALCULATION_METHOD_WORKING_DAY = "工日法"; private static final Set OWNERSHIP_TYPES = new HashSet<>(Arrays.asList( @@ -35,9 +37,14 @@ public final class ProjectPlanningBizTypeConstants { CALCULATION_METHOD_VIRTUAL_OUTPUT )); + private static final Set DESIGN_PARTS = new HashSet<>(Arrays.asList( + DESIGN_PART_REAL_ESTATE, + DESIGN_PART_UNDERGROUND + )); + 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_GUIDANCE_TOTAL_PRICE, VIRTUAL_CALCULATION_METHOD_WORKING_DAY )); @@ -52,6 +59,10 @@ public final class ProjectPlanningBizTypeConstants { return CALCULATION_METHODS.contains(value); } + public static boolean isValidDesignPart(String value) { + return DESIGN_PARTS.contains(value); + } + public static boolean isValidVirtualCalculationMethod(String value) { return VIRTUAL_CALCULATION_METHODS.contains(value); } @@ -80,4 +91,16 @@ public final class ProjectPlanningBizTypeConstants { return CALCULATION_METHOD_VIRTUAL_OUTPUT.equals(value); } + public static boolean isWorkingDay(String value) { + return VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(value); + } + + public static boolean isVirtualGuidancePrice(String value) { + return VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE.equals(value); + } + + public static boolean isVirtualGuidanceTotalPrice(String value) { + return VIRTUAL_CALCULATION_METHOD_GUIDANCE_TOTAL_PRICE.equals(value); + } + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employee/EmployeeService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employee/EmployeeService.java new file mode 100644 index 0000000..1ecf6bf --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employee/EmployeeService.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.service.employee; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO; + +import javax.validation.Valid; +import java.util.List; + +public interface EmployeeService { + + Long createEmployee(@Valid EmployeeSaveReqVO createReqVO); + + void updateEmployee(@Valid EmployeeSaveReqVO updateReqVO); + + void deleteEmployee(Long id); + + EmployeeRespVO getEmployee(Long id); + + EmployeeDO validateEmployeeExists(Long id); + + PageResult getEmployeePage(EmployeePageReqVO pageReqVO); + + List getEmployeeSimpleList(String keyword, Long officeId, String status, Boolean enabledFlag); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employee/EmployeeServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employee/EmployeeServiceImpl.java new file mode 100644 index 0000000..3357604 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employee/EmployeeServiceImpl.java @@ -0,0 +1,149 @@ +package cn.iocoder.lyzsys.module.tjt.service.employee; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils; +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.employee.vo.EmployeePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRolePersonSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.specialtyrolesplit.SpecialtyRoleSplitDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget.EmployeeYearCostBudgetMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.employee.EmployeeMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.office.OfficeMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.specialtyrolesplit.SpecialtyRoleSplitMapper; +import cn.iocoder.lyzsys.module.tjt.service.office.OfficeService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_IN_USE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_NOT_EXISTS; + +@Service +@Validated +public class EmployeeServiceImpl implements EmployeeService { + + @Resource + private EmployeeMapper employeeMapper; + @Resource + private OfficeMapper officeMapper; + @Resource + private EmployeeYearCostBudgetMapper employeeYearCostBudgetMapper; + @Resource + private ProjectRolePersonMapper projectRolePersonMapper; + @Resource + private SpecialtyRoleSplitMapper specialtyRoleSplitMapper; + @Resource + private OfficeService officeService; + + @Override + public Long createEmployee(EmployeeSaveReqVO createReqVO) { + officeService.validateOfficeExists(createReqVO.getOfficeId()); + EmployeeDO employee = BeanUtils.toBean(createReqVO, EmployeeDO.class); + if (employee.getEnabledFlag() == null) { + employee.setEnabledFlag(Boolean.TRUE); + } + employeeMapper.insert(employee); + return employee.getId(); + } + + @Override + public void updateEmployee(EmployeeSaveReqVO updateReqVO) { + validateEmployeeExists(updateReqVO.getId()); + officeService.validateOfficeExists(updateReqVO.getOfficeId()); + employeeMapper.updateById(BeanUtils.toBean(updateReqVO, EmployeeDO.class)); + } + + @Override + public void deleteEmployee(Long id) { + validateEmployeeExists(id); + if (employeeYearCostBudgetMapper.selectCountByEmployeeId(id) > 0 + || projectRolePersonMapper.selectCountByEmployeeId(id) > 0 + || existsInSpecialtyRoleSplit(id)) { + throw exception(EMPLOYEE_IN_USE); + } + employeeMapper.deleteById(id); + } + + @Override + public EmployeeRespVO getEmployee(Long id) { + EmployeeDO employee = validateEmployeeExists(id); + Map officeMap = getOfficeMap(Collections.singleton(employee.getOfficeId())); + return toRespVO(employee, officeMap); + } + + @Override + public EmployeeDO validateEmployeeExists(Long id) { + EmployeeDO employee = employeeMapper.selectById(id); + if (employee == null) { + throw exception(EMPLOYEE_NOT_EXISTS); + } + return employee; + } + + @Override + public PageResult getEmployeePage(EmployeePageReqVO pageReqVO) { + PageResult pageResult = employeeMapper.selectPage(pageReqVO); + Map officeMap = getOfficeMap(CollectionUtils.convertSet(pageResult.getList(), EmployeeDO::getOfficeId)); + return BeanUtils.toBean(pageResult, EmployeeRespVO.class, respVO -> + respVO.setOfficeName(officeMap.containsKey(respVO.getOfficeId()) + ? officeMap.get(respVO.getOfficeId()).getOfficeName() : null)); + } + + @Override + public List getEmployeeSimpleList(String keyword, Long officeId, String status, Boolean enabledFlag) { + List list = employeeMapper.selectSimpleList(keyword, officeId, status, enabledFlag == null ? Boolean.TRUE : enabledFlag); + Map officeMap = getOfficeMap(CollectionUtils.convertSet(list, EmployeeDO::getOfficeId)); + return BeanUtils.toBean(list, EmployeeSimpleRespVO.class, respVO -> + respVO.setOfficeName(officeMap.containsKey(respVO.getOfficeId()) + ? officeMap.get(respVO.getOfficeId()).getOfficeName() : null)); + } + + private boolean existsInSpecialtyRoleSplit(Long employeeId) { + List candidateList = specialtyRoleSplitMapper.selectListByPersonsJsonKeyword("\"employeeId\":" + employeeId); + for (SpecialtyRoleSplitDO item : candidateList) { + List persons = JsonUtils.parseArray(item.getPersonsJson(), SpecialtyRolePersonSaveReqVO.class); + if (persons == null) { + continue; + } + boolean matched = persons.stream() + .filter(Objects::nonNull) + .anyMatch(person -> Objects.equals(person.getEmployeeId(), employeeId)); + if (matched) { + return true; + } + } + return false; + } + + private Map getOfficeMap(Set officeIds) { + if (officeIds == null || officeIds.isEmpty()) { + return Collections.emptyMap(); + } + officeIds.removeIf(Objects::isNull); + if (officeIds.isEmpty()) { + return Collections.emptyMap(); + } + return CollectionUtils.convertMap(officeMapper.selectBatchIds(officeIds), OfficeDO::getId); + } + + private EmployeeRespVO toRespVO(EmployeeDO employee, Map officeMap) { + return BeanUtils.toBean(employee, EmployeeRespVO.class, respVO -> + respVO.setOfficeName(officeMap.containsKey(respVO.getOfficeId()) + ? officeMap.get(respVO.getOfficeId()).getOfficeName() : null)); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employeeyearcostbudget/EmployeeYearCostBudgetService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employeeyearcostbudget/EmployeeYearCostBudgetService.java new file mode 100644 index 0000000..b401483 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employeeyearcostbudget/EmployeeYearCostBudgetService.java @@ -0,0 +1,22 @@ +package cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetSaveReqVO; + +import javax.validation.Valid; + +public interface EmployeeYearCostBudgetService { + + Long createEmployeeYearCostBudget(@Valid EmployeeYearCostBudgetSaveReqVO createReqVO); + + void updateEmployeeYearCostBudget(@Valid EmployeeYearCostBudgetSaveReqVO updateReqVO); + + void deleteEmployeeYearCostBudget(Long id); + + EmployeeYearCostBudgetRespVO getEmployeeYearCostBudget(Long id); + + PageResult getEmployeeYearCostBudgetPage(EmployeeYearCostBudgetPageReqVO pageReqVO); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employeeyearcostbudget/EmployeeYearCostBudgetServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employeeyearcostbudget/EmployeeYearCostBudgetServiceImpl.java new file mode 100644 index 0000000..7cf5572 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/employeeyearcostbudget/EmployeeYearCostBudgetServiceImpl.java @@ -0,0 +1,85 @@ +package cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget; + +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.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget.EmployeeYearCostBudgetDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget.EmployeeYearCostBudgetMapper; +import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Objects; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_YEAR_COST_BUDGET_DUPLICATE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_YEAR_COST_BUDGET_NOT_EXISTS; + +@Service +@Validated +public class EmployeeYearCostBudgetServiceImpl implements EmployeeYearCostBudgetService { + + @Resource + private EmployeeYearCostBudgetMapper employeeYearCostBudgetMapper; + @Resource + private EmployeeService employeeService; + + @Override + public Long createEmployeeYearCostBudget(EmployeeYearCostBudgetSaveReqVO createReqVO) { + EmployeeDO employee = employeeService.validateEmployeeExists(createReqVO.getEmployeeId()); + validateDuplicate(null, createReqVO.getEmployeeId(), createReqVO.getBudgetYear()); + EmployeeYearCostBudgetDO budget = BeanUtils.toBean(createReqVO, EmployeeYearCostBudgetDO.class); + budget.setEmployeeName(employee.getEmployeeName()); + if (budget.getEnabledFlag() == null) { + budget.setEnabledFlag(Boolean.TRUE); + } + employeeYearCostBudgetMapper.insert(budget); + return budget.getId(); + } + + @Override + public void updateEmployeeYearCostBudget(EmployeeYearCostBudgetSaveReqVO updateReqVO) { + validateExists(updateReqVO.getId()); + EmployeeDO employee = employeeService.validateEmployeeExists(updateReqVO.getEmployeeId()); + validateDuplicate(updateReqVO.getId(), updateReqVO.getEmployeeId(), updateReqVO.getBudgetYear()); + EmployeeYearCostBudgetDO updateObj = BeanUtils.toBean(updateReqVO, EmployeeYearCostBudgetDO.class); + updateObj.setEmployeeName(employee.getEmployeeName()); + employeeYearCostBudgetMapper.updateById(updateObj); + } + + @Override + public void deleteEmployeeYearCostBudget(Long id) { + validateExists(id); + employeeYearCostBudgetMapper.deleteById(id); + } + + @Override + public EmployeeYearCostBudgetRespVO getEmployeeYearCostBudget(Long id) { + return BeanUtils.toBean(validateExists(id), EmployeeYearCostBudgetRespVO.class); + } + + @Override + public PageResult getEmployeeYearCostBudgetPage(EmployeeYearCostBudgetPageReqVO pageReqVO) { + return BeanUtils.toBean(employeeYearCostBudgetMapper.selectPage(pageReqVO), EmployeeYearCostBudgetRespVO.class); + } + + private EmployeeYearCostBudgetDO validateExists(Long id) { + EmployeeYearCostBudgetDO budget = employeeYearCostBudgetMapper.selectById(id); + if (budget == null) { + throw exception(EMPLOYEE_YEAR_COST_BUDGET_NOT_EXISTS); + } + return budget; + } + + private void validateDuplicate(Long id, Long employeeId, Integer budgetYear) { + EmployeeYearCostBudgetDO duplicate = employeeYearCostBudgetMapper.selectByEmployeeIdAndBudgetYear(employeeId, budgetYear); + if (duplicate != null && !Objects.equals(duplicate.getId(), id)) { + throw exception(EMPLOYEE_YEAR_COST_BUDGET_DUPLICATE); + } + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/office/OfficeService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/office/OfficeService.java new file mode 100644 index 0000000..8a8f395 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/office/OfficeService.java @@ -0,0 +1,29 @@ +package cn.iocoder.lyzsys.module.tjt.service.office; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO; + +import javax.validation.Valid; +import java.util.List; + +public interface OfficeService { + + Long createOffice(@Valid OfficeSaveReqVO createReqVO); + + void updateOffice(@Valid OfficeSaveReqVO updateReqVO); + + void deleteOffice(Long id); + + OfficeRespVO getOffice(Long id); + + OfficeDO validateOfficeExists(Long id); + + PageResult getOfficePage(OfficePageReqVO pageReqVO); + + List getOfficeSimpleList(); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/office/OfficeServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/office/OfficeServiceImpl.java new file mode 100644 index 0000000..babe710 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/office/OfficeServiceImpl.java @@ -0,0 +1,80 @@ +package cn.iocoder.lyzsys.module.tjt.service.office; + +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.office.vo.OfficePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.employee.EmployeeMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.office.OfficeMapper; +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.OFFICE_IN_USE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.OFFICE_NOT_EXISTS; + +@Service +@Validated +public class OfficeServiceImpl implements OfficeService { + + @Resource + private OfficeMapper officeMapper; + @Resource + private EmployeeMapper employeeMapper; + + @Override + public Long createOffice(OfficeSaveReqVO createReqVO) { + OfficeDO office = BeanUtils.toBean(createReqVO, OfficeDO.class); + if (office.getEnabledFlag() == null) { + office.setEnabledFlag(Boolean.TRUE); + } + officeMapper.insert(office); + return office.getId(); + } + + @Override + public void updateOffice(OfficeSaveReqVO updateReqVO) { + validateOfficeExists(updateReqVO.getId()); + officeMapper.updateById(BeanUtils.toBean(updateReqVO, OfficeDO.class)); + } + + @Override + public void deleteOffice(Long id) { + validateOfficeExists(id); + if (employeeMapper.selectCountByOfficeId(id) > 0) { + throw exception(OFFICE_IN_USE); + } + officeMapper.deleteById(id); + } + + @Override + public OfficeRespVO getOffice(Long id) { + return BeanUtils.toBean(validateOfficeExists(id), OfficeRespVO.class); + } + + @Override + public OfficeDO validateOfficeExists(Long id) { + OfficeDO office = officeMapper.selectById(id); + if (office == null) { + throw exception(OFFICE_NOT_EXISTS); + } + return office; + } + + @Override + public PageResult getOfficePage(OfficePageReqVO pageReqVO) { + return BeanUtils.toBean(officeMapper.selectPage(pageReqVO), OfficeRespVO.class); + } + + @Override + public List getOfficeSimpleList() { + return BeanUtils.toBean(officeMapper.selectSimpleList(), OfficeSimpleRespVO.class); + } + +} 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 index 0cc1654..56da7c7 100644 --- 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 @@ -6,9 +6,11 @@ import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutpu 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.projectroleperson.ProjectRolePersonDO; 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.dal.mysql.projectroleperson.ProjectRolePersonMapper; import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -18,7 +20,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -49,6 +53,8 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService private ProjectPlanningMapper projectPlanningMapper; @Resource private ProjectMapper projectMapper; + @Resource + private ProjectRolePersonMapper projectRolePersonMapper; @Override public ProjectOutputSplitRespVO getProjectOutputSplit(Long planningId) { @@ -134,18 +140,20 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService 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 projectLeadAmount = multiplyAmount(assessmentOutputValue, outputSplit.getProjectLeadRatio()); BigDecimal officeAmount = multiplyAmount(assessmentOutputValue, outputSplit.getOfficeRatio()); + List rolePersons = projectRolePersonMapper.selectListByProjectId(project.getId()); + String projectManagerName = joinRoleNames(rolePersons, OutputSplitBizConstants.ROLE_PROJECT_MANAGER); + String engineeringPrincipalName = joinRoleNames(rolePersons, OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL); 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.setProjectManagerName(projectManagerName); + respVO.setEngineeringLeaderName(engineeringPrincipalName); + respVO.setProjectLeadName(joinProjectLeadName(projectManagerName, engineeringPrincipalName)); + respVO.setProjectLeadAmount(projectLeadAmount); respVO.setOfficeAmount(officeAmount); respVO.setArchAmount(multiplyAmount(officeAmount, outputSplit.getArchRatio())); respVO.setDecorAmount(multiplyAmount(officeAmount, outputSplit.getDecorRatio())); @@ -181,8 +189,7 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService outputSplit.setProjectId(planning.getProjectId()); outputSplit.setPlanningId(planning.getId()); outputSplit.setYear(getPlanningYear(planning)); - outputSplit.setProjectManagerRatio(ZERO_RATIO); - outputSplit.setEngineeringLeaderRatio(ZERO_RATIO); + outputSplit.setProjectLeadRatio(ZERO_RATIO); outputSplit.setOfficeRatio(ONE_RATIO); outputSplit.setArchRatio(ONE_RATIO); outputSplit.setDecorRatio(ZERO_RATIO); @@ -199,8 +206,16 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService } private void validateOutputSplitRatios(ProjectOutputSplitSaveReqVO reqVO) { - BigDecimal projectTotal = ratio(reqVO.getProjectManagerRatio()) - .add(ratio(reqVO.getEngineeringLeaderRatio())) + validateRatioRange(reqVO.getProjectLeadRatio()); + validateRatioRange(reqVO.getOfficeRatio()); + validateRatioRange(reqVO.getArchRatio()); + validateRatioRange(reqVO.getDecorRatio()); + validateRatioRange(reqVO.getStructRatio()); + validateRatioRange(reqVO.getWaterRatio()); + validateRatioRange(reqVO.getElecRatio()); + validateRatioRange(reqVO.getHvacRatio()); + validateRatioRange(reqVO.getDigitalRatio()); + BigDecimal projectTotal = ratio(reqVO.getProjectLeadRatio()) .add(ratio(reqVO.getOfficeRatio())) .setScale(RATIO_SCALE, RoundingMode.HALF_UP); BigDecimal specialtyTotal = ratio(reqVO.getArchRatio()) @@ -211,14 +226,20 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService .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) { + if (projectTotal.compareTo(ONE_RATIO) != 0 || specialtyTotal.compareTo(ONE_RATIO) != 0) { + throw exception(PROJECT_OUTPUT_SPLIT_RATIO_INVALID); + } + } + + private void validateRatioRange(BigDecimal value) { + BigDecimal normalized = ratio(value); + if (normalized.compareTo(ZERO_RATIO) < 0 || normalized.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::setProjectLeadRatio, outputSplit.getProjectLeadRatio()); normalize(outputSplit::setOfficeRatio, outputSplit.getOfficeRatio()); normalize(outputSplit::setArchRatio, outputSplit.getArchRatio()); normalize(outputSplit::setDecorRatio, outputSplit.getDecorRatio()); @@ -266,4 +287,26 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP); } + private String joinRoleNames(List rolePersons, String roleCode) { + return rolePersons.stream() + .filter(item -> Objects.equals(item.getRoleCode(), roleCode)) + .map(ProjectRolePersonDO::getEmployeeName) + .filter(Objects::nonNull) + .collect(Collectors.joining("、")); + } + + private String joinProjectLeadName(String projectManagerName, String engineeringPrincipalName) { + if ((projectManagerName == null || projectManagerName.isEmpty()) + && (engineeringPrincipalName == null || engineeringPrincipalName.isEmpty())) { + return ""; + } + if (projectManagerName == null || projectManagerName.isEmpty()) { + return engineeringPrincipalName; + } + if (engineeringPrincipalName == null || engineeringPrincipalName.isEmpty()) { + return projectManagerName; + } + return projectManagerName + " / " + engineeringPrincipalName; + } + } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java index fb3a85d..a8eb37f 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/planning/ProjectPlanningServiceImpl.java @@ -29,13 +29,15 @@ 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_DESIGN_PART_INVALID; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_NOT_EXISTS; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID; 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; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED; /** * 合约规划 Service 实现类 @@ -57,7 +59,6 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { 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 @@ -160,7 +161,12 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { if (!ProjectPlanningBizTypeConstants.isValidOwnershipType(reqVO.getOwnershipType())) { throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID); } - if (!ProjectPlanningBizTypeConstants.isValidCalculationMethod(reqVO.getCalculationMethod())) { + if (StrUtil.isNotBlank(reqVO.getDesignPart()) + && !ProjectPlanningBizTypeConstants.isValidDesignPart(reqVO.getDesignPart())) { + throw exception(PROJECT_PLANNING_DESIGN_PART_INVALID); + } + if (StrUtil.isNotBlank(reqVO.getCalculationMethod()) + && !ProjectPlanningBizTypeConstants.isValidCalculationMethod(reqVO.getCalculationMethod())) { throw exception(PROJECT_PLANNING_CALCULATION_METHOD_INVALID); } if (ProjectPlanningBizTypeConstants.isVirtualOutput(reqVO.getCalculationMethod()) @@ -168,15 +174,20 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { && !ProjectPlanningBizTypeConstants.isValidVirtualCalculationMethod(reqVO.getVirtualCalculationMethod())) { throw exception(PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID); } + if (ProjectPlanningBizTypeConstants.isWorkingDay(reqVO.getVirtualCalculationMethod())) { + if (reqVO.getWorkingDayCount() == null) { + throw exception(PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED); + } + if (reqVO.getWorkingDayUnitPrice() == null) { + throw exception(PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED); + } + } 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) { @@ -219,12 +230,6 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { 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) { @@ -309,17 +314,14 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService { } private BigDecimal calculateVirtualOutputValue(ProjectPlanningDO planning) { - if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(planning.getVirtualCalculationMethod())) { + if (ProjectPlanningBizTypeConstants.isWorkingDay(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()); - } + if (ProjectPlanningBizTypeConstants.isVirtualGuidancePrice(planning.getVirtualCalculationMethod())) { return multiplyAmount(planning.getGuidanceUnitPrice(), planning.getPlanningArea()); } - if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE.equals(planning.getVirtualCalculationMethod())) { - return amount(planning.getVirtualTotalPrice()); + if (ProjectPlanningBizTypeConstants.isVirtualGuidanceTotalPrice(planning.getVirtualCalculationMethod())) { + return amount(planning.getGuidanceTotalPrice()); } return ZERO_AMOUNT; } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java index 202e01b..4c3be72 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/profit/ProjectProfitServiceImpl.java @@ -6,9 +6,13 @@ 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.planningquarter.ProjectPlanningQuarterDO; import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO; import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper; import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper; import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -16,29 +20,34 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_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); + private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(4, RoundingMode.HALF_UP); + private static final BigDecimal DEFAULT_INNOVATION_OUTPUT_RATE = new BigDecimal("0.0100"); + private static final BigDecimal DEFAULT_YEAR_K_VALUE = new BigDecimal("0.4000"); @Resource private ProjectMapper projectMapper; @Resource private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; + @Resource + private YearKValueMapper yearKValueMapper; @Override public ProjectProfitRespVO getProjectProfit(Long projectId) { @@ -59,9 +68,9 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { if (pageResult.getList().isEmpty()) { return new PageResult<>(Collections.emptyList(), pageResult.getTotal()); } + Set projectIds = CollectionUtils.convertSet(pageResult.getList(), ProjectDO::getId); Map> planningMap = CollectionUtils.convertMultiMap( - projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, - CollectionUtils.convertSet(pageResult.getList(), ProjectDO::getId)), + projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, projectIds), ProjectPlanningDO::getProjectId); List list = CollectionUtils.convertList(pageResult.getList(), project -> buildProjectProfit(project, planningMap.get(project.getId()))); @@ -73,6 +82,10 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { BigDecimal comprehensivePlanningAmount = ZERO_AMOUNT; BigDecimal subcontractPlanningAmount = ZERO_AMOUNT; BigDecimal majorOutputValue = ZERO_AMOUNT; + List majorPlanningList = safePlanningList.stream() + .filter(planning -> ProjectPlanningBizTypeConstants.isMajor(planning.getOwnershipType())) + .collect(Collectors.toList()); + for (ProjectPlanningDO planning : safePlanningList) { if (ProjectPlanningBizTypeConstants.isComprehensive(planning.getOwnershipType())) { comprehensivePlanningAmount = comprehensivePlanningAmount.add(amount(planning.getPlanningAmount())); @@ -86,13 +99,22 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { majorOutputValue = majorOutputValue.add(amount(planning.getAssessmentOutputValue())); } } - BigDecimal expectedKValue = ratio(project.getExpectedKValue()); - BigDecimal majorExpectedPerformance = majorOutputValue.multiply(expectedKValue).setScale(2, RoundingMode.HALF_UP); + + BigDecimal majorExpectedPerformance = calculateMajorExpectedPerformance(majorPlanningList); + BigDecimal contractAmount = amount(project.getContractAmount()); BigDecimal finalSettlementAmount = amount(project.getFinalSettlementAmount()); + BigDecimal innovationOutputRate = project.getInnovationOutputRate() == null + ? DEFAULT_INNOVATION_OUTPUT_RATE : ratio(project.getInnovationOutputRate()); + BigDecimal innovationOutputValue = contractAmount.compareTo(BigDecimal.ZERO) <= 0 + ? ZERO_AMOUNT + : contractAmount.multiply(innovationOutputRate).setScale(2, RoundingMode.HALF_UP); + BigDecimal otherCost = amount(project.getOtherCost()); BigDecimal profitLossValue = finalSettlementAmount .subtract(comprehensivePlanningAmount) .subtract(subcontractPlanningAmount) .subtract(majorExpectedPerformance) + .subtract(innovationOutputValue) + .subtract(otherCost) .setScale(2, RoundingMode.HALF_UP); BigDecimal profitLossRate = finalSettlementAmount.compareTo(BigDecimal.ZERO) == 0 ? ZERO_RATIO @@ -102,13 +124,15 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { respVO.setProjectId(project.getId()); respVO.setProjectName(project.getProjectName()); respVO.setContractSignedFlag(project.getContractSignedFlag()); - respVO.setContractAmount(amount(project.getContractAmount())); + respVO.setContractAmount(contractAmount); 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.setInnovationOutputRate(innovationOutputRate); + respVO.setInnovationOutputValue(innovationOutputValue); + respVO.setOtherCost(otherCost); respVO.setProfitLossValue(profitLossValue); respVO.setProfitLossRate(profitLossRate); respVO.setProjectStartYear(project.getProjectStartYear()); @@ -116,6 +140,43 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { return respVO; } + private BigDecimal calculateMajorExpectedPerformance(List majorPlanningList) { + if (majorPlanningList == null || majorPlanningList.isEmpty()) { + return ZERO_AMOUNT; + } + List planningIds = majorPlanningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList()); + Map> quarterMap = CollectionUtils.convertMultiMap( + projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId); + Set years = quarterMap.values().stream() + .flatMap(Collection::stream) + .map(ProjectPlanningQuarterDO::getDistributionYear) + .collect(Collectors.toSet()); + Map yearKValueMap = yearKValueMapper.selectEnabledListByYears(years).stream() + .collect(Collectors.toMap(YearKValueDO::getKYear, item -> ratio(item.getKValue()), (a, b) -> b)); + + BigDecimal total = ZERO_AMOUNT; + for (ProjectPlanningDO planning : majorPlanningList) { + BigDecimal assessmentOutputValue = amount(planning.getAssessmentOutputValue()); + Map yearRatioMap = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream() + .collect(Collectors.groupingBy(ProjectPlanningQuarterDO::getDistributionYear, + Collectors.reducing(ZERO_RATIO, + item -> ratio(item.getDistributionRatio()), + BigDecimal::add))); + BigDecimal allocatedRatio = ZERO_RATIO; + for (Map.Entry entry : yearRatioMap.entrySet()) { + BigDecimal yearRatio = ratio(entry.getValue()); + allocatedRatio = allocatedRatio.add(yearRatio).setScale(4, RoundingMode.HALF_UP); + BigDecimal kValue = yearKValueMap.getOrDefault(entry.getKey(), DEFAULT_YEAR_K_VALUE); + total = total.add(multiplyAmount(assessmentOutputValue, yearRatio, kValue)); + } + BigDecimal unallocatedRatio = ONE_RATIO.subtract(allocatedRatio).setScale(4, RoundingMode.HALF_UP); + if (unallocatedRatio.compareTo(ZERO_RATIO) > 0) { + total = total.add(multiplyAmount(assessmentOutputValue, unallocatedRatio, DEFAULT_YEAR_K_VALUE)); + } + } + return total.setScale(2, RoundingMode.HALF_UP); + } + private ProjectDO validateProjectExists(Long projectId) { ProjectDO project = projectMapper.selectById(projectId); if (project == null) { @@ -132,4 +193,12 @@ public class ProjectProfitServiceImpl implements ProjectProfitService { 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/project/ProjectService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectService.java index 009e057..7f961e4 100644 --- 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 @@ -2,8 +2,8 @@ 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.ProjectRespVO; 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; @@ -22,8 +22,8 @@ public interface ProjectService { void deleteProjectList(List ids); - PageResult getProjectPage(ProjectPageReqVO pageReqVO); + PageResult getProjectPage(ProjectPageReqVO pageReqVO); - ProjectDO getProject(Long id); + ProjectRespVO 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 index 804c01f..35c54e4 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/project/ProjectServiceImpl.java @@ -1,16 +1,38 @@ package cn.iocoder.lyzsys.module.tjt.service.project; 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.project.vo.ProjectPageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRolePersonRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRolePersonSaveReqVO; import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO; +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.dataobject.projectroleperson.ProjectRolePersonDO; +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.dal.mysql.projectroleperson.ProjectRolePersonMapper; +import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService; +import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService; +import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; @@ -24,49 +46,261 @@ import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_ @Validated public class ProjectServiceImpl implements ProjectService { + private static final String ROLE_CODE_PROJECT_MANAGER = "project_manager"; + private static final String ROLE_CODE_ENGINEERING_PRINCIPAL = "engineering_principal"; + private static final String ROLE_NAME_PROJECT_MANAGER = "项目经理"; + private static final String ROLE_NAME_ENGINEERING_PRINCIPAL = "项目负责人"; + private static final String STATUS_IN_PROGRESS = "进行中"; + private static final String STATUS_COMPLETED = "完成"; + private static final String STATUS_PAUSED = "暂停"; + private static final String STATUS_TERMINATED = "中止"; + @Resource private ProjectMapper projectMapper; + @Resource + private ProjectRolePersonMapper projectRolePersonMapper; + @Resource + private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; + @Resource + private EmployeeService employeeService; + @Resource + private ProjectOutputSplitService projectOutputSplitService; + @Resource + private SpecialtyRoleSplitService specialtyRoleSplitService; @Override public Long createProject(ProjectSaveReqVO createReqVO) { ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class); + applyProjectDefaults(project, null); projectMapper.insert(project); + saveProjectRolePersons(project.getId(), createReqVO.getRolePersons()); return project.getId(); } @Override public void updateProject(ProjectSaveReqVO updateReqVO) { - validateProjectExists(updateReqVO.getId()); + ProjectDO dbProject = validateProjectExists(updateReqVO.getId()); ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class); + applyProjectDefaults(updateObj, dbProject); projectMapper.updateById(updateObj); + saveProjectRolePersons(updateReqVO.getId(), updateReqVO.getRolePersons()); } @Override public void deleteProject(Long id) { validateProjectExists(id); + deleteProjectDependencies(Collections.singletonList(id)); + projectRolePersonMapper.delete(ProjectRolePersonDO::getProjectId, id); projectMapper.deleteById(id); } @Override public void deleteProjectList(List ids) { ids.forEach(this::validateProjectExists); + deleteProjectDependencies(ids); + projectRolePersonMapper.deleteBatch(ProjectRolePersonDO::getProjectId, ids); projectMapper.deleteBatchIds(ids); } @Override - public PageResult getProjectPage(ProjectPageReqVO pageReqVO) { - return projectMapper.selectPage(pageReqVO); + public PageResult getProjectPage(ProjectPageReqVO pageReqVO) { + PageResult pageResult = projectMapper.selectPage(pageReqVO); + if (pageResult.getList().isEmpty()) { + return new PageResult<>(Collections.emptyList(), pageResult.getTotal()); + } + List projects = pageResult.getList(); + List projectIds = projects.stream().map(ProjectDO::getId).collect(Collectors.toList()); + Map> rolePersonMap = CollectionUtils.convertMultiMap( + projectRolePersonMapper.selectListByProjectIds(projectIds), ProjectRolePersonDO::getProjectId); + List list = projects.stream() + .map(project -> buildProjectRespVO(syncProjectStatus(project), rolePersonMap.get(project.getId()))) + .collect(Collectors.toList()); + return new PageResult<>(list, pageResult.getTotal()); } @Override - public ProjectDO getProject(Long id) { - return projectMapper.selectById(id); + public ProjectRespVO getProject(Long id) { + ProjectDO project = syncProjectStatus(validateProjectExists(id)); + return buildProjectRespVO(project, projectRolePersonMapper.selectListByProjectId(id)); } - private void validateProjectExists(Long id) { - if (projectMapper.selectById(id) == null) { + private ProjectDO validateProjectExists(Long id) { + ProjectDO project = projectMapper.selectById(id); + if (project == null) { throw exception(PROJECT_NOT_EXISTS); } + return project; + } + + private void applyProjectDefaults(ProjectDO project, ProjectDO dbProject) { + if (project.getProjectStatus() == null || project.getProjectStatus().trim().isEmpty()) { + project.setProjectStatus(dbProject == null ? STATUS_IN_PROGRESS : dbProject.getProjectStatus()); + } + if (project.getArchiveFlag() == null) { + project.setArchiveFlag(dbProject != null && Boolean.TRUE.equals(dbProject.getArchiveFlag())); + } + if (project.getArchiveTime() == null && dbProject != null) { + project.setArchiveTime(dbProject.getArchiveTime()); + } + } + + private void saveProjectRolePersons(Long projectId, List reqList) { + projectRolePersonMapper.delete(ProjectRolePersonDO::getProjectId, projectId); + if (reqList == null || reqList.isEmpty()) { + return; + } + List createList = new ArrayList<>(); + int autoSortNo = 1; + for (ProjectRolePersonSaveReqVO item : reqList) { + if (item == null || item.getEmployeeId() == null) { + continue; + } + String roleCode = item.getRoleCode() == null ? "" : item.getRoleCode().trim(); + if (!Objects.equals(roleCode, ROLE_CODE_PROJECT_MANAGER) + && !Objects.equals(roleCode, ROLE_CODE_ENGINEERING_PRINCIPAL)) { + continue; + } + EmployeeDO employee = employeeService.validateEmployeeExists(item.getEmployeeId()); + ProjectRolePersonDO createObj = new ProjectRolePersonDO(); + createObj.setProjectId(projectId); + createObj.setRoleCode(roleCode); + createObj.setRoleName(getRoleName(roleCode)); + createObj.setEmployeeId(item.getEmployeeId()); + createObj.setEmployeeName(employee.getEmployeeName()); + createObj.setSortNo(item.getSortNo() == null ? autoSortNo++ : item.getSortNo()); + createObj.setEnabledFlag(Boolean.TRUE); + createList.add(createObj); + } + for (ProjectRolePersonDO createObj : createList) { + projectRolePersonMapper.insert(createObj); + } + } + + private ProjectRespVO buildProjectRespVO(ProjectDO project, List rolePersons) { + List safeRolePersons = rolePersons == null ? Collections.emptyList() : rolePersons; + ProjectRespVO respVO = BeanUtils.toBean(project, ProjectRespVO.class); + respVO.setProjectManagerName(joinPersonNamesByRoleCode(safeRolePersons, ROLE_CODE_PROJECT_MANAGER)); + respVO.setEngineeringPrincipalName(joinPersonNamesByRoleCode(safeRolePersons, ROLE_CODE_ENGINEERING_PRINCIPAL)); + respVO.setRolePersons(BeanUtils.toBean(safeRolePersons, ProjectRolePersonRespVO.class)); + return respVO; + } + + private String joinPersonNamesByRoleCode(List rolePersons, String roleCode) { + return rolePersons.stream() + .filter(item -> Objects.equals(item.getRoleCode(), roleCode)) + .map(ProjectRolePersonDO::getEmployeeName) + .filter(Objects::nonNull) + .collect(Collectors.joining("、")); + } + + private String getRoleName(String roleCode) { + if (Objects.equals(roleCode, ROLE_CODE_PROJECT_MANAGER)) { + return ROLE_NAME_PROJECT_MANAGER; + } + if (Objects.equals(roleCode, ROLE_CODE_ENGINEERING_PRINCIPAL)) { + return ROLE_NAME_ENGINEERING_PRINCIPAL; + } + return roleCode; + } + + private ProjectDO syncProjectStatus(ProjectDO project) { + String nextStatus = calculateProjectStatus(project); + Boolean nextArchiveFlag = STATUS_COMPLETED.equals(nextStatus); + LocalDateTime nextArchiveTime = nextArchiveFlag + ? (project.getArchiveTime() == null ? LocalDateTime.now() : project.getArchiveTime()) + : null; + boolean needUpdate = !Objects.equals(project.getProjectStatus(), nextStatus) + || !Objects.equals(project.getArchiveFlag(), nextArchiveFlag) + || !Objects.equals(project.getArchiveTime(), nextArchiveTime); + if (needUpdate) { + ProjectDO updateObj = new ProjectDO(); + updateObj.setId(project.getId()); + updateObj.setProjectStatus(nextStatus); + updateObj.setArchiveFlag(nextArchiveFlag); + updateObj.setArchiveTime(nextArchiveTime); + if (!nextArchiveFlag) { + updateObj.setArchiveTime(null); + } + projectMapper.updateById(updateObj); + project.setProjectStatus(nextStatus); + project.setArchiveFlag(nextArchiveFlag); + project.setArchiveTime(nextArchiveTime); + } + return project; + } + + private String calculateProjectStatus(ProjectDO project) { + if (STATUS_TERMINATED.equals(project.getProjectStatus())) { + return STATUS_TERMINATED; + } + if (isProjectCompleted(project.getId())) { + return STATUS_COMPLETED; + } + if (STATUS_PAUSED.equals(project.getProjectStatus()) + && project.getPauseReason() != null + && !project.getPauseReason().trim().isEmpty()) { + return STATUS_PAUSED; + } + int currentYear = LocalDateTime.now().getYear(); + if (project.getProjectStartYear() != null && currentYear - project.getProjectStartYear() >= 5) { + return STATUS_PAUSED; + } + return STATUS_IN_PROGRESS; + } + + private boolean isProjectCompleted(Long projectId) { + List planningList = projectPlanningMapper.selectListByProjectId(projectId); + if (planningList == null || planningList.isEmpty()) { + return false; + } + List planningIds = planningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList()); + Map> quarterMap = CollectionUtils.convertMultiMap( + projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId); + for (ProjectPlanningDO planning : planningList) { + java.math.BigDecimal allocatedRatio = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream() + .map(ProjectPlanningQuarterDO::getDistributionRatio) + .filter(Objects::nonNull) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add) + .setScale(4, java.math.RoundingMode.HALF_UP); + java.math.BigDecimal totalDistributionAmount = planning.getTotalDistributionAmount() == null + ? java.math.BigDecimal.ONE.setScale(4, java.math.RoundingMode.HALF_UP) + : planning.getTotalDistributionAmount().setScale(4, java.math.RoundingMode.HALF_UP); + if (allocatedRatio.compareTo(totalDistributionAmount) < 0) { + return false; + } + } + return true; + } + + private void deleteProjectDependencies(Collection projectIds) { + if (projectIds == null || projectIds.isEmpty()) { + return; + } + List planningList = projectPlanningMapper.selectList( + new cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX() + .inIfPresent(ProjectPlanningDO::getProjectId, projectIds)); + if (planningList == null || planningList.isEmpty()) { + return; + } + List planningIds = planningList.stream() + .map(ProjectPlanningDO::getId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (planningIds.isEmpty()) { + return; + } + Map outputSplitMap = projectOutputSplitService.getProjectOutputSplitMap(planningIds); + if (!outputSplitMap.isEmpty()) { + specialtyRoleSplitService.deleteByOutputSplitIds(outputSplitMap.values().stream() + .map(ProjectOutputSplitDO::getId) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + projectOutputSplitService.deleteByPlanningIds(planningIds); + } + projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, planningIds); + projectPlanningMapper.deleteBatchIds(planningIds); } } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportService.java new file mode 100644 index 0000000..be9decb --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportService.java @@ -0,0 +1,34 @@ +package cn.iocoder.lyzsys.module.tjt.service.report; + +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectBudgetExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectLeadQuarterOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectOverviewExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectQuarterOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.SpecialtyPersonOutputExportReqVO; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public interface ProjectOutputReportService { + + void exportProjectBudgetExcel(HttpServletResponse response, ProjectBudgetExportReqVO reqVO) + throws IOException; + + void exportProjectQuarterOutputExcel(HttpServletResponse response, ProjectQuarterOutputExportReqVO reqVO) + throws IOException; + + void exportProjectLeadQuarterOutputExcel(HttpServletResponse response, + ProjectLeadQuarterOutputExportReqVO reqVO) + throws IOException; + + void exportSpecialtyPersonOutputExcel(HttpServletResponse response, SpecialtyPersonOutputExportReqVO reqVO) + throws IOException; + + void exportProjectOverviewExcel(HttpServletResponse response, ProjectOverviewExportReqVO reqVO) + throws IOException; + + void exportEmployeeOutputSummaryExcel(HttpServletResponse response, EmployeeOutputSummaryExportReqVO reqVO) + throws IOException; + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java new file mode 100644 index 0000000..cb66f0e --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java @@ -0,0 +1,1268 @@ +package cn.iocoder.lyzsys.module.tjt.service.report; + +import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils; +import cn.iocoder.lyzsys.framework.excel.core.util.ExcelUtils; +import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExcelRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectBudgetExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectLeadQuarterOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectOverviewExcelRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectOverviewExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectQuarterOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.SpecialtyPersonOutputExportReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRolePersonRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitRespVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget.EmployeeYearCostBudgetDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.employee.EmployeeMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget.EmployeeYearCostBudgetMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.office.OfficeMapper; +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.dal.mysql.projectroleperson.ProjectRolePersonMapper; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper; +import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants; +import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; +import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService; +import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectBudgetExcelBuilder; +import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectLeadQuarterOutputExcelBuilder; +import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectQuarterOutputExcelBuilder; +import cn.iocoder.lyzsys.module.tjt.service.report.builder.SpecialtyPersonOutputExcelBuilder; +import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService; +import lombok.Data; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_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_PLANNING_NOT_EXISTS; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID; + +@Service +@Validated +public class ProjectOutputReportServiceImpl implements ProjectOutputReportService { + + private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); + private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP); + private static final BigDecimal DEFAULT_K_VALUE = new BigDecimal("0.4000"); + + private static final String SORT_OUTPUT_DESC = "output_desc"; + private static final String SORT_OUTPUT_ASC = "output_asc"; + private static final String SORT_NAME_ASC = "name_asc"; + private static final String SORT_ANNUAL_TOTAL_DESC = "annual_total_desc"; + private static final String SORT_ANNUAL_TOTAL_ASC = "annual_total_asc"; + + @Resource + private ProjectPlanningMapper projectPlanningMapper; + @Resource + private ProjectMapper projectMapper; + @Resource + private ProjectPlanningQuarterMapper projectPlanningQuarterMapper; + @Resource + private ProjectRolePersonMapper projectRolePersonMapper; + @Resource + private EmployeeMapper employeeMapper; + @Resource + private OfficeMapper officeMapper; + @Resource + private EmployeeYearCostBudgetMapper employeeYearCostBudgetMapper; + @Resource + private YearKValueMapper yearKValueMapper; + @Resource + private ProjectOutputSplitService projectOutputSplitService; + @Resource + private SpecialtyRoleSplitService specialtyRoleSplitService; + @Resource + private ProjectBudgetExcelBuilder projectBudgetExcelBuilder; + @Resource + private ProjectQuarterOutputExcelBuilder projectQuarterOutputExcelBuilder; + @Resource + private ProjectLeadQuarterOutputExcelBuilder projectLeadQuarterOutputExcelBuilder; + @Resource + private SpecialtyPersonOutputExcelBuilder specialtyPersonOutputExcelBuilder; + + @Override + public void exportProjectBudgetExcel(HttpServletResponse response, ProjectBudgetExportReqVO reqVO) + throws IOException { + ProjectDO project = validateProjectExists(reqVO.getProjectId()); + List planningList = sortPlanningList(projectPlanningMapper.selectListByProjectId(project.getId())); + List rolePersons = projectRolePersonMapper.selectListByProjectId(project.getId()); + Map> quarterMap = getQuarterMap(planningList); + + ProjectBudgetExcelBuilder.ExportData data = new ProjectBudgetExcelBuilder.ExportData(); + data.setProjectCode(""); + data.setProjectName(project.getProjectName()); + data.setProjectManagerName(joinProjectRoleNames(rolePersons, OutputSplitBizConstants.ROLE_PROJECT_MANAGER)); + data.setEngineeringPrincipalName(joinProjectRoleNames(rolePersons, OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL)); + data.setTotalConstructionArea(amount(project.getTotalConstructionArea())); + data.setContractUnitPrice(firstNonZero(planningList.stream() + .map(ProjectPlanningDO::getContractUnitPrice) + .collect(Collectors.toList()))); + data.setInternalGuidanceUnitPrice(firstNonZero(planningList.stream() + .map(ProjectPlanningDO::getInternalGuidanceUnitPrice) + .collect(Collectors.toList()))); + data.setRemark(joinPlanningRemarks(planningList)); + + BigDecimal totalAssessmentArea = ZERO_AMOUNT; + BigDecimal totalAssessmentOutputValue = ZERO_AMOUNT; + for (ProjectPlanningDO planning : planningList) { + totalAssessmentArea = totalAssessmentArea.add(amount(planning.getAssessmentArea())).setScale(2, RoundingMode.HALF_UP); + totalAssessmentOutputValue = totalAssessmentOutputValue.add(amount(planning.getAssessmentOutputValue())) + .setScale(2, RoundingMode.HALF_UP); + } + data.setBudgetRows(buildProjectBudgetRows(planningList)); + data.setTotalAssessmentArea(totalAssessmentArea); + data.setTotalAssessmentOutputValue(totalAssessmentOutputValue); + data.setTotalAssessmentOutputValueWan(amountToWan(totalAssessmentOutputValue)); + data.setQuarterBudgetRows(buildQuarterBudgetRows(planningList, quarterMap, LocalDate.now().getYear())); + data.setYearRangeText(buildProjectBudgetYearRangeText(planningList, quarterMap)); + + projectBudgetExcelBuilder.writeWorkbook(response, + buildFileName(project.getProjectName(), "项目考核产值预算表"), + projectBudgetExcelBuilder.build(data)); + } + + @Override + public void exportProjectQuarterOutputExcel(HttpServletResponse response, ProjectQuarterOutputExportReqVO reqVO) + throws IOException { + ProjectPlanningDO anchorPlanning = validateMajorPlanning(reqVO.getPlanningId()); + ProjectDO project = validateProjectExists(anchorPlanning.getProjectId()); + Integer reportYear = resolveReportYear(anchorPlanning); + List planningList = sortPlanningList(getProjectPlanningScope(anchorPlanning, true)); + Map> quarterMap = getQuarterMap(planningList); + Map outputSplitMap = getOutputSplitMap(planningList); + + ProjectQuarterOutputExcelBuilder.ExportData data = new ProjectQuarterOutputExcelBuilder.ExportData(); + data.setProjectName(project.getProjectName()); + data.setYear(reportYear); + + List rows = new ArrayList<>(); + for (ProjectPlanningDO planning : planningList) { + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reportYear); + ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); + if (outputSplit == null) { + continue; + } + ProjectQuarterOutputExcelBuilder.QuarterRow row = new ProjectQuarterOutputExcelBuilder.QuarterRow(); + row.setOwnershipType(planning.getOwnershipType()); + row.setPlanningContent(planning.getPlanningContent()); + row.setHistoricalIssuedRatio(quarterSummary.getHistoricalIssuedRatio()); + row.setCurrentYearRatio(quarterSummary.getCurrentYearRatio()); + row.setPendingRatio(quarterSummary.getPendingRatio()); + row.setQuarterOneAmount(quarterSummary.getQuarterOneAmount()); + row.setQuarterTwoAmount(quarterSummary.getQuarterTwoAmount()); + row.setQuarterThreeAmount(quarterSummary.getQuarterThreeAmount()); + row.setQuarterFourAmount(quarterSummary.getQuarterFourAmount()); + row.setYearTotalAmount(quarterSummary.getYearTotalAmount()); + row.setProjectLeadAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), outputSplit.getProjectLeadRatio())); + row.setOfficeAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), outputSplit.getOfficeRatio())); + row.setSpecialtySummaryText(buildSpecialtySummary(outputSplit, quarterSummary.getYearTotalAmount())); + row.setAssessmentOutputValue(amount(planning.getAssessmentOutputValue())); + rows.add(row); + } + data.setRows(rows); + + projectQuarterOutputExcelBuilder.writeWorkbook(response, + buildFileName(project.getProjectName(), "项目级年度季度计取表"), + projectQuarterOutputExcelBuilder.build(data)); + } + + @Override + public void exportProjectLeadQuarterOutputExcel(HttpServletResponse response, + ProjectLeadQuarterOutputExportReqVO reqVO) + throws IOException { + ProjectPlanningDO anchorPlanning = validateMajorPlanning(reqVO.getPlanningId()); + ProjectDO project = validateProjectExists(anchorPlanning.getProjectId()); + Integer reportYear = resolveReportYear(anchorPlanning); + List planningList = sortPlanningList(getProjectPlanningScope(anchorPlanning, true)); + Map> quarterMap = getQuarterMap(planningList); + Map outputSplitMap = getOutputSplitMap(planningList); + Map> roleSplitMap = getRoleSplitMap(planningList); + + ProjectLeadQuarterOutputExcelBuilder.ExportData data = new ProjectLeadQuarterOutputExcelBuilder.ExportData(); + data.setProjectName(project.getProjectName()); + data.setYear(reportYear); + + List rows = new ArrayList<>(); + for (ProjectPlanningDO planning : planningList) { + ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); + if (outputSplit == null) { + continue; + } + BigDecimal projectLeadRatio = ratio(outputSplit.getProjectLeadRatio()); + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reportYear); + List roleList = roleSplitMap.getOrDefault(planning.getId(), Collections.emptyList()); + SpecialtyRoleSplitRespVO projectManagerRow = findRoleRow(roleList, + OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD, OutputSplitBizConstants.ROLE_PROJECT_MANAGER); + SpecialtyRoleSplitRespVO engineeringPrincipalRow = findRoleRow(roleList, + OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD, OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL); + + BigDecimal projectManagerRatio = projectManagerRow == null ? ZERO_RATIO : ratio(projectManagerRow.getRoleRatio()); + BigDecimal engineeringPrincipalRatio = engineeringPrincipalRow == null + ? ZERO_RATIO : ratio(engineeringPrincipalRow.getRoleRatio()); + + ProjectLeadQuarterOutputExcelBuilder.LeadQuarterRow row = + new ProjectLeadQuarterOutputExcelBuilder.LeadQuarterRow(); + row.setPlanningContent(planning.getPlanningContent()); + row.setProjectManagerNames(joinRoleSplitPersonNames(projectManagerRow)); + row.setProjectManagerRatio(projectManagerRatio); + row.setEngineeringPrincipalNames(joinRoleSplitPersonNames(engineeringPrincipalRow)); + row.setEngineeringPrincipalRatio(engineeringPrincipalRatio); + row.setProjectManagerQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), + projectLeadRatio, projectManagerRatio)); + row.setProjectManagerQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), + projectLeadRatio, projectManagerRatio)); + row.setProjectManagerQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), + projectLeadRatio, projectManagerRatio)); + row.setProjectManagerQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), + projectLeadRatio, projectManagerRatio)); + row.setProjectManagerYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), + projectLeadRatio, projectManagerRatio)); + row.setEngineeringPrincipalQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), + projectLeadRatio, engineeringPrincipalRatio)); + row.setEngineeringPrincipalQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), + projectLeadRatio, engineeringPrincipalRatio)); + row.setEngineeringPrincipalQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), + projectLeadRatio, engineeringPrincipalRatio)); + row.setEngineeringPrincipalQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), + projectLeadRatio, engineeringPrincipalRatio)); + row.setEngineeringPrincipalYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), + projectLeadRatio, engineeringPrincipalRatio)); + row.setTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), projectLeadRatio)); + rows.add(row); + } + data.setRows(rows); + + projectLeadQuarterOutputExcelBuilder.writeWorkbook(response, + buildFileName(project.getProjectName(), "项目负责人年度季度计取表"), + projectLeadQuarterOutputExcelBuilder.build(data)); + } + + @Override + public void exportSpecialtyPersonOutputExcel(HttpServletResponse response, + SpecialtyPersonOutputExportReqVO reqVO) throws IOException { + validateSpecialtyCode(reqVO.getSpecialtyCode()); + ProjectPlanningDO planning = validateMajorPlanning(reqVO.getPlanningId()); + ProjectDO project = validateProjectExists(planning.getProjectId()); + Integer reportYear = resolveReportYear(planning); + ProjectOutputSplitDO outputSplit = projectOutputSplitService.getOrCreateProjectOutputSplit(planning.getId()); + QuarterSummary quarterSummary = buildQuarterSummary(planning, + projectPlanningQuarterMapper.selectListByPlanningId(planning.getId()), reportYear); + BigDecimal specialtyBaseRatio = multiplyRatio(outputSplit.getOfficeRatio(), + getSpecialtyRatio(outputSplit, reqVO.getSpecialtyCode())); + + List roleList = specialtyRoleSplitService.getSpecialtyRoleSplitListByPlanningId(planning.getId()); + List specialtyRoleList = roleList.stream() + .filter(item -> Objects.equals(item.getSpecialtyCode(), reqVO.getSpecialtyCode())) + .collect(Collectors.toList()); + + SpecialtyPersonOutputExcelBuilder.ExportData data = new SpecialtyPersonOutputExcelBuilder.ExportData(); + data.setProjectName(project.getProjectName()); + data.setPlanningContent(planning.getPlanningContent()); + data.setSpecialtyName(OutputSplitBizConstants.getSpecialtyName(reqVO.getSpecialtyCode())); + data.setYear(reportYear); + + List rows = new ArrayList<>(); + for (SpecialtyRoleSplitRespVO roleRow : specialtyRoleList) { + BigDecimal roleRatio = ratio(roleRow.getRoleRatio()); + List persons = roleRow.getPersons() == null + ? Collections.emptyList() : roleRow.getPersons(); + if (persons.isEmpty()) { + SpecialtyPersonOutputExcelBuilder.PersonQuarterRow row = + new SpecialtyPersonOutputExcelBuilder.PersonQuarterRow(); + row.setRoleName(roleRow.getRoleName()); + row.setEmployeeName("-"); + row.setRoleRatio(roleRatio); + row.setPersonRatio(ZERO_RATIO); + row.setQuarterOneAmount(ZERO_AMOUNT); + row.setQuarterTwoAmount(ZERO_AMOUNT); + row.setQuarterThreeAmount(ZERO_AMOUNT); + row.setQuarterFourAmount(ZERO_AMOUNT); + row.setYearTotalAmount(ZERO_AMOUNT); + rows.add(row); + continue; + } + for (SpecialtyRolePersonRespVO person : persons) { + BigDecimal personRatio = ratio(person.getPersonRatio()); + BigDecimal totalRatio = multiplyRatio(specialtyBaseRatio, roleRatio, personRatio); + SpecialtyPersonOutputExcelBuilder.PersonQuarterRow row = + new SpecialtyPersonOutputExcelBuilder.PersonQuarterRow(); + row.setRoleName(roleRow.getRoleName()); + row.setEmployeeName(person.getEmployeeName()); + row.setRoleRatio(roleRatio); + row.setPersonRatio(personRatio); + row.setQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), totalRatio)); + row.setQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), totalRatio)); + row.setQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), totalRatio)); + row.setQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), totalRatio)); + row.setYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), totalRatio)); + rows.add(row); + } + } + data.setRows(rows); + + specialtyPersonOutputExcelBuilder.writeWorkbook(response, + buildFileName(project.getProjectName(), + data.getSpecialtyName() + "专业内人员年度季度计取表"), + specialtyPersonOutputExcelBuilder.build(data)); + } + + @Override + public void exportProjectOverviewExcel(HttpServletResponse response, ProjectOverviewExportReqVO reqVO) + throws IOException { + validateSpecialtyCode(reqVO.getSpecialtyCode()); + List majorPlanningList = projectPlanningMapper.selectList( + new LambdaQueryWrapperX() + .eq(ProjectPlanningDO::getOwnershipType, ProjectPlanningBizTypeConstants.OWNERSHIP_TYPE_MAJOR)); + if (majorPlanningList.isEmpty()) { + ExcelUtils.write(response, "项目总览表.xlsx", "项目总览", ProjectOverviewExcelRespVO.class, + Collections.emptyList()); + return; + } + + Map projectMap = getProjectMap(majorPlanningList); + Map> quarterMap = getQuarterMap(majorPlanningList); + Map outputSplitMap = getOutputSplitMap(majorPlanningList); + + List rows = new ArrayList<>(); + for (ProjectPlanningDO planning : majorPlanningList) { + ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); + if (outputSplit == null) { + continue; + } + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reqVO.getYear()); + BigDecimal combinedRatio = addRatio(outputSplit.getProjectLeadRatio(), + multiplyRatio(outputSplit.getOfficeRatio(), getSpecialtyRatio(outputSplit, reqVO.getSpecialtyCode()))); + if (combinedRatio.compareTo(ZERO_RATIO) <= 0 || quarterSummary.getYearTotalAmount().compareTo(ZERO_AMOUNT) <= 0) { + continue; + } + ProjectDO project = projectMap.get(planning.getProjectId()); + ProjectOverviewExcelRespVO row = new ProjectOverviewExcelRespVO(); + row.setProjectName(project == null ? "" : project.getProjectName()); + row.setPlanningContent(planning.getPlanningContent()); + row.setProgressText(buildProgressText(project, planning)); + row.setDesignStage(planning.getDesignStage()); + row.setTotalOutputAmount(multiplyAmount(amount(planning.getAssessmentOutputValue()), + defaultDistributionRatio(planning), combinedRatio)); + row.setHistoricalIssuedRatioText(percentText(quarterSummary.getHistoricalIssuedRatio())); + row.setCurrentSettlementAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), combinedRatio)); + row.setPendingRatioText(percentText(quarterSummary.getPendingRatio())); + row.setQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), combinedRatio)); + row.setQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), combinedRatio)); + row.setQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), combinedRatio)); + row.setQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), combinedRatio)); + row.setYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), combinedRatio)); + rows.add(row); + } + + sortProjectOverviewRows(rows, reqVO.getSortType()); + for (int i = 0; i < rows.size(); i++) { + rows.get(i).setSerialNo(i + 1); + } + ExcelUtils.write(response, + buildFileName(OutputSplitBizConstants.getSpecialtyName(reqVO.getSpecialtyCode()), "项目总览表"), + "项目总览", ProjectOverviewExcelRespVO.class, rows); + } + + @Override + public void exportEmployeeOutputSummaryExcel(HttpServletResponse response, + EmployeeOutputSummaryExportReqVO reqVO) throws IOException { + List employeeList = employeeMapper.selectList(new LambdaQueryWrapperX() + .eqIfPresent(EmployeeDO::getId, reqVO.getEmployeeId()) + .eqIfPresent(EmployeeDO::getOfficeId, reqVO.getOfficeId()) + .eqIfPresent(EmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus()) + .eq(EmployeeDO::getEnabledFlag, Boolean.TRUE) + .orderByAsc(EmployeeDO::getSortNo) + .orderByAsc(EmployeeDO::getId)); + if (employeeList.isEmpty()) { + ExcelUtils.write(response, "员工个人考核产值汇总.xlsx", "员工汇总", + EmployeeOutputSummaryExcelRespVO.class, Collections.emptyList()); + return; + } + + Set employeeIds = employeeList.stream().map(EmployeeDO::getId).collect(Collectors.toCollection(LinkedHashSet::new)); + Map employeeMap = employeeList.stream() + .collect(Collectors.toMap(EmployeeDO::getId, item -> item, (a, b) -> a, LinkedHashMap::new)); + Map officeMap = getOfficeMap(employeeList); + + List majorPlanningList = projectPlanningMapper.selectList( + new LambdaQueryWrapperX() + .eq(ProjectPlanningDO::getOwnershipType, ProjectPlanningBizTypeConstants.OWNERSHIP_TYPE_MAJOR)); + Map> quarterMap = getQuarterMap(majorPlanningList); + + List relevantPlanningList = majorPlanningList.stream() + .filter(item -> hasYearAmount(quarterMap.get(item.getId()), reqVO.getYear())) + .collect(Collectors.toList()); + + Map outputSplitMap = getOutputSplitMap(relevantPlanningList); + Map> roleSplitMap = getRoleSplitMap(relevantPlanningList); + Map accumulatorMap = new LinkedHashMap<>(); + employeeIds.forEach(id -> accumulatorMap.put(id, new EmployeeAccumulator())); + + for (ProjectPlanningDO planning : relevantPlanningList) { + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reqVO.getYear()); + ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); + if (outputSplit == null) { + continue; + } + List roleList = roleSplitMap.getOrDefault(planning.getId(), Collections.emptyList()); + for (SpecialtyRoleSplitRespVO roleRow : roleList) { + BigDecimal categoryRatio = getCategoryRatio(outputSplit, roleRow.getSpecialtyCode()); + BigDecimal roleRatio = ratio(roleRow.getRoleRatio()); + List persons = roleRow.getPersons() == null + ? Collections.emptyList() : roleRow.getPersons(); + for (SpecialtyRolePersonRespVO person : persons) { + if (person.getEmployeeId() == null || !employeeIds.contains(person.getEmployeeId())) { + continue; + } + BigDecimal totalRatio = multiplyRatio(categoryRatio, roleRatio, person.getPersonRatio()); + EmployeeAccumulator accumulator = accumulatorMap.get(person.getEmployeeId()); + accumulator.addQuarterAmount(1, multiplyAmount(quarterSummary.getQuarterOneAmount(), totalRatio)); + accumulator.addQuarterAmount(2, multiplyAmount(quarterSummary.getQuarterTwoAmount(), totalRatio)); + accumulator.addQuarterAmount(3, multiplyAmount(quarterSummary.getQuarterThreeAmount(), totalRatio)); + accumulator.addQuarterAmount(4, multiplyAmount(quarterSummary.getQuarterFourAmount(), totalRatio)); + } + } + } + + Map costBudgetMap = employeeYearCostBudgetMapper.selectList( + new LambdaQueryWrapperX() + .eq(EmployeeYearCostBudgetDO::getBudgetYear, reqVO.getYear()) + .eq(EmployeeYearCostBudgetDO::getEnabledFlag, Boolean.TRUE) + .inIfPresent(EmployeeYearCostBudgetDO::getEmployeeId, employeeIds)) + .stream() + .collect(Collectors.toMap(EmployeeYearCostBudgetDO::getEmployeeId, item -> item, (a, b) -> a)); + YearKValueDO yearKValue = yearKValueMapper.selectEnabledByKYear(reqVO.getYear()); + BigDecimal kValue = yearKValue == null ? DEFAULT_K_VALUE : ratio(yearKValue.getKValue()); + + List rows = new ArrayList<>(); + for (EmployeeDO employee : employeeList) { + EmployeeAccumulator accumulator = accumulatorMap.get(employee.getId()); + if (accumulator == null) { + accumulator = new EmployeeAccumulator(); + } + EmployeeYearCostBudgetDO costBudget = costBudgetMap.get(employee.getId()); + BigDecimal expectedCostAmount = costBudget == null ? ZERO_AMOUNT : amount(costBudget.getExpectedCostAmount()); + BigDecimal officeLeaderOrBimAmount = ZERO_AMOUNT; + BigDecimal annualTotalAmount = accumulator.getAnnualTotalAmount(); + BigDecimal totalAssessmentOutputAmount = annualTotalAmount.add(officeLeaderOrBimAmount) + .setScale(2, RoundingMode.HALF_UP); + BigDecimal basicAssessmentOutputAmount = kValue.compareTo(BigDecimal.ZERO) == 0 + ? ZERO_AMOUNT + : expectedCostAmount.divide(kValue, 2, RoundingMode.HALF_UP); + BigDecimal remainingOutputAmount = totalAssessmentOutputAmount.subtract(basicAssessmentOutputAmount) + .setScale(2, RoundingMode.HALF_UP); + BigDecimal estimatedYearEndPerformanceAmount = remainingOutputAmount.multiply(kValue) + .setScale(2, RoundingMode.HALF_UP); + + EmployeeOutputSummaryExcelRespVO row = new EmployeeOutputSummaryExcelRespVO(); + row.setEmployeeName(employee.getEmployeeName()); + row.setOfficeName(getOfficeName(officeMap, employee.getOfficeId())); + row.setQuarterOneAmount(accumulator.getQuarterOneAmount()); + row.setQuarterTwoAmount(accumulator.getQuarterTwoAmount()); + row.setQuarterThreeAmount(accumulator.getQuarterThreeAmount()); + row.setQuarterFourAmount(accumulator.getQuarterFourAmount()); + row.setAnnualTotalAmount(annualTotalAmount); + row.setOfficeLeaderOrBimAmount(officeLeaderOrBimAmount); + row.setTotalAssessmentOutputAmount(totalAssessmentOutputAmount); + row.setExpectedCostAmount(expectedCostAmount); + row.setBasicAssessmentOutputAmount(basicAssessmentOutputAmount); + row.setRemainingOutputAmount(remainingOutputAmount); + row.setEstimatedYearEndPerformanceAmount(estimatedYearEndPerformanceAmount); + rows.add(row); + } + + sortEmployeeRows(rows, reqVO.getSortType()); + for (int i = 0; i < rows.size(); i++) { + rows.get(i).setSerialNo(i + 1); + } + ExcelUtils.write(response, buildFileName(String.valueOf(reqVO.getYear()), "员工个人考核产值汇总"), + "员工汇总", EmployeeOutputSummaryExcelRespVO.class, rows); + } + + private ProjectPlanningDO validatePlanningExists(Long planningId) { + ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId); + if (planning == null) { + throw exception(PROJECT_PLANNING_NOT_EXISTS); + } + return planning; + } + + private ProjectPlanningDO validateMajorPlanning(Long planningId) { + ProjectPlanningDO planning = validatePlanningExists(planningId); + if (!ProjectPlanningBizTypeConstants.isMajor(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 void validateSpecialtyCode(String specialtyCode) { + if (!OutputSplitBizConstants.isValidSpecialtyCode(specialtyCode) + || OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + throw exception(SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID); + } + } + + private List getProjectPlanningScope(ProjectPlanningDO anchorPlanning, boolean majorOnly) { + List projectPlanningList = projectPlanningMapper.selectListByProjectId(anchorPlanning.getProjectId()); + Integer targetYear = resolveReportYear(anchorPlanning); + return projectPlanningList.stream() + .filter(item -> Objects.equals(resolveReportYear(item), targetYear)) + .filter(item -> !majorOnly || ProjectPlanningBizTypeConstants.isMajor(item.getOwnershipType())) + .collect(Collectors.toList()); + } + + private List buildProjectBudgetRows(List planningList) { + if (planningList == null || planningList.isEmpty()) { + return Collections.emptyList(); + } + Map> planningContentMap = new LinkedHashMap<>(); + planningList.stream() + .sorted(Comparator.comparing(ProjectPlanningDO::getId)) + .forEach(planning -> planningContentMap + .computeIfAbsent(resolveBudgetPlanningGroupKey(planning), key -> new ArrayList<>()) + .add(planning)); + + List rows = new ArrayList<>(); + for (List planningGroup : planningContentMap.values()) { + appendBudgetPlanningRows(rows, planningGroup); + } + return rows; + } + + private void appendBudgetPlanningRows(List rows, + List planningGroup) { + if (planningGroup == null || planningGroup.isEmpty()) { + return; + } + List groundPlanningList = filterBudgetPartPlanningList( + planningGroup, ProjectPlanningBizTypeConstants.DESIGN_PART_REAL_ESTATE); + List undergroundPlanningList = filterBudgetPartPlanningList( + planningGroup, ProjectPlanningBizTypeConstants.DESIGN_PART_UNDERGROUND); + + appendBudgetPartRows(rows, ProjectPlanningBizTypeConstants.DESIGN_PART_REAL_ESTATE, groundPlanningList); + appendBudgetPartRows(rows, ProjectPlanningBizTypeConstants.DESIGN_PART_UNDERGROUND, undergroundPlanningList); + rows.add(buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType.PLANNING_TOTAL, "总计", planningGroup)); + } + + private void appendBudgetPartRows(List rows, String designPart, + List partPlanningList) { + if (partPlanningList == null || partPlanningList.isEmpty()) { + rows.add(buildBudgetEmptyRow(designPart)); + return; + } + for (ProjectPlanningDO planning : partPlanningList) { + rows.add(buildBudgetDetailRow(designPart, planning)); + } + if (partPlanningList.size() > 2) { + rows.add(buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType.PART_SUBTOTAL, + designPart + "总计", partPlanningList)); + } + } + + private List filterBudgetPartPlanningList(List planningGroup, String designPart) { + return planningGroup.stream() + .filter(item -> Objects.equals(item.getDesignPart(), designPart)) + .sorted(Comparator.comparing(ProjectPlanningDO::getId)) + .collect(Collectors.toList()); + } + + private String resolveBudgetPlanningGroupKey(ProjectPlanningDO planning) { + String planningContent = defaultString(planning == null ? null : planning.getPlanningContent()); + return planningContent.isEmpty() ? "__PLANNING__" + (planning == null ? "" : planning.getId()) : planningContent; + } + + private ProjectBudgetExcelBuilder.BudgetRow buildBudgetDetailRow(String designPart, ProjectPlanningDO planning) { + ProjectBudgetExcelBuilder.BudgetRow row = initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType.DETAIL, designPart); + row.setPlanningContent(defaultString(planning.getPlanningContent())); + row.setDisplayBuildingType(defaultString(planning.getBuildingType())); + row.setInternalGuidanceUnitPrice(planning.getInternalGuidanceUnitPrice()); + row.setDesignArea(planning.getPlanningArea()); + row.setBuildingOrUnitCount(planning.getBuildingOrUnitCount()); + row.setDrawingSetFactor(planning.getDrawingSetFactor()); + row.setScaleFactor(planning.getScaleFactor()); + row.setModificationFactor(planning.getModificationFactor()); + row.setComplexityFactor(planning.getComplexityFactor()); + row.setSubtotalArea(planning.getAssessmentArea()); + row.setCurrentDesignStageRatio(planning.getCurrentDesignStageRatio()); + row.setAssessmentOutputValueWan(planning.getAssessmentOutputValue() == null + ? null : amountToWan(planning.getAssessmentOutputValue())); + return row; + } + + private ProjectBudgetExcelBuilder.BudgetRow buildBudgetEmptyRow(String designPart) { + return initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType.PART_EMPTY, designPart); + } + + private ProjectBudgetExcelBuilder.BudgetRow buildBudgetSummaryRow(ProjectBudgetExcelBuilder.BudgetRowType rowType, + String displayDesignPart, + List planningList) { + ProjectBudgetExcelBuilder.BudgetRow row = initBudgetRow(rowType, displayDesignPart); + row.setDesignArea(sumPlanningArea(planningList)); + row.setSubtotalArea(sumAssessmentArea(planningList)); + row.setAssessmentOutputValueWan(amountToWan(sumAssessmentOutputValue(planningList))); + return row; + } + + private ProjectBudgetExcelBuilder.BudgetRow initBudgetRow(ProjectBudgetExcelBuilder.BudgetRowType rowType, + String displayDesignPart) { + ProjectBudgetExcelBuilder.BudgetRow row = new ProjectBudgetExcelBuilder.BudgetRow(); + row.setRowType(rowType); + row.setDisplayDesignPart(displayDesignPart); + return row; + } + + private BigDecimal sumPlanningArea(List planningList) { + BigDecimal total = ZERO_AMOUNT; + if (planningList == null || planningList.isEmpty()) { + return total; + } + for (ProjectPlanningDO planning : planningList) { + total = total.add(amount(planning.getPlanningArea())).setScale(2, RoundingMode.HALF_UP); + } + return total; + } + + private BigDecimal sumAssessmentArea(List planningList) { + BigDecimal total = ZERO_AMOUNT; + if (planningList == null || planningList.isEmpty()) { + return total; + } + for (ProjectPlanningDO planning : planningList) { + total = total.add(amount(planning.getAssessmentArea())).setScale(2, RoundingMode.HALF_UP); + } + return total; + } + + private BigDecimal sumAssessmentOutputValue(List planningList) { + BigDecimal total = ZERO_AMOUNT; + if (planningList == null || planningList.isEmpty()) { + return total; + } + for (ProjectPlanningDO planning : planningList) { + total = total.add(amount(planning.getAssessmentOutputValue())).setScale(2, RoundingMode.HALF_UP); + } + return total; + } + + private List buildQuarterBudgetRows( + List planningList, Map> quarterMap, + Integer reportYear) { + List rows = new ArrayList<>(); + BigDecimal totalQuarterOneAmount = ZERO_AMOUNT; + BigDecimal totalQuarterTwoAmount = ZERO_AMOUNT; + BigDecimal totalQuarterThreeAmount = ZERO_AMOUNT; + BigDecimal totalQuarterFourAmount = ZERO_AMOUNT; + BigDecimal totalYearAmount = ZERO_AMOUNT; + int serialNo = 1; + + for (ProjectPlanningDO planning : planningList) { + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reportYear); + ProjectBudgetExcelBuilder.QuarterBudgetRow row = new ProjectBudgetExcelBuilder.QuarterBudgetRow(); + row.setSerialNo(serialNo++); + row.setOutputType(resolveBudgetCategoryLabel(planning.getOwnershipType())); + row.setDesignContent(defaultString(planning.getPlanningContent())); + row.setBudgetYear(reportYear); + row.setTotalDesignArea(amount(planning.getPlanningArea())); + row.setTotalAssessmentArea(amount(planning.getAssessmentArea())); + row.setTotalAssessmentOutputWan(resolveTotalAssessmentOutputWan(planning)); + row.setDesignStageRatio(ratio(planning.getCurrentDesignStageRatio())); + row.setDesignStageOutputWan(amountToWan(planning.getAssessmentOutputValue())); + row.setHistoricalIssuedRatio(quarterSummary.getHistoricalIssuedRatio()); + row.setQuarterOneRatio(quarterSummary.getQuarterOneRatio()); + row.setQuarterTwoRatio(quarterSummary.getQuarterTwoRatio()); + row.setQuarterThreeRatio(quarterSummary.getQuarterThreeRatio()); + row.setQuarterFourRatio(quarterSummary.getQuarterFourRatio()); + row.setCurrentYearRatio(quarterSummary.getCurrentYearRatio()); + row.setPendingRatio(quarterSummary.getPendingRatio()); + row.setQuarterOneAmountWan(amountToWan(quarterSummary.getQuarterOneAmount())); + row.setQuarterTwoAmountWan(amountToWan(quarterSummary.getQuarterTwoAmount())); + row.setQuarterThreeAmountWan(amountToWan(quarterSummary.getQuarterThreeAmount())); + row.setQuarterFourAmountWan(amountToWan(quarterSummary.getQuarterFourAmount())); + row.setYearTotalAmountWan(amountToWan(quarterSummary.getYearTotalAmount())); + row.setRatioRemark(buildQuarterBudgetRemark(reportYear, quarterSummary)); + rows.add(row); + + totalQuarterOneAmount = totalQuarterOneAmount.add(quarterSummary.getQuarterOneAmount()).setScale(2, RoundingMode.HALF_UP); + totalQuarterTwoAmount = totalQuarterTwoAmount.add(quarterSummary.getQuarterTwoAmount()).setScale(2, RoundingMode.HALF_UP); + totalQuarterThreeAmount = totalQuarterThreeAmount.add(quarterSummary.getQuarterThreeAmount()).setScale(2, RoundingMode.HALF_UP); + totalQuarterFourAmount = totalQuarterFourAmount.add(quarterSummary.getQuarterFourAmount()).setScale(2, RoundingMode.HALF_UP); + totalYearAmount = totalYearAmount.add(quarterSummary.getYearTotalAmount()).setScale(2, RoundingMode.HALF_UP); + } + + if (!planningList.isEmpty()) { + ProjectBudgetExcelBuilder.QuarterBudgetRow totalRow = new ProjectBudgetExcelBuilder.QuarterBudgetRow(); + totalRow.setTotalRow(true); + totalRow.setTotalDesignArea(sumPlanningArea(planningList)); + totalRow.setTotalAssessmentArea(sumAssessmentArea(planningList)); + totalRow.setTotalAssessmentOutputWan(sumQuarterBudgetTotalAssessmentOutputWan(planningList)); + totalRow.setDesignStageOutputWan(amountToWan(sumAssessmentOutputValue(planningList))); + totalRow.setQuarterOneAmountWan(amountToWan(totalQuarterOneAmount)); + totalRow.setQuarterTwoAmountWan(amountToWan(totalQuarterTwoAmount)); + totalRow.setQuarterThreeAmountWan(amountToWan(totalQuarterThreeAmount)); + totalRow.setQuarterFourAmountWan(amountToWan(totalQuarterFourAmount)); + totalRow.setYearTotalAmountWan(amountToWan(totalYearAmount)); + rows.add(totalRow); + } + return rows; + } + + private BigDecimal sumQuarterBudgetTotalAssessmentOutputWan(List planningList) { + BigDecimal total = ZERO_AMOUNT; + if (planningList == null || planningList.isEmpty()) { + return total; + } + for (ProjectPlanningDO planning : planningList) { + total = total.add(resolveTotalAssessmentOutputWan(planning)).setScale(2, RoundingMode.HALF_UP); + } + return total; + } + + private BigDecimal resolveTotalAssessmentOutputWan(ProjectPlanningDO planning) { + if (planning == null) { + return ZERO_AMOUNT; + } + BigDecimal stageRatio = ratio(planning.getCurrentDesignStageRatio()); + if (stageRatio.compareTo(BigDecimal.ZERO) <= 0) { + return amountToWan(planning.getAssessmentOutputValue()); + } + return amount(planning.getAssessmentOutputValue()) + .divide(stageRatio, 2, RoundingMode.HALF_UP) + .divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP); + } + + private String buildQuarterBudgetRemark(Integer budgetYear, QuarterSummary summary) { + List parts = new ArrayList<>(); + if (budgetYear != null) { + parts.add("预算年度:" + budgetYear); + } + if (summary == null) { + return String.join(";", parts); + } + if (summary.getCurrentYearRatio().compareTo(BigDecimal.ZERO) == 0 + && summary.getPendingRatio().compareTo(BigDecimal.ZERO) > 0) { + parts.add("未配置本年度发放比例"); + return String.join(";", parts); + } + if (summary.getPendingRatio().compareTo(BigDecimal.ZERO) > 0) { + parts.add("本年度发放比例合计 " + percentText(summary.getCurrentYearRatio())); + parts.add("未发放比例 " + percentText(summary.getPendingRatio())); + } + return String.join(";", parts); + } + + private List sortPlanningList(List planningList) { + return planningList.stream() + .sorted(Comparator + .comparingInt((ProjectPlanningDO item) -> designPartOrder(item.getDesignPart())) + .thenComparing(ProjectPlanningDO::getId)) + .collect(Collectors.toList()); + } + + private int designPartOrder(String designPart) { + if (Objects.equals(designPart, ProjectPlanningBizTypeConstants.DESIGN_PART_REAL_ESTATE)) { + return 1; + } + if (Objects.equals(designPart, ProjectPlanningBizTypeConstants.DESIGN_PART_UNDERGROUND)) { + return 2; + } + return 9; + } + + private Integer resolveReportYear(ProjectPlanningDO planning) { + return planning.getPlanningStartYear() == null ? LocalDate.now().getYear() : planning.getPlanningStartYear(); + } + + private List resolveBudgetYears(ProjectPlanningDO planning, List quarterList) { + Set yearSet = new LinkedHashSet<>(); + if (planning != null && planning.getPlanningStartYear() != null) { + yearSet.add(planning.getPlanningStartYear()); + } + if (quarterList != null) { + quarterList.stream() + .map(ProjectPlanningQuarterDO::getDistributionYear) + .filter(Objects::nonNull) + .sorted() + .forEach(yearSet::add); + } + if (yearSet.isEmpty()) { + yearSet.add(LocalDate.now().getYear()); + } + return new ArrayList<>(yearSet); + } + + private Map> getQuarterMap(Collection planningList) { + if (planningList == null || planningList.isEmpty()) { + return Collections.emptyMap(); + } + List planningIds = planningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList()); + return projectPlanningQuarterMapper.selectListByPlanningIds(planningIds).stream() + .collect(Collectors.groupingBy(ProjectPlanningQuarterDO::getPlanningId, LinkedHashMap::new, Collectors.toList())); + } + + private Map getOutputSplitMap(Collection planningList) { + if (planningList == null || planningList.isEmpty()) { + return Collections.emptyMap(); + } + Map map = new LinkedHashMap<>(); + for (ProjectPlanningDO planning : planningList) { + map.put(planning.getId(), projectOutputSplitService.getOrCreateProjectOutputSplit(planning.getId())); + } + return map; + } + + private Map> getRoleSplitMap(Collection planningList) { + if (planningList == null || planningList.isEmpty()) { + return Collections.emptyMap(); + } + Map> map = new LinkedHashMap<>(); + for (ProjectPlanningDO planning : planningList) { + map.put(planning.getId(), specialtyRoleSplitService.getSpecialtyRoleSplitListByPlanningId(planning.getId())); + } + return map; + } + + private Map getProjectMap(Collection planningList) { + if (planningList == null || planningList.isEmpty()) { + return Collections.emptyMap(); + } + Set projectIds = planningList.stream().map(ProjectPlanningDO::getProjectId) + .collect(Collectors.toCollection(LinkedHashSet::new)); + return projectMapper.selectBatchIds(projectIds).stream() + .collect(Collectors.toMap(ProjectDO::getId, item -> item, (a, b) -> a)); + } + + private Map getOfficeMap(Collection employeeList) { + if (employeeList == null || employeeList.isEmpty()) { + return Collections.emptyMap(); + } + Set officeIds = employeeList.stream() + .map(EmployeeDO::getOfficeId) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (officeIds.isEmpty()) { + return Collections.emptyMap(); + } + return officeMapper.selectBatchIds(officeIds).stream() + .collect(Collectors.toMap(OfficeDO::getId, item -> item, (a, b) -> a)); + } + + private QuarterSummary buildQuarterSummary(ProjectPlanningDO planning, List quarterList, Integer reportYear) { + QuarterSummary summary = new QuarterSummary(); + summary.setHistoricalIssuedRatio(ZERO_RATIO); + summary.setQuarterOneRatio(ZERO_RATIO); + summary.setQuarterTwoRatio(ZERO_RATIO); + summary.setQuarterThreeRatio(ZERO_RATIO); + summary.setQuarterFourRatio(ZERO_RATIO); + summary.setCurrentYearRatio(ZERO_RATIO); + summary.setPendingRatio(defaultDistributionRatio(planning)); + summary.setQuarterOneAmount(ZERO_AMOUNT); + summary.setQuarterTwoAmount(ZERO_AMOUNT); + summary.setQuarterThreeAmount(ZERO_AMOUNT); + summary.setQuarterFourAmount(ZERO_AMOUNT); + summary.setYearTotalAmount(ZERO_AMOUNT); + if (quarterList == null || quarterList.isEmpty()) { + return summary; + } + + BigDecimal allocatedRatio = ZERO_RATIO; + for (ProjectPlanningQuarterDO quarter : quarterList) { + BigDecimal distributionRatio = ratio(quarter.getDistributionRatio()); + allocatedRatio = allocatedRatio.add(distributionRatio).setScale(4, RoundingMode.HALF_UP); + if (quarter.getDistributionYear() != null && quarter.getDistributionYear() < reportYear) { + summary.setHistoricalIssuedRatio(summary.getHistoricalIssuedRatio().add(distributionRatio) + .setScale(4, RoundingMode.HALF_UP)); + continue; + } + if (!Objects.equals(quarter.getDistributionYear(), reportYear)) { + continue; + } + BigDecimal distributionAmount = amount(quarter.getDistributionAmount()); + summary.setCurrentYearRatio(summary.getCurrentYearRatio().add(distributionRatio) + .setScale(4, RoundingMode.HALF_UP)); + if (Objects.equals(quarter.getQuarterNo(), 1)) { + summary.setQuarterOneRatio(summary.getQuarterOneRatio().add(distributionRatio) + .setScale(4, RoundingMode.HALF_UP)); + summary.setQuarterOneAmount(summary.getQuarterOneAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } else if (Objects.equals(quarter.getQuarterNo(), 2)) { + summary.setQuarterTwoRatio(summary.getQuarterTwoRatio().add(distributionRatio) + .setScale(4, RoundingMode.HALF_UP)); + summary.setQuarterTwoAmount(summary.getQuarterTwoAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } else if (Objects.equals(quarter.getQuarterNo(), 3)) { + summary.setQuarterThreeRatio(summary.getQuarterThreeRatio().add(distributionRatio) + .setScale(4, RoundingMode.HALF_UP)); + summary.setQuarterThreeAmount(summary.getQuarterThreeAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } else if (Objects.equals(quarter.getQuarterNo(), 4)) { + summary.setQuarterFourRatio(summary.getQuarterFourRatio().add(distributionRatio) + .setScale(4, RoundingMode.HALF_UP)); + summary.setQuarterFourAmount(summary.getQuarterFourAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } + summary.setYearTotalAmount(summary.getYearTotalAmount().add(distributionAmount).setScale(2, RoundingMode.HALF_UP)); + } + + BigDecimal pendingRatio = defaultDistributionRatio(planning).subtract(allocatedRatio) + .setScale(4, RoundingMode.HALF_UP); + summary.setPendingRatio(pendingRatio.compareTo(BigDecimal.ZERO) < 0 ? ZERO_RATIO : pendingRatio); + return summary; + } + + private BigDecimal getCategoryRatio(ProjectOutputSplitDO outputSplit, String specialtyCode) { + if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + return ratio(outputSplit.getProjectLeadRatio()); + } + return multiplyRatio(outputSplit.getOfficeRatio(), getSpecialtyRatio(outputSplit, specialtyCode)); + } + + private BigDecimal getSpecialtyRatio(ProjectOutputSplitDO outputSplit, String specialtyCode) { + if (outputSplit == null || specialtyCode == null) { + return ZERO_RATIO; + } + 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 String buildSpecialtySummary(ProjectOutputSplitDO outputSplit, BigDecimal baseAmount) { + List summaryList = new ArrayList<>(); + summaryList.add("建筑 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getArchRatio())).toPlainString()); + summaryList.add("装饰 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getDecorRatio())).toPlainString()); + summaryList.add("结构 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getStructRatio())).toPlainString()); + summaryList.add("给排水 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getWaterRatio())).toPlainString()); + summaryList.add("电气 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getElecRatio())).toPlainString()); + summaryList.add("暖通 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getHvacRatio())).toPlainString()); + summaryList.add("数字化设计 " + multiplyAmount(baseAmount, + multiplyRatio(outputSplit.getOfficeRatio(), outputSplit.getDigitalRatio())).toPlainString()); + return String.join(";", summaryList); + } + + private String resolveBudgetCategoryLabel(String ownershipType) { + if (ProjectPlanningBizTypeConstants.isMajor(ownershipType)) { + return "六大专业考核产值"; + } + if (ProjectPlanningBizTypeConstants.isSubcontract(ownershipType)) { + return "专业分包产值"; + } + if (ProjectPlanningBizTypeConstants.isComprehensive(ownershipType)) { + return "内部协作产值"; + } + return "其他产值"; + } + + private String buildPlanningDisplayName(ProjectPlanningDO planning) { + if (planning == null) { + return ""; + } + String planningContent = defaultString(planning.getPlanningContent()); + String ownershipType = defaultString(planning.getOwnershipType()); + if (planningContent.isEmpty()) { + return ownershipType; + } + if (ownershipType.isEmpty()) { + return planningContent; + } + return planningContent + "(" + ownershipType + ")"; + } + + private String joinPlanningRemarks(List planningList) { + if (planningList == null || planningList.isEmpty()) { + return ""; + } + return planningList.stream() + .map(ProjectPlanningDO::getProgressRemark) + .map(this::defaultString) + .filter(item -> !item.isEmpty()) + .distinct() + .collect(Collectors.joining(";")); + } + + private String buildProjectBudgetYearRangeText(List planningList, + Map> quarterMap) { + if (planningList == null || planningList.isEmpty()) { + return String.valueOf(LocalDate.now().getYear()); + } + Set yearSet = new LinkedHashSet<>(); + for (ProjectPlanningDO planning : planningList) { + yearSet.addAll(resolveBudgetYears(planning, quarterMap.get(planning.getId()))); + } + List years = yearSet.stream().sorted().collect(Collectors.toList()); + if (years.isEmpty()) { + return String.valueOf(LocalDate.now().getYear()); + } + if (years.size() == 1) { + return String.valueOf(years.get(0)); + } + return years.get(0) + "-" + years.get(years.size() - 1); + } + + private String joinProjectRoleNames(List rolePersons, String roleCode) { + if (rolePersons == null || rolePersons.isEmpty()) { + return ""; + } + return rolePersons.stream() + .filter(item -> Objects.equals(item.getRoleCode(), roleCode)) + .map(ProjectRolePersonDO::getEmployeeName) + .filter(Objects::nonNull) + .collect(Collectors.joining("、")); + } + + private SpecialtyRoleSplitRespVO findRoleRow(List roleList, + String specialtyCode, String roleCode) { + if (roleList == null || roleList.isEmpty()) { + return null; + } + return roleList.stream() + .filter(item -> Objects.equals(item.getSpecialtyCode(), specialtyCode) + && Objects.equals(item.getRoleCode(), roleCode)) + .findFirst() + .orElse(null); + } + + private String joinRoleSplitPersonNames(SpecialtyRoleSplitRespVO roleRow) { + if (roleRow == null || roleRow.getPersons() == null || roleRow.getPersons().isEmpty()) { + return ""; + } + return roleRow.getPersons().stream() + .map(SpecialtyRolePersonRespVO::getEmployeeName) + .filter(Objects::nonNull) + .collect(Collectors.joining("、")); + } + + private String buildProgressText(ProjectDO project, ProjectPlanningDO planning) { + String projectStatus = project == null ? "" : defaultString(project.getProjectStatus()); + String planningRemark = planning == null ? "" : defaultString(planning.getProgressRemark()); + if (projectStatus.isEmpty()) { + return planningRemark; + } + if (planningRemark.isEmpty()) { + return projectStatus; + } + return projectStatus + ";" + planningRemark; + } + + private String getOfficeName(Map officeMap, Long officeId) { + if (officeId == null || officeMap == null || !officeMap.containsKey(officeId)) { + return ""; + } + return defaultString(officeMap.get(officeId).getOfficeName()); + } + + private void sortProjectOverviewRows(List rows, String sortType) { + Comparator comparator; + if (Objects.equals(sortType, SORT_OUTPUT_ASC)) { + comparator = Comparator.comparing(ProjectOverviewExcelRespVO::getTotalOutputAmount, this::compareAmount) + .thenComparing(ProjectOverviewExcelRespVO::getProjectName, String.CASE_INSENSITIVE_ORDER); + } else if (Objects.equals(sortType, SORT_NAME_ASC)) { + comparator = Comparator.comparing(ProjectOverviewExcelRespVO::getProjectName, String.CASE_INSENSITIVE_ORDER) + .thenComparing(ProjectOverviewExcelRespVO::getPlanningContent, String.CASE_INSENSITIVE_ORDER); + } else { + comparator = Comparator.comparing(ProjectOverviewExcelRespVO::getTotalOutputAmount, this::compareAmount) + .reversed() + .thenComparing(ProjectOverviewExcelRespVO::getProjectName, String.CASE_INSENSITIVE_ORDER); + } + rows.sort(comparator); + } + + private void sortEmployeeRows(List rows, String sortType) { + Comparator comparator; + if (Objects.equals(sortType, SORT_NAME_ASC)) { + comparator = Comparator.comparing(EmployeeOutputSummaryExcelRespVO::getEmployeeName, + String.CASE_INSENSITIVE_ORDER); + } else if (Objects.equals(sortType, SORT_ANNUAL_TOTAL_ASC)) { + comparator = Comparator.comparing(EmployeeOutputSummaryExcelRespVO::getAnnualTotalAmount, this::compareAmount) + .thenComparing(EmployeeOutputSummaryExcelRespVO::getEmployeeName, String.CASE_INSENSITIVE_ORDER); + } else { + comparator = Comparator.comparing(EmployeeOutputSummaryExcelRespVO::getAnnualTotalAmount, this::compareAmount) + .reversed() + .thenComparing(EmployeeOutputSummaryExcelRespVO::getEmployeeName, String.CASE_INSENSITIVE_ORDER); + } + rows.sort(comparator); + } + + private int compareAmount(BigDecimal left, BigDecimal right) { + return amount(left).compareTo(amount(right)); + } + + private boolean hasYearAmount(List quarterList, Integer year) { + if (quarterList == null || quarterList.isEmpty()) { + return false; + } + return quarterList.stream() + .anyMatch(item -> Objects.equals(item.getDistributionYear(), year) + && amount(item.getDistributionAmount()).compareTo(ZERO_AMOUNT) > 0); + } + + private BigDecimal firstNonZero(List values) { + if (values == null || values.isEmpty()) { + return ZERO_AMOUNT; + } + return values.stream() + .filter(Objects::nonNull) + .map(this::amount) + .filter(value -> value.compareTo(BigDecimal.ZERO) > 0) + .findFirst() + .orElse(ZERO_AMOUNT); + } + + private BigDecimal defaultDistributionRatio(ProjectPlanningDO planning) { + if (planning == null || planning.getTotalDistributionAmount() == null) { + return new BigDecimal("1.0000"); + } + return ratio(planning.getTotalDistributionAmount()); + } + + private BigDecimal amount(BigDecimal value) { + return value == null ? ZERO_AMOUNT : value.setScale(2, RoundingMode.HALF_UP); + } + + private BigDecimal amountToWan(BigDecimal value) { + return amount(value).divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP); + } + + private BigDecimal ratio(BigDecimal value) { + return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP); + } + + private BigDecimal multiplyAmount(BigDecimal amount, BigDecimal... ratios) { + BigDecimal result = amount(amount); + for (BigDecimal ratio : ratios) { + result = result.multiply(ratio(ratio)); + } + return result.setScale(2, RoundingMode.HALF_UP); + } + + private BigDecimal multiplyRatio(BigDecimal... ratios) { + BigDecimal result = BigDecimal.ONE; + for (BigDecimal ratio : ratios) { + result = result.multiply(ratio(ratio)); + } + return result.setScale(4, RoundingMode.HALF_UP); + } + + private BigDecimal addRatio(BigDecimal left, BigDecimal right) { + return ratio(left).add(ratio(right)).setScale(4, RoundingMode.HALF_UP); + } + + private String percentText(BigDecimal value) { + return ratio(value).multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP) + "%"; + } + + private String buildFileName(String prefix, String suffix) { + return sanitizeFileName(prefix) + "_" + sanitizeFileName(suffix) + ".xlsx"; + } + + private String sanitizeFileName(String value) { + if (value == null || value.trim().isEmpty()) { + return "报表"; + } + return value.replaceAll("[\\\\/:*?\"<>|]", "_"); + } + + private String defaultString(String value) { + return value == null ? "" : value.trim(); + } + + @Data + private static class QuarterSummary { + private BigDecimal historicalIssuedRatio; + private BigDecimal quarterOneRatio; + private BigDecimal quarterTwoRatio; + private BigDecimal quarterThreeRatio; + private BigDecimal quarterFourRatio; + private BigDecimal currentYearRatio; + private BigDecimal pendingRatio; + private BigDecimal quarterOneAmount; + private BigDecimal quarterTwoAmount; + private BigDecimal quarterThreeAmount; + private BigDecimal quarterFourAmount; + private BigDecimal yearTotalAmount; + } + + @Data + private static class EmployeeAccumulator { + private BigDecimal quarterOneAmount = ZERO_AMOUNT; + private BigDecimal quarterTwoAmount = ZERO_AMOUNT; + private BigDecimal quarterThreeAmount = ZERO_AMOUNT; + private BigDecimal quarterFourAmount = ZERO_AMOUNT; + + public void addQuarterAmount(int quarterNo, BigDecimal amount) { + if (quarterNo == 1) { + quarterOneAmount = quarterOneAmount.add(amount == null ? ZERO_AMOUNT : amount).setScale(2, RoundingMode.HALF_UP); + return; + } + if (quarterNo == 2) { + quarterTwoAmount = quarterTwoAmount.add(amount == null ? ZERO_AMOUNT : amount).setScale(2, RoundingMode.HALF_UP); + return; + } + if (quarterNo == 3) { + quarterThreeAmount = quarterThreeAmount.add(amount == null ? ZERO_AMOUNT : amount).setScale(2, RoundingMode.HALF_UP); + return; + } + if (quarterNo == 4) { + quarterFourAmount = quarterFourAmount.add(amount == null ? ZERO_AMOUNT : amount).setScale(2, RoundingMode.HALF_UP); + } + } + + public BigDecimal getAnnualTotalAmount() { + return quarterOneAmount.add(quarterTwoAmount).add(quarterThreeAmount).add(quarterFourAmount) + .setScale(2, RoundingMode.HALF_UP); + } + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/AbstractProjectOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/AbstractProjectOutputExcelBuilder.java new file mode 100644 index 0000000..30e53a3 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/AbstractProjectOutputExcelBuilder.java @@ -0,0 +1,207 @@ +package cn.iocoder.lyzsys.module.tjt.service.report.builder; + +import cn.iocoder.lyzsys.framework.common.util.http.HttpUtils; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; + +public abstract class AbstractProjectOutputExcelBuilder { + + 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); + + protected Workbook createWorkbook() { + return new XSSFWorkbook(); + } + + protected Sheet createSheet(Workbook workbook, String preferredName, String fallbackName) { + return workbook.createSheet(buildSheetName(preferredName, fallbackName)); + } + + protected CellStyle createTitleStyle(Workbook workbook) { + CellStyle style = createBaseStyle(workbook); + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 14); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + return style; + } + + protected CellStyle createInfoStyle(Workbook workbook) { + CellStyle style = createBaseStyle(workbook); + Font font = workbook.createFont(); + font.setFontHeightInPoints((short) 10); + style.setFont(font); + return style; + } + + protected CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = createBaseStyle(workbook); + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 10); + style.setFont(font); + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setAlignment(HorizontalAlignment.CENTER); + return style; + } + + protected CellStyle createCellStyle(Workbook workbook) { + CellStyle style = createBaseStyle(workbook); + Font font = workbook.createFont(); + font.setFontHeightInPoints((short) 10); + style.setFont(font); + return style; + } + + protected CellStyle createDerivedStyle(Workbook workbook, CellStyle sourceStyle, + IndexedColors fillColor, HorizontalAlignment alignment, + boolean bold, short fontHeightInPoints) { + CellStyle style = workbook.createCellStyle(); + style.cloneStyleFrom(sourceStyle); + if (fillColor != null) { + style.setFillForegroundColor(fillColor.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + } + if (alignment != null) { + style.setAlignment(alignment); + } + Font font = workbook.createFont(); + font.setBold(bold); + font.setFontHeightInPoints(fontHeightInPoints); + style.setFont(font); + return style; + } + + protected void setText(Row row, int cellIndex, Object value, CellStyle style) { + Cell cell = row.createCell(cellIndex); + cell.setCellValue(value == null ? "" : String.valueOf(value)); + if (style != null) { + cell.setCellStyle(style); + } + } + + protected Row getOrCreateRow(Sheet sheet, int rowIndex) { + Row row = sheet.getRow(rowIndex); + return row != null ? row : sheet.createRow(rowIndex); + } + + protected int writeMergedTitleRow(Sheet sheet, int rowIndex, String title, CellStyle style, int lastCol) { + Row row = getOrCreateRow(sheet, rowIndex); + setText(row, 0, title, style); + merge(sheet, rowIndex, rowIndex, 0, lastCol); + return rowIndex + 1; + } + + protected int writeRow(Sheet sheet, int rowIndex, CellStyle style, Object... values) { + Row row = getOrCreateRow(sheet, rowIndex); + for (int i = 0; i < values.length; i++) { + setText(row, i, values[i], style); + } + return rowIndex + 1; + } + + protected void writeLabelValue(Row row, int labelCellIndex, int valueCellIndex, + Object label, Object value, CellStyle style) { + setText(row, labelCellIndex, label, style); + setText(row, valueCellIndex, value, style); + } + + protected void merge(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) { + sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol)); + } + + protected void setMergedRegionText(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol, + String text, CellStyle style) { + for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) { + Row row = getOrCreateRow(sheet, rowIndex); + for (int cellIndex = firstCol; cellIndex <= lastCol; cellIndex++) { + setText(row, cellIndex, rowIndex == firstRow && cellIndex == firstCol ? text : "", style); + } + } + if (firstRow != lastRow || firstCol != lastCol) { + merge(sheet, firstRow, lastRow, firstCol, lastCol); + } + } + + protected void setColumnWidths(Sheet sheet, int... widths) { + for (int i = 0; i < widths.length; i++) { + sheet.setColumnWidth(i, widths[i] * 256); + } + } + + protected BigDecimal amount(BigDecimal value) { + return value == null ? ZERO_AMOUNT : value.setScale(2, RoundingMode.HALF_UP); + } + + protected BigDecimal ratio(BigDecimal value) { + return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP); + } + + protected String percentText(BigDecimal value) { + return ratio(value).multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP) + "%"; + } + + protected String text(BigDecimal value) { + return amount(value).toPlainString(); + } + + protected String safeText(String value) { + return value == null ? "" : value.trim(); + } + + public void writeWorkbook(HttpServletResponse response, String fileName, Workbook workbook) throws IOException { + try { + response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(fileName)); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + workbook.write(response.getOutputStream()); + response.flushBuffer(); + } finally { + workbook.close(); + } + } + + private CellStyle createBaseStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setWrapText(true); + return style; + } + + private String buildSheetName(String preferred, String fallback) { + String sheetName = sanitizeSheetName(preferred); + if (sheetName.isEmpty()) { + sheetName = sanitizeSheetName(fallback); + } + if (sheetName.length() > 31) { + sheetName = sheetName.substring(0, 31); + } + return sheetName.isEmpty() ? "Sheet1" : sheetName; + } + + private String sanitizeSheetName(String value) { + return safeText(value).replaceAll("[\\\\/?*\\[\\]:]", "_"); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java new file mode 100644 index 0000000..368a35b --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java @@ -0,0 +1,366 @@ +package cn.iocoder.lyzsys.module.tjt.service.report.builder; + +import lombok.Data; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +@Component +public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder { + + private static final String SUMMARY_PLACEHOLDER = "/"; + private static final String BUDGET_SHEET_NAME = "建筑(装饰)工程项目考核产值预算表"; + private static final String BUDGET_SHEET_FALLBACK_NAME = "项目考核产值预算表"; + private static final String BUDGET_SHEET_TITLE = "附件4 建筑(装饰)工程项目考核产值预算表"; + private static final String QUARTER_BUDGET_SHEET_NAME = "项目考核产值年度季度预算计取表"; + private static final String QUARTER_BUDGET_SHEET_FALLBACK_NAME = "年度季度预算计取表"; + private static final String QUARTER_BUDGET_SHEET_TITLE = "项目考核产值年度/季度预算计取表"; + + public Workbook build(ExportData data) { + Workbook workbook = createWorkbook(); + buildBudgetSheet(workbook, data); + buildQuarterBudgetSheet(workbook, data); + return workbook; + } + + private void buildBudgetSheet(Workbook workbook, ExportData data) { + Sheet sheet = createSheet(workbook, BUDGET_SHEET_NAME, BUDGET_SHEET_FALLBACK_NAME); + setColumnWidths(sheet, 16, 14, 18, 16, 18, 14, 12, 12, 12, 18, 12, 14, 16); + + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle infoStyle = createInfoStyle(workbook); + CellStyle infoValueStyle = createDerivedStyle(workbook, infoStyle, + null, HorizontalAlignment.LEFT, false, (short) 10); + CellStyle baseHeaderStyle = createHeaderStyle(workbook); + CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle, + IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10); + CellStyle cellStyle = createCellStyle(workbook); + CellStyle subtotalStyle = headerStyle; + CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle, + IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10); + + int rowIndex = 0; + rowIndex = writeMergedTitleRow(sheet, rowIndex, BUDGET_SHEET_TITLE, titleStyle, 12); + rowIndex = buildBudgetHeader(sheet, rowIndex, headerStyle); + + if (data.getBudgetRows() != null) { + for (BudgetRow rowData : data.getBudgetRows()) { + if (rowData == null || rowData.getRowType() == null) { + continue; + } + if (BudgetRowType.DETAIL.equals(rowData.getRowType())) { + writeBudgetDetailRow(sheet, rowIndex++, rowData, cellStyle); + continue; + } + if (BudgetRowType.PART_EMPTY.equals(rowData.getRowType())) { + writeBudgetEmptyRow(sheet, rowIndex++, rowData, cellStyle); + continue; + } + if (BudgetRowType.PART_SUBTOTAL.equals(rowData.getRowType())) { + writeBudgetSummaryRow(sheet, rowIndex++, rowData, subtotalStyle); + continue; + } + if (BudgetRowType.PLANNING_TOTAL.equals(rowData.getRowType())) { + writeBudgetSummaryRow(sheet, rowIndex++, rowData, totalStyle); + } + } + } + + Row remarkRow = sheet.createRow(rowIndex++); + setMergedRegionText(sheet, remarkRow.getRowNum(), remarkRow.getRowNum(), 0, 12, + "备注:" + safeText(data.getRemark()), infoValueStyle); + + Row signRow = sheet.createRow(rowIndex); + setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 0, 3, + "项目经理/工程负责人:", infoValueStyle); + setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 4, 7, + "设计中心负责人:", infoValueStyle); + setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 8, 12, + "市场运营中心负责人:", infoValueStyle); + } + + private int buildBudgetHeader(Sheet sheet, int rowIndex, CellStyle headerStyle) { + int firstRow = rowIndex; + sheet.createRow(firstRow); + sheet.createRow(firstRow + 1); + sheet.createRow(firstRow + 2); + + setMergedRegionText(sheet, firstRow, firstRow + 1, 0, 0, "项目规模", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 1, 1, 2, "建筑工程(㎡)\n精装工程(㎡)", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 2, 3, 3, "内部指导单价(元 /㎡)", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow, 4, 10, "考核产值面积", headerStyle); + setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 10, 10, "小计(㎡)", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 2, 11, 11, "设计阶段占比", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 2, 12, 12, "考核产值小计(万元)", headerStyle); + + setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 4, 4, "建筑(装饰)设计面积(㎡)", headerStyle); + setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 5, 6, "套图系数", headerStyle); + setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 7, 9, "调整系数", headerStyle); + + writeRow(sheet, firstRow + 2, headerStyle, (Object[]) new String[]{ + "规划内容", "设计部位", "建筑类型", "", "", "栋数 / 户型数", + "套图系数", "规模系数", "修改系数", "复杂系数 / 复杂等级", "", "", "" + }); + return rowIndex + 3; + } + + private void writeBudgetDetailRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) { + Row row = sheet.createRow(rowIndex); + setText(row, 0, "", style); + setText(row, 1, safeText(rowData.getDisplayDesignPart()), style); + setText(row, 2, safeText(rowData.getDisplayBuildingType()), style); + setText(row, 3, textOrBlank(rowData.getInternalGuidanceUnitPrice()), style); + setText(row, 4, textOrBlank(rowData.getDesignArea()), style); + setText(row, 5, integerText(rowData.getBuildingOrUnitCount()), style); + setText(row, 6, factorTextOrBlank(rowData.getDrawingSetFactor(), 2), style); + setText(row, 7, factorTextOrBlank(rowData.getScaleFactor(), 2), style); + setText(row, 8, factorTextOrBlank(rowData.getModificationFactor(), 2), style); + setText(row, 9, factorTextOrBlank(rowData.getComplexityFactor(), 2), style); + setText(row, 10, textOrBlank(rowData.getSubtotalArea()), style); + setText(row, 11, percentTextOrBlank(rowData.getCurrentDesignStageRatio()), style); + setText(row, 12, textOrBlank(rowData.getAssessmentOutputValueWan()), style); + } + + private void writeBudgetEmptyRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) { + Row row = sheet.createRow(rowIndex); + setText(row, 0, "", style); + setText(row, 1, safeText(rowData.getDisplayDesignPart()), style); + for (int i = 2; i <= 12; i++) { + setText(row, i, "", style); + } + } + + private void writeBudgetSummaryRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) { + Row row = sheet.createRow(rowIndex); + setText(row, 0, "", style); + setText(row, 1, safeText(rowData.getDisplayDesignPart()), style); + setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, SUMMARY_PLACEHOLDER, style); + setText(row, 4, text(rowData.getDesignArea()), style); + setMergedRegionText(sheet, rowIndex, rowIndex, 5, 9, SUMMARY_PLACEHOLDER, style); + setText(row, 10, text(rowData.getSubtotalArea()), style); + setText(row, 11, SUMMARY_PLACEHOLDER, style); + setText(row, 12, text(rowData.getAssessmentOutputValueWan()), style); + } + + private void buildQuarterBudgetSheet(Workbook workbook, ExportData data) { + Sheet sheet = createSheet(workbook, QUARTER_BUDGET_SHEET_NAME, QUARTER_BUDGET_SHEET_FALLBACK_NAME); + setColumnWidths(sheet, 10, 20, 18, 24, 16, 16, 16, 12, 14, 14, 12, 12, 12, 12, 14, 14, 12, 12, 12, 12, 14, 24); + + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle baseHeaderStyle = createHeaderStyle(workbook); + CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle, + IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10); + CellStyle cellStyle = createCellStyle(workbook); + CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle, + IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10); + CellStyle centeredCellStyle = createDerivedStyle(workbook, cellStyle, + null, HorizontalAlignment.CENTER, false, (short) 10); + + int rowIndex = 0; + rowIndex = writeMergedTitleRow(sheet, rowIndex, QUARTER_BUDGET_SHEET_TITLE, titleStyle, 21); + rowIndex = buildQuarterBudgetHeader(sheet, rowIndex, data, headerStyle, centeredCellStyle); + + for (QuarterBudgetRow rowData : data.getQuarterBudgetRows()) { + Row row = sheet.createRow(rowIndex++); + if (rowData.isTotalRow()) { + writeQuarterBudgetTotalRow(sheet, row, rowData, totalStyle); + continue; + } + + setText(row, 0, rowData.getSerialNo(), cellStyle); + setText(row, 1, safeText(data.getProjectName()), cellStyle); + setText(row, 2, safeText(rowData.getOutputType()), cellStyle); + setText(row, 3, safeText(rowData.getDesignContent()), cellStyle); + setText(row, 4, textOrBlank(rowData.getTotalDesignArea()), cellStyle); + setText(row, 5, textOrBlank(rowData.getTotalAssessmentArea()), cellStyle); + setText(row, 6, textOrBlank(rowData.getTotalAssessmentOutputWan()), cellStyle); + setText(row, 7, percentTextOrBlank(rowData.getDesignStageRatio()), cellStyle); + setText(row, 8, textOrBlank(rowData.getDesignStageOutputWan()), cellStyle); + setText(row, 9, percentTextOrBlank(rowData.getHistoricalIssuedRatio()), cellStyle); + setText(row, 10, percentTextOrBlank(rowData.getQuarterOneRatio()), cellStyle); + setText(row, 11, percentTextOrBlank(rowData.getQuarterTwoRatio()), cellStyle); + setText(row, 12, percentTextOrBlank(rowData.getQuarterThreeRatio()), cellStyle); + setText(row, 13, percentTextOrBlank(rowData.getQuarterFourRatio()), cellStyle); + setText(row, 14, percentTextOrBlank(rowData.getCurrentYearRatio()), cellStyle); + setText(row, 15, percentTextOrBlank(rowData.getPendingRatio()), cellStyle); + setText(row, 16, textOrBlank(rowData.getQuarterOneAmountWan()), cellStyle); + setText(row, 17, textOrBlank(rowData.getQuarterTwoAmountWan()), cellStyle); + setText(row, 18, textOrBlank(rowData.getQuarterThreeAmountWan()), cellStyle); + setText(row, 19, textOrBlank(rowData.getQuarterFourAmountWan()), cellStyle); + setText(row, 20, textOrBlank(rowData.getYearTotalAmountWan()), cellStyle); + setText(row, 21, safeText(rowData.getRatioRemark()), cellStyle); + } + } + + private int buildQuarterBudgetHeader(Sheet sheet, int rowIndex, ExportData data, + CellStyle headerStyle, CellStyle valueStyle) { + int firstRow = rowIndex; + sheet.createRow(firstRow); + sheet.createRow(firstRow + 1); + sheet.createRow(firstRow + 2); + + setMergedRegionText(sheet, firstRow, firstRow + 1, 0, 0, "工程编号", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 1, 1, 1, safeText(data.getProjectCode()), valueStyle); + setText(sheet.getRow(firstRow + 2), 0, "序号", headerStyle); + setText(sheet.getRow(firstRow + 2), 1, "项目名称", headerStyle); + + setMergedRegionText(sheet, firstRow, firstRow + 1, 2, 3, "类别", headerStyle); + setText(sheet.getRow(firstRow + 2), 2, "产值类型", headerStyle); + setText(sheet.getRow(firstRow + 2), 3, "设计内容", headerStyle); + + setMergedRegionText(sheet, firstRow, firstRow + 2, 4, 4, "总建筑(精装)设计面积(㎡)", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 2, 5, 5, "总考核产值面积(㎡)", headerStyle); + setMergedRegionText(sheet, firstRow, firstRow + 2, 6, 6, "总考核产值(万元)", headerStyle); + + setMergedRegionText(sheet, firstRow, firstRow + 1, 7, 8, "设计阶段考核产值分配", headerStyle); + setText(sheet.getRow(firstRow + 2), 7, "阶段占比", headerStyle); + setText(sheet.getRow(firstRow + 2), 8, "考核产值", headerStyle); + + setMergedRegionText(sheet, firstRow, firstRow, 9, 15, "发放情况", headerStyle); + setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 9, 9, "往年已发放比例", headerStyle); + setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 10, 14, "本年度发放比例", headerStyle); + setText(sheet.getRow(firstRow + 2), 10, "一季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 11, "二季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 12, "三季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 13, "四季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 14, "本年度小计", headerStyle); + setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 15, 15, "未发放比例", headerStyle); + + setMergedRegionText(sheet, firstRow, firstRow + 1, 16, 20, "本年度项目考核产值(万元)", headerStyle); + setText(sheet.getRow(firstRow + 2), 16, "一季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 17, "二季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 18, "三季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 19, "四季度", headerStyle); + setText(sheet.getRow(firstRow + 2), 20, "本年度小计", headerStyle); + + setMergedRegionText(sheet, firstRow, firstRow + 2, 21, 21, "备注(本年度比例说明)", headerStyle); + return rowIndex + 3; + } + + private void writeQuarterBudgetTotalRow(Sheet sheet, Row row, QuarterBudgetRow rowData, CellStyle totalStyle) { + int rowIndex = row.getRowNum(); + setMergedRegionText(sheet, rowIndex, rowIndex, 0, 3, "项目合计", totalStyle); + setText(row, 4, textOrBlank(rowData.getTotalDesignArea()), totalStyle); + setText(row, 5, textOrBlank(rowData.getTotalAssessmentArea()), totalStyle); + setText(row, 6, textOrBlank(rowData.getTotalAssessmentOutputWan()), totalStyle); + setText(row, 7, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 8, textOrBlank(rowData.getDesignStageOutputWan()), totalStyle); + setText(row, 9, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 10, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 11, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 12, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 13, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 14, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 15, SUMMARY_PLACEHOLDER, totalStyle); + setText(row, 16, textOrBlank(rowData.getQuarterOneAmountWan()), totalStyle); + setText(row, 17, textOrBlank(rowData.getQuarterTwoAmountWan()), totalStyle); + setText(row, 18, textOrBlank(rowData.getQuarterThreeAmountWan()), totalStyle); + setText(row, 19, textOrBlank(rowData.getQuarterFourAmountWan()), totalStyle); + setText(row, 20, textOrBlank(rowData.getYearTotalAmountWan()), totalStyle); + setText(row, 21, "", totalStyle); + } + + private String factorTextOrBlank(BigDecimal value, int digits) { + if (value == null) { + return ""; + } + return value.setScale(digits, RoundingMode.HALF_UP).toPlainString(); + } + + private String textOrBlank(BigDecimal value) { + if (value == null) { + return ""; + } + return value.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } + + private String percentTextOrBlank(BigDecimal value) { + if (value == null) { + return ""; + } + return percentText(value); + } + + private String integerText(Integer value) { + return value == null ? "" : String.valueOf(value); + } + + @Data + public static class ExportData { + private String projectCode; + private String projectName; + private String projectManagerName; + private String engineeringPrincipalName; + private BigDecimal totalConstructionArea; + private BigDecimal contractUnitPrice; + private BigDecimal internalGuidanceUnitPrice; + private BigDecimal totalAssessmentArea; + private BigDecimal totalAssessmentOutputValue; + private BigDecimal totalAssessmentOutputValueWan; + private String remark; + private String yearRangeText; + private List budgetRows; + private List quarterBudgetRows; + } + + public enum BudgetRowType { + DETAIL, + PART_EMPTY, + PART_SUBTOTAL, + PLANNING_TOTAL + } + + @Data + public static class BudgetRow { + private BudgetRowType rowType; + private String planningContent; + private String displayDesignPart; + private String displayBuildingType; + private BigDecimal internalGuidanceUnitPrice; + private BigDecimal designArea; + private Integer buildingOrUnitCount; + private BigDecimal drawingSetFactor; + private BigDecimal scaleFactor; + private BigDecimal modificationFactor; + private BigDecimal complexityFactor; + private BigDecimal subtotalArea; + private BigDecimal currentDesignStageRatio; + private BigDecimal assessmentOutputValueWan; + } + + @Data + public static class QuarterBudgetRow { + private Integer serialNo; + private boolean totalRow; + private String outputType; + private String designContent; + private Integer budgetYear; + private BigDecimal totalDesignArea; + private BigDecimal totalAssessmentArea; + private BigDecimal totalAssessmentOutputWan; + private BigDecimal designStageRatio; + private BigDecimal designStageOutputWan; + private BigDecimal historicalIssuedRatio; + private BigDecimal quarterOneRatio; + private BigDecimal quarterTwoRatio; + private BigDecimal quarterThreeRatio; + private BigDecimal quarterFourRatio; + private BigDecimal currentYearRatio; + private BigDecimal pendingRatio; + private BigDecimal quarterOneAmountWan; + private BigDecimal quarterTwoAmountWan; + private BigDecimal quarterThreeAmountWan; + private BigDecimal quarterFourAmountWan; + private BigDecimal yearTotalAmountWan; + private String ratioRemark; + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java new file mode 100644 index 0000000..113ce6b --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java @@ -0,0 +1,91 @@ +package cn.iocoder.lyzsys.module.tjt.service.report.builder; + +import lombok.Data; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; + +@Component +public class ProjectLeadQuarterOutputExcelBuilder extends AbstractProjectOutputExcelBuilder { + + public Workbook build(ExportData data) { + Workbook workbook = createWorkbook(); + Sheet sheet = createSheet(workbook, "项目负责人计取", "项目负责人计取"); + setColumnWidths(sheet, 8, 24, 14, 20, 12, 20, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12); + + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle infoStyle = createInfoStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle cellStyle = createCellStyle(workbook); + + int rowIndex = 0; + rowIndex = writeMergedTitleRow(sheet, rowIndex, "项目负责人年度/季度计取表", titleStyle, 16); + + Row infoRow = sheet.createRow(rowIndex++); + writeLabelValue(infoRow, 0, 1, "项目名称", data.getProjectName(), infoStyle); + writeLabelValue(infoRow, 4, 5, "年度", data.getYear(), infoStyle); + + rowIndex = writeRow(sheet, rowIndex, headerStyle, (Object[]) new String[]{ + "序号", "规划内容", "项目经理人员", "项目经理比例", "项目负责人人员", "项目负责人比例", + "项目经理一季度", "项目经理二季度", "项目经理三季度", "项目经理四季度", "项目经理年度合计", + "项目负责人一季度", "项目负责人二季度", "项目负责人三季度", "项目负责人四季度", + "项目负责人年度合计", "项目经理/项目负责人合计" + }); + + int serialNo = 1; + for (LeadQuarterRow rowData : data.getRows()) { + Row row = sheet.createRow(rowIndex++); + setText(row, 0, serialNo++, cellStyle); + setText(row, 1, rowData.getPlanningContent(), cellStyle); + setText(row, 2, rowData.getProjectManagerNames(), cellStyle); + setText(row, 3, percentText(rowData.getProjectManagerRatio()), cellStyle); + setText(row, 4, rowData.getEngineeringPrincipalNames(), cellStyle); + setText(row, 5, percentText(rowData.getEngineeringPrincipalRatio()), cellStyle); + setText(row, 6, text(rowData.getProjectManagerQuarterOneAmount()), cellStyle); + setText(row, 7, text(rowData.getProjectManagerQuarterTwoAmount()), cellStyle); + setText(row, 8, text(rowData.getProjectManagerQuarterThreeAmount()), cellStyle); + setText(row, 9, text(rowData.getProjectManagerQuarterFourAmount()), cellStyle); + setText(row, 10, text(rowData.getProjectManagerYearTotalAmount()), cellStyle); + setText(row, 11, text(rowData.getEngineeringPrincipalQuarterOneAmount()), cellStyle); + setText(row, 12, text(rowData.getEngineeringPrincipalQuarterTwoAmount()), cellStyle); + setText(row, 13, text(rowData.getEngineeringPrincipalQuarterThreeAmount()), cellStyle); + setText(row, 14, text(rowData.getEngineeringPrincipalQuarterFourAmount()), cellStyle); + setText(row, 15, text(rowData.getEngineeringPrincipalYearTotalAmount()), cellStyle); + setText(row, 16, text(rowData.getTotalAmount()), cellStyle); + } + return workbook; + } + + @Data + public static class ExportData { + private String projectName; + private Integer year; + private List rows; + } + + @Data + public static class LeadQuarterRow { + private String planningContent; + private String projectManagerNames; + private BigDecimal projectManagerRatio; + private String engineeringPrincipalNames; + private BigDecimal engineeringPrincipalRatio; + private BigDecimal projectManagerQuarterOneAmount; + private BigDecimal projectManagerQuarterTwoAmount; + private BigDecimal projectManagerQuarterThreeAmount; + private BigDecimal projectManagerQuarterFourAmount; + private BigDecimal projectManagerYearTotalAmount; + private BigDecimal engineeringPrincipalQuarterOneAmount; + private BigDecimal engineeringPrincipalQuarterTwoAmount; + private BigDecimal engineeringPrincipalQuarterThreeAmount; + private BigDecimal engineeringPrincipalQuarterFourAmount; + private BigDecimal engineeringPrincipalYearTotalAmount; + private BigDecimal totalAmount; + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java new file mode 100644 index 0000000..5922c36 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java @@ -0,0 +1,87 @@ +package cn.iocoder.lyzsys.module.tjt.service.report.builder; + +import lombok.Data; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; + +@Component +public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcelBuilder { + + public Workbook build(ExportData data) { + Workbook workbook = createWorkbook(); + Sheet sheet = createSheet(workbook, "年度季度计取", "年度季度计取"); + setColumnWidths(sheet, 8, 24, 12, 22, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12); + + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle infoStyle = createInfoStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle cellStyle = createCellStyle(workbook); + + int rowIndex = 0; + rowIndex = writeMergedTitleRow(sheet, rowIndex, "项目考核产值年度/季度计取表", titleStyle, 15); + + Row infoRow = sheet.createRow(rowIndex++); + writeLabelValue(infoRow, 0, 1, "项目名称", data.getProjectName(), infoStyle); + writeLabelValue(infoRow, 4, 5, "年度", data.getYear(), infoStyle); + + rowIndex = writeRow(sheet, rowIndex, headerStyle, (Object[]) new String[]{ + "序号", "项目名称", "归属类型", "规划内容", "往年已发放比例", "本年度发放比例", + "未发放比例", "一季度", "二季度", "三季度", "四季度", "本年度小计", + "项目经理/项目负责人", "专业所合计", "专业分配说明", "考核产值" + }); + + int serialNo = 1; + for (QuarterRow rowData : data.getRows()) { + Row row = sheet.createRow(rowIndex++); + setText(row, 0, serialNo++, cellStyle); + setText(row, 1, data.getProjectName(), cellStyle); + setText(row, 2, rowData.getOwnershipType(), cellStyle); + setText(row, 3, rowData.getPlanningContent(), cellStyle); + setText(row, 4, percentText(rowData.getHistoricalIssuedRatio()), cellStyle); + setText(row, 5, percentText(rowData.getCurrentYearRatio()), cellStyle); + setText(row, 6, percentText(rowData.getPendingRatio()), cellStyle); + setText(row, 7, text(rowData.getQuarterOneAmount()), cellStyle); + setText(row, 8, text(rowData.getQuarterTwoAmount()), cellStyle); + setText(row, 9, text(rowData.getQuarterThreeAmount()), cellStyle); + setText(row, 10, text(rowData.getQuarterFourAmount()), cellStyle); + setText(row, 11, text(rowData.getYearTotalAmount()), cellStyle); + setText(row, 12, text(rowData.getProjectLeadAmount()), cellStyle); + setText(row, 13, text(rowData.getOfficeAmount()), cellStyle); + setText(row, 14, rowData.getSpecialtySummaryText(), cellStyle); + setText(row, 15, text(rowData.getAssessmentOutputValue()), cellStyle); + } + return workbook; + } + + @Data + public static class ExportData { + private String projectName; + private Integer year; + private List rows; + } + + @Data + public static class QuarterRow { + private String ownershipType; + private String planningContent; + private BigDecimal historicalIssuedRatio; + private BigDecimal currentYearRatio; + private BigDecimal pendingRatio; + private BigDecimal quarterOneAmount; + private BigDecimal quarterTwoAmount; + private BigDecimal quarterThreeAmount; + private BigDecimal quarterFourAmount; + private BigDecimal yearTotalAmount; + private BigDecimal projectLeadAmount; + private BigDecimal officeAmount; + private String specialtySummaryText; + private BigDecimal assessmentOutputValue; + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java new file mode 100644 index 0000000..88caf9a --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java @@ -0,0 +1,81 @@ +package cn.iocoder.lyzsys.module.tjt.service.report.builder; + +import lombok.Data; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; + +@Component +public class SpecialtyPersonOutputExcelBuilder extends AbstractProjectOutputExcelBuilder { + + public Workbook build(ExportData data) { + Workbook workbook = createWorkbook(); + Sheet sheet = createSheet(workbook, "专业内人员计取", "专业内人员计取"); + setColumnWidths(sheet, 8, 20, 14, 16, 12, 12, 12, 12, 12, 12); + + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle infoStyle = createInfoStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle cellStyle = createCellStyle(workbook); + + int rowIndex = 0; + rowIndex = writeMergedTitleRow(sheet, rowIndex, "专业内人员项目考核产值年度/季度计取表", titleStyle, 9); + + Row infoRowOne = sheet.createRow(rowIndex++); + writeLabelValue(infoRowOne, 0, 1, "项目名称", data.getProjectName(), infoStyle); + writeLabelValue(infoRowOne, 4, 5, "规划内容", data.getPlanningContent(), infoStyle); + + Row infoRowTwo = sheet.createRow(rowIndex++); + writeLabelValue(infoRowTwo, 0, 1, "专业", data.getSpecialtyName(), infoStyle); + writeLabelValue(infoRowTwo, 4, 5, "年度", data.getYear(), infoStyle); + + rowIndex = writeRow(sheet, rowIndex, headerStyle, (Object[]) new String[]{ + "序号", "角色", "员工姓名", "角色比例", "人员比例", + "一季度", "二季度", "三季度", "四季度", "年度合计" + }); + + int serialNo = 1; + for (PersonQuarterRow rowData : data.getRows()) { + Row row = sheet.createRow(rowIndex++); + setText(row, 0, serialNo++, cellStyle); + setText(row, 1, rowData.getRoleName(), cellStyle); + setText(row, 2, rowData.getEmployeeName(), cellStyle); + setText(row, 3, percentText(rowData.getRoleRatio()), cellStyle); + setText(row, 4, percentText(rowData.getPersonRatio()), cellStyle); + setText(row, 5, text(rowData.getQuarterOneAmount()), cellStyle); + setText(row, 6, text(rowData.getQuarterTwoAmount()), cellStyle); + setText(row, 7, text(rowData.getQuarterThreeAmount()), cellStyle); + setText(row, 8, text(rowData.getQuarterFourAmount()), cellStyle); + setText(row, 9, text(rowData.getYearTotalAmount()), cellStyle); + } + return workbook; + } + + @Data + public static class ExportData { + private String projectName; + private String planningContent; + private String specialtyName; + private Integer year; + private List rows; + } + + @Data + public static class PersonQuarterRow { + private String roleName; + private String employeeName; + private BigDecimal roleRatio; + private BigDecimal personRatio; + private BigDecimal quarterOneAmount; + private BigDecimal quarterTwoAmount; + private BigDecimal quarterThreeAmount; + private BigDecimal quarterFourAmount; + private BigDecimal yearTotalAmount; + } + +} 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 index d2d5e20..d58ad4d 100644 --- 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 @@ -1,6 +1,5 @@ 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; @@ -8,16 +7,19 @@ import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.Speci 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.employee.EmployeeDO; 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.projectroleperson.ProjectRolePersonDO; 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.projectroleperson.ProjectRolePersonMapper; 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.employee.EmployeeService; 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; @@ -25,9 +27,11 @@ import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -40,21 +44,15 @@ import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_RO 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 HALF_RATIO = new BigDecimal("0.5000"); private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); @Resource @@ -65,6 +63,10 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService private ProjectPlanningMapper projectPlanningMapper; @Resource private ProjectMapper projectMapper; + @Resource + private ProjectRolePersonMapper projectRolePersonMapper; + @Resource + private EmployeeService employeeService; @Override public List getSpecialtyRoleSplitListByPlanningId(Long planningId) { @@ -74,22 +76,30 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService List dbList = specialtyRoleSplitMapper.selectListByOutputSplitId(outputSplit.getId()); Map dbMap = dbList.stream().collect(Collectors.toMap( item -> item.getSpecialtyCode() + ":" + item.getRoleCode(), item -> item, (a, b) -> b)); + Map> projectRolePersonMap = projectRolePersonMapper + .selectListByProjectId(project.getId()).stream() + .collect(Collectors.groupingBy(ProjectRolePersonDO::getRoleCode, LinkedHashMap::new, Collectors.toList())); + 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) { + for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.ASSIGNMENT_SPECIALTY_ITEMS) { + BigDecimal specialtyAmount = getSpecialtyAmount(outputSplit, assessmentOutputValue, specialtyItem.getCode()); + Map defaultRoleRatioMap = buildDefaultRoleRatioMap( + specialtyItem.getCode(), projectRolePersonMap); + for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.getRoleItems(specialtyItem.getCode())) { SpecialtyRoleSplitDO dbItem = dbMap.get(specialtyItem.getCode() + ":" + roleItem.getCode()); - BigDecimal roleRatio = getStoredRoleRatio(dbItem, roleItem.getCode()); + BigDecimal roleRatio = getStoredRoleRatio( + dbItem, specialtyItem.getCode(), roleItem.getCode(), defaultRoleRatioMap); BigDecimal roleAmount = multiplyAmount(specialtyAmount, roleRatio); - List persons = buildRespPersons(dbItem, roleAmount, roleRatio); + List persons = buildRespPersons( + dbItem, specialtyItem.getCode(), roleItem.getCode(), roleAmount, roleRatio, projectRolePersonMap); SpecialtyRoleSplitRespVO respVO = dbItem == null ? new SpecialtyRoleSplitRespVO() : BeanUtils.toBean(dbItem, SpecialtyRoleSplitRespVO.class); if (respVO.getId() == null) { respVO.setOutputSplitId(outputSplit.getId()); - respVO.setSortNo(roleItem.getSortNo()); + respVO.setSortNo(OutputSplitBizConstants.getSpecialtySortNo(specialtyItem.getCode()) * 10 + + OutputSplitBizConstants.getRoleSortNo(roleItem.getCode())); } respVO.setPlanningId(planning.getId()); respVO.setProjectName(project.getProjectName()); @@ -117,30 +127,25 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService 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) { + + for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.ASSIGNMENT_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 specialtyAmount = getSpecialtyAmount(outputSplit, assessmentOutputValue, specialtyItem.getCode()); + for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.getRoleItems(specialtyItem.getCode())) { + BigDecimal roleRatio = getSaveRoleRatio(roleMap, specialtyItem.getCode(), 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); + upsertRole(dbMap, outputSplit.getId(), specialtyItem.getCode(), specialtyItem.getName(), + roleItem.getCode(), roleRatio, + OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyItem.getCode()) + ? null : toPersonJson(persons)); } 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()))); - } } } @@ -160,11 +165,14 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService private Map> buildGroupedInput(List items) { Map> result = new LinkedHashMap<>(); + if (items == null || items.isEmpty()) { + return result; + } for (SpecialtyRoleSplitSaveItemReqVO item : items) { if (!OutputSplitBizConstants.isValidSpecialtyCode(item.getSpecialtyCode())) { throw exception(SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID); } - if (!OutputSplitBizConstants.isValidRoleCode(item.getRoleCode())) { + if (!OutputSplitBizConstants.isValidRoleCode(item.getSpecialtyCode(), item.getRoleCode())) { throw exception(SPECIALTY_ROLE_SPLIT_ROLE_INVALID); } result.computeIfAbsent(item.getSpecialtyCode(), key -> new LinkedHashMap<>()) @@ -174,7 +182,7 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService } private void upsertRole(Map dbMap, Long outputSplitId, String specialtyCode, - String specialtyName, String roleCode, BigDecimal roleRatio, String personNames) { + String specialtyName, String roleCode, BigDecimal roleRatio, String personsJson) { String key = specialtyCode + ":" + roleCode; SpecialtyRoleSplitDO dbItem = dbMap.get(key); Integer sortNo = dbItem != null && dbItem.getSortNo() != null @@ -189,7 +197,7 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService createObj.setRoleCode(roleCode); createObj.setRoleName(OutputSplitBizConstants.getRoleName(roleCode)); createObj.setRoleRatio(ratio(roleRatio)); - createObj.setPersonNames(personNames); + createObj.setPersonsJson(personsJson); createObj.setSortNo(sortNo); specialtyRoleSplitMapper.insert(createObj); return; @@ -202,7 +210,7 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService updateObj.setRoleCode(roleCode); updateObj.setRoleName(OutputSplitBizConstants.getRoleName(roleCode)); updateObj.setRoleRatio(ratio(roleRatio)); - updateObj.setPersonNames(personNames); + updateObj.setPersonsJson(personsJson); updateObj.setSortNo(sortNo); specialtyRoleSplitMapper.updateById(updateObj); } @@ -213,9 +221,13 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService return item == null ? new ArrayList<>() : item.getPersons(); } - private BigDecimal getSaveRoleRatio(Map roleMap, String roleCode) { + private BigDecimal getSaveRoleRatio(Map roleMap, + String specialtyCode, String roleCode) { SpecialtyRoleSplitSaveItemReqVO item = roleMap.get(roleCode); BigDecimal roleRatio = item == null ? ZERO_RATIO : ratio(item.getRoleRatio()); + if (!OutputSplitBizConstants.isValidRoleCode(specialtyCode, roleCode)) { + throw exception(SPECIALTY_ROLE_SPLIT_ROLE_INVALID); + } if (roleRatio.compareTo(ZERO_RATIO) < 0 || roleRatio.compareTo(ONE_RATIO) > 0) { throw exception(SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID); } @@ -231,18 +243,20 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService if (person == null) { continue; } - String personName = StrUtil.trim(person.getPersonName()); + Long employeeId = person.getEmployeeId(); BigDecimal personRatio = person.getPersonRatio(); - boolean hasName = StrUtil.isNotBlank(personName); + boolean hasEmployee = employeeId != null; boolean hasRatio = personRatio != null; - if (!hasName && !hasRatio) { + if (!hasEmployee && !hasRatio) { continue; } - if (!hasName || !hasRatio) { + if (!hasEmployee || !hasRatio) { throw exception(SPECIALTY_ROLE_SPLIT_PERSON_INVALID); } + EmployeeDO employee = employeeService.validateEmployeeExists(employeeId); SpecialtyRolePersonSaveReqVO normalized = new SpecialtyRolePersonSaveReqVO(); - normalized.setPersonName(personName); + normalized.setEmployeeId(employeeId); + normalized.setEmployeeName(employee.getEmployeeName()); BigDecimal normalizedRatio = ratio(personRatio); if (normalizedRatio.compareTo(ZERO_RATIO) < 0 || normalizedRatio.compareTo(ONE_RATIO) > 0) { throw exception(SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID); @@ -271,19 +285,32 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService } } - private BigDecimal getStoredRoleRatio(SpecialtyRoleSplitDO dbItem, String roleCode) { - if (dbItem == null) { - return OutputSplitBizConstants.ROLE_DESIGN.equals(roleCode) ? ONE_RATIO : ZERO_RATIO; + private BigDecimal getStoredRoleRatio(SpecialtyRoleSplitDO dbItem, String specialtyCode, String roleCode, + Map defaultRoleRatioMap) { + if (dbItem != null) { + return ratio(dbItem.getRoleRatio()); } - return ratio(dbItem.getRoleRatio()); + if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + return ratio(defaultRoleRatioMap.get(roleCode)); + } + return OutputSplitBizConstants.ROLE_DESIGN.equals(roleCode) ? ONE_RATIO : ZERO_RATIO; } - private List buildRespPersons(SpecialtyRoleSplitDO dbItem, BigDecimal roleAmount, - BigDecimal roleRatio) { + private List buildRespPersons(SpecialtyRoleSplitDO dbItem, String specialtyCode, + String roleCode, BigDecimal roleAmount, + BigDecimal roleRatio, + Map> projectRolePersonMap) { + List sourcePersons; + if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + sourcePersons = buildDefaultProjectLeadPersons(projectRolePersonMap.get(roleCode)); + } else { + sourcePersons = parseStoredPersons(dbItem); + } List result = new ArrayList<>(); - for (SpecialtyRolePersonSaveReqVO person : parseStoredPersons(dbItem, roleRatio)) { + for (SpecialtyRolePersonSaveReqVO person : sourcePersons) { SpecialtyRolePersonRespVO respVO = new SpecialtyRolePersonRespVO(); - respVO.setPersonName(person.getPersonName()); + respVO.setEmployeeId(person.getEmployeeId()); + respVO.setEmployeeName(person.getEmployeeName()); respVO.setPersonRatio(ratio(person.getPersonRatio())); respVO.setPersonAmount(multiplyAmount(roleAmount, respVO.getPersonRatio())); result.add(respVO); @@ -291,66 +318,87 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService return result; } - private List parseStoredPersons(SpecialtyRoleSplitDO dbItem, BigDecimal roleRatio) { + private List parseStoredPersons(SpecialtyRoleSplitDO dbItem) { + if (dbItem == null || dbItem.getPersonsJson() == null || dbItem.getPersonsJson().trim().isEmpty()) { + return new ArrayList<>(); + } + List persons = JsonUtils.parseArray(dbItem.getPersonsJson(), SpecialtyRolePersonSaveReqVO.class); + if (persons == null) { + return new ArrayList<>(); + } + return persons.stream() + .filter(Objects::nonNull) + .map(person -> { + SpecialtyRolePersonSaveReqVO normalized = new SpecialtyRolePersonSaveReqVO(); + normalized.setEmployeeId(person.getEmployeeId()); + normalized.setEmployeeName(person.getEmployeeName()); + normalized.setPersonRatio(ratio(person.getPersonRatio())); + return normalized; + }) + .filter(person -> person.getEmployeeId() != null + && person.getEmployeeName() != null + && !person.getEmployeeName().trim().isEmpty()) + .collect(Collectors.toList()); + } + + private List buildDefaultProjectLeadPersons(List rolePersons) { + if (rolePersons == null || rolePersons.isEmpty()) { + return new ArrayList<>(); + } 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; + BigDecimal perRatio = BigDecimal.ONE.divide(BigDecimal.valueOf(rolePersons.size()), RATIO_SCALE, RoundingMode.HALF_UP); + BigDecimal accumulated = ZERO_RATIO; + for (int i = 0; i < rolePersons.size(); i++) { + ProjectRolePersonDO rolePerson = rolePersons.get(i); + SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO(); + person.setEmployeeId(rolePerson.getEmployeeId()); + person.setEmployeeName(rolePerson.getEmployeeName()); + BigDecimal personRatio = i == rolePersons.size() - 1 + ? ONE_RATIO.subtract(accumulated).setScale(RATIO_SCALE, RoundingMode.HALF_UP) + : perRatio; + person.setPersonRatio(personRatio); + accumulated = accumulated.add(personRatio).setScale(RATIO_SCALE, RoundingMode.HALF_UP); + result.add(person); } return result; } - private List convertLegacyPersons(List persons, - BigDecimal roleRatio) { - List result = new ArrayList<>(); - if (roleRatio.compareTo(ZERO_RATIO) <= 0) { - return persons; + private Map buildDefaultRoleRatioMap(String specialtyCode, + Map> projectRolePersonMap) { + if (!OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + return Collections.emptyMap(); } - 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); + boolean hasProjectManager = projectRolePersonMap.containsKey(OutputSplitBizConstants.ROLE_PROJECT_MANAGER) + && !projectRolePersonMap.get(OutputSplitBizConstants.ROLE_PROJECT_MANAGER).isEmpty(); + boolean hasEngineeringPrincipal = projectRolePersonMap.containsKey(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL) + && !projectRolePersonMap.get(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL).isEmpty(); + Map result = new LinkedHashMap<>(); + if (hasProjectManager && hasEngineeringPrincipal) { + result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, HALF_RATIO); + result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, HALF_RATIO); + return result; } + if (hasProjectManager) { + result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, ONE_RATIO); + result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, ZERO_RATIO); + return result; + } + if (hasEngineeringPrincipal) { + result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, ZERO_RATIO); + result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, ONE_RATIO); + return result; + } + result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, HALF_RATIO); + result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, HALF_RATIO); return result; } - private SpecialtyRolePersonSaveReqVO buildStoredPerson(String personName, BigDecimal personRatio) { - SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO(); - person.setPersonName(personName); - person.setPersonRatio(ratio(personRatio)); - return person; + private BigDecimal getSpecialtyAmount(ProjectOutputSplitDO outputSplit, BigDecimal assessmentOutputValue, + String specialtyCode) { + if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { + return multiplyAmount(assessmentOutputValue, outputSplit.getProjectLeadRatio()); + } + return projectOutputSplitService.getSpecialtyAmount(outputSplit, assessmentOutputValue, specialtyCode); } private String toPersonJson(List persons) { diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/yearkvalue/YearKValueService.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/yearkvalue/YearKValueService.java new file mode 100644 index 0000000..105cb86 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/yearkvalue/YearKValueService.java @@ -0,0 +1,20 @@ +package cn.iocoder.lyzsys.module.tjt.service.yearkvalue; + +import cn.iocoder.lyzsys.framework.common.pojo.PageResult; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValuePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO; + +public interface YearKValueService { + + Long createYearKValue(YearKValueSaveReqVO createReqVO); + + void updateYearKValue(YearKValueSaveReqVO updateReqVO); + + void deleteYearKValue(Long id); + + YearKValueRespVO getYearKValue(Long id); + + PageResult getYearKValuePage(YearKValuePageReqVO pageReqVO); + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/yearkvalue/YearKValueServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/yearkvalue/YearKValueServiceImpl.java new file mode 100644 index 0000000..d46a6e8 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/yearkvalue/YearKValueServiceImpl.java @@ -0,0 +1,79 @@ +package cn.iocoder.lyzsys.module.tjt.service.yearkvalue; + +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.yearkvalue.vo.YearKValuePageReqVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO; +import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO; +import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO; +import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; + +import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.YEAR_K_VALUE_DUPLICATE; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.YEAR_K_VALUE_NOT_EXISTS; + +@Service +@Validated +public class YearKValueServiceImpl implements YearKValueService { + + @Resource + private YearKValueMapper yearKValueMapper; + + @Override + public Long createYearKValue(YearKValueSaveReqVO createReqVO) { + validateYearUnique(null, createReqVO.getKYear()); + YearKValueDO yearKValue = BeanUtils.toBean(createReqVO, YearKValueDO.class); + if (yearKValue.getEnabledFlag() == null) { + yearKValue.setEnabledFlag(Boolean.TRUE); + } + yearKValueMapper.insert(yearKValue); + return yearKValue.getId(); + } + + @Override + public void updateYearKValue(YearKValueSaveReqVO updateReqVO) { + validateYearKValueExists(updateReqVO.getId()); + validateYearUnique(updateReqVO.getId(), updateReqVO.getKYear()); + YearKValueDO updateObj = BeanUtils.toBean(updateReqVO, YearKValueDO.class); + yearKValueMapper.updateById(updateObj); + } + + @Override + public void deleteYearKValue(Long id) { + validateYearKValueExists(id); + yearKValueMapper.deleteById(id); + } + + @Override + public YearKValueRespVO getYearKValue(Long id) { + return BeanUtils.toBean(validateYearKValueExists(id), YearKValueRespVO.class); + } + + @Override + public PageResult getYearKValuePage(YearKValuePageReqVO pageReqVO) { + return BeanUtils.toBean(yearKValueMapper.selectPage(pageReqVO), YearKValueRespVO.class); + } + + private void validateYearUnique(Long id, Integer kYear) { + YearKValueDO yearKValue = yearKValueMapper.selectByKYear(kYear); + if (yearKValue == null) { + return; + } + if (id == null || !yearKValue.getId().equals(id)) { + throw exception(YEAR_K_VALUE_DUPLICATE); + } + } + + private YearKValueDO validateYearKValueExists(Long id) { + YearKValueDO yearKValue = yearKValueMapper.selectById(id); + if (yearKValue == null) { + throw exception(YEAR_K_VALUE_NOT_EXISTS); + } + return yearKValue; + } + +}