package com.ruoyi.device.service.impl;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.ruoyi.common.constant.MenuJumpPathConstants;
import com.ruoyi.common.core.domain.entity.InformationNotification;
import com.ruoyi.common.core.domain.entity.User;
import com.ruoyi.common.utils.QueryWrappers;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.WxCpUtils;
import com.ruoyi.device.dto.DeviceExaminePlanDetailsDto;
import com.ruoyi.device.dto.DeviceExaminePlanDto;
import com.ruoyi.device.excel.upload.DeviceExaminePlanUpload;
import com.ruoyi.device.mapper.DeviceExaminePlanDetailsMapper;
import com.ruoyi.device.mapper.DeviceExaminePlanMapper;
import com.ruoyi.device.mapper.DeviceMapper;
import com.ruoyi.device.pojo.Device;
import com.ruoyi.device.pojo.DeviceExaminePlan;
import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
import com.ruoyi.device.service.DeviceExaminePlanDetailsService;
import com.ruoyi.device.service.DeviceExaminePlanService;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
import com.ruoyi.inspect.util.UserUtils;
import com.ruoyi.system.mapper.UserMapper;
import com.ruoyi.system.service.InformationNotificationService;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
*
* 设备核查计划主表 服务实现类
*
*
* @author 江苏鵷雏网络科技有限公司
* @since 2024-12-16 07:14:04
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class DeviceExaminePlanServiceImpl extends ServiceImpl implements DeviceExaminePlanService {
@Resource
private DeviceExaminePlanDetailsMapper deviceExaminePlanDetailsMapper;
@Resource
private DeviceExaminePlanDetailsService deviceExaminePlanDetailsService;
@Resource
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Resource
private InformationNotificationService informationNotificationService;
@Resource
private DeviceMapper deviceMapper;
@Resource
private UserMapper userMapper;
/**
* 新增设备核查计划
* @param examinePlanDto
* @return
*/
@Override
public boolean addDeviceExaminePlan(DeviceExaminePlanDto examinePlanDto) {
// 当前登录用户
Integer userId = SecurityUtils.getUserId().intValue();
// 文件名称
examinePlanDto.setWriteUserId(userId);
examinePlanDto.setWriteTime(LocalDateTime.now());
baseMapper.insert(examinePlanDto);
// 添加详情
if (CollectionUtils.isNotEmpty(examinePlanDto.getExaminePlanDetailsList())) {
for (DeviceExaminePlanDetails deviceExaminePlanDetails : examinePlanDto.getExaminePlanDetailsList()) {
deviceExaminePlanDetails.setPlanId(examinePlanDto.getPlanId());
}
deviceExaminePlanDetailsService.saveBatch(examinePlanDto.getExaminePlanDetailsList());
}
return true;
}
/**
* 导入设备核查计划
* @param file
* @return
*/
@Override
public boolean importDeviceExaminePlan(MultipartFile file) {
// 当前登录用户
Integer userId = SecurityUtils.getUserId().intValue();
// 文件名称
String fileName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf("."));
DeviceExaminePlan examinePlan = new DeviceExaminePlan();
examinePlan.setPlanName(fileName);
examinePlan.setWriteUserId(userId);
examinePlan.setWriteTime(LocalDateTime.now());
baseMapper.insert(examinePlan);
List examinePlanDetails = new ArrayList<>();
// 导入附件内容
try {
// excel解析
EasyExcel.read(file.getInputStream(), DeviceExaminePlanUpload.class, new AnalysisEventListener() {
@Override
public void invoke(DeviceExaminePlanUpload detailsUpload, AnalysisContext analysisContext) {
// 判断是否为空
if (StringUtils.isNotBlank(detailsUpload.getDeviceName()) &&
StringUtils.isNotBlank(detailsUpload.getDeviceNumber())) {
// 对象复制
DeviceExaminePlanDetails planDetails = new DeviceExaminePlanDetails();
BeanUtils.copyProperties(detailsUpload, planDetails);
planDetails.setPlanId(examinePlan.getPlanId());
examinePlanDetails.add(planDetails);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}).sheet().doRead();
for (DeviceExaminePlanDetails examinePlanDetail : examinePlanDetails) {
Device device = deviceMapper.selectOne(Wrappers.lambdaQuery()
.eq(Device::getManagementNumber, examinePlanDetail.getDeviceNumber().trim()));
if (device == null) {
throw new ErrorException("设备编号" + examinePlanDetail.getDeviceNumber() + "未查询到设备, 请重新导入");
}
User user = userMapper.selectOne(Wrappers.lambdaQuery()
.eq(User::getName, examinePlanDetail.getCheckChargerUser()));
if (user == null) {
throw new ErrorException("设备编号" + examinePlanDetail.getDeviceNumber() + "未查询到核查人");
}
examinePlanDetail.setDeviceId(device.getId());
examinePlanDetail.setCheckChargerUserId(user.getId());
}
deviceExaminePlanDetailsService.saveBatch(examinePlanDetails);
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
/**
* 提交批准
* @param deviceExaminePlan
* @return
*/
@Override
public boolean submitRatifyDeviceExaminePlan(DeviceExaminePlan deviceExaminePlan) {
baseMapper.update(null, Wrappers.lambdaUpdate()
.eq(DeviceExaminePlan::getPlanId, deviceExaminePlan.getPlanId())
.set(DeviceExaminePlan::getRatifyUserId, deviceExaminePlan.getRatifyUserId())
.set(DeviceExaminePlan::getRatifyRemark, null)
.set(DeviceExaminePlan::getRatifyStatus, null)
.set(DeviceExaminePlan::getRatifyTime, null)
);
DeviceExaminePlan examinePlan = baseMapper.selectById(deviceExaminePlan.getPlanId());
// 发送消息
Integer userId = SecurityUtils.getUserId().intValue();
User user = userMapper.selectById(userId);
// 消息发送
InformationNotification info = new InformationNotification();
// 发送人
info.setCreateUser(user.getName());
info.setMessageType("6");
info.setTheme("CNAS设备核查计划批准通知");
info.setContent(examinePlan.getPlanName() + "的设备核查计划待批准");
info.setSenderId(userId);
// 接收人
info.setConsigneeId(examinePlan.getRatifyUserId());
info.setJumpPath(MenuJumpPathConstants.DEVICE);
informationNotificationService.addInformationNotification(info);
// 发送企业微信通知
threadPoolTaskExecutor.execute(() -> {
// 查询接收人
User personnel = userMapper.selectById(examinePlan.getRatifyUserId());
String message = "";
message += "CNAS设备核查计划批准通知";
message += "\n请去资源管理-设备一层目录-设备核查计划填写";
message += "\n" + examinePlan.getPlanName() + "的校准核查划待批准";
//发送企业微信消息通知
try {
WxCpUtils.inform(personnel.getAccount(), message, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return false;
}
/**
* 设备核查计划批准
* @param deviceExaminePlan
* @return
*/
@Override
public boolean ratifyDeviceExaminePlan(DeviceExaminePlan deviceExaminePlan) {
// 当前登录用户
baseMapper.update(null, Wrappers.lambdaUpdate()
.eq(DeviceExaminePlan::getPlanId, deviceExaminePlan.getPlanId())
.set(DeviceExaminePlan::getRatifyRemark, deviceExaminePlan.getRatifyRemark())
.set(DeviceExaminePlan::getRatifyStatus, deviceExaminePlan.getRatifyStatus())
.set(DeviceExaminePlan::getRatifyTime, LocalDateTime.now())
);
return true;
}
/**
* 设备核查计划列表
* @param page
* @param deviceExaminePlan
* @return
*/
@Override
public IPage pageDeviceExaminePlan(Page page, DeviceExaminePlan deviceExaminePlan) {
return baseMapper.deviceExaminePlanDetailsMapper(page, QueryWrappers.queryWrappers(deviceExaminePlan));
}
/**
* 设备核查计划详情列表
* @param page
* @param deviceExaminePlanDetails
* @return
*/
@Override
public IPage pageDeviceExaminePlanDetail(Page page, DeviceExaminePlanDetails deviceExaminePlanDetails) {
if (deviceExaminePlanDetails.getPlanId() == null) {
return new Page();
}
return deviceExaminePlanDetailsMapper.pageDeviceExaminePlanDetail(page, QueryWrappers.queryWrappers(deviceExaminePlanDetails));
}
/**
* 导出设备核查计划
* @param deviceExaminePlanId 设备核查计划id
* @param response 响应体
*/
@Override
public void exportDeviceExaminePlanDetail(Integer deviceExaminePlanId, HttpServletResponse response) {
// 查询设备核查计划
DeviceExaminePlanDto deviceExaminePlanDto = baseMapper.selectExamineExaminePlanDto(deviceExaminePlanId);
// 查询设备核查计划详情
List deviceExaminePlanDetailsList = deviceExaminePlanDetailsMapper.selectList(Wrappers.lambdaQuery().eq(DeviceExaminePlanDetails::getPlanId, deviceExaminePlanId)
.orderByAsc(DeviceExaminePlanDetails::getCheckTime));
// 设置序号
ArrayList deviceExaminePlanDetailsDtoList = new ArrayList<>();
deviceExaminePlanDetailsList.forEach(deviceExamineRecordContrastDetail -> {
DeviceExaminePlanDetailsDto deviceExaminePlanDetailsDto = new DeviceExaminePlanDetailsDto();
BeanUtils.copyProperties(deviceExamineRecordContrastDetail, deviceExaminePlanDetailsDto);
deviceExaminePlanDetailsDto.setIndex(deviceExaminePlanDetailsList.indexOf(deviceExamineRecordContrastDetail) + 1);
deviceExaminePlanDetailsDtoList.add(deviceExaminePlanDetailsDto);
});
// 获取路径
InputStream inputStream = this.getClass().getResourceAsStream("/static/word/examine-plan-detail.docx");
Configure configure = Configure.builder()
.bind("deviceExaminePlanDetailsDtoList", new HackLoopTableRenderPolicy())
.build();
XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
new HashMap() {{
put("deviceExaminePlan", deviceExaminePlanDto);
put("deviceExaminePlanDetailsDtoList", deviceExaminePlanDetailsDtoList);
//获取核查人的签名地址
put("writeUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getWriteUserId()));
//获取审查人的签名地址
put("reviewUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getRatifyUserId()));
}});
try {
response.setContentType("application/msword");
String fileName = URLEncoder.encode(
"设备核查计划", "UTF-8");
response.setHeader("Content-disposition",
"attachment;filename=" + fileName + ".docx");
OutputStream os = response.getOutputStream();
template.write(os);
os.flush();
os.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("导出失败");
}
}
@Override
public boolean updateDeviceExaminePlan(DeviceExaminePlanDto examinePlanDto) {
// 当前登录用户
Integer userId = SecurityUtils.getUserId().intValue();
// 文件名称
examinePlanDto.setWriteUserId(userId);
examinePlanDto.setWriteTime(LocalDateTime.now());
baseMapper.updateById(examinePlanDto);
deviceExaminePlanDetailsService.remove(Wrappers.lambdaQuery()
.eq(DeviceExaminePlanDetails::getPlanId, examinePlanDto.getPlanId()));
// 添加详情
if (CollectionUtils.isNotEmpty(examinePlanDto.getExaminePlanDetailsList())) {
for (DeviceExaminePlanDetails deviceExaminePlanDetails : examinePlanDto.getExaminePlanDetailsList()) {
deviceExaminePlanDetails.setPlanId(examinePlanDto.getPlanId());
}
deviceExaminePlanDetailsService.saveBatch(examinePlanDto.getExaminePlanDetailsList());
}
return true;
}
@Override
public DeviceExaminePlanDto getDeviceExaminePlan(Integer planId) {
DeviceExaminePlan examinePlan = baseMapper.selectById(planId);
DeviceExaminePlanDto deviceExaminePlanDto = new DeviceExaminePlanDto();
BeanUtils.copyProperties(examinePlan, deviceExaminePlanDto);
// 查询详情
List list = deviceExaminePlanDetailsService.list(Wrappers.lambdaQuery()
.eq(DeviceExaminePlanDetails::getPlanId, planId)
.orderByAsc(DeviceExaminePlanDetails::getCheckTime));
deviceExaminePlanDto.setExaminePlanDetailsList(list);
return deviceExaminePlanDto;
}
/**
* 按年份查询设备核查计划(不存在则自动创建),并自动补全已到点的设备明细
*/
@Override
public DeviceExaminePlanDto getOrCreatePlanByYear(String planYear) {
if (StringUtils.isBlank(planYear)) {
planYear = String.valueOf(LocalDate.now().getYear());
}
DeviceExaminePlan plan = getOrCreatePlanEntity(planYear);
ensureExamineDetails(plan);
DeviceExaminePlanDto planDto = new DeviceExaminePlanDto();
BeanUtils.copyProperties(plan, planDto);
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
planDto.setWriteTimeStr(plan.getWriteTime() == null ? null : plan.getWriteTime().format(dateFormatter));
planDto.setRatifyTimeStr(plan.getRatifyTime() == null ? null : plan.getRatifyTime().format(dateFormatter));
if (plan.getWriteUserId() != null) {
User writeUser = userMapper.selectById(plan.getWriteUserId());
if (writeUser != null) {
planDto.setWriteName(writeUser.getName());
}
}
if (plan.getRatifyUserId() != null) {
User ratifyUser = userMapper.selectById(plan.getRatifyUserId());
if (ratifyUser != null) {
planDto.setRatifyName(ratifyUser.getName());
}
}
return planDto;
}
/**
* 设备档案变更后,把设备同步到对应年份的设备核查计划明细
*/
@Override
public void syncDeviceExamineDetail(Device device) {
LocalDate remind = calcExamineRemindDate(device);
if (remind == null || remind.isAfter(LocalDate.now())) {
return;
}
DeviceExaminePlan plan = getOrCreatePlanEntity(String.valueOf(remind.getYear()));
fillExamineDetailIfAbsent(plan, device);
}
/**
* 复核通过后,把设备档案的最近/下次核查日期滚动到本轮
*/
@Override
public void updateDeviceExamineDate(Integer planDetailsId) {
DeviceExaminePlanDetails detail = deviceExaminePlanDetailsMapper.selectById(planDetailsId);
if (detail == null || detail.getDeviceId() == null) {
return;
}
Device device = deviceMapper.selectById(detail.getDeviceId());
if (device == null) {
return;
}
LocalDate checkDate = parseCheckDate(detail.getCheckTime());
if (checkDate.isAfter(LocalDate.now())) {
checkDate = LocalDate.now();
}
LocalDateTime checkDateTime = checkDate.atStartOfDay();
// 只允许向前滚动,避免重复复核旧记录时把日期改回去
if (device.getLastExamineDate() != null && !checkDateTime.isAfter(device.getLastExamineDate())) {
return;
}
device.setLastExamineDate(checkDateTime);
if (StringUtils.isNotBlank(device.getExamineCycle())) {
try {
device.setNextExamineDate(checkDateTime.plusMonths(Long.parseLong(device.getExamineCycle().trim())));
} catch (NumberFormatException ignored) {
}
}
deviceMapper.updateById(device);
}
/**
* 查询年份对应的计划,不存在则创建
*/
private DeviceExaminePlan getOrCreatePlanEntity(String planYear) {
DeviceExaminePlan plan = baseMapper.selectOne(Wrappers.lambdaQuery()
.eq(DeviceExaminePlan::getPlanYear, planYear)
.orderByDesc(DeviceExaminePlan::getPlanId)
.last("limit 1"));
if (plan == null) {
plan = new DeviceExaminePlan();
plan.setPlanName(planYear + "年度设备核查计划");
plan.setPlanYear(planYear);
plan.setWriteUserId(SecurityUtils.getUserId().intValue());
plan.setWriteTime(LocalDateTime.now());
baseMapper.insert(plan);
}
return plan;
}
/**
* 扫描所有设备,把已到点(下次核查日期前1个月起,含已过期)的设备补进本年度计划
*/
private void ensureExamineDetails(DeviceExaminePlan plan) {
List devices = deviceMapper.selectList(null);
for (Device device : devices) {
fillExamineDetailIfAbsent(plan, device);
}
}
/**
* 设备提醒起点:下次核查日期前1个月(没填下次日期时用最近日期+核查周期兜底)
*/
private LocalDate calcExamineRemindDate(Device device) {
LocalDateTime base = device.getNextExamineDate();
if (base == null && device.getLastExamineDate() != null && StringUtils.isNotBlank(device.getExamineCycle())) {
try {
base = device.getLastExamineDate().plusMonths(Long.parseLong(device.getExamineCycle().trim()));
} catch (NumberFormatException ignored) {
}
}
return base == null ? null : base.toLocalDate().minusMonths(1);
}
/**
* 已到点且计划中尚无该设备本轮明细时自动补一条
*/
private void fillExamineDetailIfAbsent(DeviceExaminePlan plan, Device device) {
LocalDate remind = calcExamineRemindDate(device);
if (remind == null || remind.isAfter(LocalDate.now())) {
return;
}
if (plan.getPlanYear() != null && String.valueOf(remind.getYear()).compareTo(plan.getPlanYear()) > 0) {
return;
}
String checkTime = remind.format(DateTimeFormatter.ofPattern("yyyy-MM"));
Long exist = deviceExaminePlanDetailsMapper.selectCount(Wrappers.lambdaQuery()
.eq(DeviceExaminePlanDetails::getPlanId, plan.getPlanId())
.eq(DeviceExaminePlanDetails::getDeviceId, device.getId())
.likeRight(DeviceExaminePlanDetails::getCheckTime, checkTime));
if (exist != null && exist > 0) {
return;
}
DeviceExaminePlanDetails detail = new DeviceExaminePlanDetails();
detail.setPlanId(plan.getPlanId());
detail.setDeviceId(device.getId());
String deviceName = device.getDeviceName() == null ? "" : device.getDeviceName();
if (StringUtils.isNotBlank(device.getSpecificationModel())) {
deviceName = deviceName + " " + device.getSpecificationModel();
}
detail.setDeviceName(deviceName);
detail.setDeviceNumber(device.getManagementNumber());
detail.setCheckTime(checkTime);
deviceExaminePlanDetailsMapper.insert(detail);
}
/**
* 计划明细的核查时间可能是 yyyy-MM 或 yyyy-MM-dd,统一取月份首日
*/
private LocalDate parseCheckDate(String checkTime) {
if (StringUtils.isNotBlank(checkTime)) {
String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
if (normalized.length() >= 7) {
try {
return YearMonth.parse(normalized.substring(0, 7)).atDay(1);
} catch (Exception ignored) {
}
}
}
return LocalDate.now();
}
}