From c21cb3d8cebd9cdef4df2efa372c5b9257c713e4 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期三, 28 十月 2026 17:57:40 +0800
Subject: [PATCH] feat(device,supply,satisfaction): 设备计划后端改造、耗材存放位置与满意度照片支持
---
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java | 781 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 737 insertions(+), 44 deletions(-)
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
index 4a4d45b..5e8fd20 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
@@ -9,8 +9,6 @@
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;
@@ -20,20 +18,40 @@
import com.ruoyi.device.dto.DeviceExaminePlanDetailsDto;
import com.ruoyi.device.dto.DeviceExaminePlanDto;
import com.ruoyi.device.excel.upload.DeviceExaminePlanUpload;
+import com.ruoyi.device.mapper.DeviceExamineAttachmentMapper;
import com.ruoyi.device.mapper.DeviceExaminePlanDetailsMapper;
import com.ruoyi.device.mapper.DeviceExaminePlanMapper;
+import com.ruoyi.device.mapper.DeviceExamineRecordContrastDetailsMapper;
+import com.ruoyi.device.mapper.DeviceExamineRecordContrastMapper;
+import com.ruoyi.device.mapper.DeviceExamineRecordDetailMapper;
+import com.ruoyi.device.mapper.DeviceExamineRecordMapper;
import com.ruoyi.device.mapper.DeviceMapper;
import com.ruoyi.device.pojo.Device;
+import com.ruoyi.device.pojo.DeviceExamineAttachment;
import com.ruoyi.device.pojo.DeviceExaminePlan;
import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
+import com.ruoyi.device.pojo.DeviceExamineRecord;
+import com.ruoyi.device.pojo.DeviceExamineRecordContrast;
+import com.ruoyi.device.pojo.DeviceExamineRecordContrastDetails;
+import com.ruoyi.device.pojo.DeviceExamineRecordDetail;
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.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -41,14 +59,20 @@
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
+import java.io.File;
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.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* <p>
@@ -74,6 +98,22 @@
private DeviceMapper deviceMapper;
@Resource
private UserMapper userMapper;
+ @Resource
+ private DeviceExamineRecordMapper deviceExamineRecordMapper;
+ @Resource
+ private DeviceExamineRecordContrastMapper deviceExamineRecordContrastMapper;
+ @Resource
+ private DeviceExamineRecordDetailMapper deviceExamineRecordDetailMapper;
+ @Resource
+ private DeviceExamineRecordContrastDetailsMapper deviceExamineRecordContrastDetailsMapper;
+ @Resource
+ private DeviceExamineAttachmentMapper deviceExamineAttachmentMapper;
+
+ @Value("${file.path}")
+ private String filePath;
+
+ @Value("${wordUrl}")
+ private String wordUrl;
/**
* 鏂板璁惧鏍告煡璁″垝
@@ -152,6 +192,7 @@
throw new ErrorException("璁惧缂栧彿" + examinePlanDetail.getDeviceNumber() + "鏈煡璇㈠埌鏍告煡浜�");
}
examinePlanDetail.setDeviceId(device.getId());
+ examinePlanDetail.setDeviceModel(device.getSpecificationModel());
examinePlanDetail.setCheckChargerUserId(user.getId());
}
@@ -258,57 +299,196 @@
}
/**
- * 瀵煎嚭璁惧鏍告煡璁″垝
+ * 瀵煎嚭璁惧鏍告煡璁″垝锛圗xcel锛夛細鏈夌瓫閫夋潯浠舵椂瀵煎嚭绛涢�夌粨鏋滐紝娌℃湁绛涢�夋椂瀵煎嚭璇ュ勾浠藉叏閮�
* @param deviceExaminePlanId 璁惧鏍告煡璁″垝id
+ * @param deviceName 璁惧鍚嶇О绛涢��
+ * @param deviceNumber 璁惧缂栧彿绛涢��
* @param response 鍝嶅簲浣�
*/
@Override
- public void exportDeviceExaminePlanDetail(Integer deviceExaminePlanId, HttpServletResponse response) {
- // 鏌ヨ璁惧鏍告煡璁″垝
- DeviceExaminePlanDto deviceExaminePlanDto = baseMapper.selectExamineExaminePlanDto(deviceExaminePlanId);
+ public void exportDeviceExaminePlanDetail(Integer deviceExaminePlanId, String deviceName, String deviceNumber, HttpServletResponse response) {
+ DeviceExaminePlan deviceExaminePlan = baseMapper.selectById(deviceExaminePlanId);
+ if (deviceExaminePlan == null) {
+ throw new ErrorException("璁惧鏍告煡璁″垝涓嶅瓨鍦�");
+ }
+ DeviceExaminePlanDetails query = new DeviceExaminePlanDetails();
+ query.setPlanId(deviceExaminePlanId);
+ query.setDeviceName(deviceName);
+ query.setDeviceNumber(deviceNumber);
+ List<DeviceExaminePlanDetailsDto> deviceExaminePlanDetailsDtoList =
+ deviceExaminePlanDetailsMapper.listDeviceExaminePlanDetail(QueryWrappers.queryWrappers(query));
- // 鏌ヨ璁惧鏍告煡璁″垝璇︽儏
- List<DeviceExaminePlanDetails> deviceExaminePlanDetailsList = deviceExaminePlanDetailsMapper.selectList(Wrappers.<DeviceExaminePlanDetails>lambdaQuery().eq(DeviceExaminePlanDetails::getPlanId, deviceExaminePlanId)
- .orderByAsc(DeviceExaminePlanDetails::getCheckTime));
- // 璁剧疆搴忓彿
- ArrayList<DeviceExaminePlanDetailsDto> deviceExaminePlanDetailsDtoList = new ArrayList<>();
- deviceExaminePlanDetailsList.forEach(deviceExamineRecordContrastDetail -> {
- DeviceExaminePlanDetailsDto deviceExaminePlanDetailsDto = new DeviceExaminePlanDetailsDto();
- BeanUtils.copyProperties(deviceExamineRecordContrastDetail, deviceExaminePlanDetailsDto);
- deviceExaminePlanDetailsDto.setIndex(deviceExaminePlanDetailsList.indexOf(deviceExamineRecordContrastDetail) + 1);
- deviceExaminePlanDetailsDtoList.add(deviceExaminePlanDetailsDto);
- });
+ String planName = StringUtils.isNotBlank(deviceExaminePlan.getPlanName()) ? deviceExaminePlan.getPlanName() : "璁惧鏍告煡璁″垝";
+ String[] columns = {"搴忓彿", "璁惧缂栧彿", "璁惧鍚嶇О", "璁惧鍨嬪彿", "鏍告煡鏃堕棿", "鏍告煡鎸囨爣",
+ "鏍告煡鏂规硶", "缁撴灉濡備綍鍒ゅ畾", "鏍告煡璐熻矗浜�", "瀹℃牳鐘舵��", "澶囨敞"};
+ List<String[]> dataRows = new ArrayList<>();
+ int index = 1;
+ for (DeviceExaminePlanDetailsDto detail : deviceExaminePlanDetailsDtoList) {
+ String checkTime = StringUtils.isNotBlank(detail.getCompleteDate()) ? detail.getCompleteDate() : detail.getCheckTime();
+ dataRows.add(new String[]{
+ String.valueOf(index++),
+ blankToEmpty(detail.getDeviceNumber()),
+ blankToEmpty(detail.getDeviceName()),
+ blankToEmpty(detail.getDeviceModel()),
+ blankToEmpty(checkTime),
+ blankToEmpty(detail.getCheckIndex()),
+ blankToEmpty(detail.getCheckMethod()),
+ blankToEmpty(detail.getHowResults()),
+ blankToEmpty(detail.getCheckChargerUser()),
+ recordStatusText(detail.getRecordStatus()),
+ blankToEmpty(detail.getRemark())
+ });
+ }
- // 鑾峰彇璺緞
- 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<String, Object>() {{
- put("deviceExaminePlan", deviceExaminePlanDto);
- put("deviceExaminePlanDetailsDtoList", deviceExaminePlanDetailsDtoList);
- //鑾峰彇鏍告煡浜虹殑绛惧悕鍦板潃
- put("writeUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExaminePlanDto.getWriteUserId()));
- //鑾峰彇瀹℃煡浜虹殑绛惧悕鍦板潃
- put("reviewUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExaminePlanDto.getRatifyUserId()));
- }});
-
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ response.setCharacterEncoding("UTF-8");
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();
+ "attachment;filename=" + URLEncoder.encode(planName, "UTF-8") + ".xlsx");
+ writePlanExcel(response, planName, columns, dataRows);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("瀵煎嚭澶辫触");
}
+ }
+
+ /**
+ * 鍐欒鍒掕〃锛氭爣棰樿 + 鍒楀悕 + 鏁版嵁锛屾暣浣撳彧鍔犳渶澶栬竟妗嗭紝鍏ㄩ儴灞呬腑锛屾棤鑳屾櫙鑹�
+ */
+ private void writePlanExcel(HttpServletResponse response, String planName, String[] columns,
+ List<String[]> dataRows) throws IOException {
+ Workbook workbook = new XSSFWorkbook();
+ Map<String, CellStyle> styleCache = new HashMap<>();
+ Font titleFont = buildExcelFont(workbook, true, 16);
+ Font headerFont = buildExcelFont(workbook, true, 12);
+ Font contentFont = buildExcelFont(workbook, false, 12);
+
+ Sheet sheet = workbook.createSheet("璁惧鏍告煡璁″垝");
+ int lastColumn = columns.length - 1;
+
+ Row titleRow = sheet.createRow(0);
+ titleRow.setHeightInPoints(30);
+ for (int c = 0; c <= lastColumn; c++) {
+ Cell cell = titleRow.createCell(c);
+ if (c == 0) {
+ cell.setCellValue(planName);
+ }
+ cell.setCellStyle(exportStyle(workbook, styleCache, titleFont, c == 0, c == lastColumn, true, false));
+ }
+ sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, lastColumn));
+
+ Row headerRow = sheet.createRow(1);
+ headerRow.setHeightInPoints(22);
+ for (int c = 0; c <= lastColumn; c++) {
+ Cell cell = headerRow.createCell(c);
+ cell.setCellValue(columns[c]);
+ cell.setCellStyle(exportStyle(workbook, styleCache, headerFont,
+ c == 0, c == lastColumn, false, dataRows.isEmpty()));
+ }
+
+ for (int r = 0; r < dataRows.size(); r++) {
+ Row row = sheet.createRow(r + 2);
+ boolean lastDataRow = r == dataRows.size() - 1;
+ for (int c = 0; c <= lastColumn; c++) {
+ Cell cell = row.createCell(c);
+ cell.setCellValue(dataRows.get(r)[c]);
+ cell.setCellStyle(exportStyle(workbook, styleCache, contentFont,
+ c == 0, c == lastColumn, false, lastDataRow));
+ }
+ }
+
+ for (int c = 0; c <= lastColumn; c++) {
+ int width = textWidth(columns[c]);
+ for (String[] row : dataRows) {
+ width = Math.max(width, textWidth(row[c]));
+ }
+ sheet.setColumnWidth(c, Math.min(255, width + 4) * 256);
+ }
+
+ OutputStream os = response.getOutputStream();
+ workbook.write(os);
+ os.flush();
+ workbook.close();
+ }
+
+ private Font buildExcelFont(Workbook workbook, boolean bold, int size) {
+ Font font = workbook.createFont();
+ font.setFontName("瀹嬩綋");
+ font.setBold(bold);
+ font.setFontHeightInPoints((short) size);
+ return font;
+ }
+
+ /**
+ * 鍗曞厓鏍兼牱寮忥細灞呬腑銆佹棤鑳屾櫙鑹诧紱鍐呴儴缁嗙綉鏍肩嚎锛屾暣浣撴渶澶栧湀绮楄竟妗�
+ */
+ private CellStyle exportStyle(Workbook workbook, Map<String, CellStyle> styleCache, Font font,
+ boolean outerLeft, boolean outerRight, boolean outerTop, boolean outerBottom) {
+ String key = font.getBold() + "-" + font.getFontHeightInPoints() + "-" + (outerLeft ? 1 : 0)
+ + (outerRight ? 1 : 0) + (outerTop ? 1 : 0) + (outerBottom ? 1 : 0);
+ CellStyle cached = styleCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ CellStyle style = workbook.createCellStyle();
+ style.setFont(font);
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setBorderLeft(BorderStyle.THIN);
+ style.setBorderRight(BorderStyle.THIN);
+ style.setBorderTop(BorderStyle.THIN);
+ style.setBorderBottom(BorderStyle.THIN);
+ if (outerLeft) {
+ style.setBorderLeft(BorderStyle.MEDIUM);
+ }
+ if (outerRight) {
+ style.setBorderRight(BorderStyle.MEDIUM);
+ }
+ if (outerTop) {
+ style.setBorderTop(BorderStyle.MEDIUM);
+ }
+ if (outerBottom) {
+ style.setBorderBottom(BorderStyle.MEDIUM);
+ }
+ styleCache.put(key, style);
+ return style;
+ }
+
+ /**
+ * 浼扮畻鏄剧ず瀹藉害锛堜腑鏂囨寜涓や釜瀛楃瀹斤級
+ */
+ private int textWidth(String text) {
+ if (text == null) {
+ return 0;
+ }
+ int width = 0;
+ for (int i = 0; i < text.length(); i++) {
+ width += text.charAt(i) > 0xFF ? 2 : 1;
+ }
+ return width;
+ }
+
+ /**
+ * 璁板綍鐘舵�佹枃妗堬細0鏈紑濮嬨��1寰呮壒鍑嗐��2閫氳繃銆�3涓嶉�氳繃
+ */
+ private String recordStatusText(Integer recordStatus) {
+ if (recordStatus == null) {
+ return "鏈紑濮�";
+ }
+ switch (recordStatus) {
+ case 1:
+ return "寰呮壒鍑�";
+ case 2:
+ return "閫氳繃";
+ case 3:
+ return "涓嶉�氳繃";
+ default:
+ return "鏈紑濮�";
+ }
+ }
+
+ private String blankToEmpty(String value) {
+ return value == null ? "" : value;
}
@Override
@@ -346,4 +526,517 @@
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 List<String> getExaminePlanYears() {
+ List<DeviceExaminePlan> plans = baseMapper.selectList(null);
+ for (DeviceExaminePlan plan : plans) {
+ Long detailCount = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+ .eq(DeviceExaminePlanDetails::getPlanId, plan.getPlanId()));
+ if (detailCount == null || detailCount == 0) {
+ removeById(plan.getPlanId());
+ }
+ }
+ return baseMapper.selectPlanYears();
+ }
+
+ /**
+ * 璁惧妗f鍙樻洿鍚庯紝绔嬪嵆鎶婅澶囩櫥璁板埌涓嬫鏍告煡鏃ユ湡鎵�鍦ㄥ勾浠界殑璁惧鏍告煡璁″垝鏄庣粏
+ */
+ @Override
+ public void syncDeviceExamineDetail(Device device) {
+ LocalDate due = calcExamineDueDate(device);
+ if (due == null) {
+ return;
+ }
+ DeviceExaminePlan plan = getOrCreatePlanEntity(String.valueOf(due.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;
+ }
+ // 瀹℃牳閫氳繃褰撳ぉ鍗宠涓哄疄闄呮牳鏌ュ畬鎴愭椂闂�
+ LocalDateTime checkDateTime = LocalDate.now().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);
+ // 鏈疆鏍告煡瀹屾垚鍚庣珛鍗崇櫥璁颁笅涓�杞紝鍘嗗彶鏄庣粏鍜岃褰曚繚鎸佷笉鍔�
+ syncDeviceExamineDetail(device);
+ }
+
+ /**
+ * 鏌ヨ骞翠唤瀵瑰簲鐨勮鍒掞紝涓嶅瓨鍦ㄥ垯鍒涘缓
+ */
+ private DeviceExaminePlan getOrCreatePlanEntity(String planYear) {
+ DeviceExaminePlan plan = baseMapper.selectOne(Wrappers.<DeviceExaminePlan>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;
+ }
+
+ /**
+ * 鎵弿鎵�鏈夎澶囷紝鎶婃湰骞村害鍙婂線骞撮�炬湡鏈牳鏌ョ殑璁惧琛ヨ繘璁″垝
+ */
+ private void ensureExamineDetails(DeviceExaminePlan plan) {
+ List<Device> devices = deviceMapper.selectList(null);
+ for (Device device : devices) {
+ fillExamineDetailIfAbsent(plan, device);
+ }
+ }
+
+ /**
+ * 璁惧鏈疆搴旀牳鏌ユ棩鏈燂紙娌″~涓嬫鏃ユ湡鏃剁敤鏈�杩戞棩鏈�+鏍告煡鍛ㄦ湡鍏滃簳锛�
+ */
+ private LocalDate calcExamineDueDate(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();
+ }
+
+ /**
+ * 璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃惰嚜鍔ㄨˉ涓�鏉★紱鏍告煡鏃堕棿鍙栨。妗堜笅娆℃牳鏌ユ棩鏈燂紙绮剧‘鍒版棩锛�
+ */
+ private void fillExamineDetailIfAbsent(DeviceExaminePlan plan, Device device) {
+ LocalDate due = calcExamineDueDate(device);
+ if (due == null) {
+ return;
+ }
+ if (StringUtils.isBlank(plan.getPlanYear())) {
+ return;
+ }
+ int planYearValue;
+ try {
+ planYearValue = Integer.parseInt(plan.getPlanYear().trim());
+ } catch (NumberFormatException e) {
+ return;
+ }
+ int dueYear = due.getYear();
+ // 鏈潵骞翠唤鐨勮鍒掑彧鐧昏搴旀牳鏌ユ棩鏈熷氨鍦ㄨ骞翠唤鐨勮澶囷紝閬垮厤鎶婂線骞寸殑鏃ф棩鏈熻ˉ杩涙湭鏉ョ殑璁″垝
+ if (planYearValue > LocalDate.now().getYear()) {
+ if (dueYear != planYearValue) {
+ return;
+ }
+ } else if (dueYear > planYearValue) {
+ return;
+ }
+ String checkTime = due.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+ Long exist = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>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());
+ detail.setDeviceName(device.getDeviceName() == null ? "" : device.getDeviceName());
+ detail.setDeviceModel(device.getSpecificationModel());
+ detail.setDeviceNumber(device.getManagementNumber());
+ detail.setCheckTime(checkTime);
+ deviceExaminePlanDetailsMapper.insert(detail);
+ }
+
+ /**
+ * 涓烘寚瀹氳澶囨壘涓�鏉″彲褰曞叆鏍告煡璁板綍鐨勮鍒掓槑缁嗭紱娌℃湁鍙敤鏄庣粏鏃跺湪褰撳墠骞村害璁″垝鏂板鏈疆鏄庣粏
+ */
+ @Override
+ public DeviceExaminePlanDetails resolveExaminePlanDetail(Integer deviceId, String checkTime) {
+ return findExaminePlanDetail(deviceId, checkTime, true);
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾璁惧鍙鐢ㄧ殑璁″垝鏄庣粏锛涘彧鏌ヨ锛屼笉鏂板璁″垝鎴栨槑缁�
+ */
+ @Override
+ public DeviceExaminePlanDetails findExaminePlanDetail(Integer deviceId, String checkTime) {
+ return findExaminePlanDetail(deviceId, checkTime, false);
+ }
+
+ private DeviceExaminePlanDetails findExaminePlanDetail(Integer deviceId, String checkTime, boolean createIfAbsent) {
+ if (deviceId == null) {
+ throw new ErrorException("璇烽�夋嫨鏍告煡璁惧");
+ }
+ Device device = deviceMapper.selectById(deviceId);
+ if (device == null) {
+ throw new ErrorException("鏍告煡璁惧涓嶅瓨鍦�");
+ }
+ String checkMonth = normalizeCheckMonth(checkTime);
+ String planYear = checkMonth.substring(0, 4);
+ DeviceExaminePlan plan;
+ if (createIfAbsent) {
+ plan = getOrCreatePlanEntity(planYear);
+ } else {
+ plan = baseMapper.selectOne(Wrappers.<DeviceExaminePlan>lambdaQuery()
+ .eq(DeviceExaminePlan::getPlanYear, planYear)
+ .orderByDesc(DeviceExaminePlan::getPlanId)
+ .last("limit 1"));
+ }
+ List<DeviceExaminePlanDetails> details = deviceExaminePlanDetailsMapper.selectList(
+ Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+ .eq(DeviceExaminePlanDetails::getDeviceId, deviceId)
+ .orderByAsc(DeviceExaminePlanDetails::getCheckTime));
+ DeviceExaminePlanDetails nearest = null;
+ long nearestDistance = Long.MAX_VALUE;
+ for (DeviceExaminePlanDetails detail : details) {
+ // 宸叉湁鏍告煡璁板綍鐨勬槑缁嗕唬琛ㄥ凡瀹屾垚鐨勪竴杞紝鍚庣画鏍告煡姘歌繙涓嶅鐢ㄣ�佷笉瑕嗙洊
+ Long recordCount = deviceExamineRecordMapper.selectCount(Wrappers.<DeviceExamineRecord>lambdaQuery()
+ .eq(DeviceExamineRecord::getPlanDetailsId, detail.getPlanDetailsId()));
+ if (recordCount != null && recordCount > 0) {
+ continue;
+ }
+ if (plan != null && detail.getPlanId() != null && detail.getPlanId().equals(plan.getPlanId())) {
+ return detail;
+ }
+ long distance = monthDistance(detail.getCheckTime(), checkMonth);
+ if (distance < nearestDistance) {
+ nearest = detail;
+ nearestDistance = distance;
+ }
+ }
+ // 鎻愬墠鏍告煡鏃朵紭鍏堝鐢ㄧ郴缁熺櫥璁般�佸皻鏈牳鏌ョ殑杩欎竴杞紙鍙兘鍦ㄥ埆鐨勫勾浠借鍒掗噷锛�
+ if (nearest != null) {
+ return nearest;
+ }
+ if (!createIfAbsent) {
+ return null;
+ }
+ DeviceExaminePlanDetails detail = new DeviceExaminePlanDetails();
+ detail.setPlanId(plan.getPlanId());
+ detail.setDeviceId(device.getId());
+ detail.setDeviceName(device.getDeviceName() == null ? "" : device.getDeviceName());
+ detail.setDeviceModel(device.getSpecificationModel());
+ detail.setDeviceNumber(device.getManagementNumber());
+ String detailCheckTime = normalizeCheckDate(checkTime);
+ detail.setCheckTime(StringUtils.isBlank(detailCheckTime) ? checkMonth : detailCheckTime);
+ deviceExaminePlanDetailsMapper.insert(detail);
+ return detail;
+ }
+
+ /**
+ * 瑙勮寖鏍告煡鏈堜唤涓� yyyy-MM锛岄潪娉曟垨涓虹┖鏃跺彇褰撳墠鏈堜唤
+ */
+ private String normalizeCheckMonth(String checkTime) {
+ if (StringUtils.isNotBlank(checkTime)) {
+ String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+ if (normalized.length() >= 7) {
+ try {
+ return YearMonth.parse(normalized.substring(0, 7)).toString();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
+ }
+
+ /**
+ * 瑙勮寖鏍告煡鏃ユ湡锛氬甫鍏蜂綋鏃ユ湡鐨勪繚鐣欏埌鏃ワ紝鍙~鏈堜唤鐨勮鑼冧负 yyyy-MM
+ */
+ private String normalizeCheckDate(String checkTime) {
+ if (StringUtils.isBlank(checkTime)) {
+ return null;
+ }
+ String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+ if (normalized.length() >= 10) {
+ try {
+ return LocalDate.parse(normalized.substring(0, 10)).toString();
+ } catch (Exception ignored) {
+ }
+ }
+ return normalizeCheckMonth(normalized);
+ }
+
+ /**
+ * 涓や釜鏈堜唤涔嬮棿鐩稿樊鐨勬湀鏁�
+ */
+ private long monthDistance(String fromCheckTime, String targetMonth) {
+ try {
+ YearMonth from = YearMonth.parse(normalizeCheckMonth(fromCheckTime));
+ YearMonth target = YearMonth.parse(targetMonth);
+ return Math.abs(ChronoUnit.MONTHS.between(from, target));
+ } catch (Exception e) {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /**
+ * 淇敼璁″垝鏄庣粏锛涙牳鏌ユ湀浠借法骞存椂锛屾妸鏄庣粏褰掑埌鏍告煡鏈堜唤鎵�灞炲勾浠界殑璁″垝涓�
+ */
+ @Override
+ public boolean updateExaminePlanDetail(DeviceExaminePlanDetails detail) {
+ if (detail == null || detail.getPlanDetailsId() == null) {
+ throw new ErrorException("缂哄皯璁惧鏍告煡璁″垝鏄庣粏id");
+ }
+ if (StringUtils.isNotBlank(detail.getCheckTime())) {
+ String checkTime = normalizeCheckDate(detail.getCheckTime());
+ detail.setCheckTime(checkTime);
+ DeviceExaminePlan plan = getOrCreatePlanEntity(checkTime.substring(0, 4));
+ detail.setPlanId(plan.getPlanId());
+ }
+ return deviceExaminePlanDetailsMapper.updateById(detail) > 0;
+ }
+
+ /**
+ * 鍒犻櫎璁″垝鏄庣粏锛涘凡瀹℃牳瀹屾垚鐨勬牳鏌�/姣斿璁板綍闅忓巻鍙茬暀妗d笉鍏佽鍒犻櫎锛屾湭瀹℃牳鐨勮褰曢殢鏄庣粏涓�璧峰垹闄�
+ */
+ @Override
+ public boolean removeExaminePlanDetail(Integer planDetailsId) {
+ if (planDetailsId == null) {
+ throw new ErrorException("缂哄皯璁惧鏍告煡璁″垝鏄庣粏id");
+ }
+ DeviceExaminePlanDetails detail = deviceExaminePlanDetailsMapper.selectById(planDetailsId);
+ if (detail == null) {
+ return false;
+ }
+ List<DeviceExamineRecord> records = deviceExamineRecordMapper.selectList(Wrappers.<DeviceExamineRecord>lambdaQuery()
+ .eq(DeviceExamineRecord::getPlanDetailsId, planDetailsId));
+ List<DeviceExamineRecordContrast> contrasts = deviceExamineRecordContrastMapper.selectList(Wrappers.<DeviceExamineRecordContrast>lambdaQuery()
+ .eq(DeviceExamineRecordContrast::getPlanDetailsId, planDetailsId));
+ // 瀹℃牳涓嶉�氳繃鐨勮褰曞厑璁歌繛鍚屾槑缁嗕竴璧峰垹闄わ紱瀹℃牳閫氳繃鐨勮褰曚负鍘嗗彶鐣欐。锛屼笉鍏佽鍒犻櫎
+ boolean hasPassedRecord = records.stream().anyMatch(item -> item.getReviewStatus() != null
+ && item.getReviewStatus() == 1);
+ boolean hasPassedContrast = contrasts.stream().anyMatch(item -> item.getReviewStatus() != null
+ && item.getReviewStatus() == 1);
+ if (hasPassedRecord || hasPassedContrast) {
+ throw new ErrorException("璇ヨ鍒掑凡瀛樺湪瀹℃牳閫氳繃鐨勬牳鏌ヨ褰曪紝涓轰繚鐣欏巻鍙茶褰曚笉鍏佽鍒犻櫎");
+ }
+ if (!records.isEmpty()) {
+ List<Integer> recordIds = records.stream().map(DeviceExamineRecord::getRecordId).collect(Collectors.toList());
+ deviceExamineRecordDetailMapper.delete(Wrappers.<DeviceExamineRecordDetail>lambdaQuery()
+ .in(DeviceExamineRecordDetail::getRecordId, recordIds));
+ deviceExamineRecordMapper.deleteBatchIds(recordIds);
+ }
+ if (!contrasts.isEmpty()) {
+ List<Integer> contrastIds = contrasts.stream().map(DeviceExamineRecordContrast::getRecordContrastId).collect(Collectors.toList());
+ deviceExamineRecordContrastDetailsMapper.delete(Wrappers.<DeviceExamineRecordContrastDetails>lambdaQuery()
+ .in(DeviceExamineRecordContrastDetails::getRecordContrastId, contrastIds));
+ deviceExamineRecordContrastMapper.deleteBatchIds(contrastIds);
+ }
+ deviceExamineAttachmentMapper.delete(Wrappers.<DeviceExamineAttachment>lambdaQuery()
+ .eq(DeviceExamineAttachment::getPlanDetailsId, planDetailsId));
+ boolean removed = deviceExaminePlanDetailsService.removeById(planDetailsId);
+ if (removed && detail.getDeviceId() != null) {
+ rollbackDeviceExamineDate(detail);
+ }
+ return removed;
+ }
+
+ /**
+ * 鍒犻櫎鏈鏍哥殑璁″垝鍚庯紝鎶婅澶囨。妗堟仮澶嶅埌鏈�杩戜竴鏉″鏍搁�氳繃鐨勮褰曪紱
+ * 娌℃湁宸插畬鎴愯褰曟椂锛屾墠娓呯┖妗f鐨勬牳鏌ユ棩鏈熷拰鍛ㄦ湡
+ */
+ private void rollbackDeviceExamineDate(DeviceExaminePlanDetails removedDetail) {
+ Device device = deviceMapper.selectById(removedDetail.getDeviceId());
+ if (device == null) {
+ return;
+ }
+ Long remainCount = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+ .eq(DeviceExaminePlanDetails::getDeviceId, removedDetail.getDeviceId()));
+ boolean isCurrentRound = sameExamineMonth(removedDetail.getCheckTime(), device.getNextExamineDate());
+ // 鍒犻櫎鐨勪笉鏄。妗堝綋鍓嶇櫥璁扮殑閭d竴杞紝涓旇澶囪繕鏈夊叾浠栬鍒掓椂锛屼笉鍔ㄦ。妗�
+ if (!isCurrentRound && remainCount != null && remainCount > 0) {
+ return;
+ }
+ LocalDate lastDone = null;
+ List<DeviceExaminePlanDetails> details = deviceExaminePlanDetailsMapper.selectList(
+ Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+ .eq(DeviceExaminePlanDetails::getDeviceId, removedDetail.getDeviceId()));
+ for (DeviceExaminePlanDetails item : details) {
+ if (!hasPassedExamineRecord(item.getPlanDetailsId())) {
+ continue;
+ }
+ LocalDate date = parseCheckMonth(item.getCheckTime());
+ if (date != null && (lastDone == null || date.isAfter(lastDone))) {
+ lastDone = date;
+ }
+ }
+ if (lastDone == null) {
+ deviceMapper.update(null, Wrappers.<Device>lambdaUpdate()
+ .eq(Device::getId, removedDetail.getDeviceId())
+ .set(Device::getLastExamineDate, null)
+ .set(Device::getNextExamineDate, null)
+ .set(Device::getExamineCycle, null));
+ return;
+ }
+ LocalDateTime last = lastDone.atStartOfDay();
+ LocalDateTime next = null;
+ if (StringUtils.isNotBlank(device.getExamineCycle())) {
+ try {
+ next = last.plusMonths(Long.parseLong(device.getExamineCycle().trim()));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ deviceMapper.update(null, Wrappers.<Device>lambdaUpdate()
+ .eq(Device::getId, removedDetail.getDeviceId())
+ .set(Device::getLastExamineDate, last)
+ .set(Device::getNextExamineDate, next));
+ }
+
+ /**
+ * 鏄庣粏涓嬫槸鍚﹀瓨鍦ㄥ鏍搁�氳繃鐨勬牳鏌ヨ褰曟垨姣斿璁板綍
+ */
+ private boolean hasPassedExamineRecord(Integer planDetailsId) {
+ Long recordCount = deviceExamineRecordMapper.selectCount(Wrappers.<DeviceExamineRecord>lambdaQuery()
+ .eq(DeviceExamineRecord::getPlanDetailsId, planDetailsId)
+ .eq(DeviceExamineRecord::getReviewStatus, 1));
+ if (recordCount != null && recordCount > 0) {
+ return true;
+ }
+ Long contrastCount = deviceExamineRecordContrastMapper.selectCount(Wrappers.<DeviceExamineRecordContrast>lambdaQuery()
+ .eq(DeviceExamineRecordContrast::getPlanDetailsId, planDetailsId)
+ .eq(DeviceExamineRecordContrast::getReviewStatus, 1));
+ return contrastCount != null && contrastCount > 0;
+ }
+
+ /**
+ * 璁″垝鏄庣粏鐨勬牳鏌ユ湀浠芥槸鍚﹀氨鏄。妗堜笅娆℃牳鏌ユ棩鏈熸墍鍦ㄦ湀浠�
+ */
+ private boolean sameExamineMonth(String checkTime, LocalDateTime nextExamineDate) {
+ if (StringUtils.isBlank(checkTime) || nextExamineDate == null) {
+ return false;
+ }
+ String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+ return normalized.length() >= 7
+ && normalized.substring(0, 7).equals(nextExamineDate.format(DateTimeFormatter.ofPattern("yyyy-MM")));
+ }
+
+ /**
+ * 瑙f瀽鏄庣粏鏍告煡鏈堜唤涓哄綋鏈堥鏃ワ紝鏍煎紡涓嶅悎娉曟椂杩斿洖 null
+ */
+ private LocalDate parseCheckMonth(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 null;
+ }
+
+ @Override
+ public List<DeviceExamineAttachment> selectAttachments(Integer planDetailsId) {
+ if (planDetailsId == null) {
+ return new ArrayList<>();
+ }
+ return deviceExamineAttachmentMapper.selectList(Wrappers.<DeviceExamineAttachment>lambdaQuery()
+ .eq(DeviceExamineAttachment::getPlanDetailsId, planDetailsId)
+ .orderByAsc(DeviceExamineAttachment::getSort, DeviceExamineAttachment::getId));
+ }
+
+ @Override
+ public DeviceExamineAttachment uploadAttachment(Integer planDetailsId, MultipartFile file) {
+ if (planDetailsId == null) {
+ throw new ErrorException("缂哄皯鏍告煡璁″垝鏄庣粏id");
+ }
+ if (deviceExaminePlanDetailsMapper.selectById(planDetailsId) == null) {
+ throw new ErrorException("鏍告煡璁″垝鏄庣粏涓嶅瓨鍦�");
+ }
+ if (file == null || file.isEmpty()) {
+ throw new ErrorException("闄勪欢涓嶈兘涓虹┖");
+ }
+ // 绯荤粺鍏ㄥ眬涓婁紶涓婇檺涓�200MB锛岃繖閲屾彁鍓嶇粰鍑哄弸濂芥彁绀�
+ if (file.getSize() > 200L * 1024 * 1024) {
+ throw new ErrorException("闄勪欢涓嶈兘瓒呰繃200MB");
+ }
+ String originalName = file.getOriginalFilename();
+ if (originalName == null || originalName.trim().isEmpty()) {
+ originalName = "鏈懡鍚嶆枃浠�";
+ }
+ // 涓埆娴忚鍣ㄥ彲鑳藉甫涓婅矾寰勫墠缂�锛屽彧淇濈暀鏂囦欢鍚嶆湰韬�
+ originalName = originalName.replaceAll(".*[/\\\\]", "");
+ try {
+ // 鍥剧墖鏀惧浘鐗囩洰褰曪紙/img 闈欐�佽矾寰勶級锛屽叾浣欎换鎰忔牸寮忔斁 word 鐩綍锛�/word 闈欐�佽矾寰勶級
+ boolean image = originalName.matches("(?i).+\\.(jpg|jpeg|png|gif)$");
+ File directory = new File(image ? filePath : wordUrl);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+ String fileUrl = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS")) + "-" + originalName;
+ file.transferTo(new File(directory, fileUrl));
+ DeviceExamineAttachment attachment = new DeviceExamineAttachment();
+ attachment.setPlanDetailsId(planDetailsId);
+ attachment.setFileName(originalName);
+ attachment.setFileUrl(fileUrl);
+ attachment.setSort(selectAttachments(planDetailsId).size());
+ deviceExamineAttachmentMapper.insert(attachment);
+ return attachment;
+ } catch (IOException e) {
+ throw new ErrorException("闄勪欢涓婁紶澶辫触");
+ }
+ }
+
+ @Override
+ public boolean deleteAttachment(Long id) {
+ return deviceExamineAttachmentMapper.deleteById(id) > 0;
+ }
}
--
Gitblit v1.9.3