From a50dbd89ced3253720a7b3bbc34b3a77a4210577 Mon Sep 17 00:00:00 2001 From: wangna0328 <3402195679@qq.com> Date: Mon, 4 Aug 2025 17:24:13 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=88=B6=E5=BA=A6=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/dto/RegulationTypeRequest.java | 27 +++++++++ .../domain/entity/RegulationTypeEntity.java | 59 +++++++++++++++++++ .../resources/mapper/RegulationTypeMapper.xml | 32 ++++++++++ pom.xml | 1 - 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/dite/znpt/domain/dto/RegulationTypeRequest.java create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java create mode 100644 core/src/main/resources/mapper/RegulationTypeMapper.xml diff --git a/core/src/main/java/com/dite/znpt/domain/dto/RegulationTypeRequest.java b/core/src/main/java/com/dite/znpt/domain/dto/RegulationTypeRequest.java new file mode 100644 index 0000000..0c99771 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/dto/RegulationTypeRequest.java @@ -0,0 +1,27 @@ +package com.dite.znpt.domain.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author wangna + * @date 2025/07/29 + * @Description: 制度类型请求DTO + */ +@Data +@ApiModel(value = "RegulationTypeRequest", description = "制度类型请求") +public class RegulationTypeRequest { + + @ApiModelProperty("类型名称") + private String typeName; + + @ApiModelProperty("排序顺序") + private Integer sortOrder; + + @ApiModelProperty("是否启用") + private String isEnabled; + + @ApiModelProperty("备注") + private String remark; +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java new file mode 100644 index 0000000..1057ff8 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java @@ -0,0 +1,59 @@ +package com.dite.znpt.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import com.alibaba.excel.annotation.ExcelProperty; + +import java.io.Serializable; + +/** + * @author wangna + * @date 2025/07/29 + * @Description: 制度类型实体类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("regulation_type") +@ApiModel(value="RegulationTypeEntity对象", description="制度类型") +public class RegulationTypeEntity extends AuditableEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @ExcelProperty("类型ID") + @ApiModelProperty("类型ID") + @TableId(value = "type_id", type = IdType.ASSIGN_UUID) + private String typeId; + + @ExcelProperty("类型名称") + @ApiModelProperty("类型名称") + @TableField("type_name") + private String typeName; + + @ExcelProperty("状态") + @ApiModelProperty("状态:ENABLED-启用,DISABLED-禁用") + @TableField("status") + private String status; + + @ExcelProperty("排序顺序") + @ApiModelProperty("排序顺序") + @TableField("sort_order") + private Integer sortOrder; + + @ExcelProperty("备注") + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + @ExcelProperty("删除标志(0代表存在,1代表删除)") + @ApiModelProperty("删除标志(0代表存在,1代表删除)") + @TableField("del_flag") + private String delFlag; + + @TableField(exist = false) + @ApiModelProperty("创建人姓名") + private String createByName; +} \ No newline at end of file diff --git a/core/src/main/resources/mapper/RegulationTypeMapper.xml b/core/src/main/resources/mapper/RegulationTypeMapper.xml new file mode 100644 index 0000000..7ce5f6b --- /dev/null +++ b/core/src/main/resources/mapper/RegulationTypeMapper.xml @@ -0,0 +1,32 @@ + + + + + + rt.type_id, rt.type_name, rt.status, rt.sort_order, rt.remark, rt.del_flag, + rt.create_by, rt.create_time, rt.update_by, rt.update_time + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index fcdc462..4991235 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,6 @@ core - sip web flowable From 6ceda40d18a749e09c1c676a1c9d22fe40fe7c13 Mon Sep 17 00:00:00 2001 From: wangna0328 <3402195679@qq.com> Date: Mon, 4 Aug 2025 19:32:38 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=A6=82=E5=88=9D?= =?UTF-8?q?=EF=BC=8C=E4=BD=86=E5=8F=91=E7=8E=B0=E5=85=B6=E4=BB=96bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../znpt/domain/entity/RegulationEntity.java | 6 +++- .../domain/entity/RegulationTypeEntity.java | 10 +++--- .../znpt/service/RegulationTypeService.java | 2 +- .../service/impl/RegulationServiceImpl.java | 5 +++ .../impl/RegulationTypeServiceImpl.java | 2 +- .../resources/mapper/RegulationMapper.xml | 32 +++++++++++++++++++ .../resources/mapper/RegulationTypeMapper.xml | 10 +++--- 7 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 core/src/main/resources/mapper/RegulationMapper.xml diff --git a/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java index 25f144f..6dd36f1 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java @@ -42,7 +42,7 @@ public class RegulationEntity extends AuditableEntity implements Serializable { @ExcelProperty("制度类型") @ApiModelProperty("制度类型") @TableField("regulation_type") - private String regulationType; + private String type; @ExcelProperty("制度状态") @ApiModelProperty("制度状态:DRAFT-草案,APPROVED-已公示,PUBLISHED-已发布,ARCHIVED-已归档") @@ -96,4 +96,8 @@ public class RegulationEntity extends AuditableEntity implements Serializable { @TableField(exist = false) @ApiModelProperty("创建人姓名") private String createByName; + + @TableField(exist = false) + @ApiModelProperty("制度类型") + private String regulationType; } \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java index 1057ff8..eb8154c 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/RegulationTypeEntity.java @@ -33,10 +33,10 @@ public class RegulationTypeEntity extends AuditableEntity implements Serializabl @TableField("type_name") private String typeName; - @ExcelProperty("状态") - @ApiModelProperty("状态:ENABLED-启用,DISABLED-禁用") - @TableField("status") - private String status; + @ExcelProperty("是否启用") + @ApiModelProperty("是否启用(1-启用,0-禁用)") + @TableField("is_enabled") + private String isEnabled; @ExcelProperty("排序顺序") @ApiModelProperty("排序顺序") @@ -55,5 +55,5 @@ public class RegulationTypeEntity extends AuditableEntity implements Serializabl @TableField(exist = false) @ApiModelProperty("创建人姓名") - private String createByName; + private String createrName; } \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/RegulationTypeService.java b/core/src/main/java/com/dite/znpt/service/RegulationTypeService.java index c5b8715..e221f60 100644 --- a/core/src/main/java/com/dite/znpt/service/RegulationTypeService.java +++ b/core/src/main/java/com/dite/znpt/service/RegulationTypeService.java @@ -16,7 +16,7 @@ public interface RegulationTypeService extends IService { * @param page 页码 * @param size 页大小 * @param typeName 类型名称 - * @param status 状态 + * @param status 是否启用(1-启用,0-禁用) * @param remark 备注 * @return 结果 */ diff --git a/core/src/main/java/com/dite/znpt/service/impl/RegulationServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/RegulationServiceImpl.java index cf52e5f..f0cb608 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/RegulationServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/RegulationServiceImpl.java @@ -203,6 +203,11 @@ public class RegulationServiceImpl extends ServiceImpl() - .eq(RegulationEntity::getRegulationType, typeName) + .eq(RegulationEntity::getType, typeName) .eq(RegulationEntity::getDelFlag, "0") ); // 如果有制度使用此类型,则不允许删除 diff --git a/core/src/main/resources/mapper/RegulationMapper.xml b/core/src/main/resources/mapper/RegulationMapper.xml new file mode 100644 index 0000000..d3b6177 --- /dev/null +++ b/core/src/main/resources/mapper/RegulationMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/core/src/main/resources/mapper/RegulationTypeMapper.xml b/core/src/main/resources/mapper/RegulationTypeMapper.xml index 7ce5f6b..53211ec 100644 --- a/core/src/main/resources/mapper/RegulationTypeMapper.xml +++ b/core/src/main/resources/mapper/RegulationTypeMapper.xml @@ -3,24 +3,24 @@ - rt.type_id, rt.type_name, rt.status, rt.sort_order, rt.remark, rt.del_flag, + rt.type_id, rt.type_name, rt.is_enabled, rt.sort_order, rt.remark, rt.del_flag, rt.create_by, rt.create_time, rt.update_by, rt.update_time diff --git a/web/src/main/java/com/dite/znpt/web/controller/RegulationController.java b/web/src/main/java/com/dite/znpt/web/controller/RegulationController.java index c27dc10..19e770b 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/RegulationController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/RegulationController.java @@ -28,8 +28,9 @@ public class RegulationController { @RequestParam(required = false) String status, @RequestParam(required = false) String type, @RequestParam(required = false) String title, - @RequestParam(required = false) String proposer) { - return regulationService.getRegulationList(page, size, status, type, title, proposer); + @RequestParam(required = false) String proposer, + @RequestParam(required = false) String confirmStatus) { + return regulationService.getRegulationList(page, size, status, type, title, proposer, confirmStatus); } @ApiOperation(value = "创建制度提案", httpMethod = "POST") From ec5cca1dabf65f09b66721c6ec9680e1e92098cb Mon Sep 17 00:00:00 2001 From: wangna0328 <3402195679@qq.com> Date: Tue, 5 Aug 2025 11:00:08 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E5=88=9D=E6=AD=A5=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=BF=98=E6=9C=AA?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dite/znpt/config/Schedule.java | 14 + .../com/dite/znpt/config/WebMvcConfig.java | 12 + .../entity/AutoExpirationConfigEntity.java | 84 +++++ .../domain/entity/ExpirationResultEntity.java | 85 +++++ .../mapper/AutoExpirationConfigMapper.java | 14 + .../znpt/mapper/ExpirationResultMapper.java | 14 + .../znpt/service/AutoExpirationService.java | 61 ++++ .../impl/AutoExpirationServiceImpl.java | 341 ++++++++++++++++++ .../service/job/AutoExpirationJobService.java | 14 + .../impl/AutoExpirationJobServiceImpl.java | 31 ++ doc/auto_expiration_readme.md | 138 +++++++ doc/auto_expiration_tables.sql | 51 +++ web/pom.xml | 10 +- .../controller/AutoExpirationController.java | 178 +++++++++ .../web/controller/EquipmentController.java | 1 + web/src/main/resources/application.yml | 1 + web/src/main/resources/logback.xml | 8 +- 17 files changed, 1048 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java create mode 100644 core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java create mode 100644 core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java create mode 100644 core/src/main/java/com/dite/znpt/service/AutoExpirationService.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java create mode 100644 core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java create mode 100644 core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java create mode 100644 doc/auto_expiration_readme.md create mode 100644 doc/auto_expiration_tables.sql create mode 100644 web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java diff --git a/core/src/main/java/com/dite/znpt/config/Schedule.java b/core/src/main/java/com/dite/znpt/config/Schedule.java index 6e38623..6b3c65a 100644 --- a/core/src/main/java/com/dite/znpt/config/Schedule.java +++ b/core/src/main/java/com/dite/znpt/config/Schedule.java @@ -7,6 +7,7 @@ import com.dite.znpt.domain.vo.PartResp; import com.dite.znpt.enums.FilePathEnum; import com.dite.znpt.service.ImageService; import com.dite.znpt.service.PartService; +import com.dite.znpt.service.job.AutoExpirationJobService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -26,6 +27,7 @@ public class Schedule { private final ImageService imageService; private final PartService partService; private final ExtUtilConfig extUtilConfig; + private final AutoExpirationJobService autoExpirationJobService; /** * 功能描述:图像预处理,持续运行 @@ -67,4 +69,16 @@ public class Schedule { } imageService.updateBatchById(successList); } + + /** + * 功能描述:自动到期检测,每天凌晨2点执行 + * + * @author System + * @date 2025/1/1 + **/ + @Scheduled(cron = "0 0 2 * * ?") + public void autoExpirationCheck() { + log.info("开始执行自动到期检测定时任务..."); + autoExpirationJobService.executeAutoExpirationJob(); + } } diff --git a/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java b/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java index c09ff82..718f7ff 100644 --- a/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java +++ b/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java @@ -2,10 +2,14 @@ package com.dite.znpt.config; import cn.hutool.core.collection.ListUtil; import com.dite.znpt.enums.FilePathEnum; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -28,6 +32,14 @@ public class WebMvcConfig implements WebMvcConfigurer { } } + /** + * 配置HTTP消息转换器,确保UTF-8编码 + */ + @Override + public void configureMessageConverters(List> converters) { + converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); + } + // @Override // public void addInterceptors(InterceptorRegistry registry) { // // 注册 Sa-Token 拦截器,定义详细认证规则 diff --git a/core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java new file mode 100644 index 0000000..7d9f608 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java @@ -0,0 +1,84 @@ +package com.dite.znpt.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测配置实体 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("auto_expiration_config") +@ApiModel(value="AutoExpirationConfigEntity对象", description="自动到期检测配置") +public class AutoExpirationConfigEntity extends AuditableEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ApiModelProperty("配置ID") + @TableId(value = "config_id", type = IdType.ASSIGN_UUID) + private String configId; + + @ApiModelProperty("表名") + @TableField("table_name") + private String tableName; + + @ApiModelProperty("表描述") + @TableField("table_desc") + private String tableDesc; + + @ApiModelProperty("主键字段") + @TableField("primary_key_field") + private String primaryKeyField; + + @ApiModelProperty("到期时间字段") + @TableField("expire_date_field") + private String expireDateField; + + @ApiModelProperty("关联用户字段") + @TableField("user_id_field") + private String userIdField; + + @ApiModelProperty("用户姓名字段") + @TableField("user_name_field") + private String userNameField; + + @ApiModelProperty("业务名称字段") + @TableField("business_name_field") + private String businessNameField; + + @ApiModelProperty("提前提醒天数") + @TableField("remind_days") + private Integer remindDays; + + @ApiModelProperty("是否启用(0-禁用,1-启用)") + @TableField("enabled") + private String enabled; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + @ApiModelProperty("删除标志(0代表存在,1代表删除)") + @TableField("del_flag") + private String delFlag; + + /** + * 保存前处理 + */ + public void preSave() { + // 可以在这里添加保存前的逻辑处理 + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java new file mode 100644 index 0000000..fb2fc23 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java @@ -0,0 +1,85 @@ +package com.dite.znpt.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @author System + * @date 2025/1/1 + * @description 到期检测结果实体 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("expiration_result") +@ApiModel(value="ExpirationResultEntity对象", description="到期检测结果") +public class ExpirationResultEntity extends AuditableEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ApiModelProperty("结果ID") + @TableId(value = "result_id", type = IdType.ASSIGN_UUID) + private String resultId; + + @ApiModelProperty("配置ID") + @TableField("config_id") + private String configId; + + @ApiModelProperty("表名") + @TableField("table_name") + private String tableName; + + @ApiModelProperty("业务ID") + @TableField("business_id") + private String businessId; + + @ApiModelProperty("业务名称") + @TableField("business_name") + private String businessName; + + @ApiModelProperty("关联用户ID") + @TableField("user_id") + private String userId; + + @ApiModelProperty("关联用户姓名") + @TableField("user_name") + private String userName; + + @ApiModelProperty("到期日期") + @TableField("expire_date") + private LocalDate expireDate; + + @ApiModelProperty("剩余天数") + @TableField("remaining_days") + private Integer remainingDays; + + @ApiModelProperty("状态(0-正常,1-即将到期,2-已到期)") + @TableField("status") + private String status; + + @ApiModelProperty("检测时间") + @TableField("check_time") + private java.time.LocalDateTime checkTime; + + @ApiModelProperty("删除标志(0代表存在,1代表删除)") + @TableField("del_flag") + private String delFlag; + + /** + * 保存前处理 + */ + public void preSave() { + // 可以在这里添加保存前的逻辑处理 + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java b/core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java new file mode 100644 index 0000000..5ea8531 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java @@ -0,0 +1,14 @@ +package com.dite.znpt.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测配置Mapper + */ +@Mapper +public interface AutoExpirationConfigMapper extends BaseMapper { +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java b/core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java new file mode 100644 index 0000000..b6d3991 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java @@ -0,0 +1,14 @@ +package com.dite.znpt.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.domain.entity.ExpirationResultEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author System + * @date 2025/1/1 + * @description 到期检测结果Mapper + */ +@Mapper +public interface ExpirationResultMapper extends BaseMapper { +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/AutoExpirationService.java b/core/src/main/java/com/dite/znpt/service/AutoExpirationService.java new file mode 100644 index 0000000..35d2b76 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/AutoExpirationService.java @@ -0,0 +1,61 @@ +package com.dite.znpt.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import com.dite.znpt.domain.entity.ExpirationResultEntity; + +import java.util.List; +import java.util.Map; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测服务接口 + */ +public interface AutoExpirationService extends IService { + + /** + * 执行自动到期检测 + */ + void executeAutoExpirationCheck(); + + /** + * 获取到期检测结果 + * @param status 状态筛选 + * @return 检测结果列表 + */ + List getExpirationResults(String status); + + /** + * 获取统计信息 + * @return 统计信息 + */ + Map getStatistics(); + + /** + * 手动检测指定表 + * @param tableName 表名 + */ + void checkTableExpiration(String tableName); + + /** + * 检查表是否已配置到期检测 + * @param tableName 表名 + * @return 配置信息,如果未配置返回null + */ + AutoExpirationConfigEntity getTableConfig(String tableName); + + /** + * 批量检查多个表的配置状态 + * @param tableNames 表名列表 + * @return 配置状态映射 + */ + Map getTablesConfigStatus(List tableNames); + + /** + * 智能配置表检测 + * @param tableName 表名 + * @return 配置结果 + */ + AutoExpirationConfigEntity autoConfigureTable(String tableName); +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java new file mode 100644 index 0000000..ffa2ba7 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java @@ -0,0 +1,341 @@ +package com.dite.znpt.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import com.dite.znpt.domain.entity.ExpirationResultEntity; +import com.dite.znpt.mapper.AutoExpirationConfigMapper; +import com.dite.znpt.mapper.ExpirationResultMapper; +import com.dite.znpt.service.AutoExpirationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测服务实现类 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class AutoExpirationServiceImpl extends ServiceImpl implements AutoExpirationService { + + @Autowired + private ExpirationResultMapper expirationResultMapper; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public void executeAutoExpirationCheck() { + log.info("开始执行自动到期检测..."); + + // 获取所有启用的配置 + List configs = lambdaQuery() + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .list(); + + if (CollUtil.isEmpty(configs)) { + log.info("没有找到启用的到期检测配置"); + return; + } + + // 清空之前的检测结果 + expirationResultMapper.delete(null); + + // 逐个检测每个配置的表 + for (AutoExpirationConfigEntity config : configs) { + try { + checkTableExpiration(config); + } catch (Exception e) { + log.error("检测表 {} 时发生错误: {}", config.getTableName(), e.getMessage(), e); + } + } + + log.info("自动到期检测完成,共检测 {} 个表", configs.size()); + } + + @Override + public List getExpirationResults(String status) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ExpirationResultEntity::getDelFlag, "0"); + + if (StrUtil.isNotBlank(status)) { + wrapper.eq(ExpirationResultEntity::getStatus, status); + } + + wrapper.orderByDesc(ExpirationResultEntity::getExpireDate); + + return expirationResultMapper.selectList(wrapper); + } + + @Override + public Map getStatistics() { + Map statistics = new HashMap<>(); + + // 统计各状态的数量 + List allResults = expirationResultMapper.selectList(null); + + long normalCount = allResults.stream() + .filter(r -> "0".equals(r.getStatus())) + .count(); + long expiringCount = allResults.stream() + .filter(r -> "1".equals(r.getStatus())) + .count(); + long expiredCount = allResults.stream() + .filter(r -> "2".equals(r.getStatus())) + .count(); + + statistics.put("total", allResults.size()); + statistics.put("normal", normalCount); + statistics.put("expiring", expiringCount); + statistics.put("expired", expiredCount); + + return statistics; + } + + @Override + public void checkTableExpiration(String tableName) { + AutoExpirationConfigEntity config = lambdaQuery() + .eq(AutoExpirationConfigEntity::getTableName, tableName) + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .one(); + + if (config == null) { + throw new RuntimeException("未找到表 " + tableName + " 的配置信息"); + } + + checkTableExpiration(config); + } + + /** + * 检测指定配置的表 + */ + private void checkTableExpiration(AutoExpirationConfigEntity config) { + log.info("开始检测表: {}", config.getTableName()); + + // 构建查询SQL + String sql = buildQuerySql(config); + + try { + List> results = jdbcTemplate.queryForList(sql); + + for (Map row : results) { + processExpirationResult(config, row); + } + + log.info("表 {} 检测完成,共处理 {} 条记录", config.getTableName(), results.size()); + + } catch (Exception e) { + log.error("检测表 {} 时发生错误: {}", config.getTableName(), e.getMessage(), e); + } + } + + /** + * 构建查询SQL + */ + private String buildQuerySql(AutoExpirationConfigEntity config) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT "); + sql.append(config.getPrimaryKeyField()).append(" as business_id, "); + sql.append(config.getExpireDateField()).append(" as expire_date "); + + if (StrUtil.isNotBlank(config.getUserIdField())) { + sql.append(", ").append(config.getUserIdField()).append(" as user_id "); + } + if (StrUtil.isNotBlank(config.getUserNameField())) { + sql.append(", ").append(config.getUserNameField()).append(" as user_name "); + } + if (StrUtil.isNotBlank(config.getBusinessNameField())) { + sql.append(", ").append(config.getBusinessNameField()).append(" as business_name "); + } + + sql.append(" FROM ").append(config.getTableName()); + sql.append(" WHERE ").append(config.getExpireDateField()).append(" IS NOT NULL"); + + return sql.toString(); + } + + /** + * 处理到期检测结果 + */ + private void processExpirationResult(AutoExpirationConfigEntity config, Map row) { + try { + ExpirationResultEntity result = new ExpirationResultEntity(); + result.preSave(); + + result.setConfigId(config.getConfigId()); + result.setTableName(config.getTableName()); + result.setBusinessId(String.valueOf(row.get("business_id"))); + result.setBusinessName(String.valueOf(row.get("business_name"))); + result.setUserId(String.valueOf(row.get("user_id"))); + result.setUserName(String.valueOf(row.get("user_name"))); + + // 处理到期日期 + Object expireDateObj = row.get("expire_date"); + if (expireDateObj != null) { + LocalDate expireDate = parseDate(expireDateObj); + result.setExpireDate(expireDate); + + // 计算剩余天数和状态 + LocalDate today = LocalDate.now(); + long remainingDays = ChronoUnit.DAYS.between(today, expireDate); + result.setRemainingDays((int) remainingDays); + + // 设置状态 + if (remainingDays < 0) { + result.setStatus("2"); // 已到期 + } else if (remainingDays <= config.getRemindDays()) { + result.setStatus("1"); // 即将到期 + } else { + result.setStatus("0"); // 正常 + } + } + + result.setCheckTime(LocalDateTime.now()); + result.setDelFlag("0"); + + expirationResultMapper.insert(result); + + } catch (Exception e) { + log.error("处理检测结果时发生错误: {}", e.getMessage(), e); + } + } + + /** + * 解析日期 + */ + private LocalDate parseDate(Object dateObj) { + if (dateObj instanceof LocalDate) { + return (LocalDate) dateObj; + } else if (dateObj instanceof java.sql.Date) { + return ((java.sql.Date) dateObj).toLocalDate(); + } else if (dateObj instanceof java.util.Date) { + // 使用 DateUtil.toLocalDateTime 然后转换为 LocalDate + return DateUtil.toLocalDateTime((java.util.Date) dateObj).toLocalDate(); + } else { + return LocalDate.parse(dateObj.toString()); + } + } + + @Override + public AutoExpirationConfigEntity getTableConfig(String tableName) { + return lambdaQuery() + .eq(AutoExpirationConfigEntity::getTableName, tableName) + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .one(); + } + + @Override + public Map getTablesConfigStatus(List tableNames) { + Map statusMap = new HashMap<>(); + + if (CollUtil.isEmpty(tableNames)) { + return statusMap; + } + + // 查询所有已配置的表 + List configs = lambdaQuery() + .in(AutoExpirationConfigEntity::getTableName, tableNames) + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .list(); + + // 构建已配置的表名集合 + Set configuredTables = configs.stream() + .map(AutoExpirationConfigEntity::getTableName) + .collect(Collectors.toSet()); + + // 为每个表设置配置状态 + for (String tableName : tableNames) { + statusMap.put(tableName, configuredTables.contains(tableName)); + } + + return statusMap; + } + + @Override + public AutoExpirationConfigEntity autoConfigureTable(String tableName) { + // 检查是否已配置 + AutoExpirationConfigEntity existingConfig = getTableConfig(tableName); + if (existingConfig != null) { + log.info("表 {} 已存在配置", tableName); + return existingConfig; + } + + // 根据表名生成默认配置 + AutoExpirationConfigEntity config = generateDefaultConfig(tableName); + + try { + config.preSave(); + save(config); + log.info("为表 {} 自动创建配置", tableName); + return config; + } catch (Exception e) { + log.error("自动配置表 {} 失败: {}", tableName, e.getMessage(), e); + throw new RuntimeException("自动配置失败: " + e.getMessage()); + } + } + + /** + * 根据表名生成默认配置 + */ + private AutoExpirationConfigEntity generateDefaultConfig(String tableName) { + AutoExpirationConfigEntity config = new AutoExpirationConfigEntity(); + config.setConfigId("config-" + System.currentTimeMillis()); + config.setTableName(tableName); + config.setEnabled("1"); + config.setDelFlag("0"); + config.setRemindDays(30); + config.setRemark("自动配置的到期检测"); + + // 根据表名设置默认字段 + switch (tableName) { + case "certification": + config.setTableDesc("证书到期检测"); + config.setPrimaryKeyField("certification_id"); + config.setExpireDateField("validity_date_end"); + config.setUserIdField("user_id"); + config.setBusinessNameField("certification_name"); + break; + case "insurance_info": + config.setTableDesc("保险到期检测"); + config.setPrimaryKeyField("insurance_id"); + config.setExpireDateField("expire_date"); + config.setUserIdField("user_id"); + config.setBusinessNameField("insurance_name"); + break; + case "regulation": + config.setTableDesc("规章制度到期检测"); + config.setPrimaryKeyField("regulation_id"); + config.setExpireDateField("expire_date"); + config.setUserIdField("create_by"); + config.setBusinessNameField("regulation_name"); + break; + default: + // 通用配置 + config.setTableDesc(tableName + "到期检测"); + config.setPrimaryKeyField("id"); + config.setExpireDateField("expire_date"); + config.setUserIdField("user_id"); + config.setBusinessNameField("name"); + break; + } + + return config; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java b/core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java new file mode 100644 index 0000000..dba4ee1 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java @@ -0,0 +1,14 @@ +package com.dite.znpt.service.job; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测定时任务服务接口 + */ +public interface AutoExpirationJobService { + + /** + * 执行自动到期检测任务 + */ + void executeAutoExpirationJob(); +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java b/core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java new file mode 100644 index 0000000..a0bd57b --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java @@ -0,0 +1,31 @@ +package com.dite.znpt.service.job.impl; + +import com.dite.znpt.service.AutoExpirationService; +import com.dite.znpt.service.job.AutoExpirationJobService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测定时任务服务实现类 + */ +@Slf4j +@Service +public class AutoExpirationJobServiceImpl implements AutoExpirationJobService { + + @Autowired + private AutoExpirationService autoExpirationService; + + @Override + public void executeAutoExpirationJob() { + log.info("开始执行自动到期检测定时任务..."); + try { + autoExpirationService.executeAutoExpirationCheck(); + log.info("自动到期检测定时任务执行完成"); + } catch (Exception e) { + log.error("自动到期检测定时任务执行失败: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/doc/auto_expiration_readme.md b/doc/auto_expiration_readme.md new file mode 100644 index 0000000..c3a0041 --- /dev/null +++ b/doc/auto_expiration_readme.md @@ -0,0 +1,138 @@ +# 自动到期检测功能使用说明 + +## 功能概述 + +自动到期检测功能是一个智能的到期时间监控系统,能够自动检测数据库中各种表格的到期时间字段,并提供到期提醒功能。 + +## 主要特性 + +1. **自动检测**: 系统会自动扫描配置的表格,检测其中的到期时间字段 +2. **灵活配置**: 可以配置任意表格的到期检测规则 +3. **智能提醒**: 支持提前提醒天数配置 +4. **状态管理**: 自动计算剩余天数并设置状态(正常/即将到期/已到期) +5. **定时执行**: 每天凌晨2点自动执行检测任务 + +## 数据库表结构 + +### 1. 自动到期检测配置表 (auto_expiration_config) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| config_id | varchar(64) | 配置ID | +| table_name | varchar(100) | 表名 | +| table_desc | varchar(200) | 表描述 | +| primary_key_field | varchar(100) | 主键字段 | +| expire_date_field | varchar(100) | 到期时间字段 | +| user_id_field | varchar(100) | 关联用户字段 | +| user_name_field | varchar(100) | 用户姓名字段 | +| business_name_field | varchar(100) | 业务名称字段 | +| remind_days | int | 提前提醒天数 | +| enabled | varchar(1) | 是否启用(0-禁用,1-启用) | + +### 2. 到期检测结果表 (expiration_result) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| result_id | varchar(64) | 结果ID | +| table_name | varchar(100) | 表名 | +| business_id | varchar(64) | 业务ID | +| business_name | varchar(200) | 业务名称 | +| user_id | varchar(64) | 关联用户ID | +| user_name | varchar(100) | 关联用户姓名 | +| expire_date | date | 到期日期 | +| remaining_days | int | 剩余天数 | +| status | varchar(1) | 状态(0-正常,1-即将到期,2-已到期) | + +## API接口 + +### 1. 手动执行到期检测 +``` +POST /auto-expiration/execute +``` + +### 2. 获取到期检测结果 +``` +GET /auto-expiration/results?status=1 +``` +参数说明: +- status: 状态筛选(0-正常,1-即将到期,2-已到期) + +### 3. 获取统计信息 +``` +GET /auto-expiration/statistics +``` + +### 4. 检测指定表 +``` +POST /auto-expiration/check-table/{tableName} +``` + +### 5. 保存配置 +``` +POST /auto-expiration/config +``` + +### 6. 获取所有配置 +``` +GET /auto-expiration/configs +``` + +### 7. 删除配置 +``` +DELETE /auto-expiration/config/{configId} +``` + +## 配置示例 + +### 人员资质证书检测配置 +```json +{ + "tableName": "certification", + "tableDesc": "人员资质证书", + "primaryKeyField": "certification_id", + "expireDateField": "validity_date_end", + "userIdField": "user_id", + "businessNameField": "certification_name", + "remindDays": 30, + "enabled": "1" +} +``` + +### 人员保险检测配置 +```json +{ + "tableName": "insurance_info", + "tableDesc": "人员保险", + "primaryKeyField": "insurance_info_id", + "expireDateField": "expire_date", + "userIdField": "user_id", + "userNameField": "name", + "businessNameField": "insurance_type_name", + "remindDays": 30, + "enabled": "1" +} +``` + +## 使用步骤 + +1. **创建数据库表**: 执行 `auto_expiration_tables.sql` 创建必要的表结构 +2. **配置检测规则**: 通过API或直接插入数据配置需要检测的表格 +3. **启动定时任务**: 系统会自动在每天凌晨2点执行检测 +4. **查看检测结果**: 通过API接口查看检测结果和统计信息 + +## 扩展说明 + +系统支持检测任意表格的到期时间字段,只需要在配置表中添加相应的配置即可。支持的字段类型包括: +- LocalDate +- LocalDateTime +- Date +- Timestamp + +系统会自动识别并处理这些日期类型。 + +## 注意事项 + +1. 确保配置的字段名在目标表中存在 +2. 到期时间字段不能为空 +3. 建议为检测结果表添加适当的索引以提高查询性能 +4. 可以根据业务需要调整提前提醒天数 \ No newline at end of file diff --git a/doc/auto_expiration_tables.sql b/doc/auto_expiration_tables.sql new file mode 100644 index 0000000..793373c --- /dev/null +++ b/doc/auto_expiration_tables.sql @@ -0,0 +1,51 @@ +-- 自动到期检测配置表 +CREATE TABLE `auto_expiration_config` ( + `config_id` varchar(64) NOT NULL COMMENT '配置ID', + `table_name` varchar(100) NOT NULL COMMENT '表名', + `table_desc` varchar(200) DEFAULT NULL COMMENT '表描述', + `primary_key_field` varchar(100) NOT NULL COMMENT '主键字段', + `expire_date_field` varchar(100) NOT NULL COMMENT '到期时间字段', + `user_id_field` varchar(100) DEFAULT NULL COMMENT '关联用户字段', + `user_name_field` varchar(100) DEFAULT NULL COMMENT '用户姓名字段', + `business_name_field` varchar(100) DEFAULT NULL COMMENT '业务名称字段', + `remind_days` int(11) DEFAULT 30 COMMENT '提前提醒天数', + `enabled` varchar(1) DEFAULT '1' COMMENT '是否启用(0-禁用,1-启用)', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', + `del_flag` varchar(1) DEFAULT '0' COMMENT '删除标志(0代表存在,1代表删除)', + PRIMARY KEY (`config_id`), + UNIQUE KEY `uk_table_name` (`table_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自动到期检测配置表'; + +-- 到期检测结果表 +CREATE TABLE `expiration_result` ( + `result_id` varchar(64) NOT NULL COMMENT '结果ID', + `config_id` varchar(64) DEFAULT NULL COMMENT '配置ID', + `table_name` varchar(100) NOT NULL COMMENT '表名', + `business_id` varchar(64) NOT NULL COMMENT '业务ID', + `business_name` varchar(200) DEFAULT NULL COMMENT '业务名称', + `user_id` varchar(64) DEFAULT NULL COMMENT '关联用户ID', + `user_name` varchar(100) DEFAULT NULL COMMENT '关联用户姓名', + `expire_date` date NOT NULL COMMENT '到期日期', + `remaining_days` int(11) DEFAULT NULL COMMENT '剩余天数', + `status` varchar(1) DEFAULT '0' COMMENT '状态(0-正常,1-即将到期,2-已到期)', + `check_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '检测时间', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', + `del_flag` varchar(1) DEFAULT '0' COMMENT '删除标志(0代表存在,1代表删除)', + PRIMARY KEY (`result_id`), + KEY `idx_table_name` (`table_name`), + KEY `idx_status` (`status`), + KEY `idx_expire_date` (`expire_date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='到期检测结果表'; + +-- 插入示例配置数据 +INSERT INTO `auto_expiration_config` (`config_id`, `table_name`, `table_desc`, `primary_key_field`, `expire_date_field`, `user_id_field`, `user_name_field`, `business_name_field`, `remind_days`, `enabled`, `remark`) VALUES +('1', 'certification', '人员资质证书', 'certification_id', 'validity_date_end', 'user_id', NULL, 'certification_name', 30, '1', '人员资质证书到期检测'), +('2', 'insurance_info', '人员保险', 'insurance_info_id', 'expire_date', 'user_id', 'name', 'insurance_type_name', 30, '1', '人员保险到期检测'), +('3', 'regulation', '制度管理', 'regulation_id', 'expire_time', NULL, NULL, 'regulation_name', 30, '1', '制度到期检测'); \ No newline at end of file diff --git a/web/pom.xml b/web/pom.xml index c6d6423..4dd81fc 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -20,11 +20,11 @@ - - com.dite.znpt - sip - 1.0.0-SNAPSHOT - + + + + + com.dite.znpt core diff --git a/web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java b/web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java new file mode 100644 index 0000000..9b27a29 --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java @@ -0,0 +1,178 @@ +package com.dite.znpt.web.controller; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import com.dite.znpt.domain.entity.ExpirationResultEntity; +import com.dite.znpt.service.AutoExpirationService; +import com.dite.znpt.service.job.AutoExpirationJobService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * @author wangna + * @date 2025/08/05 + * @description 自动到期检测控制器 + */ +@Slf4j +@RestController +@RequestMapping("/auto-expiration") +@Api(tags = "自动到期检测") +public class AutoExpirationController { + + @Autowired + private AutoExpirationService autoExpirationService; + + @Autowired + private AutoExpirationJobService autoExpirationJobService; + + @PostMapping("/execute") + @ApiOperation("手动执行到期检测") + public Result executeExpirationCheck() { + try { + autoExpirationService.executeAutoExpirationCheck(); + return Result.okM("到期检测执行成功"); + } catch (Exception e) { + log.error("执行到期检测失败: {}", e.getMessage(), e); + return Result.error("执行到期检测失败: " + e.getMessage()); + } + } + + @GetMapping("/results") + @ApiOperation("获取到期检测结果") + public Result> getExpirationResults( + @ApiParam("状态筛选:0-正常,1-即将到期,2-已到期") @RequestParam(required = false) String status) { + try { + List results = autoExpirationService.getExpirationResults(status); + return Result.ok(results); + } catch (Exception e) { + log.error("获取到期检测结果失败: {}", e.getMessage(), e); + return Result.error("获取到期检测结果失败: " + e.getMessage()); + } + } + + @GetMapping("/statistics") + @ApiOperation("获取统计信息") + public Result> getStatistics() { + try { + Map statistics = autoExpirationService.getStatistics(); + return Result.ok(statistics); + } catch (Exception e) { + log.error("获取统计信息失败: {}", e.getMessage(), e); + return Result.error("获取统计信息失败: " + e.getMessage()); + } + } + + @PostMapping("/check-table/{tableName}") + @ApiOperation("检测指定表") + public Result checkTableExpiration(@PathVariable String tableName) { + try { + autoExpirationService.checkTableExpiration(tableName); + return Result.okM("表 " + tableName + " 检测完成"); + } catch (Exception e) { + log.error("检测表 {} 失败: {}", tableName, e.getMessage(), e); + return Result.error("检测表失败: " + e.getMessage()); + } + } + + @PostMapping("/config") + @ApiOperation("保存配置") + public Result saveConfig(@RequestBody AutoExpirationConfigEntity config) { + try { + // 检查是否已存在相同表的配置 + AutoExpirationConfigEntity existingConfig = autoExpirationService.lambdaQuery() + .eq(AutoExpirationConfigEntity::getTableName, config.getTableName()) + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .one(); + + if (existingConfig != null) { + // 如果存在,更新配置 + config.setConfigId(existingConfig.getConfigId()); + config.setCreateTime(existingConfig.getCreateTime()); + config.setCreateBy(existingConfig.getCreateBy()); + log.info("更新已存在的配置: {}", config.getTableName()); + } else { + // 如果不存在,生成新的配置ID + config.setConfigId("config-" + System.currentTimeMillis()); + log.info("创建新配置: {}", config.getTableName()); + } + + config.preSave(); + autoExpirationService.saveOrUpdate(config); + return Result.ok(config); + } catch (Exception e) { + log.error("保存配置失败: {}", e.getMessage(), e); + return Result.error("保存配置失败: " + e.getMessage()); + } + } + + @GetMapping("/configs") + @ApiOperation("获取所有配置") + public Result> getConfigs() { + try { + List configs = autoExpirationService.list(); + return Result.ok(configs); + } catch (Exception e) { + log.error("获取配置失败: {}", e.getMessage(), e); + return Result.error("获取配置失败: " + e.getMessage()); + } + } + + @DeleteMapping("/config/{configId}") + @ApiOperation("删除配置") + public Result deleteConfig(@PathVariable String configId) { + try { + autoExpirationService.removeById(configId); + return Result.okM("配置删除成功"); + } catch (Exception e) { + log.error("删除配置失败: {}", e.getMessage(), e); + return Result.error("删除配置失败: " + e.getMessage()); + } + } + + @GetMapping("/check-table/{tableName}") + @ApiOperation("检查表是否已配置到期检测") + public Result checkTableConfig(@PathVariable String tableName) { + try { + AutoExpirationConfigEntity config = autoExpirationService.getTableConfig(tableName); + if (config != null) { + return Result.ok(config); + } else { + return Result.ok(null); + } + } catch (Exception e) { + log.error("检查表配置失败: {}", e.getMessage(), e); + return Result.error("检查表配置失败: " + e.getMessage()); + } + } + + @PostMapping("/check-tables") + @ApiOperation("批量检查多个表的配置状态") + public Result> checkTablesConfig(@RequestBody List tableNames) { + try { + Map statusMap = autoExpirationService.getTablesConfigStatus(tableNames); + return Result.ok(statusMap); + } catch (Exception e) { + log.error("批量检查表配置失败: {}", e.getMessage(), e); + return Result.error("批量检查表配置失败: " + e.getMessage()); + } + } + + @PostMapping("/auto-config/{tableName}") + @ApiOperation("智能配置表检测") + public Result autoConfigureTable(@PathVariable String tableName) { + try { + AutoExpirationConfigEntity config = autoExpirationService.autoConfigureTable(tableName); + return Result.ok(config); + } catch (Exception e) { + log.error("智能配置表失败: {}", e.getMessage(), e); + return Result.error("智能配置表失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java b/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java index b3318b5..238e447 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java @@ -65,4 +65,5 @@ public class EquipmentController { equipmentService.deleteById(equipmentId); return Result.ok(); } + } diff --git a/web/src/main/resources/application.yml b/web/src/main/resources/application.yml index 694c057..7adbb7d 100644 --- a/web/src/main/resources/application.yml +++ b/web/src/main/resources/application.yml @@ -22,6 +22,7 @@ spring: allow-circular-references: true allow-bean-definition-overriding: true + # flowable相关表 flowable: # true 会对数据库中所有表进行更新操作。如果表不存在,则自动创建(建议开发时使用) diff --git a/web/src/main/resources/logback.xml b/web/src/main/resources/logback.xml index 5f99223..c99788b 100644 --- a/web/src/main/resources/logback.xml +++ b/web/src/main/resources/logback.xml @@ -7,7 +7,7 @@ - + ${log.pattern} @@ -24,7 +24,7 @@ 10mb 200mb - + ${log.pattern} @@ -48,7 +48,7 @@ 10mb 300mb - + ${log.pattern} @@ -72,7 +72,7 @@ 10mb 500mb - + ${log.pattern} From ade816ab3f8daf7b307b59e8d86146c96dfe5427 Mon Sep 17 00:00:00 2001 From: wangna0328 <3402195679@qq.com> Date: Tue, 5 Aug 2025 20:31:52 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=83=A8=E5=88=86=E9=87=8D=E6=96=B0=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../znpt/domain/entity/ProjectEntity.java | 17 +- .../domain/entity/ProjectMemberEntity.java | 99 ++++ .../znpt/domain/vo/ProjectDetailResp.java | 180 ++++++++ .../znpt/domain/vo/ProjectKanbanDataResp.java | 107 +++++ .../domain/vo/ProjectKanbanStatsResp.java | 81 ++++ .../znpt/domain/vo/ProjectMemberListReq.java | 45 ++ .../dite/znpt/domain/vo/ProjectMemberReq.java | 62 +++ .../znpt/domain/vo/ProjectMemberResp.java | 83 ++++ .../dite/znpt/enums/ProjectJobCodeEnum.java | 72 +++ .../dite/znpt/enums/ProjectRoleTypeEnum.java | 53 +++ .../dite/znpt/mapper/ProjectMemberMapper.java | 54 +++ .../znpt/service/ProjectMemberService.java | 107 +++++ .../impl/ProjectMemberServiceImpl.java | 431 ++++++++++++++++++ .../resources/mapper/ProjectMemberMapper.xml | 314 +++++++++++++ doc/project_member_extended_data.sql | 107 +++++ doc/project_member_tables.sql | 137 ++++++ doc/project_member_test_data.sql | 53 +++ .../controller/ProjectMemberController.java | 153 +++++++ web/src/main/resources/application-dev.yml | 5 + 19 files changed, 2155 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/ProjectMemberEntity.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanStatsResp.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberListReq.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberReq.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberResp.java create mode 100644 core/src/main/java/com/dite/znpt/enums/ProjectJobCodeEnum.java create mode 100644 core/src/main/java/com/dite/znpt/enums/ProjectRoleTypeEnum.java create mode 100644 core/src/main/java/com/dite/znpt/mapper/ProjectMemberMapper.java create mode 100644 core/src/main/java/com/dite/znpt/service/ProjectMemberService.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java create mode 100644 core/src/main/resources/mapper/ProjectMemberMapper.xml create mode 100644 doc/project_member_extended_data.sql create mode 100644 doc/project_member_tables.sql create mode 100644 doc/project_member_test_data.sql create mode 100644 web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java index 0060dc2..718558f 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java @@ -91,24 +91,31 @@ public class ProjectEntity extends AuditableEntity implements Serializable { @TableField("turbine_model") private String turbineModel; - @ApiModelProperty("施工人员id") + // 人员管理已迁移到 project_member 表 + // 以下字段保留用于向后兼容,但建议使用 ProjectMemberService 进行人员管理 + @ApiModelProperty("施工人员id(已废弃,请使用ProjectMemberService)") @TableField("constructor_ids") + @Deprecated private String constructorIds; - @ApiModelProperty("安全员id") + @ApiModelProperty("安全员id(已废弃,请使用ProjectMemberService)") @TableField("auditor_id") + @Deprecated private String auditorId; - @ApiModelProperty("质量员id") + @ApiModelProperty("质量员id(已废弃,请使用ProjectMemberService)") @TableField("quality_officer_id") + @Deprecated private String qualityOfficerId; - @ApiModelProperty("项目经理id") + @ApiModelProperty("项目经理id(已废弃,请使用ProjectMemberService)") @TableField("project_manager_id") + @Deprecated private String projectManagerId; - @ApiModelProperty("施工组长id") + @ApiModelProperty("施工组长id(已废弃,请使用ProjectMemberService)") @TableField("construct_team_leader_id") + @Deprecated private String constructTeamLeaderId; @ApiModelProperty("技术方案图片,多个用逗号隔开") diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ProjectMemberEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ProjectMemberEntity.java new file mode 100644 index 0000000..d3cc17a --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/ProjectMemberEntity.java @@ -0,0 +1,99 @@ +package com.dite.znpt.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员关联表实体类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("project_member") +@ApiModel(value="ProjectMemberEntity对象", description="项目人员关联表") +public class ProjectMemberEntity extends AuditableEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ApiModelProperty("关联ID") + @TableId(value = "member_id", type = IdType.ASSIGN_UUID) + private String memberId; + + @ApiModelProperty("项目ID") + @TableField("project_id") + private String projectId; + + @ApiModelProperty("机组ID(可选,关联到具体机组)") + @TableField("turbine_id") + private String turbineId; + + @ApiModelProperty("任务组ID(可选,关联到具体任务组)") + @TableField("task_group_id") + private String taskGroupId; + + @ApiModelProperty("任务ID(可选,关联到具体任务)") + @TableField("task_id") + private String taskId; + + @ApiModelProperty("用户ID") + @TableField("user_id") + private String userId; + + @ApiModelProperty("项目角色类型:PROJECT_MANAGER-项目经理,SAFETY_OFFICER-安全员,QUALITY_OFFICER-质量员,CONSTRUCTOR-施工人员,TEAM_LEADER-施工组长") + @TableField("role_type") + private String roleType; + + @ApiModelProperty("具体岗位代码(如:GROUND_SERVICE-地勤,DRIVER-司机,ASCENDING-登高等)") + @TableField("job_code") + private String jobCode; + + @ApiModelProperty("岗位描述") + @TableField("job_desc") + private String jobDesc; + + @ApiModelProperty("加入时间") + @TableField("join_date") + private LocalDate joinDate; + + @ApiModelProperty("离开时间") + @TableField("leave_date") + private LocalDate leaveDate; + + @ApiModelProperty("状态:ACTIVE-在职,INACTIVE-离职,SUSPENDED-暂停") + @TableField("status") + private String status; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + // 非数据库字段,用于显示 + @TableField(exist = false) + @ApiModelProperty("用户姓名") + private String userName; + + @TableField(exist = false) + @ApiModelProperty("用户账号") + private String userAccount; + + @TableField(exist = false) + @ApiModelProperty("角色类型描述") + private String roleTypeDesc; + + @TableField(exist = false) + @ApiModelProperty("岗位代码描述") + private String jobCodeDesc; +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java new file mode 100644 index 0000000..2266301 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java @@ -0,0 +1,180 @@ +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; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目详情响应VO + */ +@Data +@ApiModel(value="ProjectDetailResp对象", description="项目详情响应") +public class ProjectDetailResp { + + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("项目名称") + private String projectName; + + @ApiModelProperty("项目封面") + private String coverUrl; + + @ApiModelProperty("风场名称") + private String farmName; + + @ApiModelProperty("风场地址") + private String farmAddress; + + @ApiModelProperty("委托单位") + private String client; + + @ApiModelProperty("委托单位联系人") + private String clientContact; + + @ApiModelProperty("委托单位联系电话") + private String clientPhone; + + @ApiModelProperty("检查单位") + private String inspectionUnit; + + @ApiModelProperty("检查单位联系人") + private String inspectionContact; + + @ApiModelProperty("检查单位联系电话") + private String inspectionPhone; + + @ApiModelProperty("项目规模") + private String scale; + + @ApiModelProperty("总工期(天数)") + private Integer duration; + + @ApiModelProperty("风机型号") + private String turbineModel; + + @ApiModelProperty("项目状态") + private Integer status; + + @ApiModelProperty("项目状态描述") + private String statusLabel; + + @ApiModelProperty("开始时间") + private LocalDate startDate; + + @ApiModelProperty("结束时间") + private LocalDate endDate; + + @ApiModelProperty("创建时间") + private String createTime; + + @ApiModelProperty("更新时间") + private String updateTime; + + // 项目人员信息(从新关联表获取) + @ApiModelProperty("项目人员列表") + private List projectMembers; + + // 项目机组信息 + @ApiModelProperty("项目机组列表") + private List turbines; + + // 项目任务信息 + @ApiModelProperty("项目任务列表") + private List tasks; + + // 项目预算信息 + @ApiModelProperty("项目预算列表") + private List budgets; + + // 项目日报信息 + @ApiModelProperty("项目日报列表") + private List dailyReports; + + @Data + @ApiModel(value="TurbineInfo对象", description="机组信息") + public static class TurbineInfo { + @ApiModelProperty("机组ID") + private String turbineId; + + @ApiModelProperty("机组名称") + private String turbineName; + + @ApiModelProperty("机组编码") + private String turbineCode; + + @ApiModelProperty("机组状态") + private Integer status; + + @ApiModelProperty("机组状态描述") + private String statusLabel; + } + + @Data + @ApiModel(value="TaskInfo对象", description="任务信息") + public static class TaskInfo { + @ApiModelProperty("任务ID") + private String taskId; + + @ApiModelProperty("任务名称") + private String taskName; + + @ApiModelProperty("任务状态") + private Integer status; + + @ApiModelProperty("任务状态描述") + private String statusLabel; + + @ApiModelProperty("计划开始时间") + private LocalDate planStartDate; + + @ApiModelProperty("计划结束时间") + private LocalDate planEndDate; + + @ApiModelProperty("实际开始时间") + private LocalDate actualStartDate; + + @ApiModelProperty("实际结束时间") + private LocalDate actualEndDate; + } + + @Data + @ApiModel(value="BudgetInfo对象", description="预算信息") + public static class BudgetInfo { + @ApiModelProperty("预算ID") + private String budgetId; + + @ApiModelProperty("预算名称") + private String budgetName; + + @ApiModelProperty("预算类型") + private String budgetType; + + @ApiModelProperty("预算金额(万元)") + private Double budgetAmount; + + @ApiModelProperty("预算说明") + private String budgetDesc; + } + + @Data + @ApiModel(value="DailyReportInfo对象", description="日报信息") + public static class DailyReportInfo { + @ApiModelProperty("日报ID") + private String reportId; + + @ApiModelProperty("日报日期") + private LocalDate reportDate; + + @ApiModelProperty("日报提交人") + private String submitUserName; + + @ApiModelProperty("创建时间") + private String createTime; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java new file mode 100644 index 0000000..f80c696 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java @@ -0,0 +1,107 @@ +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; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目看板数据响应VO + */ +@Data +@ApiModel(value="ProjectKanbanDataResp对象", description="项目看板数据响应") +public class ProjectKanbanDataResp { + + @ApiModelProperty("待施工项目列表") + private List pendingProjects; + + @ApiModelProperty("施工中项目列表") + private List inProgressProjects; + + @ApiModelProperty("已完工项目列表") + private List completedProjects; + + @ApiModelProperty("已审核项目列表") + private List auditedProjects; + + @ApiModelProperty("已验收项目列表") + private List acceptedProjects; + + @Data + @ApiModel(value="ProjectKanbanItem对象", description="项目看板项目项") + public static class ProjectKanbanItem { + + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("项目名称") + private String projectName; + + @ApiModelProperty("项目封面") + private String coverUrl; + + @ApiModelProperty("风场名称") + private String farmName; + + @ApiModelProperty("风场地址") + private String farmAddress; + + @ApiModelProperty("项目规模") + private String scale; + + @ApiModelProperty("总工期(天数)") + private Integer duration; + + @ApiModelProperty("风机型号") + private String turbineModel; + + @ApiModelProperty("项目状态") + private Integer status; + + @ApiModelProperty("项目状态描述") + private String statusLabel; + + @ApiModelProperty("开始时间") + private LocalDate startDate; + + @ApiModelProperty("结束时间") + private LocalDate endDate; + + @ApiModelProperty("项目经理") + private String projectManagerName; + + @ApiModelProperty("安全员") + private String safetyOfficerName; + + @ApiModelProperty("质量员") + private String qualityOfficerName; + + @ApiModelProperty("施工组长") + private String constructionTeamLeaderName; + + @ApiModelProperty("施工人员") + private String constructorNames; + + @ApiModelProperty("机组数量") + private Long turbineCount; + + @ApiModelProperty("任务数量") + private Long taskCount; + + @ApiModelProperty("已完成任务数量") + private Long completedTaskCount; + + @ApiModelProperty("项目进度百分比") + private Integer progressPercentage; + + @ApiModelProperty("创建时间") + private String createTime; + + @ApiModelProperty("更新时间") + private String updateTime; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanStatsResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanStatsResp.java new file mode 100644 index 0000000..fe2f6f0 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanStatsResp.java @@ -0,0 +1,81 @@ +package com.dite.znpt.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目看板统计数据响应VO + */ +@Data +@ApiModel(value="ProjectKanbanStatsResp对象", description="项目看板统计数据响应") +public class ProjectKanbanStatsResp { + + @ApiModelProperty("总项目数") + private Long totalProjectsCount; + + @ApiModelProperty("待施工项目数") + private Long pendingProjectCount; + + @ApiModelProperty("施工中项目数") + private Long inProgressProjectCount; + + @ApiModelProperty("已完工项目数") + private Long completedProjectCount; + + @ApiModelProperty("已审核项目数") + private Long auditedProjectCount; + + @ApiModelProperty("已验收项目数") + private Long acceptedProjectCount; + + @ApiModelProperty("总机组数") + private Long totalTurbineCount; + + @ApiModelProperty("待施工机组数") + private Long pendingTurbineCount; + + @ApiModelProperty("施工中机组数") + private Long inProgressTurbineCount; + + @ApiModelProperty("已完工机组数") + private Long completedTurbineCount; + + @ApiModelProperty("已审核机组数") + private Long auditedTurbineCount; + + @ApiModelProperty("已验收机组数") + private Long acceptedTurbineCount; + + @ApiModelProperty("总任务数") + private Long totalTaskCount; + + @ApiModelProperty("未开始任务数") + private Long pendingTaskCount; + + @ApiModelProperty("进行中任务数") + private Long inProgressTaskCount; + + @ApiModelProperty("已完成任务数") + private Long completedTaskCount; + + @ApiModelProperty("总人员数") + private Long totalMemberCount; + + @ApiModelProperty("项目经理数") + private Long projectManagerCount; + + @ApiModelProperty("安全员数") + private Long safetyOfficerCount; + + @ApiModelProperty("质量员数") + private Long qualityOfficerCount; + + @ApiModelProperty("施工人员数") + private Long constructorCount; + + @ApiModelProperty("施工组长数") + private Long teamLeaderCount; +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberListReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberListReq.java new file mode 100644 index 0000000..92eda45 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberListReq.java @@ -0,0 +1,45 @@ +package com.dite.znpt.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员查询请求VO + */ +@Data +@ApiModel(value="ProjectMemberListReq对象", description="项目人员查询请求") +public class ProjectMemberListReq { + + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("机组ID") + private String turbineId; + + @ApiModelProperty("任务组ID") + private String taskGroupId; + + @ApiModelProperty("任务ID") + private String taskId; + + @ApiModelProperty("用户ID") + private String userId; + + @ApiModelProperty("角色类型") + private String roleType; + + @ApiModelProperty("岗位代码") + private String jobCode; + + @ApiModelProperty("状态") + private String status; + + @ApiModelProperty("用户姓名(模糊查询)") + private String userName; + + @ApiModelProperty("用户账号(模糊查询)") + private String userAccount; +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberReq.java new file mode 100644 index 0000000..d2ffb17 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberReq.java @@ -0,0 +1,62 @@ +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.time.LocalDate; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员请求VO + */ +@Data +@ApiModel(value="ProjectMemberReq对象", description="项目人员请求") +public class ProjectMemberReq { + + @ApiModelProperty("关联ID(更新时必填)") + private String memberId; + + @NotBlank(message = "项目ID不能为空") + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("机组ID(可选)") + private String turbineId; + + @ApiModelProperty("任务组ID(可选)") + private String taskGroupId; + + @ApiModelProperty("任务ID(可选)") + private String taskId; + + @NotBlank(message = "用户ID不能为空") + @ApiModelProperty("用户ID") + private String userId; + + @NotBlank(message = "角色类型不能为空") + @ApiModelProperty("角色类型") + private String roleType; + + @ApiModelProperty("岗位代码") + private String jobCode; + + @ApiModelProperty("岗位描述") + private String jobDesc; + + @NotNull(message = "加入时间不能为空") + @ApiModelProperty("加入时间") + private LocalDate joinDate; + + @ApiModelProperty("离开时间") + private LocalDate leaveDate; + + @ApiModelProperty("状态") + private String status; + + @ApiModelProperty("备注") + private String remark; +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberResp.java new file mode 100644 index 0000000..a633545 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectMemberResp.java @@ -0,0 +1,83 @@ +package com.dite.znpt.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDate; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员响应VO + */ +@Data +@ApiModel(value="ProjectMemberResp对象", description="项目人员响应") +public class ProjectMemberResp { + + @ApiModelProperty("关联ID") + private String memberId; + + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("项目名称") + private String projectName; + + @ApiModelProperty("机组ID") + private String turbineId; + + @ApiModelProperty("机组名称") + private String turbineName; + + @ApiModelProperty("任务组ID") + private String taskGroupId; + + @ApiModelProperty("任务组名称") + private String taskGroupName; + + @ApiModelProperty("任务ID") + private String taskId; + + @ApiModelProperty("任务名称") + private String taskName; + + @ApiModelProperty("用户ID") + private String userId; + + @ApiModelProperty("用户姓名") + private String userName; + + @ApiModelProperty("用户账号") + private String userAccount; + + @ApiModelProperty("用户头像") + private String userAvatar; + + @ApiModelProperty("角色类型") + private String roleType; + + @ApiModelProperty("角色类型描述") + private String roleTypeDesc; + + @ApiModelProperty("岗位代码") + private String jobCode; + + @ApiModelProperty("岗位代码描述") + private String jobCodeDesc; + + @ApiModelProperty("岗位描述") + private String jobDesc; + + @ApiModelProperty("加入时间") + private LocalDate joinDate; + + @ApiModelProperty("离开时间") + private LocalDate leaveDate; + + @ApiModelProperty("状态") + private String status; + + @ApiModelProperty("备注") + private String remark; +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/enums/ProjectJobCodeEnum.java b/core/src/main/java/com/dite/znpt/enums/ProjectJobCodeEnum.java new file mode 100644 index 0000000..fd50316 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/enums/ProjectJobCodeEnum.java @@ -0,0 +1,72 @@ +package com.dite.znpt.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目岗位代码枚举 + */ +@Getter +@AllArgsConstructor +public enum ProjectJobCodeEnum { + + // 地勤相关岗位 + GROUND_SERVICE("GROUND_SERVICE", "地勤"), + GROUND_SERVICE_LEADER("GROUND_SERVICE_LEADER", "地勤组长"), + + // 司机相关岗位 + DRIVER("DRIVER", "司机"), + DRIVER_LEADER("DRIVER_LEADER", "司机组长"), + + // 登高相关岗位 + ASCENDING("ASCENDING", "登高"), + ASCENDING_LEADER("ASCENDING_LEADER", "登高组长"), + + // 防雷相关岗位 + ANTI_THUNDER("ANTI_THUNDER", "防雷"), + ANTI_THUNDER_LEADER("ANTI_THUNDER_LEADER", "防雷组长"), + + // 外部工作相关岗位 + OUT_WORK("OUT_WORK", "外部工作"), + OUT_WORK_LEADER("OUT_WORK_LEADER", "外部工作组长"), + + // 管理岗位 + PROJECT_MANAGER("PROJECT_MANAGER", "项目经理"), + SAFETY_MANAGER("SAFETY_MANAGER", "安全经理"), + QUALITY_MANAGER("QUALITY_MANAGER", "质量经理"), + SITE_MANAGER("SITE_MANAGER", "现场经理"), + + // 其他岗位 + TECHNICIAN("TECHNICIAN", "技术员"), + SUPERVISOR("SUPERVISOR", "监理"), + COORDINATOR("COORDINATOR", "协调员"); + + private final String code; + private final String desc; + + /** + * 根据代码获取描述 + */ + public static String getDescByCode(String code) { + for (ProjectJobCodeEnum jobCode : values()) { + if (jobCode.getCode().equals(code)) { + return jobCode.getDesc(); + } + } + return ""; + } + + /** + * 根据代码获取枚举 + */ + public static ProjectJobCodeEnum getByCode(String code) { + for (ProjectJobCodeEnum jobCode : values()) { + if (jobCode.getCode().equals(code)) { + return jobCode; + } + } + return null; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/enums/ProjectRoleTypeEnum.java b/core/src/main/java/com/dite/znpt/enums/ProjectRoleTypeEnum.java new file mode 100644 index 0000000..a750a4c --- /dev/null +++ b/core/src/main/java/com/dite/znpt/enums/ProjectRoleTypeEnum.java @@ -0,0 +1,53 @@ +package com.dite.znpt.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目角色类型枚举 + */ +@Getter +@AllArgsConstructor +public enum ProjectRoleTypeEnum { + + PROJECT_MANAGER("PROJECT_MANAGER", "项目经理"), + SAFETY_OFFICER("SAFETY_OFFICER", "安全员"), + QUALITY_OFFICER("QUALITY_OFFICER", "质量员"), + CONSTRUCTOR("CONSTRUCTOR", "施工人员"), + TEAM_LEADER("TEAM_LEADER", "施工组长"), + SENIOR_PROJECT_MANAGER("SENIOR_PROJECT_MANAGER", "大项目经理"), + REMOTE_ADVISOR("REMOTE_ADVISOR", "项目远程顾问"), + EXTERNAL_COLLABORATOR("EXTERNAL_COLLABORATOR", "外部协作者"), + FINANCIAL_MANAGER("FINANCIAL_MANAGER", "财务经理"), + BUSINESS_MANAGER("BUSINESS_MANAGER", "商务经理"), + SITE_MANAGER("SITE_MANAGER", "现场经理"); + + private final String code; + private final String desc; + + /** + * 根据代码获取描述 + */ + public static String getDescByCode(String code) { + for (ProjectRoleTypeEnum roleType : values()) { + if (roleType.getCode().equals(code)) { + return roleType.getDesc(); + } + } + return ""; + } + + /** + * 根据代码获取枚举 + */ + public static ProjectRoleTypeEnum getByCode(String code) { + for (ProjectRoleTypeEnum roleType : values()) { + if (roleType.getCode().equals(code)) { + return roleType; + } + } + return null; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/mapper/ProjectMemberMapper.java b/core/src/main/java/com/dite/znpt/mapper/ProjectMemberMapper.java new file mode 100644 index 0000000..d514e90 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/ProjectMemberMapper.java @@ -0,0 +1,54 @@ +package com.dite.znpt.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.domain.entity.ProjectMemberEntity; +import com.dite.znpt.domain.vo.ProjectMemberListReq; +import com.dite.znpt.domain.vo.ProjectMemberResp; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员关联表Mapper接口 + */ +@Mapper +public interface ProjectMemberMapper extends BaseMapper { + + /** + * 根据条件查询项目人员列表 + */ + List queryBySelective(ProjectMemberListReq req); + + /** + * 根据项目ID查询项目人员列表 + */ + List queryByProjectId(@Param("projectId") String projectId); + + /** + * 根据机组ID查询机组人员列表 + */ + List queryByTurbineId(@Param("turbineId") String turbineId); + + /** + * 根据任务组ID查询任务组人员列表 + */ + List queryByTaskGroupId(@Param("taskGroupId") String taskGroupId); + + /** + * 根据任务ID查询任务人员列表 + */ + List queryByTaskId(@Param("taskId") String taskId); + + /** + * 根据用户ID查询用户参与的项目列表 + */ + List queryByUserId(@Param("userId") String userId); + + /** + * 根据项目ID和角色类型查询人员列表 + */ + List queryByProjectIdAndRoleType(@Param("projectId") String projectId, @Param("roleType") String roleType); +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/ProjectMemberService.java b/core/src/main/java/com/dite/znpt/service/ProjectMemberService.java new file mode 100644 index 0000000..9004093 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/ProjectMemberService.java @@ -0,0 +1,107 @@ +package com.dite.znpt.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.domain.entity.ProjectMemberEntity; +import com.dite.znpt.domain.vo.*; + +import java.util.List; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员关联表服务接口 + */ +public interface ProjectMemberService extends IService { + + /** + * 查询项目人员列表 + */ + List selectList(ProjectMemberListReq req); + + /** + * 根据项目ID查询项目人员列表 + */ + List selectByProjectId(String projectId); + + /** + * 根据机组ID查询机组人员列表 + */ + List selectByTurbineId(String turbineId); + + /** + * 根据任务组ID查询任务组人员列表 + */ + List selectByTaskGroupId(String taskGroupId); + + /** + * 根据任务ID查询任务人员列表 + */ + List selectByTaskId(String taskId); + + /** + * 根据用户ID查询用户参与的项目列表 + */ + List selectByUserId(String userId); + + /** + * 根据项目ID和角色类型查询人员列表 + */ + List selectByProjectIdAndRoleType(String projectId, String roleType); + + /** + * 新增项目人员 + */ + void saveData(ProjectMemberReq req); + + /** + * 更新项目人员 + */ + void updateData(ProjectMemberReq req); + + /** + * 删除项目人员 + */ + void deleteById(String memberId); + + /** + * 批量添加项目人员 + */ + void batchAddMembers(List reqList); + + /** + * 根据项目ID删除所有项目人员 + */ + void deleteByProjectId(String projectId); + + /** + * 根据机组ID删除所有机组人员 + */ + void deleteByTurbineId(String turbineId); + + /** + * 根据任务组ID删除所有任务组人员 + */ + void deleteByTaskGroupId(String taskGroupId); + + /** + * 根据任务ID删除所有任务人员 + */ + void deleteByTaskId(String taskId); + + // ========================== 项目看板相关方法 ========================== + + /** + * 获取项目看板统计数据 + */ + ProjectKanbanStatsResp getProjectKanbanStats(); + + /** + * 获取项目看板数据 + */ + ProjectKanbanDataResp getProjectKanbanData(); + + /** + * 获取项目详情 + */ + ProjectDetailResp getProjectDetail(String projectId); +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java new file mode 100644 index 0000000..08f8ab8 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java @@ -0,0 +1,431 @@ +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.extension.service.impl.ServiceImpl; +import com.dite.znpt.constant.Message; +import com.dite.znpt.domain.entity.ProjectMemberEntity; +import com.dite.znpt.domain.entity.UserEntity; +import com.dite.znpt.domain.entity.ProjectEntity; +import com.dite.znpt.domain.entity.TurbineEntity; +import com.dite.znpt.domain.entity.ProjectTaskEntity; +import com.dite.znpt.domain.entity.ProjectBudgetInfoEntity; +import com.dite.znpt.domain.entity.ProjectDailyReportEntity; +import com.dite.znpt.domain.vo.ProjectMemberListReq; +import com.dite.znpt.domain.vo.ProjectMemberReq; +import com.dite.znpt.domain.vo.ProjectMemberResp; +import com.dite.znpt.domain.vo.ProjectKanbanStatsResp; +import com.dite.znpt.domain.vo.ProjectKanbanDataResp; +import com.dite.znpt.domain.vo.ProjectDetailResp; +import com.dite.znpt.enums.ProjectJobCodeEnum; +import com.dite.znpt.enums.ProjectRoleTypeEnum; +import com.dite.znpt.enums.ProjectStatusEnum; +import com.dite.znpt.enums.ProjectTaskStateEnum; +import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.mapper.ProjectMemberMapper; +import com.dite.znpt.service.ProjectMemberService; +import com.dite.znpt.service.UserService; +import com.dite.znpt.service.ProjectService; +import com.dite.znpt.service.TurbineService; +import com.dite.znpt.service.ProjectTaskService; +import com.dite.znpt.service.ProjectBudgetInfoService; +import com.dite.znpt.service.ProjectDailyReportService; +import com.dite.znpt.util.PageUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员关联表服务实现类 + */ +@Service +@RequiredArgsConstructor +public class ProjectMemberServiceImpl extends ServiceImpl implements ProjectMemberService { + + private final UserService userService; + + // 添加其他服务依赖 + private final ProjectService projectService; + private final TurbineService turbineService; + private final ProjectTaskService projectTaskService; + private final ProjectBudgetInfoService projectBudgetInfoService; + private final ProjectDailyReportService projectDailyReportService; + + @Override + public List selectList(ProjectMemberListReq req) { + PageUtil.startPage(); + List list = this.baseMapper.queryBySelective(req); + enrichMemberInfo(list); + return list; + } + + @Override + public List selectByProjectId(String projectId) { + List list = this.baseMapper.queryByProjectId(projectId); + enrichMemberInfo(list); + return list; + } + + @Override + public List selectByTurbineId(String turbineId) { + List list = this.baseMapper.queryByTurbineId(turbineId); + enrichMemberInfo(list); + return list; + } + + @Override + public List selectByTaskGroupId(String taskGroupId) { + List list = this.baseMapper.queryByTaskGroupId(taskGroupId); + enrichMemberInfo(list); + return list; + } + + @Override + public List selectByTaskId(String taskId) { + List list = this.baseMapper.queryByTaskId(taskId); + enrichMemberInfo(list); + return list; + } + + @Override + public List selectByUserId(String userId) { + List list = this.baseMapper.queryByUserId(userId); + enrichMemberInfo(list); + return list; + } + + @Override + public List selectByProjectIdAndRoleType(String projectId, String roleType) { + List list = this.baseMapper.queryByProjectIdAndRoleType(projectId, roleType); + enrichMemberInfo(list); + return list; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveData(ProjectMemberReq req) { + // 验证用户是否存在 + UserEntity user = userService.getById(req.getUserId()); + if (user == null) { + throw new ServiceException(Message.USER_ID_NOT_EXIST_OR_ILLEGAL); + } + + // 检查是否已存在相同的关联 + boolean exists = lambdaQuery() + .eq(ProjectMemberEntity::getProjectId, req.getProjectId()) + .eq(ProjectMemberEntity::getUserId, req.getUserId()) + .eq(StrUtil.isNotEmpty(req.getTurbineId()), ProjectMemberEntity::getTurbineId, req.getTurbineId()) + .eq(StrUtil.isNotEmpty(req.getTaskGroupId()), ProjectMemberEntity::getTaskGroupId, req.getTaskGroupId()) + .eq(StrUtil.isNotEmpty(req.getTaskId()), ProjectMemberEntity::getTaskId, req.getTaskId()) + .eq(ProjectMemberEntity::getRoleType, req.getRoleType()) + .exists(); + + if (exists) { + throw new ServiceException("该用户在此项目中已存在相同角色"); + } + + ProjectMemberEntity entity = BeanUtil.copyProperties(req, ProjectMemberEntity.class); + if (StrUtil.isEmpty(entity.getStatus())) { + entity.setStatus("ACTIVE"); + } + save(entity); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateData(ProjectMemberReq req) { + if (StrUtil.isEmpty(req.getMemberId())) { + throw new ServiceException("关联ID不能为空"); + } + + ProjectMemberEntity entity = getById(req.getMemberId()); + if (entity == null) { + throw new ServiceException(Message.PROJECT_ID_IS_NOT_EXIST); + } + + BeanUtil.copyProperties(req, entity); + updateById(entity); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteById(String memberId) { + removeById(memberId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void batchAddMembers(List reqList) { + if (CollUtil.isEmpty(reqList)) { + return; + } + + for (ProjectMemberReq req : reqList) { + saveData(req); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByProjectId(String projectId) { + lambdaUpdate() + .eq(ProjectMemberEntity::getProjectId, projectId) + .remove(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByTurbineId(String turbineId) { + lambdaUpdate() + .eq(ProjectMemberEntity::getTurbineId, turbineId) + .remove(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByTaskGroupId(String taskGroupId) { + lambdaUpdate() + .eq(ProjectMemberEntity::getTaskGroupId, taskGroupId) + .remove(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByTaskId(String taskId) { + lambdaUpdate() + .eq(ProjectMemberEntity::getTaskId, taskId) + .remove(); + } + + /** + * 丰富成员信息(用户信息、角色描述、岗位描述) + */ + private void enrichMemberInfo(List list) { + if (CollUtil.isEmpty(list)) { + return; + } + + // 获取所有用户ID + List userIds = list.stream() + .map(ProjectMemberResp::getUserId) + .distinct() + .collect(Collectors.toList()); + + // 查询用户信息 + Map userMap = userService.listByIds(userIds) + .stream() + .collect(Collectors.toMap(UserEntity::getUserId, Function.identity())); + + // 填充用户信息和描述 + list.forEach(member -> { + // 填充用户信息 + UserEntity user = userMap.get(member.getUserId()); + if (user != null) { + member.setUserName(user.getName()); + member.setUserAccount(user.getAccount()); + member.setUserAvatar(user.getAvatar()); + } + + // 填充角色类型描述 + member.setRoleTypeDesc(ProjectRoleTypeEnum.getDescByCode(member.getRoleType())); + + // 填充岗位代码描述 + if (StrUtil.isNotEmpty(member.getJobCode())) { + member.setJobCodeDesc(ProjectJobCodeEnum.getDescByCode(member.getJobCode())); + } + }); + } + + // ========================== 项目看板相关方法实现 ========================== + + @Override + public ProjectKanbanStatsResp getProjectKanbanStats() { + ProjectKanbanStatsResp resp = new ProjectKanbanStatsResp(); + + // 统计项目数量 + resp.setTotalProjectsCount(projectService.count()); + resp.setPendingProjectCount(projectService.lambdaQuery().eq(ProjectEntity::getStatus, 0).count()); + resp.setInProgressProjectCount(projectService.lambdaQuery().eq(ProjectEntity::getStatus, 1).count()); + resp.setCompletedProjectCount(projectService.lambdaQuery().eq(ProjectEntity::getStatus, 2).count()); + resp.setAuditedProjectCount(projectService.lambdaQuery().eq(ProjectEntity::getStatus, 3).count()); + resp.setAcceptedProjectCount(projectService.lambdaQuery().eq(ProjectEntity::getStatus, 4).count()); + + // 统计机组数量 + resp.setTotalTurbineCount(turbineService.count()); + resp.setPendingTurbineCount(turbineService.lambdaQuery().eq(TurbineEntity::getStatus, 0).count()); + resp.setInProgressTurbineCount(turbineService.lambdaQuery().eq(TurbineEntity::getStatus, 1).count()); + resp.setCompletedTurbineCount(turbineService.lambdaQuery().eq(TurbineEntity::getStatus, 2).count()); + resp.setAuditedTurbineCount(turbineService.lambdaQuery().eq(TurbineEntity::getStatus, 3).count()); + resp.setAcceptedTurbineCount(turbineService.lambdaQuery().eq(TurbineEntity::getStatus, 4).count()); + + // 统计任务数量 + resp.setTotalTaskCount(projectTaskService.count()); + resp.setPendingTaskCount(projectTaskService.lambdaQuery().eq(ProjectTaskEntity::getStatus, 0).count()); + resp.setInProgressTaskCount(projectTaskService.lambdaQuery().eq(ProjectTaskEntity::getStatus, 1).count()); + resp.setCompletedTaskCount(projectTaskService.lambdaQuery().eq(ProjectTaskEntity::getStatus, 2).count()); + + // 统计人员数量 + resp.setTotalMemberCount(this.count()); + resp.setProjectManagerCount(this.lambdaQuery().eq(ProjectMemberEntity::getRoleType, "PROJECT_MANAGER").count()); + resp.setSafetyOfficerCount(this.lambdaQuery().eq(ProjectMemberEntity::getRoleType, "SAFETY_OFFICER").count()); + resp.setQualityOfficerCount(this.lambdaQuery().eq(ProjectMemberEntity::getRoleType, "QUALITY_OFFICER").count()); + resp.setConstructorCount(this.lambdaQuery().eq(ProjectMemberEntity::getRoleType, "CONSTRUCTOR").count()); + resp.setTeamLeaderCount(this.lambdaQuery().eq(ProjectMemberEntity::getRoleType, "TEAM_LEADER").count()); + + return resp; + } + + @Override + public ProjectKanbanDataResp getProjectKanbanData() { + ProjectKanbanDataResp resp = new ProjectKanbanDataResp(); + + // 获取各状态的项目列表 + resp.setPendingProjects(getProjectKanbanItems(0)); + resp.setInProgressProjects(getProjectKanbanItems(1)); + resp.setCompletedProjects(getProjectKanbanItems(2)); + resp.setAuditedProjects(getProjectKanbanItems(3)); + resp.setAcceptedProjects(getProjectKanbanItems(4)); + + return resp; + } + + @Override + public ProjectDetailResp getProjectDetail(String projectId) { + // 获取项目基本信息 + ProjectEntity project = projectService.getById(projectId); + if (project == null) { + throw new ServiceException(Message.PROJECT_ID_IS_NOT_EXIST); + } + + ProjectDetailResp resp = new ProjectDetailResp(); + BeanUtil.copyProperties(project, resp); + resp.setStatusLabel(ProjectStatusEnum.getDescByCode(resp.getStatus())); + + // 获取项目人员信息 + resp.setProjectMembers(selectByProjectId(projectId)); + + // 获取项目机组信息 + List turbines = turbineService.lambdaQuery() + .eq(TurbineEntity::getProjectId, projectId) + .list(); + resp.setTurbines(turbines.stream().map(turbine -> { + ProjectDetailResp.TurbineInfo info = new ProjectDetailResp.TurbineInfo(); + BeanUtil.copyProperties(turbine, info); + info.setStatusLabel(ProjectStatusEnum.getDescByCode(info.getStatus())); + return info; + }).collect(Collectors.toList())); + + // 获取项目任务信息 + List tasks = projectTaskService.lambdaQuery() + .eq(ProjectTaskEntity::getProjectId, projectId) + .list(); + resp.setTasks(tasks.stream().map(task -> { + ProjectDetailResp.TaskInfo info = new ProjectDetailResp.TaskInfo(); + BeanUtil.copyProperties(task, info); + info.setStatusLabel(ProjectTaskStateEnum.getDescByCode(info.getStatus())); + return info; + }).collect(Collectors.toList())); + + // 获取项目预算信息 + List budgets = projectBudgetInfoService.lambdaQuery() + .eq(ProjectBudgetInfoEntity::getProjectId, projectId) + .list(); + resp.setBudgets(budgets.stream().map(budget -> { + ProjectDetailResp.BudgetInfo info = new ProjectDetailResp.BudgetInfo(); + BeanUtil.copyProperties(budget, info); + return info; + }).collect(Collectors.toList())); + + // 获取项目日报信息 + List dailyReports = projectDailyReportService.lambdaQuery() + .eq(ProjectDailyReportEntity::getProjectId, projectId) + .orderByDesc(ProjectDailyReportEntity::getReportDate) + .last("LIMIT 10") + .list(); + resp.setDailyReports(dailyReports.stream().map(report -> { + ProjectDetailResp.DailyReportInfo info = new ProjectDetailResp.DailyReportInfo(); + BeanUtil.copyProperties(report, info); + // 获取提交人姓名 + UserEntity user = userService.getById(report.getSubmitUser()); + info.setSubmitUserName(user != null ? user.getName() : ""); + return info; + }).collect(Collectors.toList())); + + return resp; + } + + /** + * 获取项目看板项目项列表 + */ + private List getProjectKanbanItems(Integer status) { + List projects = projectService.lambdaQuery() + .eq(ProjectEntity::getStatus, status) + .orderByDesc(ProjectEntity::getCreateTime) + .list(); + + return projects.stream().map(project -> { + ProjectKanbanDataResp.ProjectKanbanItem item = new ProjectKanbanDataResp.ProjectKanbanItem(); + BeanUtil.copyProperties(project, item); + item.setStatusLabel(ProjectStatusEnum.getDescByCode(item.getStatus())); + + // 获取项目人员信息 + List members = selectByProjectId(project.getProjectId()); + + // 按角色类型分组,并去重用户名 + Map memberNames = members.stream() + .collect(Collectors.groupingBy( + ProjectMemberResp::getRoleType, + Collectors.mapping( + ProjectMemberResp::getUserName, + Collectors.collectingAndThen( + Collectors.toSet(), // 使用Set去重 + set -> String.join(",", set) + ) + ) + )); + + item.setProjectManagerName(memberNames.get("PROJECT_MANAGER")); + item.setSafetyOfficerName(memberNames.get("SAFETY_OFFICER")); + item.setQualityOfficerName(memberNames.get("QUALITY_OFFICER")); + item.setConstructionTeamLeaderName(memberNames.get("TEAM_LEADER")); + item.setConstructorNames(memberNames.get("CONSTRUCTOR")); + + // 统计机组数量 + Long turbineCount = turbineService.lambdaQuery() + .eq(TurbineEntity::getProjectId, project.getProjectId()) + .count(); + item.setTurbineCount(turbineCount); + + // 统计任务数量 + Long taskCount = projectTaskService.lambdaQuery() + .eq(ProjectTaskEntity::getProjectId, project.getProjectId()) + .count(); + item.setTaskCount(taskCount); + + // 统计已完成任务数量 + Long completedTaskCount = projectTaskService.lambdaQuery() + .eq(ProjectTaskEntity::getProjectId, project.getProjectId()) + .eq(ProjectTaskEntity::getStatus, 2) + .count(); + item.setCompletedTaskCount(completedTaskCount); + + // 计算项目进度百分比 + if (taskCount > 0) { + item.setProgressPercentage((int) (completedTaskCount * 100 / taskCount)); + } else { + item.setProgressPercentage(0); + } + + return item; + }).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/core/src/main/resources/mapper/ProjectMemberMapper.xml b/core/src/main/resources/mapper/ProjectMemberMapper.xml new file mode 100644 index 0000000..f8925d6 --- /dev/null +++ b/core/src/main/resources/mapper/ProjectMemberMapper.xml @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/project_member_extended_data.sql b/doc/project_member_extended_data.sql new file mode 100644 index 0000000..e11968c --- /dev/null +++ b/doc/project_member_extended_data.sql @@ -0,0 +1,107 @@ +-- ============================================= +-- 项目人员表扩展测试数据 +-- 使用多个真实用户ID和项目ID,使用UUID()生成member_id +-- @author AI Assistant +-- @date 2025/01/27 +-- ============================================= + +-- 清空现有数据(可选) +-- DELETE FROM project_member WHERE project_id IN ('0b71a1259c49918c6595c9720ad1db5d', '7f321271c13483aa31cb743d604603a5', '8e0da59bd0c640b5b65dccd72606ea39', '96e0debf78187300f144d7f3450a2477', 'bbd28ed8627d6bd9fd8fa7476af6242a', 'd446d998d7c40005bf9af53ac264ef6e', 'd6c5b8408a42a8390297751756ecff23', 'eb64015cf2d0b40574c1d6f476876822', 'fb7e3b731ee496cec46ca1db64117676'); + +-- 插入项目人员测试数据 +INSERT INTO project_member ( + member_id, + project_id, + user_id, + role_type, + job_code, + job_desc, + join_date, + status, + remark +) VALUES +-- 项目1: 0b71a1259c49918c6595c9720ad1db5d +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '0ab4a72af4184a1614d4c25a5bb65ece', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-15', 'ACTIVE', '项目总负责人'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '0bc98a66761915e31fbf86e468d6ba20', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-15', 'ACTIVE', '负责项目安全管理'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '149c94f937a7d36cda70a81f343d090a', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-15', 'ACTIVE', '负责项目质量管理'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '15129b4764ced7900ef5d51c20cefa35', 'TEAM_LEADER', 'SITE_MANAGER', '现场经理', '2025-01-15', 'ACTIVE', '负责现场施工管理'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '1632433c2073532a074b0079cca5eb18', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-15', 'ACTIVE', '负责地面施工工作'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '1995a0da943d188506548bd42a2ee39d', 'CONSTRUCTOR', 'DRIVER', '司机', '2025-01-15', 'ACTIVE', '负责设备运输和人员接送'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '1b2641edb4c9a8f14658edaa8e6a36e3', 'CONSTRUCTOR', 'ASCENDING', '登高人员', '2025-01-15', 'ACTIVE', '负责高空作业'), +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '1d476bc4de6d71f6a43497d943adec9c', 'CONSTRUCTOR', 'ANTI_THUNDER', '防雷人员', '2025-01-15', 'ACTIVE', '负责防雷设施安装'), + +-- 项目2: 7f321271c13483aa31cb743d604603a5 +(UUID(), '7f321271c13483aa31cb743d604603a5', '1dae46a6ecbc2408c8b137187fc13d06', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-16', 'ACTIVE', '项目总负责人'), +(UUID(), '7f321271c13483aa31cb743d604603a5', '1f319a6f25b65ebd50711707e5e08d1a', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-16', 'ACTIVE', '负责项目安全管理'), +(UUID(), '7f321271c13483aa31cb743d604603a5', '22ff9d065f3ad826f5e22ba18a20248c', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-16', 'ACTIVE', '负责项目质量管理'), +(UUID(), '7f321271c13483aa31cb743d604603a5', '2fd0581e10f4c4f70e5148de3d61bc06', 'TEAM_LEADER', 'SITE_MANAGER', '现场经理', '2025-01-16', 'ACTIVE', '负责现场施工管理'), +(UUID(), '7f321271c13483aa31cb743d604603a5', '3042cb085eb41721faa8f4d985921424', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-16', 'ACTIVE', '负责地面施工工作'), +(UUID(), '7f321271c13483aa31cb743d604603a5', '3169163eb58fff961955179fbdc22507', 'CONSTRUCTOR', 'DRIVER', '司机', '2025-01-16', 'ACTIVE', '负责设备运输和人员接送'), + +-- 项目3: 8e0da59bd0c640b5b65dccd72606ea39 +(UUID(), '8e0da59bd0c640b5b65dccd72606ea39', '324afaf4e89213b6f5fcf1fa57663940', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-17', 'ACTIVE', '项目总负责人'), +(UUID(), '8e0da59bd0c640b5b65dccd72606ea39', '375f87a103e5bcad6e6a402177044891', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-17', 'ACTIVE', '负责项目安全管理'), +(UUID(), '8e0da59bd0c640b5b65dccd72606ea39', '3b5f2db9bac776be536d893f726deeba', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-17', 'ACTIVE', '负责项目质量管理'), +(UUID(), '8e0da59bd0c640b5b65dccd72606ea39', '44d335410542c5fd4989780dd11dca66', 'TEAM_LEADER', 'SITE_MANAGER', '现场经理', '2025-01-17', 'ACTIVE', '负责现场施工管理'), +(UUID(), '8e0da59bd0c640b5b65dccd72606ea39', '4e171ce81c18183bc8f06da8bd47a3d8', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-17', 'ACTIVE', '负责地面施工工作'), + +-- 项目4: 96e0debf78187300f144d7f3450a2477 +(UUID(), '96e0debf78187300f144d7f3450a2477', '0ab4a72af4184a1614d4c25a5bb65ece', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-18', 'ACTIVE', '项目总负责人'), +(UUID(), '96e0debf78187300f144d7f3450a2477', '0bc98a66761915e31fbf86e468d6ba20', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-18', 'ACTIVE', '负责项目安全管理'), +(UUID(), '96e0debf78187300f144d7f3450a2477', '149c94f937a7d36cda70a81f343d090a', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-18', 'ACTIVE', '负责项目质量管理'), +(UUID(), '96e0debf78187300f144d7f3450a2477', '15129b4764ced7900ef5d51c20cefa35', 'CONSTRUCTOR', 'ASCENDING', '登高人员', '2025-01-18', 'ACTIVE', '负责高空作业'), +(UUID(), '96e0debf78187300f144d7f3450a2477', '1632433c2073532a074b0079cca5eb18', 'CONSTRUCTOR', 'ANTI_THUNDER', '防雷人员', '2025-01-18', 'ACTIVE', '负责防雷设施安装'), + +-- 项目5: bbd28ed8627d6bd9fd8fa7476af6242a +(UUID(), 'bbd28ed8627d6bd9fd8fa7476af6242a', '1995a0da943d188506548bd42a2ee39d', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-19', 'ACTIVE', '项目总负责人'), +(UUID(), 'bbd28ed8627d6bd9fd8fa7476af6242a', '1b2641edb4c9a8f14658edaa8e6a36e3', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-19', 'ACTIVE', '负责项目安全管理'), +(UUID(), 'bbd28ed8627d6bd9fd8fa7476af6242a', '1d476bc4de6d71f6a43497d943adec9c', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-19', 'ACTIVE', '负责项目质量管理'), +(UUID(), 'bbd28ed8627d6bd9fd8fa7476af6242a', '1dae46a6ecbc2408c8b137187fc13d06', 'TEAM_LEADER', 'SITE_MANAGER', '现场经理', '2025-01-19', 'ACTIVE', '负责现场施工管理'), +(UUID(), 'bbd28ed8627d6bd9fd8fa7476af6242a', '1f319a6f25b65ebd50711707e5e08d1a', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-19', 'ACTIVE', '负责地面施工工作'), + +-- 项目6: d446d998d7c40005bf9af53ac264ef6e +(UUID(), 'd446d998d7c40005bf9af53ac264ef6e', '22ff9d065f3ad826f5e22ba18a20248c', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-20', 'ACTIVE', '项目总负责人'), +(UUID(), 'd446d998d7c40005bf9af53ac264ef6e', '2fd0581e10f4c4f70e5148de3d61bc06', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-20', 'ACTIVE', '负责项目安全管理'), +(UUID(), 'd446d998d7c40005bf9af53ac264ef6e', '3042cb085eb41721faa8f4d985921424', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-20', 'ACTIVE', '负责项目质量管理'), +(UUID(), 'd446d998d7c40005bf9af53ac264ef6e', '3169163eb58fff961955179fbdc22507', 'CONSTRUCTOR', 'DRIVER', '司机', '2025-01-20', 'ACTIVE', '负责设备运输和人员接送'), + +-- 项目7: d6c5b8408a42a8390297751756ecff23 +(UUID(), 'd6c5b8408a42a8390297751756ecff23', '324afaf4e89213b6f5fcf1fa57663940', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-21', 'ACTIVE', '项目总负责人'), +(UUID(), 'd6c5b8408a42a8390297751756ecff23', '375f87a103e5bcad6e6a402177044891', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-21', 'ACTIVE', '负责项目安全管理'), +(UUID(), 'd6c5b8408a42a8390297751756ecff23', '3b5f2db9bac776be536d893f726deeba', 'CONSTRUCTOR', 'ASCENDING', '登高人员', '2025-01-21', 'ACTIVE', '负责高空作业'), +(UUID(), 'd6c5b8408a42a8390297751756ecff23', '44d335410542c5fd4989780dd11dca66', 'CONSTRUCTOR', 'ANTI_THUNDER', '防雷人员', '2025-01-21', 'ACTIVE', '负责防雷设施安装'), + +-- 项目8: eb64015cf2d0b40574c1d6f476876822 +(UUID(), 'eb64015cf2d0b40574c1d6f476876822', '4e171ce81c18183bc8f06da8bd47a3d8', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-22', 'ACTIVE', '项目总负责人'), +(UUID(), 'eb64015cf2d0b40574c1d6f476876822', '0ab4a72af4184a1614d4c25a5bb65ece', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-22', 'ACTIVE', '负责项目安全管理'), +(UUID(), 'eb64015cf2d0b40574c1d6f476876822', '0bc98a66761915e31fbf86e468d6ba20', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-22', 'ACTIVE', '负责项目质量管理'), +(UUID(), 'eb64015cf2d0b40574c1d6f476876822', '149c94f937a7d36cda70a81f343d090a', 'TEAM_LEADER', 'SITE_MANAGER', '现场经理', '2025-01-22', 'ACTIVE', '负责现场施工管理'), + +-- 项目9: fb7e3b731ee496cec46ca1db64117676 +(UUID(), 'fb7e3b731ee496cec46ca1db64117676', '15129b4764ced7900ef5d51c20cefa35', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-23', 'ACTIVE', '项目总负责人'), +(UUID(), 'fb7e3b731ee496cec46ca1db64117676', '1632433c2073532a074b0079cca5eb18', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-23', 'ACTIVE', '负责项目安全管理'), +(UUID(), 'fb7e3b731ee496cec46ca1db64117676', '1995a0da943d188506548bd42a2ee39d', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-23', 'ACTIVE', '负责项目质量管理'), +(UUID(), 'fb7e3b731ee496cec46ca1db64117676', '1b2641edb4c9a8f14658edaa8e6a36e3', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-23', 'ACTIVE', '负责地面施工工作'), +(UUID(), 'fb7e3b731ee496cec46ca1db64117676', '1d476bc4de6d71f6a43497d943adec9c', 'CONSTRUCTOR', 'DRIVER', '司机', '2025-01-23', 'ACTIVE', '负责设备运输和人员接送'); + +-- ============================================= +-- 查询示例 +-- ============================================= + +-- 查询所有项目的在职人员 +-- SELECT * FROM project_member WHERE status = 'ACTIVE'; + +-- 查询特定项目的所有人员 +-- SELECT * FROM project_member WHERE project_id = '0b71a1259c49918c6595c9720ad1db5d' AND status = 'ACTIVE'; + +-- 查询所有项目经理 +-- SELECT * FROM project_member WHERE role_type = 'PROJECT_MANAGER' AND status = 'ACTIVE'; + +-- 按项目统计人员数量 +-- SELECT project_id, COUNT(*) as member_count FROM project_member WHERE status = 'ACTIVE' GROUP BY project_id; + +-- 按角色类型统计人员数量 +-- SELECT role_type, COUNT(*) as count FROM project_member WHERE status = 'ACTIVE' GROUP BY role_type; + +-- 查询特定用户参与的所有项目 +-- SELECT DISTINCT project_id FROM project_member WHERE user_id = '0ab4a72af4184a1614d4c25a5bb65ece' AND status = 'ACTIVE'; \ No newline at end of file diff --git a/doc/project_member_tables.sql b/doc/project_member_tables.sql new file mode 100644 index 0000000..b30097e --- /dev/null +++ b/doc/project_member_tables.sql @@ -0,0 +1,137 @@ +-- ============================================= +-- 项目人员关联表 +-- 用于管理项目、机组、任务组、任务的人员关系 +-- @author wangna +-- @date 2025/08/05 +-- ============================================= + +-- 项目人员关联表 +CREATE TABLE `project_member` ( + `member_id` varchar(64) NOT NULL COMMENT '关联ID', + `project_id` varchar(64) NOT NULL COMMENT '项目ID', + `turbine_id` varchar(64) DEFAULT NULL COMMENT '机组ID(可选,关联到具体机组)', + `task_group_id` varchar(64) DEFAULT NULL COMMENT '任务组ID(可选,关联到具体任务组)', + `task_id` varchar(64) DEFAULT NULL COMMENT '任务ID(可选,关联到具体任务)', + `user_id` varchar(64) NOT NULL COMMENT '用户ID', + `role_type` varchar(50) NOT NULL COMMENT '项目角色类型', + `job_code` varchar(50) DEFAULT NULL COMMENT '具体岗位代码', + `job_desc` varchar(500) DEFAULT NULL COMMENT '岗位描述', + `join_date` date NOT NULL COMMENT '加入时间', + `leave_date` date DEFAULT NULL COMMENT '离开时间', + `status` varchar(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE-在职,INACTIVE-离职,SUSPENDED-暂停', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`member_id`), + KEY `idx_project_id` (`project_id`), + KEY `idx_turbine_id` (`turbine_id`), + KEY `idx_task_group_id` (`task_group_id`), + KEY `idx_task_id` (`task_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_role_type` (`role_type`), + KEY `idx_status` (`status`), + UNIQUE KEY `uk_project_user_role` (`project_id`, `user_id`, `role_type`, `turbine_id`, `task_group_id`, `task_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目人员关联表'; + +-- 添加复合索引以提高查询性能 +CREATE INDEX `idx_project_member_composite` ON `project_member` (`project_id`, `status`, `role_type`); +CREATE INDEX `idx_project_member_user_status` ON `project_member` (`user_id`, `status`); +CREATE INDEX `idx_project_member_turbine_status` ON `project_member` (`turbine_id`, `status`); +CREATE INDEX `idx_project_member_task_status` ON `project_member` (`task_id`, `status`); + +-- ============================================= +-- 数据迁移脚本(可选) +-- 将现有项目的人员数据迁移到新表 +-- ============================================= + +-- 迁移项目经理数据 +INSERT INTO project_member (member_id, project_id, user_id, role_type, job_code, join_date, status, create_time) +SELECT + UUID() as member_id, + project_id, + project_manager_id as user_id, + 'PROJECT_MANAGER' as role_type, + 'PROJECT_MANAGER' as job_code, + create_time as join_date, + 'ACTIVE' as status, + create_time +FROM project +WHERE project_manager_id IS NOT NULL AND project_manager_id != ''; + +-- 迁移安全员数据 +INSERT INTO project_member (member_id, project_id, user_id, role_type, job_code, join_date, status, create_time) +SELECT + UUID() as member_id, + project_id, + auditor_id as user_id, + 'SAFETY_OFFICER' as role_type, + 'SAFETY_MANAGER' as job_code, + create_time as join_date, + 'ACTIVE' as status, + create_time +FROM project +WHERE auditor_id IS NOT NULL AND auditor_id != ''; + +-- 迁移质量员数据 +INSERT INTO project_member (member_id, project_id, user_id, role_type, job_code, join_date, status, create_time) +SELECT + UUID() as member_id, + project_id, + quality_officer_id as user_id, + 'QUALITY_OFFICER' as role_type, + 'QUALITY_MANAGER' as job_code, + create_time as join_date, + 'ACTIVE' as status, + create_time +FROM project +WHERE quality_officer_id IS NOT NULL AND quality_officer_id != ''; + +-- 迁移施工组长数据 +INSERT INTO project_member (member_id, project_id, user_id, role_type, job_code, join_date, status, create_time) +SELECT + UUID() as member_id, + project_id, + construct_team_leader_id as user_id, + 'TEAM_LEADER' as role_type, + 'TEAM_LEADER' as job_code, + create_time as join_date, + 'ACTIVE' as status, + create_time +FROM project +WHERE construct_team_leader_id IS NOT NULL AND construct_team_leader_id != ''; + +-- 迁移施工人员数据(需要处理逗号分隔的多个ID) +-- 注意:这个脚本需要根据实际情况调整,因为constructor_ids是逗号分隔的字符串 +-- 建议在应用层处理这个迁移逻辑 + +-- ============================================= +-- 示例数据插入 +-- ============================================= + +-- 插入示例项目人员数据 +INSERT INTO project_member (member_id, project_id, user_id, role_type, job_code, job_desc, join_date, status, remark) VALUES +('pm001', 'project001', 'user001', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-01', 'ACTIVE', '项目负责人'), +('pm002', 'project001', 'user002', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-01', 'ACTIVE', '负责项目安全'), +('pm003', 'project001', 'user003', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-01', 'ACTIVE', '负责项目质量'), +('pm004', 'project001', 'user004', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-01', 'ACTIVE', '地勤工作'), +('pm005', 'project001', 'user005', 'CONSTRUCTOR', 'DRIVER', '司机', '2025-01-01', 'ACTIVE', '负责运输'), +('pm006', 'project001', 'user006', 'CONSTRUCTOR', 'ASCENDING', '登高人员', '2025-01-01', 'ACTIVE', '高空作业'); + +-- ============================================= +-- 查询示例 +-- ============================================= + +-- 查询项目所有人员 +-- SELECT * FROM project_member WHERE project_id = 'project001' AND status = 'ACTIVE'; + +-- 查询项目的项目经理 +-- SELECT * FROM project_member WHERE project_id = 'project001' AND role_type = 'PROJECT_MANAGER' AND status = 'ACTIVE'; + +-- 查询用户参与的所有项目 +-- SELECT DISTINCT project_id FROM project_member WHERE user_id = 'user001' AND status = 'ACTIVE'; + +-- 查询机组人员 +-- SELECT * FROM project_member WHERE turbine_id = 'turbine001' AND status = 'ACTIVE'; +CREATE INDEX `idx_project_member_task_status` ON `project_member` (`task_id`, `status`); \ No newline at end of file diff --git a/doc/project_member_test_data.sql b/doc/project_member_test_data.sql new file mode 100644 index 0000000..92fa0fb --- /dev/null +++ b/doc/project_member_test_data.sql @@ -0,0 +1,53 @@ +-- ============================================= +-- 项目人员表测试数据 +-- 基于项目ID: 0b71a1259c49918c6595c9720ad1db5d +-- 使用真实用户ID,使用UUID()生成member_id +-- @author AI Assistant +-- @date 2025/01/27 +-- ============================================= + +-- 清空现有数据(可选) +-- DELETE FROM project_member WHERE project_id = '0b71a1259c49918c6595c9720ad1db5d'; + +-- 插入项目人员测试数据 +INSERT INTO project_member ( + member_id, + project_id, + user_id, + role_type, + job_code, + job_desc, + join_date, + status, + remark +) VALUES +-- 项目经理 +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '008ecfac153be83890c42f9fed0a48cd', 'PROJECT_MANAGER', 'PROJECT_MANAGER', '项目经理', '2025-01-15', 'ACTIVE', '项目总负责人'), + +-- 安全员 +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '014d6fd83182ac03d05967e16748ab80', 'SAFETY_OFFICER', 'SAFETY_MANAGER', '安全经理', '2025-01-15', 'ACTIVE', '负责项目安全管理'), + +-- 质量员 +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '0465342288d0637398b64ca73da76090', 'QUALITY_OFFICER', 'QUALITY_MANAGER', '质量经理', '2025-01-15', 'ACTIVE', '负责项目质量管理'), + +-- 施工组长 +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '008ecfac153be83890c42f9fed0a48cd', 'TEAM_LEADER', 'SITE_MANAGER', '现场经理', '2025-01-15', 'ACTIVE', '负责现场施工管理'), + +-- 地勤人员 +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '014d6fd83182ac03d05967e16748ab80', 'CONSTRUCTOR', 'GROUND_SERVICE', '地勤人员', '2025-01-15', 'ACTIVE', '负责地面施工工作'), + +-- 司机 +(UUID(), '0b71a1259c49918c6595c9720ad1db5d', '0465342288d0637398b64ca73da76090', 'CONSTRUCTOR', 'DRIVER', '司机', '2025-01-15', 'ACTIVE', '负责设备运输和人员接送'); + +-- ============================================= +-- 查询示例 +-- ============================================= + +-- 查询项目所有在职人员 +-- SELECT * FROM project_member WHERE project_id = '0b71a1259c49918c6595c9720ad1db5d' AND status = 'ACTIVE'; + +-- 查询项目的项目经理 +-- SELECT * FROM project_member WHERE project_id = '0b71a1259c49918c6595c9720ad1db5d' AND role_type = 'PROJECT_MANAGER' AND status = 'ACTIVE'; + +-- 按角色类型统计人员数量 +-- SELECT role_type, COUNT(*) as count FROM project_member WHERE project_id = '0b71a1259c49918c6595c9720ad1db5d' AND status = 'ACTIVE' GROUP BY role_type; \ No newline at end of file diff --git a/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java b/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java new file mode 100644 index 0000000..85d786a --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java @@ -0,0 +1,153 @@ +package com.dite.znpt.web.controller; + +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.vo.ProjectMemberListReq; +import com.dite.znpt.domain.vo.ProjectMemberReq; +import com.dite.znpt.domain.vo.ProjectMemberResp; +import com.dite.znpt.domain.vo.ProjectKanbanStatsResp; +import com.dite.znpt.domain.vo.ProjectKanbanDataResp; +import com.dite.znpt.domain.vo.ProjectDetailResp; +import com.dite.znpt.service.ProjectMemberService; +import com.dite.znpt.util.PageUtil; +import com.dite.znpt.util.ValidationGroup; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author wangna + * @date 2025/08/05 + * @Description: 项目人员管理Controller + */ +@Api(tags = "项目人员管理") +@RestController +@RequestMapping("/project-member") +public class ProjectMemberController { + + @Resource + private ProjectMemberService projectMemberService; + + @ApiOperation(value = "查询项目人员列表", httpMethod = "GET") + @GetMapping("/list") + public PageResult list(ProjectMemberListReq req) { + PageUtil.startPage(); + return PageResult.ok(projectMemberService.selectList(req)); + } + + @ApiOperation(value = "根据项目ID查询项目人员", httpMethod = "GET") + @GetMapping("/project/{projectId}") + public Result> getByProjectId(@PathVariable String projectId) { + return Result.ok(projectMemberService.selectByProjectId(projectId)); + } + + @ApiOperation(value = "根据机组ID查询机组人员", httpMethod = "GET") + @GetMapping("/turbine/{turbineId}") + public Result> getByTurbineId(@PathVariable String turbineId) { + return Result.ok(projectMemberService.selectByTurbineId(turbineId)); + } + + @ApiOperation(value = "根据任务组ID查询任务组人员", httpMethod = "GET") + @GetMapping("/task-group/{taskGroupId}") + public Result> getByTaskGroupId(@PathVariable String taskGroupId) { + return Result.ok(projectMemberService.selectByTaskGroupId(taskGroupId)); + } + + @ApiOperation(value = "根据任务ID查询任务人员", httpMethod = "GET") + @GetMapping("/task/{taskId}") + public Result> getByTaskId(@PathVariable String taskId) { + return Result.ok(projectMemberService.selectByTaskId(taskId)); + } + + @ApiOperation(value = "根据用户ID查询用户参与的项目", httpMethod = "GET") + @GetMapping("/user/{userId}") + public Result> getByUserId(@PathVariable String userId) { + return Result.ok(projectMemberService.selectByUserId(userId)); + } + + @ApiOperation(value = "根据项目ID和角色类型查询人员", httpMethod = "GET") + @GetMapping("/project/{projectId}/role/{roleType}") + public Result> getByProjectIdAndRoleType(@PathVariable String projectId, @PathVariable String roleType) { + return Result.ok(projectMemberService.selectByProjectIdAndRoleType(projectId, roleType)); + } + + @ApiOperation(value = "新增项目人员", httpMethod = "POST") + @PostMapping + public Result add(@Validated(ValidationGroup.Insert.class) @RequestBody ProjectMemberReq req) { + projectMemberService.saveData(req); + return Result.ok(); + } + + @ApiOperation(value = "修改项目人员", httpMethod = "PUT") + @PutMapping + public Result edit(@Validated(ValidationGroup.Update.class) @RequestBody ProjectMemberReq req) { + projectMemberService.updateData(req); + return Result.ok(); + } + + @ApiOperation(value = "删除项目人员", httpMethod = "DELETE") + @DeleteMapping("/{memberId}") + public Result remove(@PathVariable String memberId) { + projectMemberService.deleteById(memberId); + return Result.ok(); + } + + @ApiOperation(value = "批量添加项目人员", httpMethod = "POST") + @PostMapping("/batch") + public Result batchAdd(@RequestBody List reqList) { + projectMemberService.batchAddMembers(reqList); + return Result.ok(); + } + + @ApiOperation(value = "根据项目ID删除所有项目人员", httpMethod = "DELETE") + @DeleteMapping("/project/{projectId}") + public Result removeByProjectId(@PathVariable String projectId) { + projectMemberService.deleteByProjectId(projectId); + return Result.ok(); + } + + @ApiOperation(value = "根据机组ID删除所有机组人员", httpMethod = "DELETE") + @DeleteMapping("/turbine/{turbineId}") + public Result removeByTurbineId(@PathVariable String turbineId) { + projectMemberService.deleteByTurbineId(turbineId); + return Result.ok(); + } + + @ApiOperation(value = "根据任务组ID删除所有任务组人员", httpMethod = "DELETE") + @DeleteMapping("/task-group/{taskGroupId}") + public Result removeByTaskGroupId(@PathVariable String taskGroupId) { + projectMemberService.deleteByTaskGroupId(taskGroupId); + return Result.ok(); + } + + @ApiOperation(value = "根据任务ID删除所有任务人员", httpMethod = "DELETE") + @DeleteMapping("/task/{taskId}") + public Result removeByTaskId(@PathVariable String taskId) { + projectMemberService.deleteByTaskId(taskId); + return Result.ok(); + } + + // ========================== 项目看板相关接口 ========================== + + @ApiOperation(value = "获取项目看板统计数据", httpMethod = "GET") + @GetMapping("/kanban/stats") + public Result getProjectKanbanStats() { + return Result.ok(projectMemberService.getProjectKanbanStats()); + } + + @ApiOperation(value = "获取项目看板数据", httpMethod = "GET") + @GetMapping("/kanban/data") + public Result getProjectKanbanData() { + return Result.ok(projectMemberService.getProjectKanbanData()); + } + + @ApiOperation(value = "获取项目详情", httpMethod = "GET") + @GetMapping("/project/{projectId}/detail") + public Result getProjectDetail(@PathVariable String projectId) { + return Result.ok(projectMemberService.getProjectDetail(projectId)); + } +} \ No newline at end of file diff --git a/web/src/main/resources/application-dev.yml b/web/src/main/resources/application-dev.yml index 2268475..497aa0b 100644 --- a/web/src/main/resources/application-dev.yml +++ b/web/src/main/resources/application-dev.yml @@ -3,6 +3,11 @@ server: # 服务器的HTTP端口,默认为8080 port: 8888 address : 0.0.0.0 # 监听所有网络接口 + servlet: + encoding: + enabled: true + charset: UTF-8 + force: true # 数据源配置 spring: