diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java new file mode 100644 index 00000000..66c47ffa --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java @@ -0,0 +1,146 @@ +package org.dromara.design.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.collection.CollUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import org.dromara.common.core.domain.R; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.web.core.BaseController; +import org.dromara.design.domain.DesConstructionSchedulePlan; +import org.dromara.design.domain.dto.constructionscheduleplan.*; +import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo; +import org.dromara.design.service.IDesConstructionSchedulePlanService; + + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 设计计划 + * + * @author lilemy + * @date 2025-08-01 + */ +@Validated +@RestController +@RequestMapping("/design/constructionSchedulePlan") +public class DesConstructionSchedulePlanController extends BaseController { + + @Resource + private IDesConstructionSchedulePlanService desConstructionSchedulePlanService; + + /** + * 查询设计计划列表 + */ + @SaCheckPermission("design:constructionSchedulePlan:list") + @GetMapping("/list") + public R> list(DesConstructionSchedulePlanQueryReq req) { + List list = desConstructionSchedulePlanService.queryList(req); + return R.ok(list); + } + + /** + * 导出设计计划列表 + */ + @SaCheckPermission("design:constructionSchedulePlan:export") + @Log(title = "施工进度计划", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(DesConstructionSchedulePlanQueryReq req, HttpServletResponse response) { + List list = desConstructionSchedulePlanService.queryList(req); + ExcelUtil.exportExcel(list, "施工进度计划", DesConstructionSchedulePlanVo.class, response); + } + + /** + * 根据项目id导出设计计划模版 + */ + @SaCheckPermission("design:constructionSchedulePlan:exportTemplate") + @Log(title = "施工进度计划", businessType = BusinessType.EXPORT) + @PostMapping("/exportTemplate/{projectId}") + public void exportExcelByProjectId(@NotNull(message = "项目id不能为空") + @PathVariable Long projectId, HttpServletResponse response) { + desConstructionSchedulePlanService.exportExcelByProjectId(projectId, response); + } + + /** + * 读取设计计划模版 + */ + @SaCheckPermission("design:constructionSchedulePlan:readTemplate") + @Log(title = "施工进度计划", businessType = BusinessType.IMPORT) + @PostMapping("/readTemplate") + public R readExcel(@RequestParam("file") MultipartFile file, Long projectId) { + List list = desConstructionSchedulePlanService.readExcel(file, projectId); + if (CollUtil.isNotEmpty(list)) { + List planList = desConstructionSchedulePlanService.convertToEntities(list); + return toAjax(desConstructionSchedulePlanService.saveBatch(planList)); + } + return toAjax(true); + } + + /** + * 获取设计计划详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("design:constructionSchedulePlan:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(desConstructionSchedulePlanService.queryById(id)); + } + + /** + * 新增设计计划 + */ + @SaCheckPermission("design:constructionSchedulePlan:add") + @Log(title = "施工进度计划", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated @RequestBody DesConstructionSchedulePlanCreateReq req) { + return R.ok(desConstructionSchedulePlanService.insertByBo(req)); + } + + /** + * 修改设计计划 + */ + @SaCheckPermission("design:constructionSchedulePlan:edit") + @Log(title = "施工进度计划", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated @RequestBody DesConstructionSchedulePlanUpdateReq req) { + return toAjax(desConstructionSchedulePlanService.updateByBo(req)); + } + + /** + * 修改设计计划为完成状态 + * + */ + @SaCheckPermission("design:constructionSchedulePlan:editFinish") + @Log(title = "施工进度计划", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/finish") + public R editFinish(@Validated @RequestBody DesConstructionSchedulePlanFinishReq req) { + return toAjax(desConstructionSchedulePlanService.updateFinish(req)); + } + + /** + * 删除设计计划 + * + * @param ids 主键串 + */ + @SaCheckPermission("design:constructionSchedulePlan:remove") + @Log(title = "施工进度计划", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(desConstructionSchedulePlanService.deleteByIds(List.of(ids))); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/DesConstructionSchedulePlan.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/DesConstructionSchedulePlan.java new file mode 100644 index 00000000..21b15601 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/DesConstructionSchedulePlan.java @@ -0,0 +1,87 @@ +package org.dromara.design.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.time.LocalDate; + +/** + * 施工进度计划对象 pgs_construction_schedule_plan + * + * @author lilemy + * @date 2025-08-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("des_construction_schedule_plan") +public class DesConstructionSchedulePlan extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 父ID + */ + private Long parentId; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 对应项目结构 + */ + private Long projectStructure; + + /** + * 对应项目结构名称 + */ + private String projectStructureName; + + /** + * 预计开始时间 + */ + private LocalDate planStartDate; + + /** + * 预计结束时间 + */ + private LocalDate planEndDate; + + /** + * 实际开始时间 + */ + private LocalDate practicalStartDate; + + /** + * 实际结束时间 + */ + private LocalDate practicalEndDate; + + /** + * 状态 + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanCreateReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanCreateReq.java new file mode 100644 index 00000000..de334754 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanCreateReq.java @@ -0,0 +1,77 @@ +package org.dromara.design.domain.dto.constructionscheduleplan; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @author lilemy + * @date 2025-08-01 14:05 + */ +@Data +public class DesConstructionSchedulePlanCreateReq implements Serializable { + + @Serial + private static final long serialVersionUID = 4060838737379600701L; + + /** + * 项目ID + */ + @NotNull(message = "项目ID不能为空") + private Long projectId; + + /** + * 父ID + */ + private Long parentId; + + /** + * 节点名称 + */ + @NotBlank(message = "节点名称不能为空") + private String nodeName; + + /** + * 对应项目结构 + */ + private Long projectStructure; + + /** + * 对应项目结构名称 + */ + private String projectStructureName; + + /** + * 预计开始时间 + */ + private LocalDate planStartDate; + + /** + * 预计结束时间 + */ + private LocalDate planEndDate; + + /** + * 实际开始时间 + */ + private LocalDate practicalStartDate; + + /** + * 实际结束时间 + */ + private LocalDate practicalEndDate; + + /** + * 状态 + */ + private String status; + + /** + * 备注 + */ + private String remark; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanExcelDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanExcelDto.java new file mode 100644 index 00000000..81c64a17 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanExcelDto.java @@ -0,0 +1,71 @@ +package org.dromara.design.domain.dto.constructionscheduleplan; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDate; + +/** + * @author lilemy + * @date 2025-09-06 17:12 + */ +@Data +@AllArgsConstructor +public class DesConstructionSchedulePlanExcelDto { + + /** + * 编号(1, 1.1, 1.1.1) + */ + private String number; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 对应项目结构 + */ + private Long projectStructure; + + /** + * 对应项目结构名称 + */ + private String projectStructureName; + + /** + * 预计开始时间 + */ + private LocalDate planStartDate; + + /** + * 预计结束时间 + */ + private LocalDate planEndDate; + + /** + * 实际开始时间 + */ + private LocalDate practicalStartDate; + + /** + * 实际结束时间 + */ + private LocalDate practicalEndDate; + + /** + * 状态 + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanFinishReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanFinishReq.java new file mode 100644 index 00000000..28d57335 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanFinishReq.java @@ -0,0 +1,31 @@ +package org.dromara.design.domain.dto.constructionscheduleplan; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @author lilemy + * @date 2025-09-17 10:15 + */ +@Data +public class DesConstructionSchedulePlanFinishReq implements Serializable { + + @Serial + private static final long serialVersionUID = 8139653508791280689L; + + /** + * 主键 + */ + @NotNull(message = "主键不能为空") + private Long id; + + /** + * 完成时间 + */ + @NotNull(message = "完成时间不能为空") + private LocalDate finishDate; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanQueryReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanQueryReq.java new file mode 100644 index 00000000..f55d9f1a --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanQueryReq.java @@ -0,0 +1,37 @@ +package org.dromara.design.domain.dto.constructionscheduleplan; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author lilemy + * @date 2025-08-01 14:05 + */ +@Data +public class DesConstructionSchedulePlanQueryReq implements Serializable { + + @Serial + private static final long serialVersionUID = 9021370369055688811L; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 对应项目结构名称 + */ + private String projectStructureName; + + /** + * 状态 + */ + private String status; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanUpdateReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanUpdateReq.java new file mode 100644 index 00000000..42165e5b --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/constructionscheduleplan/DesConstructionSchedulePlanUpdateReq.java @@ -0,0 +1,74 @@ +package org.dromara.design.domain.dto.constructionscheduleplan; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @author lilemy + * @date 2025-08-01 14:06 + */ +@Data +public class DesConstructionSchedulePlanUpdateReq implements Serializable { + + @Serial + private static final long serialVersionUID = 6955873817030428268L; + + /** + * 主键ID + */ + private Long id; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 父ID + */ + private Long parentId; + + /** + * 对应项目结构 + */ + private Long projectStructure; + + /** + * 对应项目结构名称 + */ + private String projectStructureName; + + /** + * 预计开始时间 + */ + private LocalDate planStartDate; + + /** + * 预计结束时间 + */ + private LocalDate planEndDate; + + /** + * 实际开始时间 + */ + private LocalDate practicalStartDate; + + /** + * 实际结束时间 + */ + private LocalDate practicalEndDate; + + /** + * 状态 + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesConstructionSchedulePlanVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesConstructionSchedulePlanVo.java new file mode 100644 index 00000000..0739804e --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesConstructionSchedulePlanVo.java @@ -0,0 +1,104 @@ +package org.dromara.design.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.progress.domain.PgsConstructionSchedulePlan; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + + +/** + * 施工进度计划视图对象 pgs_construction_schedule_plan + * + * @author lilemy + * @date 2025-08-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = PgsConstructionSchedulePlan.class) +public class DesConstructionSchedulePlanVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 项目ID + */ + @ExcelProperty(value = "项目ID") + private Long projectId; + + /** + * 父ID + */ + @ExcelProperty(value = "父ID") + private Long parentId; + + /** + * 节点名称 + */ + @ExcelProperty(value = "节点名称") + private String nodeName; + + /** + * 对应项目结构 + */ + @ExcelProperty(value = "对应项目结构") + private Long projectStructure; + + /** + * 对应项目结构名称 + */ + @ExcelProperty(value = "对应项目结构名称") + private String projectStructureName; + + /** + * 预计开始时间 + */ + @ExcelProperty(value = "预计开始时间") + private LocalDate planStartDate; + + /** + * 预计结束时间 + */ + @ExcelProperty(value = "预计结束时间") + private LocalDate planEndDate; + + /** + * 实际开始时间 + */ + @ExcelProperty(value = "实际开始时间") + private LocalDate practicalStartDate; + + /** + * 实际结束时间 + */ + @ExcelProperty(value = "实际结束时间") + private LocalDate practicalEndDate; + + /** + * 状态 + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "project_construction_status") + private String status; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/mapper/DesConstructionSchedulePlanMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/mapper/DesConstructionSchedulePlanMapper.java new file mode 100644 index 00000000..772334f4 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/mapper/DesConstructionSchedulePlanMapper.java @@ -0,0 +1,17 @@ +package org.dromara.design.mapper; + +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.design.domain.DesConstructionSchedulePlan; +import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo; +import org.dromara.progress.domain.PgsConstructionSchedulePlan; +import org.dromara.progress.domain.vo.constructionscheduleplan.PgsConstructionSchedulePlanVo; + +/** + * 施工进度计划Mapper接口 + * + * @author lilemy + * @date 2025-08-01 + */ +public interface DesConstructionSchedulePlanMapper extends BaseMapperPlus { + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java new file mode 100644 index 00000000..0c95b991 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java @@ -0,0 +1,117 @@ +package org.dromara.design.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.design.domain.DesConstructionSchedulePlan; +import org.dromara.design.domain.dto.constructionscheduleplan.*; +import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collection; +import java.util.List; + +/** + * 施工进度计划Service接口 + * + * @author lilemy + * @date 2025-08-01 + */ +public interface IDesConstructionSchedulePlanService extends IService { + + /** + * 查询施工进度计划 + * + * @param id 主键 + * @return 施工进度计划 + */ + DesConstructionSchedulePlanVo queryById(Long id); + + /** + * 查询符合条件的施工进度计划列表 + * + * @param req 查询条件 + * @return 施工进度计划列表 + */ + List queryList(DesConstructionSchedulePlanQueryReq req); + + /** + * 新增施工进度计划 + * + * @param req 施工进度计划 + * @return 新增施工进度计划封装 + */ + DesConstructionSchedulePlanVo insertByBo(DesConstructionSchedulePlanCreateReq req); + + /** + * 修改施工进度计划 + * + * @param req 施工进度计划 + * @return 是否修改成功 + */ + Boolean updateByBo(DesConstructionSchedulePlanUpdateReq req); + + /** + * 修改施工进度计划为完成状态 + * + * @param req 施工进度计划 + * @return 是否修改成功 + */ + Boolean updateFinish(DesConstructionSchedulePlanFinishReq req); + + /** + * 批量删除施工进度计划信息 + * + * @param ids 待删除的主键集合 + * @return 是否删除成功 + */ + Boolean deleteByIds(Collection ids); + + /** + * 获取施工进度计划视图对象 + * + * @param constructionSchedulePlan 施工进度计划对象 + * @return 施工进度计划视图对象 + */ + DesConstructionSchedulePlanVo getVo(DesConstructionSchedulePlan constructionSchedulePlan); + + /** + * 获取施工进度计划查询条件封装 + * + * @param req 查询条件 + * @return 查询条件封装 + */ + LambdaQueryWrapper buildQueryWrapper(DesConstructionSchedulePlanQueryReq req); + + /** + * 获取施工进度计划分页对象视图 + * + * @param constructionSchedulePlanList 施工进度计划分页对象 + * @return 施工进度计划分页对象视图 + */ + List getVoList(List constructionSchedulePlanList); + + /** + * 导出Excel + * + * @param projectId 项目id + */ + void exportExcelByProjectId(Long projectId, HttpServletResponse response); + + /** + * 读取Excel文件 + * + * @param file Excel文件 + * @param projectId 项目ID + * @return 读取的数据列表 + */ + List readExcel(MultipartFile file, Long projectId); + + /** + * 将Excel数据转换为实体列表 + * + * @param excelList Excel数据列表 + * @return 实体列表 + */ + List convertToEntities(List excelList); +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java new file mode 100644 index 00000000..3484934d --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java @@ -0,0 +1,627 @@ +package org.dromara.design.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.dromara.common.core.constant.HttpStatus; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.ObjectUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.design.domain.DesConstructionSchedulePlan; +import org.dromara.design.domain.bo.DesUserBo; +import org.dromara.design.domain.dto.constructionscheduleplan.*; +import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo; +import org.dromara.design.mapper.DesConstructionSchedulePlanMapper; +import org.dromara.design.service.IDesConstructionSchedulePlanService; + +import org.dromara.project.service.IBusProjectService; +import org.dromara.system.domain.vo.SysDictDataVo; +import org.dromara.system.service.ISysDictDataService; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.*; + +/** + * 施工进度计划Service业务层处理 + * + * @author lilemy + * @date 2025-08-01 + */ +@Slf4j +@Service +public class DesConstructionSchedulePlanServiceImpl extends ServiceImpl + implements IDesConstructionSchedulePlanService { + + @Resource + private IBusProjectService projectService; + + @Resource + private ISysDictDataService dictDataService; + + /** + * 查询施工进度计划 + * + * @param id 主键 + * @return 施工进度计划 + */ + @Override + public DesConstructionSchedulePlanVo queryById(Long id) { + DesConstructionSchedulePlan constructionSchedulePlan = this.getById(id); + if (constructionSchedulePlan == null) { + throw new ServiceException("施工进度计划信息不存在", HttpStatus.NOT_FOUND); + } + return this.getVo(constructionSchedulePlan); + } + + /** + * 查询符合条件的施工进度计划列表 + * + * @param req 查询条件 + * @return 施工进度计划列表 + */ + @Override + public List queryList(DesConstructionSchedulePlanQueryReq req) { + List result = this.list(this.buildQueryWrapper(req)); + return this.getVoList(result); + } + + /** + * 新增施工进度计划 + * + * @param req 施工进度计划 + * @return 新增施工进度计划封装 + */ + @Override + public DesConstructionSchedulePlanVo insertByBo(DesConstructionSchedulePlanCreateReq req) { + DesConstructionSchedulePlan constructionSchedulePlan = new DesConstructionSchedulePlan(); + BeanUtils.copyProperties(req, constructionSchedulePlan); + boolean save = this.save(constructionSchedulePlan); + if (!save) { + throw new ServiceException("新增施工进度计划信息异常", HttpStatus.ERROR); + } + DesConstructionSchedulePlan newConstructionSchedulePlan = this.getById(constructionSchedulePlan.getId()); + return this.getVo(newConstructionSchedulePlan); + } + + /** + * 修改施工进度计划 + * + * @param req 施工进度计划 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(DesConstructionSchedulePlanUpdateReq req) { + DesConstructionSchedulePlan constructionSchedulePlan = new DesConstructionSchedulePlan(); + BeanUtils.copyProperties(req, constructionSchedulePlan); + return this.updateById(constructionSchedulePlan); + } + + /** + * 修改施工进度计划为完成状态 + * + * @param req 施工进度计划 + * @return 是否修改成功 + */ + @Override + public Boolean updateFinish(DesConstructionSchedulePlanFinishReq req) { + DesConstructionSchedulePlan plan = this.getById(req.getId()); + if (plan == null) { + throw new ServiceException("施工进度计划信息不存在", HttpStatus.NOT_FOUND); + } + if (plan.getStatus().equals("4")) { + throw new ServiceException("施工进度计划已完成", HttpStatus.NOT_FOUND); + } + LocalDate practicalStartDate = plan.getPracticalStartDate(); + LocalDate finishDate = req.getFinishDate(); + if (practicalStartDate == null) { + throw new ServiceException("请先填写实际开始时间", HttpStatus.NOT_FOUND); + } + if (finishDate.isBefore(practicalStartDate)) { + throw new ServiceException("实际结束时间不能早于实际开始时间", HttpStatus.NOT_FOUND); + } + plan.setStatus("4"); + plan.setPracticalEndDate(finishDate); + return this.updateById(plan); + } + + /** + * 批量删除施工进度计划信息 + * + * @param ids 待删除的主键集合 + * @return 是否删除成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteByIds(Collection ids) { + return this.removeBatchByIds(ids); + } + + /** + * 获取施工进度计划视图对象 + * + * @param constructionSchedulePlan 施工进度计划对象 + * @return 施工进度计划视图对象 + */ + @Override + public DesConstructionSchedulePlanVo getVo(DesConstructionSchedulePlan constructionSchedulePlan) { + DesConstructionSchedulePlanVo vo = new DesConstructionSchedulePlanVo(); + if (constructionSchedulePlan == null) { + return vo; + } + BeanUtils.copyProperties(constructionSchedulePlan, vo); + return vo; + } + + /** + * 获取施工进度计划查询条件封装 + * + * @param req 查询条件 + * @return 查询条件封装 + */ + @Override + public LambdaQueryWrapper buildQueryWrapper(DesConstructionSchedulePlanQueryReq req) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + if (req == null) { + return lqw; + } + Long projectId = req.getProjectId(); + String nodeName = req.getNodeName(); + String status = req.getStatus(); + lqw.eq(ObjectUtils.isNotEmpty(projectId), DesConstructionSchedulePlan::getProjectId, projectId); + lqw.like(StringUtils.isNotBlank(nodeName), DesConstructionSchedulePlan::getNodeName, nodeName); + lqw.eq(StringUtils.isNotBlank(status), DesConstructionSchedulePlan::getStatus, status); + return lqw; + } + + /** + * 获取施工进度计划分页对象视图 + * + * @param constructionSchedulePlanList 施工进度计划分页对象 + * @return 施工进度计划分页对象视图 + */ + @Override + public List getVoList(List constructionSchedulePlanList) { + return constructionSchedulePlanList.stream().map(this::getVo).toList(); + } + + // region 导出 excel + + /** + * 导出Excel + * + * @param projectId 项目id + */ + @Override + public void exportExcelByProjectId(Long projectId, HttpServletResponse response) { + DesUserBo desUserBo = new DesUserBo(); + desUserBo.setProjectId(projectId); + Map projectStructureMap = projectService.getStructureAsList(projectId); + if (projectStructureMap.isEmpty()) { + throw new ServiceException("获取项目列表失败,项目为空!!!"); + } + List dictDataVos = dictDataService.selectByDictType("project_construction_status"); + if (dictDataVos == null || dictDataVos.isEmpty()) { + throw new ServiceException("项目施工状态为空!!"); + } + Map statusMap = new HashMap<>(); + for (SysDictDataVo vo : dictDataVos) { + statusMap.put(vo.getDictValue(), vo.getDictLabel()); + } + + + Workbook workbook = new XSSFWorkbook(); + // 创建主 Sheet 和隐藏 Sheet + Sheet mainSheet = workbook.createSheet("设计里程碑计划模版"); + Sheet dataSheet = workbook.createSheet("DropdownData"); + workbook.setSheetHidden(workbook.getSheetIndex(dataSheet), true); + + // 3. 创建单元格样式 + CellStyle editableStyle = createEditableCellStyle(workbook); // 可编辑单元格样式 + CellStyle protectedStyle = createProtectedCellStyle(workbook); // 受保护单元格样式(ID列用 + //填充隐藏数据Sheet + int rowIndex = 0; + // 填充项目关联结构(A列和B列) + for (Map.Entry entry : projectStructureMap.entrySet()) { + Row row = dataSheet.createRow(rowIndex++); + row.createCell(0).setCellValue(entry.getValue()); + row.createCell(1).setCellValue(entry.getKey().toString()); + } + // 重置行索引,填充人员和人员ID(C列和D列) + rowIndex = 0; + for (Map.Entry entry : statusMap.entrySet()) { + Row row = dataSheet.getRow(rowIndex); + if (row == null) { + row = dataSheet.createRow(rowIndex); + } + row.createCell(4).setCellValue(entry.getValue()); + row.createCell(5).setCellValue(entry.getKey()); + rowIndex++; + } + // 主 Sheet 设置表头 + Row sheetRow = mainSheet.createRow(0); + String[] headers = {"编号(格式:1,1.1,1.1.1。用于进行父子结构关联)", "节点名称", + "预计开始时间(输入格式:2025-09-06)", "预计结束时间", "实际开始时间", "实际结束时间", "状态", "状态编码", "备注"}; + for (int i = 0; i < headers.length; i++) { + Cell cell = sheetRow.createCell(i); + cell.setCellValue(headers[i]); + if (i == 0) { + CellStyle css = workbook.createCellStyle(); + DataFormat format = workbook.createDataFormat(); + css.setDataFormat(format.getFormat("@")); + cell.setCellStyle(css); + } + + } + // 6. 设置专业下拉列表(第二列) +// setMajorDropdown(mainSheet, projectStructureMap.size()); + setStatusDropdown(mainSheet, statusMap.size()); +// String formulaTemplate = "IFERROR(INDEX(DropdownData!$B$1:$B$" + projectStructureMap.size() + ", MATCH(C{rowNum}, DropdownData!$A$1:$A$" + projectStructureMap.size() + ", 0)),\"\")"; + String formulaTemplate1 = "IFERROR(INDEX(DropdownData!$F$1:$F$" + statusMap.size() + ", MATCH(G{rowNum}, DropdownData!$E$1:$E$" + statusMap.size() + ", 0)),\"\")"; + for (int i = 1; i <= 1000; i++) { // 从第2行到101行 + Row row = mainSheet.createRow(i); + int currentRowNum = i + 1; // Excel行号从1开始 +// String formula = formulaTemplate.replace("{rowNum}", String.valueOf(currentRowNum)); +// +// Cell cell = row.createCell(2); +// cell.setCellStyle(editableStyle); //专业不锁定 +// +// Cell idCell = row.createCell(3); +// idCell.setCellFormula(formula); +// idCell.setCellStyle(protectedStyle); // 应用隐藏公式样式 + + String formula1 = formulaTemplate1.replace("{rowNum}", String.valueOf(currentRowNum)); + + Cell cell1 = row.createCell(6); + cell1.setCellStyle(editableStyle); //专业不锁定 + + Cell idCell1 = row.createCell(7); + idCell1.setCellFormula(formula1); + idCell1.setCellStyle(protectedStyle); // 应用隐藏公式样式 + + } + + + for (int i = 1; i <= 100; i++) { + Row row = mainSheet.getRow(i); + if (row == null) { + row = mainSheet.createRow(i); + } + Cell cell = row.createCell(0); + CellStyle textStyle = workbook.createCellStyle(); + textStyle.setLocked(false); + DataFormat format = workbook.createDataFormat(); + textStyle.setDataFormat(format.getFormat("@")); // "@" 表示文本格式 + cell.setCellStyle(textStyle); + + Cell cell1 = row.createCell(1); + cell1.setCellStyle(editableStyle); + + Cell cell4 = row.createCell(2); + cell4.setCellStyle(editableStyle); + + Cell cell5 = row.createCell(3); + cell5.setCellStyle(editableStyle); + + Cell cell6 = row.createCell(4); + cell6.setCellStyle(editableStyle); + + Cell cell7 = row.createCell(5); + cell7.setCellStyle(editableStyle); + + Cell cell10 = row.createCell(8); + cell10.setCellStyle(editableStyle); + + } + + + // 保护工作表,仅允许编辑未锁定的单元格 + mainSheet.protectSheet("123456"); // 空密码 + + // 核心:锁定表头(第1行)和前1列(包含ID列) + mainSheet.createFreezePane(0, 1, 0, 1); + + // 调整列宽(更新列索引) + mainSheet.setColumnWidth(0, 20 * 320); // 编号 + mainSheet.setColumnWidth(1, 20 * 280); // 节点名称 + mainSheet.setColumnWidth(2, 20 * 320); // 预计开始时间 + mainSheet.setColumnWidth(3, 20 * 200); // 预计结束时间 + mainSheet.setColumnWidth(4, 20 * 200); // 实际开始时间 + mainSheet.setColumnWidth(5, 20 * 200); // 实际结束时间 + mainSheet.setColumnWidth(6, 20 * 100); // 状态 + mainSheet.setColumnWidth(7, 20 * 200); // 状态编码 + mainSheet.setColumnWidth(8, 20 * 200); // 备注 + + + // 2. 设置响应头 + // 设置响应头,指定Excel格式和下载文件名 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("设计里程碑计划模版.xlsx", StandardCharsets.UTF_8)); + + // 直接写入响应输出流 + try { + workbook.write(response.getOutputStream()); + workbook.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + + /** + * 创建可编辑单元格样式 + */ + private CellStyle createEditableCellStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setLocked(false); // 关键:允许编辑 + return style; + } + + /** + * 创建受保护单元格样式(用于ID列) + */ + private CellStyle createProtectedCellStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + DataFormat dataFormat = workbook.createDataFormat(); + short formatIndex = dataFormat.getFormat("0"); // 匹配“自定义→0”格式 + style.setDataFormat(formatIndex); + style.setHidden(true); // 隐藏公式 + style.setLocked(true); + return style; + } + + /** + * 设置专业下拉列表(第二列,索引1) + */ + private void setMajorDropdown(Sheet mainSheet, int majorCount) { + DataValidationHelper helper = mainSheet.getDataValidationHelper(); + + // 专业数据范围:数据Sheet的A列(从第1行到专业数量行) + String majorRange = "DropdownData!$A$1:$A$" + majorCount; + + DataValidationConstraint constraint = helper.createFormulaListConstraint(majorRange); + // 作用范围:第2行到100行,第二列 + CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, 2, 2); + + DataValidation validation = helper.createValidation(constraint, addressList); + validation.setShowErrorBox(true); + mainSheet.addValidationData(validation); + + } + + /** + * 设置专业下拉列表(第二列,索引1) + */ + private void setStatusDropdown(Sheet mainSheet, int majorCount) { + DataValidationHelper helper = mainSheet.getDataValidationHelper(); + + // 专业数据范围:数据Sheet的A列(从第1行到专业数量行) + String majorRange = "DropdownData!$E$1:$E$" + majorCount; + + DataValidationConstraint constraint = helper.createFormulaListConstraint(majorRange); + // 作用范围:第2行到100行,第二列 + CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, 6, 6); + + DataValidation validation = helper.createValidation(constraint, addressList); + validation.setShowErrorBox(true); + mainSheet.addValidationData(validation); + + } + + // endregion + + + /** + * 读取Excel文件 + * + * @param file Excel文件 + * @param projectId 项目ID + * @return 读取的数据列表 + */ + @Override + public List readExcel(MultipartFile file, Long projectId) { + List dataList = new ArrayList<>(); + + try (InputStream inputStream = file.getInputStream(); + XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) { + + XSSFSheet sheet = workbook.getSheetAt(0); + + // 从第二行(index=1)开始读取数据,跳过表头 + for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) { + Row row = sheet.getRow(rowIndex); + if (hasValidData(row)) { + String number = getCellValue(row.getCell(0)); + String nodeName = getCellValue(row.getCell(1)); + String projectStructureName = null; + Long projectStructure = null; + LocalDate planStartDate = getLocalDateValue(row.getCell(2)); + LocalDate planEndDate = getLocalDateValue(row.getCell(3)); + LocalDate practicalStartDate = getLocalDateValue(row.getCell(4)); + LocalDate practicalEndDate = getLocalDateValue(row.getCell(5)); + String status = getCellValue(row.getCell(7)); + String remark = getCellValue(row.getCell(8)); + + DesConstructionSchedulePlanExcelDto excelData = new DesConstructionSchedulePlanExcelDto( + number, projectId, nodeName, projectStructure, projectStructureName, + planStartDate, planEndDate, practicalStartDate, practicalEndDate, status, remark + ); + dataList.add(excelData); + } + } + } catch (Exception e) { + log.error("读取表格数据失败", e); + throw new ServiceException("读取表格数据失败"); + } + + return dataList; + } + + /** + * 将Excel数据转换为实体列表 + * + * @param excelList Excel数据列表 + * @return 实体列表 + */ + @Override + public List convertToEntities(List excelList) { + List result = new ArrayList<>(); + Map numberIdMap = new HashMap<>(); + + for (DesConstructionSchedulePlanExcelDto dto : excelList) { + DesConstructionSchedulePlan entity = new DesConstructionSchedulePlan(); + + LocalDate planStartDate = dto.getPlanStartDate(); + LocalDate planEndDate = dto.getPlanEndDate(); + if (planStartDate == null || planEndDate == null || planStartDate.isAfter(planEndDate)) { + throw new ServiceException("计划开始时间和计划结束时间不能为空,且计划开始时间不能大于计划结束时间", HttpStatus.BAD_REQUEST); + } + + // 生成主键 + Long id = IdWorker.getId(); + entity.setId(id); + + entity.setProjectId(dto.getProjectId()); + entity.setNodeName(dto.getNodeName()); + entity.setProjectStructure(dto.getProjectStructure()); + entity.setProjectStructureName(dto.getProjectStructureName()); + entity.setPlanStartDate(planStartDate); + entity.setPlanEndDate(planEndDate); + entity.setPracticalStartDate(dto.getPracticalStartDate()); + entity.setPracticalEndDate(dto.getPracticalEndDate()); + entity.setStatus(dto.getStatus()); + entity.setRemark(dto.getRemark()); + // 确定父ID + String number = dto.getNumber(); + if (number != null && number.contains(".")) { + String parentNumber = number.substring(0, number.lastIndexOf(".")); + Long parentId = numberIdMap.get(parentNumber); + if (parentId == null) { + throw new ServiceException("未找到父编号:" + parentNumber, HttpStatus.BAD_REQUEST); + } + entity.setParentId(parentId); + } else { + entity.setParentId(0L); // 顶级节点 + } + + // 保存当前编号对应的id + numberIdMap.put(number, id); + result.add(entity); + } + + return result; + } + + private static boolean hasValidData(Row row) { + // 遍历行中的所有单元格 + for (int cellIndex = 0; cellIndex < row.getLastCellNum(); cellIndex++) { + Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + String cellValue = getCellValue(cell).trim(); + + // 只要有一个单元格有非空值,就认为是有效行 + if (!cellValue.isEmpty()) { + return true; + } + } + return false; + } + + private static String getCellValue(Cell cell) { + if (cell == null) { + return ""; + } + // 使用CellType枚举判断单元格类型(POI 4.0+版本推荐方式) + CellType cellType = cell.getCellType(); + // 对于公式单元格,获取其计算结果的类型 + if (cellType == CellType.FORMULA) { + cellType = cell.getCachedFormulaResultType(); + } + + switch (cellType) { + case STRING: + return cell.getStringCellValue().trim(); + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + Date date = cell.getDateCellValue(); + return date.toString(); + } else { + // 处理数字类型,避免科学计数法 + // 处理数字,移除不必要的.0后缀 + String numericValue = String.valueOf(cell.getNumericCellValue()); + if (numericValue.endsWith(".0")) { + return numericValue.substring(0, numericValue.length() - 2); + } + return numericValue; + } + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + default: + return ""; + } + } + + private static LocalDate getLocalDateValue(Cell cell) { + if (cell == null) { + return null; + } + + CellType cellType = cell.getCellType(); + if (cellType == CellType.FORMULA) { + cellType = cell.getCachedFormulaResultType(); + } + + try { + if (cellType == CellType.NUMERIC) { + if (DateUtil.isCellDateFormatted(cell)) { + Date date = cell.getDateCellValue(); + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } else { + // 如果是数字但不是日期,就尝试转为 LocalDate (例如 20250730) + double numericValue = cell.getNumericCellValue(); + String text = String.valueOf((long) numericValue); + return LocalDate.parse(text); + } + } else if (cellType == CellType.STRING) { + String text = cell.getStringCellValue().trim(); + if (text.isEmpty()) { + return null; + } + // 尝试解析不同格式 + try { + return LocalDate.parse(text); // 默认 ISO 格式 yyyy-MM-dd + } catch (Exception e) { + // 如果 Excel 是 yyyy/MM/dd 或 yyyy.MM.dd,可以额外处理 + try { + return LocalDate.parse(text, java.time.format.DateTimeFormatter.ofPattern("yyyy/MM/dd")); + } catch (Exception ignored) { + } + try { + return LocalDate.parse(text, java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")); + } catch (Exception ignored) { + } + return null; // 不识别的格式就返回 null + } + } + } catch (Exception e) { + return null; + } + return null; + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsConstructionSchedulePlanServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsConstructionSchedulePlanServiceImpl.java index 8d29af2f..10d73328 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsConstructionSchedulePlanServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsConstructionSchedulePlanServiceImpl.java @@ -302,7 +302,11 @@ public class PgsConstructionSchedulePlanServiceImpl extends ServiceImpl