Merge branch 'development' of http://pms.dtyx.net:3000/cuizhibin/znpt-backend into development

# Conflicts:
#	core/src/main/java/com/dite/znpt/service/impl/ProjectServiceImpl.java
This commit is contained in:
郝彬 2025-08-14 17:56:27 +08:00
commit e7df0aa244
23 changed files with 1284 additions and 14 deletions

View File

@ -180,6 +180,12 @@ public class EquipmentEntity extends AuditableEntity implements Serializable {
@ApiModelProperty("采购状态NOT_STARTED-未开始PENDING_APPROVAL-待审批APPROVED-已通过REJECTED-已拒绝COMPLETED-已完成")
private String procurementStatus;
@ApiModelProperty("收货状态NOT_RECEIVED-未收货PARTIALLY_RECEIVED-部分收货RECEIVED-已收货")
private String receiptStatus;
@ApiModelProperty("支付状态NOT_PAID-未支付PARTIALLY_PAID-部分支付PAID-已支付")
private String paymentStatus;
@ApiModelProperty("附件")
private String attachments;

View File

@ -157,6 +157,15 @@ public class EquipmentResp implements Serializable {
@ApiModelProperty("采购状态NOT_STARTED-未开始PENDING_APPROVAL-待审批APPROVED-已通过REJECTED-已拒绝COMPLETED-已完成")
private String procurementStatus;
@ApiModelProperty("收货状态NOT_RECEIVED-未收货PARTIALLY_RECEIVED-部分收货RECEIVED-已收货")
private String receiptStatus;
@ApiModelProperty("支付状态NOT_PAID-未支付PARTIALLY_PAID-部分支付PAID-已支付")
private String paymentStatus;
@ApiModelProperty("审批状态PENDING-待审批APPROVED-已通过REJECTED-已拒绝")
private String approvalStatus;
// 移除备用状态字段使用现有的 location_status 字段
// @ApiModelProperty("备用状态")
// private String spareStatus;

View File

@ -0,0 +1,45 @@
package com.dite.znpt.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
/**
* 甘特图查询请求类
*/
@Data
@ApiModel(value = "GanttChartReq", description = "甘特图查询请求")
public class GanttChartReq {
@ApiModelProperty("项目ID")
private String projectId;
@ApiModelProperty("任务组ID")
private String taskGroupId;
@ApiModelProperty("任务状态")
private Integer status;
@ApiModelProperty("负责人ID")
private String mainUserId;
@ApiModelProperty("开始时间范围-开始")
private LocalDate startDateFrom;
@ApiModelProperty("开始时间范围-结束")
private LocalDate startDateTo;
@ApiModelProperty("结束时间范围-开始")
private LocalDate endDateFrom;
@ApiModelProperty("结束时间范围-结束")
private LocalDate endDateTo;
@ApiModelProperty("是否包含已完成任务")
private Boolean includeCompleted = true;
@ApiModelProperty("是否只显示逾期任务")
private Boolean overdueOnly = false;
}

View File

@ -0,0 +1,91 @@
package com.dite.znpt.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
* 甘特图数据响应类
*/
@Data
@ApiModel(value = "GanttChartResp", description = "甘特图数据响应")
public class GanttChartResp {
@ApiModelProperty("任务ID")
private String taskId;
@ApiModelProperty("任务名称")
private String taskName;
@ApiModelProperty("任务编号")
private String taskCode;
@ApiModelProperty("上级任务ID")
private String parentTaskId;
@ApiModelProperty("任务组ID")
private String taskGroupId;
@ApiModelProperty("任务组名称")
private String taskGroupName;
@ApiModelProperty("计划开始时间")
private LocalDate planStartDate;
@ApiModelProperty("计划结束时间")
private LocalDate planEndDate;
@ApiModelProperty("实际开始时间")
private LocalDate actualStartDate;
@ApiModelProperty("实际结束时间")
private LocalDate actualEndDate;
@ApiModelProperty("任务状态0未开始1进行中2已结束")
private Integer status;
@ApiModelProperty("任务状态描述")
private String statusDesc;
@ApiModelProperty("是否逾期0未逾期1已逾期")
private Integer overdueStatus;
@ApiModelProperty("任务负责人ID")
private String mainUserId;
@ApiModelProperty("任务负责人姓名")
private String mainUserName;
@ApiModelProperty("任务参与人")
private String userIds;
@ApiModelProperty("任务参与人姓名列表")
private List<String> participantNames;
@ApiModelProperty("进度百分比")
private Integer progress;
@ApiModelProperty("任务层级")
private Integer level;
@ApiModelProperty("任务在时间轴上的位置(天数)")
private Integer position;
@ApiModelProperty("任务持续时间(天数)")
private Integer duration;
@ApiModelProperty("子任务列表")
private List<GanttChartResp> children;
@ApiModelProperty("任务备注")
private String remark;
@ApiModelProperty("项目ID")
private String projectId;
@ApiModelProperty("项目名称")
private String projectName;
}

View File

@ -0,0 +1,160 @@
package com.dite.znpt.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 收货请求参数扩展版包含完整设备信息
*
* @author system
* @date 2025-01-08
*/
@Data
@ApiModel(value = "收货请求参数", description = "收货请求参数,包含收货信息和设备信息")
public class ReceiptRequest {
@ApiModelProperty("设备序列号(收货时自动生成)")
private String equipmentSn;
@ApiModelProperty("库存条码(收货时自动生成)")
private String inventoryBarcode;
// 收货特有信息
@NotBlank(message = "收货时间不能为空")
@ApiModelProperty("收货时间")
private String receiptTime;
@NotBlank(message = "收货人不能为空")
@ApiModelProperty("收货人")
private String receiptPerson;
@NotNull(message = "收货数量不能为空")
@ApiModelProperty("收货数量")
private Integer receiptQuantity;
@ApiModelProperty("收货备注")
private String receiptRemark;
@NotBlank(message = "外观检查结果不能为空")
@ApiModelProperty("外观检查结果")
private String appearanceCheck;
@NotBlank(message = "功能测试结果不能为空")
@ApiModelProperty("功能测试结果")
private String functionTest;
@NotBlank(message = "包装完整性不能为空")
@ApiModelProperty("包装完整性")
private String packageIntegrity;
@NotBlank(message = "配件完整性不能为空")
@ApiModelProperty("配件完整性")
private String accessoryIntegrity;
@NotBlank(message = "检查结果不能为空")
@ApiModelProperty("检查结果")
private String checkResult;
@ApiModelProperty("检查备注")
private String checkRemark;
@NotBlank(message = "入库位置不能为空")
@ApiModelProperty("入库位置")
private String storageLocation;
@NotBlank(message = "库管员不能为空")
@ApiModelProperty("库管员")
private String storageManager;
// 设备基本信息从采购数据继承
@ApiModelProperty("设备名称")
private String equipmentName;
@ApiModelProperty("设备型号")
private String equipmentModel;
@ApiModelProperty("设备类型")
private String equipmentType;
@ApiModelProperty("品牌")
private String brand;
@ApiModelProperty("配置规格/参数")
private String specification;
@ApiModelProperty("资产编号")
private String assetCode;
// 采购信息从采购数据继承
@ApiModelProperty("采购订单号")
private String purchaseOrder;
@ApiModelProperty("供应商名称")
private String supplierName;
@ApiModelProperty("采购价格")
private BigDecimal purchasePrice;
@ApiModelProperty("采购时间")
private String purchaseTime;
@ApiModelProperty("数量")
private Integer quantity;
@ApiModelProperty("单价")
private BigDecimal unitPrice;
@ApiModelProperty("总价")
private BigDecimal totalPrice;
// 入库信息
@ApiModelProperty("入库时间")
private String inStockTime;
@ApiModelProperty("物理位置")
private String physicalLocation;
@ApiModelProperty("位置状态")
private String locationStatus;
@ApiModelProperty("负责人")
private String responsiblePerson;
// 状态信息
@ApiModelProperty("设备状态")
private String equipmentStatus;
@ApiModelProperty("使用状态")
private String useStatus;
@ApiModelProperty("健康状态")
private String healthStatus;
@ApiModelProperty("收货状态")
private String receiptStatus;
// 其他管理信息
@ApiModelProperty("折旧方法")
private String depreciationMethod;
@ApiModelProperty("折旧年限")
private Integer depreciationYears;
@ApiModelProperty("残值")
private BigDecimal salvageValue;
@ApiModelProperty("当前净值")
private BigDecimal currentNetValue;
// 系统字段
@ApiModelProperty("创建时间")
private String createTime;
@ApiModelProperty("更新时间")
private String updateTime;
}

View File

@ -0,0 +1,52 @@
package com.dite.znpt.enums;
import cn.hutool.json.JSONObject;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
/**
* 支付状态枚举
*
* @author system
* @date 2025-01-08
*/
@Getter
public enum PaymentStatusEnum {
NOT_PAID("NOT_PAID", "未支付"),
PARTIALLY_PAID("PARTIALLY_PAID", "部分支付"),
PAID("PAID", "已支付");
private final String code;
private final String desc;
PaymentStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public static PaymentStatusEnum getByCode(String code) {
for (PaymentStatusEnum e : PaymentStatusEnum.values()) {
if (e.code.equals(code)) {
return e;
}
}
return null;
}
public static String getDescByCode(String code) {
PaymentStatusEnum e = getByCode(code);
return null == e ? null : e.desc;
}
public static List<JSONObject> listAll() {
List<JSONObject> list = new ArrayList<>(PaymentStatusEnum.values().length);
for (PaymentStatusEnum e : PaymentStatusEnum.values()) {
JSONObject jsonObject = new JSONObject();
jsonObject.set(e.code, e.desc);
list.add(jsonObject);
}
return list;
}
}

View File

@ -0,0 +1,52 @@
package com.dite.znpt.enums;
import cn.hutool.json.JSONObject;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
/**
* 收货状态枚举
*
* @author system
* @date 2025-01-08
*/
@Getter
public enum ReceiptStatusEnum {
NOT_RECEIVED("NOT_RECEIVED", "未收货"),
PARTIALLY_RECEIVED("PARTIALLY_RECEIVED", "部分收货"),
RECEIVED("RECEIVED", "已收货");
private final String code;
private final String desc;
ReceiptStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public static ReceiptStatusEnum getByCode(String code) {
for (ReceiptStatusEnum e : ReceiptStatusEnum.values()) {
if (e.code.equals(code)) {
return e;
}
}
return null;
}
public static String getDescByCode(String code) {
ReceiptStatusEnum e = getByCode(code);
return null == e ? null : e.desc;
}
public static List<JSONObject> listAll() {
List<JSONObject> list = new ArrayList<>(ReceiptStatusEnum.values().length);
for (ReceiptStatusEnum e : ReceiptStatusEnum.values()) {
JSONObject jsonObject = new JSONObject();
jsonObject.set(e.code, e.desc);
list.add(jsonObject);
}
return list;
}
}

View File

@ -36,4 +36,15 @@ public interface BusinessDataFileMapper {
@Param("newFileName") String newFileName,
@Param("newFilePath") String newFilePath);
// // 批量更新文件路径
// void updateFilePathByFolderId(
// @Param("folderId") Long folderId,
// @Param("newFolderPath") String newFolderPath,
// @Param("separator") String separator);
// 批量更新子文件夹下文件的路径
void updateSubFilePaths(@Param("oldParentPath1") String oldParentPath1,
@Param("newParentPath1") String newParentPath1,
@Param("oldParentPath2") String oldParentPath2
);
}

View File

@ -29,4 +29,12 @@ public interface BusinessDataMapper {
void reName(BusinessDataEntity businessDataEntity);
public List<BusinessDataEntity> ListWithCondition(@Param("folderName") String folderName);
// 批量更新子文件夹路径
void updateSubFolderPaths(@Param("oldParentPath1") String oldParentPath,
@Param("newParentPath1") String newParentPath,
@Param("processedOldParentPath") String processedOldParentPath,
@Param("processedNewParentPath") String processedNewParentPath,
@Param("folderId") Long folderId
);
}

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dite.znpt.domain.entity.ProjectTaskEntity;
import com.dite.znpt.domain.vo.ProjectTaskListReq;
import com.dite.znpt.domain.vo.ProjectTaskResp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -13,6 +14,7 @@ import java.util.List;
* @date 2025/06/24 16:44
* @Description: 项目任务信息表数据库访问层
*/
@Mapper
public interface ProjectTaskMapper extends BaseMapper<ProjectTaskEntity> {
List<ProjectTaskResp> queryBySelective(ProjectTaskListReq projectTaskReq);

View File

@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
@ApiOperation("商务资料文件service")
@Service
@ -40,4 +41,11 @@ public interface BusinessDataFileService {
@ApiOperation("预览文件")
void preview(Long fileId, HttpServletResponse response);
// @ApiOperation("批量更新文件路径")
// public void updateFilePathByFolderId(Long folderId, String newFolderPath);
@ApiOperation("批量更新子文件夹下文件的路径")
void updateSubFilePaths(String oldParentPath, String newParentPath);
}

View File

@ -15,4 +15,6 @@ public interface BusinessDataService {
Result delete(Long folderId);
Result reName(Long folderId, String newName);
void updateSubFolderPaths(String oldParentPath1, String newParentPath1, Long folderId);
}

View File

@ -6,6 +6,7 @@ import com.dite.znpt.domain.entity.EquipmentEntity;
import com.dite.znpt.domain.vo.EquipmentListReq;
import com.dite.znpt.domain.vo.EquipmentReq;
import com.dite.znpt.domain.vo.EquipmentResp;
import com.dite.znpt.domain.vo.ReceiptRequest;
import java.util.List;
import java.util.Map;
@ -53,6 +54,11 @@ public interface EquipmentService extends IService<EquipmentEntity> {
*/
Object getProcurementStats();
/**
* 确认收货并自动入库
*/
void receiveGoodsAndStockIn(String equipmentId, ReceiptRequest req);
/**
* 导出采购记录
*/

View File

@ -0,0 +1,55 @@
package com.dite.znpt.service;
import com.dite.znpt.domain.vo.GanttChartReq;
import com.dite.znpt.domain.vo.GanttChartResp;
import java.util.List;
/**
* 甘特图服务接口
*/
public interface GanttChartService {
/**
* 获取项目甘特图数据
*
* @param req 查询条件
* @return 甘特图数据列表
*/
List<GanttChartResp> getGanttChartData(GanttChartReq req);
/**
* 获取项目甘特图统计信息
*
* @param projectId 项目ID
* @return 统计信息
*/
Object getGanttChartStatistics(String projectId);
/**
* 获取项目甘特图时间轴信息
*
* @param projectId 项目ID
* @return 时间轴信息
*/
Object getGanttChartTimeline(String projectId);
/**
* 更新任务进度
*
* @param taskId 任务ID
* @param progress 进度百分比
* @return 是否成功
*/
boolean updateTaskProgress(String taskId, Integer progress);
/**
* 拖拽更新任务时间
*
* @param taskId 任务ID
* @param startDate 开始时间
* @param endDate 结束时间
* @return 是否成功
*/
boolean updateTaskTime(String taskId, String startDate, String endDate);
}

View File

@ -81,7 +81,8 @@ public class BusinessDataFileServiceImpl implements BusinessDataFileService {
}
@ApiOperation("删除文件")
public Result delete(Long fileId, Long folderId) {
//删除数据库数据
//删除文件夹时候调用这个方法才会删除所有文件的数据库数据
// 至于具体文件不用在这个方法删除
if (folderId != null){
businessDataFileMapper.delete(null,folderId);
return Result.okM("删除成功");
@ -155,8 +156,11 @@ public class BusinessDataFileServiceImpl implements BusinessDataFileService {
}
// 构建新文件路径
// 获取父目录
String parentPath = oldFile.getParent();
// 获取文件扩展名
String fileExtension = "";
// 获取文件名不包含扩展名
String fileNameWithoutExt = newFileName;
// 获取原文件扩展名
@ -246,7 +250,7 @@ public class BusinessDataFileServiceImpl implements BusinessDataFileService {
byte[] bytes = file.getBytes();
String uploadDir = businessDataService.getPath(folderId);
File uploadedFile = new File(uploadDir + "/" + file.getOriginalFilename());
File uploadedFile = new File(uploadDir + File.separator + file.getOriginalFilename());
if (uploadedFile.exists()) {
return Result.error("文件已存在");
}
@ -256,7 +260,7 @@ public class BusinessDataFileServiceImpl implements BusinessDataFileService {
BusinessDataFileEntity fileEntity = new BusinessDataFileEntity();
fileEntity.setFolderId(folderId);
fileEntity.setFileName(file.getOriginalFilename());
fileEntity.setFilePath(uploadDir + "/" + file.getOriginalFilename());
fileEntity.setFilePath(uploadDir + File.separator + file.getOriginalFilename());
fileEntity.setFileType(file.getContentType());
fileEntity.setFileSize(file.getSize()/1024);
fileEntity.setUploadTime(new Date());
@ -544,4 +548,21 @@ public class BusinessDataFileServiceImpl implements BusinessDataFileService {
}
}
// @ApiOperation("批量更新文件路径")
// @Override
// public void updateFilePathByFolderId(Long folderId, String newFolderPath) {
// businessDataFileMapper.updateFilePathByFolderId(folderId, newFolderPath, File.separator);
// }
@ApiOperation("批量更新子文件夹下文件的路径")
@Override
public void updateSubFilePaths(String oldParentPath1, String newParentPath1) {
// 处理路径中的分隔符如果是反斜杠则替换为双反斜杠
String oldParentPath2 = oldParentPath1;
if ("\\".equals(File.separator)) {
oldParentPath2 = oldParentPath1.replace("\\", "\\\\");
}
businessDataFileMapper.updateSubFilePaths(oldParentPath1, newParentPath1, oldParentPath2);
}
}

View File

@ -70,7 +70,7 @@ public class BusinessDataServiceImpl implements BusinessDataService {
}
// 文件夹名称前置一个/
String folderName1 = "/" + folderName;
String folderName1 = File.separator + folderName;
// 目标文件夹
File targetDir = Paths.get(businessDataPath, folderName1).toFile();
if (parentId != 0L) {
@ -147,23 +147,37 @@ public class BusinessDataServiceImpl implements BusinessDataService {
}
}
@ApiOperation("批量更新子文件夹路径")
@Override
public void updateSubFolderPaths(String oldParentPath1, String newParentPath1,Long folderId) {
// 处理路径中的分隔符如果是反斜杠则替换为双反斜杠
String processedOldParentPath = oldParentPath1;
String processedNewParentPath = newParentPath1;
if ("\\".equals(File.separator)) {
processedOldParentPath = oldParentPath1.replace("\\", "\\\\");
processedNewParentPath = newParentPath1.replace("\\", "\\\\");
}
businessDataMapper.updateSubFolderPaths(oldParentPath1,newParentPath1,processedOldParentPath, processedNewParentPath, folderId);
}
@ApiOperation("重命名文件夹")
@Override
public Result reName(Long folderId, String newName) {
// 获取文件夹路径
String folderPath = businessDataMapper.getPath(folderId);
String newPath = folderPath.substring(0, folderPath.lastIndexOf('\\')) + "\\" + newName;
//
// //想命名的原文件的路径
// File file = new File("f:/a/a.xlsx");
// //将原文件更改为f:\a\b.xlsx其中路径是必要的注意
// file.renameTo(new File("f:/a/b.xlsx"));
// 想命名的原文件夹的路径
String newPath = folderPath.substring(0, folderPath.lastIndexOf(File.separator)) + File.separator + newName;
// 重命名物理文件夹
File file1 = new File(folderPath);
// 将原文件夹更改为A其中路径是必要的注意
file1.renameTo(new File(newPath));
boolean renameSuccess = file1.renameTo(new File(newPath));
if (!renameSuccess) {
return Result.error("文件夹重命名失败");
}
LocalDateTime now = LocalDateTime.now();
// 更新文件夹信息
BusinessDataEntity businessDataEntity = new BusinessDataEntity(
folderId,
newName,
@ -174,6 +188,21 @@ public class BusinessDataServiceImpl implements BusinessDataService {
null,
newPath);
businessDataMapper.reName(businessDataEntity);
// // 批量更新该文件夹下所有文件的路径
// businessDataFileService.updateFilePathByFolderId(folderId, newPath);
String folderPath1 = folderPath+File.separator;
String newPath1 = newPath+File.separator;
// 批量更新子文件夹下所有文件的路径
businessDataFileService.updateSubFilePaths(folderPath1, newPath1);
// 批量更新子文件夹的路径
updateSubFolderPaths(folderPath1, newPath1,folderId);
return Result.okM("重命名成功");
}
}

View File

@ -23,9 +23,13 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import com.dite.znpt.domain.vo.ReceiptRequest;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.beans.BeanUtils;
/**
* @author Bear.G
@ -474,6 +478,15 @@ public class EquipmentServiceImpl extends ServiceImpl<EquipmentMapper, Equipment
// 设置采购状态字段
resp.setProcurementStatus(entity.getProcurementStatus());
// 设置收货状态和支付状态字段
resp.setReceiptStatus(entity.getReceiptStatus());
resp.setPaymentStatus(entity.getPaymentStatus());
// 设置审批状态根据采购状态推断
// 这里需要注入 EquipmentApprovalService 来获取审批状态
// 暂时根据采购状态推断后续通过关联查询获取
resp.setApprovalStatus(getApprovalStatus(entity.getEquipmentId(), entity.getProcurementStatus()));
// 新增字段转换
resp.setUsingDepartment(entity.getUsingDepartment());
resp.setBorrowingTime(entity.getBorrowingTime());
@ -501,6 +514,34 @@ public class EquipmentServiceImpl extends ServiceImpl<EquipmentMapper, Equipment
return resp;
}
/**
* 获取设备的审批状态
*/
private String getApprovalStatus(String equipmentId, String procurementStatus) {
try {
// 根据采购状态推断审批状态
if (procurementStatus == null) {
return "PENDING_APPROVAL";
}
switch (procurementStatus) {
case "PENDING_APPROVAL":
return "PENDING";
case "APPROVED":
return "APPROVED";
case "REJECTED":
return "REJECTED";
case "COMPLETED":
return "APPROVED"; // 已完成表示之前已审批通过
default:
return "PENDING_APPROVAL";
}
} catch (Exception e) {
log.warn("获取设备审批状态失败设备ID: {}, 错误: {}", equipmentId, e.getMessage());
return "PENDING_APPROVAL"; // 出错时返回待审批状态
}
}
// 采购管理相关方法实现
@Override
public IPage<EquipmentResp> procurementPage(EquipmentListReq req) {
@ -840,4 +881,93 @@ public class EquipmentServiceImpl extends ServiceImpl<EquipmentMapper, Equipment
log.info("批量设备盘库执行成功,处理设备数量: {}", equipmentIds.size());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveGoodsAndStockIn(String equipmentId, ReceiptRequest req) {
log.info("开始处理设备收货和入库设备ID: {}", equipmentId);
log.info("收货请求数据: {}", req);
// 1. 查找采购记录
EquipmentEntity procurementRecord = this.getById(equipmentId);
if (procurementRecord == null) {
throw new ServiceException("采购记录不存在");
}
log.info("找到采购记录: {}", procurementRecord);
// 2. 更新现有设备记录而不是创建新记录
EquipmentEntity equipment = new EquipmentEntity();
equipment.setEquipmentId(equipmentId);
// 设置收货相关信息
if (StringUtils.hasText(req.getReceiptTime())) {
try {
equipment.setInStockTime(LocalDateTime.parse(req.getReceiptTime().replace(" ", "T")));
} catch (Exception e) {
log.warn("解析收货时间失败,使用当前时间: {}", req.getReceiptTime());
equipment.setInStockTime(LocalDateTime.now());
}
} else {
equipment.setInStockTime(LocalDateTime.now());
}
// 设置收货状态为已收货
equipment.setReceiptStatus("RECEIVED");
// 设置采购状态为已完成而不是已收货
equipment.setProcurementStatus("COMPLETED");
// 设置设备状态为库存中
equipment.setLocationStatus("in_stock");
equipment.setUseStatus("0"); // 空闲中
// 设置其他收货相关字段
if (StringUtils.hasText(req.getStorageLocation())) {
equipment.setPhysicalLocation(req.getStorageLocation());
}
if (StringUtils.hasText(req.getStorageManager())) {
equipment.setResponsiblePerson(req.getStorageManager());
}
// 记录收货人信息到资产备注中
if (StringUtils.hasText(req.getReceiptPerson())) {
String currentRemark = procurementRecord.getAssetRemark();
String receiptInfo = String.format("收货人:%s收货时间%s",
req.getReceiptPerson(),
req.getReceiptTime() != null ? req.getReceiptTime() : LocalDateTime.now().toString());
if (StringUtils.hasText(currentRemark)) {
equipment.setAssetRemark(currentRemark + "" + receiptInfo);
} else {
equipment.setAssetRemark(receiptInfo);
}
}
// 设置设备序列号如果收货时生成了新的
if (StringUtils.hasText(req.getEquipmentSn())) {
equipment.setEquipmentSn(req.getEquipmentSn());
}
// 设置库存条码
if (StringUtils.hasText(req.getInventoryBarcode())) {
equipment.setInventoryBarcode(req.getInventoryBarcode());
}
// 设置更新时间和操作人
equipment.setUpdateTime(LocalDateTime.now());
equipment.setUpdateBy(StpUtil.getLoginIdAsString());
log.info("准备更新设备记录设备ID: {}, 收货状态: {}, 采购状态: {}",
equipmentId, equipment.getReceiptStatus(), equipment.getProcurementStatus());
// 3. 更新设备记录
boolean updateResult = this.updateById(equipment);
if (!updateResult) {
throw new ServiceException("更新设备记录失败");
}
log.info("设备收货和入库成功设备ID: {}, 收货状态: {}, 采购状态: {}",
equipmentId, equipment.getReceiptStatus(), equipment.getProcurementStatus());
}
}

View File

@ -0,0 +1,486 @@
package com.dite.znpt.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dite.znpt.domain.entity.ProjectTaskEntity;
import com.dite.znpt.domain.entity.UserEntity;
import com.dite.znpt.domain.vo.GanttChartReq;
import com.dite.znpt.domain.vo.GanttChartResp;
import com.dite.znpt.mapper.ProjectTaskMapper;
import com.dite.znpt.service.GanttChartService;
import com.dite.znpt.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.stream.Collectors;
/**
* 甘特图服务实现类
*/
@Service
@RequiredArgsConstructor
public class GanttChartServiceImpl implements GanttChartService {
private final ProjectTaskMapper projectTaskMapper;
private final UserService userService;
@Override
public List<GanttChartResp> getGanttChartData(GanttChartReq req) {
// 查询所有任务
QueryWrapper<ProjectTaskEntity> queryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(req.getProjectId())) {
queryWrapper.eq("project_id", req.getProjectId());
}
if (StrUtil.isNotBlank(req.getTaskGroupId())) {
queryWrapper.eq("task_group_id", req.getTaskGroupId());
}
if (req.getStatus() != null) {
queryWrapper.eq("status", req.getStatus());
}
if (StrUtil.isNotBlank(req.getMainUserId())) {
queryWrapper.eq("main_user_id", req.getMainUserId());
}
if (req.getStartDateFrom() != null) {
queryWrapper.ge("plan_start_date", req.getStartDateFrom());
}
if (req.getStartDateTo() != null) {
queryWrapper.le("plan_start_date", req.getStartDateTo());
}
if (req.getEndDateFrom() != null) {
queryWrapper.ge("plan_end_date", req.getEndDateFrom());
}
if (req.getEndDateTo() != null) {
queryWrapper.le("plan_end_date", req.getEndDateTo());
}
if (req.getOverdueOnly() != null && req.getOverdueOnly()) {
queryWrapper.eq("overdue_status", 1);
}
if (req.getIncludeCompleted() != null && !req.getIncludeCompleted()) {
queryWrapper.ne("status", 2);
}
queryWrapper.orderByAsc("plan_start_date");
List<ProjectTaskEntity> taskList = projectTaskMapper.selectList(queryWrapper);
// 获取用户信息
final Set<String> userIds = new HashSet<>();
taskList.forEach(task -> {
if (StrUtil.isNotBlank(task.getMainUserId())) {
userIds.add(task.getMainUserId());
}
if (StrUtil.isNotBlank(task.getUserIds())) {
userIds.addAll(Arrays.asList(task.getUserIds().split(",")));
}
});
final Map<String, UserEntity> userMap = new HashMap<>();
if (CollUtil.isNotEmpty(userIds)) {
List<UserEntity> users = userService.listByIds(userIds);
userMap.putAll(users.stream().collect(Collectors.toMap(UserEntity::getUserId, user -> user)));
}
// 批量获取项目时间范围避免重复查询
final Map<String, LocalDate> projectStartDates = new HashMap<>();
if (StrUtil.isNotBlank(req.getProjectId())) {
// 如果指定了项目ID直接获取该项目的时间范围
LocalDate projectStart = getProjectStartDate(req.getProjectId());
projectStartDates.put(req.getProjectId(), projectStart);
} else {
// 如果没有指定项目ID获取所有相关项目的时间范围
Set<String> projectIds = taskList.stream()
.map(ProjectTaskEntity::getProjectId)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
for (String projectId : projectIds) {
LocalDate projectStart = getProjectStartDate(projectId);
projectStartDates.put(projectId, projectStart);
}
}
// 转换为甘特图数据
List<GanttChartResp> ganttData = taskList.stream().map(task -> {
GanttChartResp resp = BeanUtil.copyProperties(task, GanttChartResp.class);
// 设置状态描述
switch (task.getStatus()) {
case 0:
resp.setStatusDesc("未开始");
break;
case 1:
resp.setStatusDesc("进行中");
break;
case 2:
resp.setStatusDesc("已结束");
break;
default:
resp.setStatusDesc("未知");
}
// 设置负责人姓名
if (StrUtil.isNotBlank(task.getMainUserId()) && userMap.containsKey(task.getMainUserId())) {
resp.setMainUserName(userMap.get(task.getMainUserId()).getName());
}
// 设置参与人姓名列表
if (StrUtil.isNotBlank(task.getUserIds())) {
List<String> participantNames = Arrays.stream(task.getUserIds().split(","))
.filter(userId -> userMap.containsKey(userId))
.map(userId -> userMap.get(userId).getName())
.collect(Collectors.toList());
resp.setParticipantNames(participantNames);
}
// 计算进度这里可以根据实际业务逻辑调整
if (task.getStatus() == 2) {
resp.setProgress(100);
} else if (task.getStatus() == 1) {
resp.setProgress(50); // 默认进度实际应该从数据库字段获取
} else {
resp.setProgress(0);
}
// 计算任务在时间轴上的位置和持续时间
LocalDate projectStart = projectStartDates.get(task.getProjectId());
if (projectStart != null && task.getPlanStartDate() != null) {
int position = (int) ChronoUnit.DAYS.between(projectStart, task.getPlanStartDate());
resp.setPosition(position);
}
// 计算任务持续时间
if (task.getPlanStartDate() != null && task.getPlanEndDate() != null) {
int duration = (int) ChronoUnit.DAYS.between(task.getPlanStartDate(), task.getPlanEndDate()) + 1;
resp.setDuration(duration);
} else {
resp.setDuration(1); // 默认持续1天
}
return resp;
}).collect(Collectors.toList());
// 构建树形结构
return buildTreeStructure(ganttData);
}
@Override
public Object getGanttChartStatistics(String projectId) {
QueryWrapper<ProjectTaskEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("project_id", projectId);
List<ProjectTaskEntity> taskList = projectTaskMapper.selectList(queryWrapper);
Map<String, Object> statistics = new HashMap<>();
statistics.put("totalTasks", taskList.size());
statistics.put("notStarted", (int) taskList.stream().filter(task -> task.getStatus() == 0).count());
statistics.put("inProgress", (int) taskList.stream().filter(task -> task.getStatus() == 1).count());
statistics.put("completed", (int) taskList.stream().filter(task -> task.getStatus() == 2).count());
statistics.put("overdue", (int) taskList.stream().filter(task -> task.getOverdueStatus() == 1).count());
// 计算总体进度
if (taskList.size() > 0) {
final double avgProgress = taskList.stream()
.mapToInt(task -> {
if (task.getStatus() == 2) return 100;
else if (task.getStatus() == 1) return 50;
else return 0;
})
.average()
.orElse(0.0);
statistics.put("overallProgress", Math.round(avgProgress));
} else {
statistics.put("overallProgress", 0);
}
return statistics;
}
@Override
public Object getGanttChartTimeline(String projectId) {
if (StrUtil.isBlank(projectId)) {
return null;
}
LocalDate projectStartDate = getProjectStartDate(projectId);
LocalDate projectEndDate = getProjectEndDate(projectId);
Map<String, Object> timeline = new HashMap<>();
timeline.put("projectId", projectId);
timeline.put("startDate", projectStartDate);
timeline.put("endDate", projectEndDate);
timeline.put("totalDays", ChronoUnit.DAYS.between(projectStartDate, projectEndDate) + 1);
// 计算时间轴上的关键时间点比如每周每月
List<Map<String, Object>> timePoints = new ArrayList<>();
LocalDate currentDate = projectStartDate;
while (!currentDate.isAfter(projectEndDate)) {
Map<String, Object> timePoint = new HashMap<>();
timePoint.put("date", currentDate);
timePoint.put("dayOfWeek", currentDate.getDayOfWeek().getDisplayName(java.time.format.TextStyle.SHORT, java.util.Locale.CHINESE));
timePoint.put("isWeekend", currentDate.getDayOfWeek().getValue() >= 6);
timePoints.add(timePoint);
currentDate = currentDate.plusDays(1);
}
timeline.put("timePoints", timePoints);
return timeline;
}
@Override
public boolean updateTaskProgress(String taskId, Integer progress) {
ProjectTaskEntity task = projectTaskMapper.selectById(taskId);
if (task == null) {
return false;
}
// 更新进度
// 这里需要根据实际业务逻辑添加进度字段
// task.setProgress(progress);
// 根据进度更新状态
if (progress >= 100) {
task.setStatus(2); // 已完成
task.setActualEndDate(LocalDate.now());
} else if (progress > 0) {
task.setStatus(1); // 进行中
if (task.getActualStartDate() == null) {
task.setActualStartDate(LocalDate.now());
}
}
return projectTaskMapper.updateById(task) > 0;
}
@Override
public boolean updateTaskTime(String taskId, String startDate, String endDate) {
ProjectTaskEntity task = projectTaskMapper.selectById(taskId);
if (task == null) {
return false;
}
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate newStartDate = null;
LocalDate newEndDate = null;
try {
if (StrUtil.isNotBlank(startDate)) {
newStartDate = LocalDate.parse(startDate, formatter);
}
if (StrUtil.isNotBlank(endDate)) {
newEndDate = LocalDate.parse(endDate, formatter);
}
} catch (DateTimeParseException e) {
return false; // 日期格式错误
}
// 验证时间约束
if (!validateTaskTimeConstraints(task, newStartDate, newEndDate)) {
return false;
}
// 更新任务时间
if (newStartDate != null) {
task.setPlanStartDate(newStartDate);
}
if (newEndDate != null) {
task.setPlanEndDate(newEndDate);
}
// 更新逾期状态
updateOverdueStatus(task);
return projectTaskMapper.updateById(task) > 0;
}
/**
* 构建树形结构
*/
private List<GanttChartResp> buildTreeStructure(List<GanttChartResp> allTasks) {
final Map<String, GanttChartResp> taskMap = allTasks.stream()
.collect(Collectors.toMap(GanttChartResp::getTaskId, task -> task));
final List<GanttChartResp> rootTasks = new ArrayList<>();
for (GanttChartResp task : allTasks) {
if (StrUtil.isBlank(task.getParentTaskId())) {
// 根任务
task.setLevel(0);
rootTasks.add(task);
} else {
// 子任务
GanttChartResp parentTask = taskMap.get(task.getParentTaskId());
if (parentTask != null) {
task.setLevel(parentTask.getLevel() + 1);
if (parentTask.getChildren() == null) {
parentTask.setChildren(new ArrayList<>());
}
parentTask.getChildren().add(task);
}
}
}
return rootTasks;
}
/**
* 获取项目开始时间
* 从项目任务中计算最早的计划开始时间作为项目开始时间
*/
private LocalDate getProjectStartDate(String projectId) {
if (StrUtil.isBlank(projectId)) {
return LocalDate.now().minusDays(30); // 默认值
}
// 查询项目中所有任务的计划开始时间取最早的时间
QueryWrapper<ProjectTaskEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("project_id", projectId)
.isNotNull("plan_start_date")
.orderByAsc("plan_start_date")
.last("LIMIT 1");
ProjectTaskEntity earliestTask = projectTaskMapper.selectOne(queryWrapper);
if (earliestTask != null && earliestTask.getPlanStartDate() != null) {
return earliestTask.getPlanStartDate();
}
// 如果没有找到有效的开始时间返回当前时间减去30天作为默认值
return LocalDate.now().minusDays(30);
}
/**
* 获取项目结束时间
* 从项目任务中计算最晚的计划结束时间作为项目结束时间
*/
private LocalDate getProjectEndDate(String projectId) {
if (StrUtil.isBlank(projectId)) {
return LocalDate.now().plusDays(30); // 默认值
}
// 查询项目中所有任务的计划结束时间取最晚的时间
QueryWrapper<ProjectTaskEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("project_id", projectId)
.isNotNull("plan_end_date")
.orderByDesc("plan_end_date")
.last("LIMIT 1");
ProjectTaskEntity latestTask = projectTaskMapper.selectOne(queryWrapper);
if (latestTask != null && latestTask.getPlanEndDate() != null) {
return latestTask.getPlanEndDate();
}
// 如果没有找到有效的结束时间返回当前时间加上30天作为默认值
return LocalDate.now().plusDays(30);
}
/**
* 获取项目时间范围
* 返回项目的开始时间和结束时间
*/
private Map<String, LocalDate> getProjectTimeRange(String projectId) {
Map<String, LocalDate> timeRange = new HashMap<>();
timeRange.put("startDate", getProjectStartDate(projectId));
timeRange.put("endDate", getProjectEndDate(projectId));
return timeRange;
}
/**
* 批量获取项目时间范围
* 通过一次查询获取多个项目的时间范围提高性能
*/
private Map<String, Map<String, LocalDate>> getBatchProjectTimeRanges(Set<String> projectIds) {
Map<String, Map<String, LocalDate>> timeRanges = new HashMap<>();
for (String projectId : projectIds) {
Map<String, LocalDate> timeRange = getProjectTimeRange(projectId);
timeRanges.put(projectId, timeRange);
}
return timeRanges;
}
/**
* 计算任务进度
*/
private Integer calculateProgress(ProjectTaskEntity task) {
if (task.getStatus() == 2) return 100; // 已完成
else if (task.getStatus() == 1) return 50; // 进行中
else return 0; // 未开始
}
/**
* 计算任务持续时间
*/
private Integer calculateDuration(ProjectTaskEntity task) {
if (task.getPlanStartDate() != null && task.getPlanEndDate() != null) {
return (int) ChronoUnit.DAYS.between(task.getPlanStartDate(), task.getPlanEndDate()) + 1;
}
return 1;
}
/**
* 计算任务在时间轴上的位置
*/
private Integer calculatePosition(ProjectTaskEntity task) {
LocalDate projectStart = getProjectStartDate(task.getProjectId());
if (task.getPlanStartDate() != null && projectStart != null) {
return (int) ChronoUnit.DAYS.between(projectStart, task.getPlanStartDate());
}
return 0;
}
/**
* 验证任务时间约束
*/
private boolean validateTaskTimeConstraints(ProjectTaskEntity task,
LocalDate newStartDate,
LocalDate newEndDate) {
// 验证子任务不能早于父任务开始
if (StrUtil.isNotBlank(task.getParentTaskId())) {
ProjectTaskEntity parentTask = projectTaskMapper.selectById(task.getParentTaskId());
if (parentTask != null && newStartDate != null) {
if (newStartDate.isBefore(parentTask.getPlanStartDate())) {
return false;
}
}
}
// 验证结束时间不能早于开始时间
if (newStartDate != null && newEndDate != null) {
if (newEndDate.isBefore(newStartDate)) {
return false;
}
}
return true;
}
/**
* 更新逾期状态
*/
private void updateOverdueStatus(ProjectTaskEntity task) {
if (task.getPlanEndDate() != null &&
task.getPlanEndDate().isBefore(LocalDate.now()) &&
task.getStatus() != 2) {
task.setOverdueStatus(1); // 标记为逾期
} else {
task.setOverdueStatus(0);
}
}
}

View File

@ -94,5 +94,20 @@
</set>
where file_id = #{fileId}
</update>
<!-- &lt;!&ndash; 批量更新文件路径 &ndash;&gt;-->
<!-- <update id="updateFilePathByFolderId">-->
<!-- update business_data_part_file-->
<!-- set file_path = concat(#{newFolderPath}, #{separator}, file_name)-->
<!-- where folder_id = #{folderId}-->
<!-- </update>-->
<!-- 批量更新子文件夹下文件的路径 -->
<update id="updateSubFilePaths">
update business_data_part_file
set file_path = replace(file_path, #{oldParentPath1}, #{newParentPath1})
where file_path like concat(#{oldParentPath2}, '%')
</update>
</mapper>

View File

@ -51,5 +51,14 @@
</if>
</where>
</select>
<!-- 批量更新子文件夹路径 -->
<update id="updateSubFolderPaths">
update business_data_part
set folder_path = replace(folder_path, #{oldParentPath1}, #{newParentPath1}),
update_time = now()
where folder_path like concat(#{processedOldParentPath}, '%')
</update>
</mapper>

View File

@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import com.dite.znpt.domain.vo.ReceiptRequest;
/**
* @author Bear.G
@ -139,4 +140,15 @@ public class EquipmentController {
public Result<?> getProcurementStats(){
return Result.ok(equipmentService.getProcurementStats());
}
@ApiOperation(value = "确认收货并自动入库", httpMethod = "POST")
@PostMapping("/procurement/receipt/{equipmentId}")
public Result<?> receiveGoods(@PathVariable String equipmentId, @Validated @RequestBody ReceiptRequest req) {
log.info("=== 设备收货接口被调用 ===");
log.info("设备ID: {}", equipmentId);
log.info("收货数据: {}", req);
equipmentService.receiveGoodsAndStockIn(equipmentId, req);
return Result.ok("收货成功,设备已自动入库");
}
}

View File

@ -0,0 +1,61 @@
package com.dite.znpt.web.controller;
import com.dite.znpt.domain.Result;
import com.dite.znpt.domain.vo.GanttChartReq;
import com.dite.znpt.domain.vo.GanttChartResp;
import com.dite.znpt.service.GanttChartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 甘特图控制器
*/
@Api(tags = "甘特图管理")
@RestController
@RequestMapping("/gantt-chart")
public class GanttChartController {
@Resource
private GanttChartService ganttChartService;
@ApiOperation(value = "获取项目甘特图数据", httpMethod = "GET")
@GetMapping("/data")
public Result<List<GanttChartResp>> getGanttChartData(GanttChartReq req) {
List<GanttChartResp> data = ganttChartService.getGanttChartData(req);
return Result.ok(data);
}
@ApiOperation(value = "获取项目甘特图统计信息", httpMethod = "GET")
@GetMapping("/statistics/{projectId}")
public Result<Object> getGanttChartStatistics(@PathVariable String projectId) {
Object statistics = ganttChartService.getGanttChartStatistics(projectId);
return Result.ok(statistics);
}
@ApiOperation(value = "获取项目甘特图时间轴信息", httpMethod = "GET")
@GetMapping("/timeline/{projectId}")
public Result<Object> getGanttChartTimeline(@PathVariable String projectId) {
Object timeline = ganttChartService.getGanttChartTimeline(projectId);
return Result.ok(timeline);
}
@ApiOperation(value = "更新任务进度", httpMethod = "PUT")
@PutMapping("/progress/{taskId}")
public Result<Boolean> updateTaskProgress(@PathVariable String taskId, @RequestParam Integer progress) {
boolean success = ganttChartService.updateTaskProgress(taskId, progress);
return Result.ok(success);
}
@ApiOperation(value = "拖拽更新任务时间", httpMethod = "PUT")
@PutMapping("/time/{taskId}")
public Result<Boolean> updateTaskTime(@PathVariable String taskId,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
boolean success = ganttChartService.updateTaskTime(taskId, startDate, endDate);
return Result.ok(success);
}
}

View File

@ -14,7 +14,7 @@ spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://39.99.201.243:3306/znpt_dev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://39.99.201.243:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: BUw8YW6%@^8q
druid: