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/DeviceExamineRecordServiceImpl.java |  424 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 384 insertions(+), 40 deletions(-)

diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
index 790f995..c9355a4 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
@@ -5,8 +5,6 @@
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 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,14 +18,23 @@
 import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
 import com.ruoyi.device.pojo.DeviceExamineRecord;
 import com.ruoyi.device.pojo.DeviceExamineRecordDetail;
+import com.ruoyi.device.service.DeviceExaminePlanService;
 import com.ruoyi.device.service.DeviceExamineRecordDetailService;
 import com.ruoyi.device.service.DeviceExamineRecordService;
 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.xwpf.usermodel.ParagraphAlignment;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
+import org.apache.poi.xwpf.usermodel.XWPFTableCell;
+import org.apache.poi.xwpf.usermodel.XWPFTableRow;
+import org.apache.xmlbeans.XmlCursor;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -36,7 +43,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URLEncoder;
-import java.util.HashMap;
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -51,8 +58,26 @@
 @Transactional(rollbackFor = Exception.class)
 public class DeviceExamineRecordServiceImpl extends ServiceImpl<DeviceExamineRecordMapper, DeviceExamineRecord> implements DeviceExamineRecordService {
 
+    /** 鎸夎澶囧鐢ㄧ殑鏈熼棿鏍告煡璁板綍妯℃澘锛堜笌璁惧鍚嶇О/鍨嬪彿鍖归厤锛� */
+    private static final String TEMPLATE_BALANCE_PERCENT = "/static/word/examine/examine-balance-percent.docx";
+    private static final String TEMPLATE_BALANCE_PERCENT_BSA2202S = "/static/word/examine/examine-balance-percent-bsa2202s.docx";
+    private static final String TEMPLATE_BALANCE_THOUSANDTH = "/static/word/examine/examine-balance-thousandth.docx";
+    private static final String TEMPLATE_BALANCE_0_1MG_ME204E = "/static/word/examine/examine-balance-0.1mg-me204e.docx";
+    private static final String TEMPLATE_BALANCE_0_1MG_AL204 = "/static/word/examine/examine-balance-0.1mg-al204.docx";
+    private static final String TEMPLATE_SURFACE_TENSION = "/static/word/examine/examine-surface-tension.docx";
+    private static final String TEMPLATE_ROTARY_VISCOSITY = "/static/word/examine/examine-rotary-viscosity.docx";
+    private static final String TEMPLATE_VISCOSITY_PIN = "/static/word/examine/examine-viscosity-pin.docx";
+    private static final String TEMPLATE_SPECTROPHOTOMETER = "/static/word/examine/examine-spectrophotometer.docx";
+    /** 娌℃湁瀵瑰簲璁惧涓撶敤妯℃澘鏃跺鐢ㄧ殑閫氱敤鏈熼棿鏍告煡璁板綍琛� */
+    private static final String TEMPLATE_GENERIC = "/static/word/examine/examine-generic.docx";
+
+    private static final String WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
+    private static final String RECORD_NUMBER_TEXT = "璁板綍缂栧彿锛歀MSY-CX09-JL02";
+
     @Resource
     private DeviceExamineRecordDetailService deviceExamineRecordDetailService;
+    @Resource
+    private DeviceExaminePlanService deviceExaminePlanService;
     @Resource
     private DeviceExaminePlanDetailsMapper deviceExaminePlanDetailsMapper;
     @Resource
@@ -174,18 +199,54 @@
         if (dto.getPlanDetailsId() == null) {
             throw new ErrorException("缂哄皯璁″垝璇︾粏淇℃伅id");
         }
-        LambdaUpdateWrapper<DeviceExamineRecord> wrapper = Wrappers.<DeviceExamineRecord>lambdaUpdate()
-                .eq(DeviceExamineRecord::getPlanDetailsId, dto.getPlanDetailsId())
-                .set(DeviceExamineRecord::getReviewStatus, dto.getReviewStatus())
-                .set(DeviceExamineRecord::getReviewRemark, dto.getReviewRemark());
-
-        // 涓�0娓呴櫎瀹℃牳浜�
-        if (dto.getReviewStatus().equals(0)) {
-            wrapper.set(DeviceExamineRecord::getReviewUserId, null)
-                    .set(DeviceExamineRecord::getReviewUser, null);
+        DeviceExaminePlanDetails planDetails = deviceExaminePlanDetailsMapper.selectById(dto.getPlanDetailsId());
+        if (planDetails == null) {
+            throw new ErrorException("鏍告煡璁″垝鏄庣粏涓嶅瓨鍦�");
+        }
+        if (planDetails.getCheckChargerUserId() == null && StringUtils.isBlank(planDetails.getCheckChargerUser())) {
+            throw new ErrorException("璇峰厛鍦ㄧ紪杈戜腑濉啓鏍告煡璐熻矗浜�");
         }
 
-        this.update(wrapper);
+        Integer reviewStatus = dto.getReviewStatus();
+        Integer reviewerId = SecurityUtils.getUserId().intValue();
+        User reviewer = userMapper.selectById(reviewerId);
+
+        LambdaUpdateWrapper<DeviceExamineRecord> wrapper = Wrappers.<DeviceExamineRecord>lambdaUpdate()
+                .eq(DeviceExamineRecord::getPlanDetailsId, dto.getPlanDetailsId())
+                .set(DeviceExamineRecord::getReviewStatus, reviewStatus)
+                .set(DeviceExamineRecord::getReviewRemark, dto.getReviewRemark())
+                // 瀹℃牳鍔ㄤ綔鏃堕棿鍗虫牳鏌ュ畬鎴愭椂闂达紝鍒楄〃銆屾牳鏌ユ椂闂淬�嶆寜瀹冩樉绀�
+                .set(DeviceExamineRecord::getUpdateTime, LocalDateTime.now());
+
+        // 涓�0娓呴櫎瀹℃牳浜�
+        if (reviewStatus != null && reviewStatus == 0) {
+            wrapper.set(DeviceExamineRecord::getReviewUserId, null)
+                    .set(DeviceExamineRecord::getReviewUser, null);
+        } else {
+            wrapper.set(DeviceExamineRecord::getReviewUserId, reviewerId)
+                    .set(DeviceExamineRecord::getReviewUser, reviewer == null ? null : reviewer.getName());
+        }
+
+        boolean updated = this.update(wrapper);
+        // 璁″垝琛屽皻鏈綍鍏ユ牳鏌ヨ褰曟椂锛岃ˉ涓�鏉″鏍哥粨鏋滆褰曪紝淇濊瘉鍒楄〃鑳芥樉绀洪�氳繃/涓嶉�氳繃鐘舵��
+        if (!updated) {
+            DeviceExamineRecord record = new DeviceExamineRecord();
+            record.setPlanDetailsId(dto.getPlanDetailsId());
+            record.setReviewStatus(reviewStatus);
+            record.setReviewRemark(dto.getReviewRemark());
+            record.setCheckerUserId(planDetails.getCheckChargerUserId());
+            record.setCheckerUser(planDetails.getCheckChargerUser());
+            if (reviewStatus != null && reviewStatus == 1) {
+                record.setReviewUserId(reviewerId);
+                record.setReviewUser(reviewer == null ? null : reviewer.getName());
+            }
+            this.save(record);
+        }
+
+        // 澶嶆牳閫氳繃鍚庯紝璁惧妗f鐨勬渶杩�/涓嬫鏍告煡鏃ユ湡鎸夊鏍稿綋澶╂粴鍔紱璁″垝琛岀殑妫�鏌ユ棩鏈熸槸璁″垝鏃ユ湡锛屽鏍镐笉鏀瑰畠
+        if (reviewStatus != null && reviewStatus == 1) {
+            deviceExaminePlanService.updateDeviceExamineDate(dto.getPlanDetailsId());
+        }
         return true;
     }
 
@@ -199,40 +260,323 @@
     public void exportReviewExamineRecordDetail(Integer planDetailsId, HttpServletResponse response) {
         // 鏌ヨ澶嶆牳鏍告煡璁板綍
         DeviceExamineRecordDto deviceExamineRecordDto = baseMapper.selectReviewExamineRecordDto(planDetailsId);
+        if (deviceExamineRecordDto == null) {
+            throw new ErrorException("璇ヨ鍒掓槑缁嗘殏鏃犳牳鏌ヨ褰曪紝璇峰厛褰曞叆鍚庡啀瀵煎嚭");
+        }
+        DeviceExaminePlanDetails planDetails = deviceExaminePlanDetailsMapper.selectById(planDetailsId);
+        Device device = planDetails != null && planDetails.getDeviceId() != null
+                ? deviceMapper.selectById(planDetails.getDeviceId()) : null;
 
-        // 鏌ヨ澶嶆牳鏍告煡璁板綍璇︽儏
-        List<DeviceExamineRecordDetail> deviceExamineRecordDetailList = deviceExamineRecordDetailService.list(Wrappers.<DeviceExamineRecordDetail>lambdaQuery().eq(DeviceExamineRecordDetail::getRecordId, deviceExamineRecordDto.getRecordId()));
-
-        // 鑾峰彇璺緞
-        InputStream inputStream = this.getClass().getResourceAsStream("/static/word/examine-record.docx");
-        Configure configure = Configure.builder()
-                .bind("deviceExamineRecordDetailList", new HackLoopTableRenderPolicy())
-                .build();
-        XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
-                new HashMap<String, Object>() {{
-                    put("deviceExamineRecordDto", deviceExamineRecordDto);
-                    put("deviceExamineRecordDetailList", deviceExamineRecordDetailList);
-                    //鑾峰彇鏍告煡浜虹殑绛惧悕鍦板潃
-                    put("checkerUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExamineRecordDto.getCheckerUserId()));
-                    //鑾峰彇瀹℃煡浜虹殑绛惧悕鍦板潃
-                    put("reviewUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExamineRecordDto.getReviewUserId()));
-                }});
-
+        String deviceName = StringUtils.isNotEmpty(deviceExamineRecordDto.getDeviceName()) ? deviceExamineRecordDto.getDeviceName() : "";
+        String fileName;
         try {
+            fileName = URLEncoder.encode(deviceName + "鏈熼棿鏍告煡璁板綍.docx", "UTF-8");
+        } catch (Exception e) {
+            fileName = "examine-record.docx";
+        }
+
+        // 鎸夎澶囧尮閰嶄笓鐢ㄦā鏉匡紝鍖归厤涓嶅埌鏃跺鐢ㄩ�氱敤鏈熼棿鏍告煡璁板綍琛�
+        String templatePath = resolveExamineRecordTemplate(planDetails, device);
+        exportByDeviceTemplate(templatePath, planDetails, deviceExamineRecordDto, fileName, response);
+    }
+
+    /**
+     * 鎸夎澶囧悕绉�/鍨嬪彿鍖归厤鏈熼棿鏍告煡璁板綍妯℃澘
+     */
+    private String resolveExamineRecordTemplate(DeviceExaminePlanDetails planDetails, Device device) {
+        String deviceName = planDetails == null ? null : planDetails.getDeviceName();
+        if (StringUtils.isBlank(deviceName) && device != null) {
+            deviceName = device.getDeviceName();
+        }
+        String deviceModel = planDetails == null ? null : planDetails.getDeviceModel();
+        if (StringUtils.isBlank(deviceModel) && device != null) {
+            deviceModel = device.getSpecificationModel();
+        }
+        String name = deviceName == null ? "" : deviceName.trim();
+        String model = deviceModel == null ? "" : deviceModel.trim().toUpperCase();
+        if (name.contains("琛ㄩ潰寮犲姏")) {
+            return TEMPLATE_SURFACE_TENSION;
+        }
+        if (name.contains("鏃嬭浆绮樺害") || name.contains("鍏��")) {
+            return TEMPLATE_ROTARY_VISCOSITY;
+        }
+        if (name.contains("鍝佹皬")) {
+            return TEMPLATE_VISCOSITY_PIN;
+        }
+        if (name.contains("鍒嗗厜鍏夊害")) {
+            return TEMPLATE_SPECTROPHOTOMETER;
+        }
+        // 鐢靛瓙澶╁钩鍏堟寜鍨嬪彿缁嗗垎锛屽啀鎸夋。浣嶅悕绉�
+        if (model.contains("BSA2202S")) {
+            return TEMPLATE_BALANCE_PERCENT_BSA2202S;
+        }
+        if (model.contains("ME204E")) {
+            return TEMPLATE_BALANCE_0_1MG_ME204E;
+        }
+        if (model.contains("AL204")) {
+            return TEMPLATE_BALANCE_0_1MG_AL204;
+        }
+        if (name.contains("涓囧垎涔�")) {
+            return TEMPLATE_BALANCE_0_1MG_AL204;
+        }
+        if (name.contains("鍗冨垎涔�")) {
+            return TEMPLATE_BALANCE_THOUSANDTH;
+        }
+        if (name.contains("鐧惧垎涔�")) {
+            return TEMPLATE_BALANCE_PERCENT;
+        }
+        return TEMPLATE_GENERIC;
+    }
+
+    /**
+     * 鎸夎澶囦笓鐢ㄦā鏉垮鍑猴細鍙~琛ㄥご淇℃伅锛屾娴嬬粨鏋滆〃鏍肩暀绌�
+     */
+    private void exportByDeviceTemplate(String templatePath, DeviceExaminePlanDetails planDetails,
+                                        DeviceExamineRecordDto recordDto, String fileName, HttpServletResponse response) {
+        try (InputStream inputStream = this.getClass().getResourceAsStream(templatePath)) {
+            if (inputStream == null) {
+                throw new ErrorException("鏍告煡璁板綍妯℃澘涓嶅瓨鍦細" + templatePath);
+            }
+            XWPFDocument document = new XWPFDocument(inputStream);
+            // 閫氱敤琛ㄦ寜鍙傝�冨浘甯﹁褰曠紪鍙凤紱璁惧涓撶敤妯℃澘淇濇寔妯℃澘鍘熸牱
+            if (TEMPLATE_GENERIC.equals(templatePath)) {
+                insertRecordNumberLine(document);
+            }
+            if (!document.getTables().isEmpty()) {
+                fillTemplateTable(document.getTables().get(0), planDetails, recordDto);
+            }
             response.setContentType("application/msword");
-            String deviceName = StringUtils.isNotEmpty(deviceExamineRecordDto.getDeviceName()) ? deviceExamineRecordDto.getDeviceName() : "";
-            String fileName = URLEncoder.encode(
-                    deviceName+ "鏍告煡璁板綍", "UTF-8");
-            response.setHeader("Content-disposition",
-                    "attachment;filename=" + fileName + ".docx");
+            response.setHeader("Content-disposition", "attachment;filename=" + fileName);
             OutputStream os = response.getOutputStream();
-            template.write(os);
+            document.write(os);
             os.flush();
             os.close();
-            inputStream.close();
+            document.close();
+        } catch (ErrorException e) {
+            throw e;
         } catch (Exception e) {
             e.printStackTrace();
             throw new RuntimeException("瀵煎嚭澶辫触");
         }
     }
+
+    /**
+     * 鍦ㄦ爣棰樹笂鏂规彃鍏ュ浐瀹氱殑璁板綍缂栧彿琛�
+     */
+    private void insertRecordNumberLine(XWPFDocument document) {
+        List<XWPFParagraph> paragraphs = document.getParagraphs();
+        if (paragraphs.isEmpty()) {
+            return;
+        }
+        XmlCursor cursor = paragraphs.get(0).getCTP().newCursor();
+        XWPFParagraph paragraph = document.insertNewParagraph(cursor);
+        paragraph.setAlignment(ParagraphAlignment.LEFT);
+        XWPFRun run = paragraph.createRun();
+        run.setFontFamily("瀹嬩綋");
+        run.setFontSize(10.5);
+        run.setText(RECORD_NUMBER_TEXT);
+    }
+
+    /**
+     * 鎸夋爣绛惧畾浣嶅苟濉啓琛ㄥご锛屼华鍣ㄧ幇鐘舵墦鍕撅紝琛ュ~璇勪环/瀹℃牳浜�
+     */
+    private void fillTemplateTable(XWPFTable table, DeviceExaminePlanDetails planDetails, DeviceExamineRecordDto recordDto) {
+        String checkDate = StringUtils.isNotEmpty(recordDto.getUpdateTimeStr()) ? recordDto.getUpdateTimeStr()
+                : (planDetails == null ? "" : planDetails.getCheckTime());
+        String checkerName = resolveCheckerName(planDetails, recordDto);
+        String reviewerName = resolveUserName(recordDto.getReviewUserId(), recordDto.getReviewUser());
+        String appearanceStatus = planDetails == null ? null : planDetails.getAppearanceStatus();
+        String runningStatus = planDetails == null ? null : planDetails.getRunningStatus();
+
+        for (XWPFTableRow row : table.getRows()) {
+            List<XWPFTableCell> cells = row.getTableCells();
+            for (int i = 0; i < cells.size(); i++) {
+                String label = normalizeCellLabel(cells.get(i).getText());
+                if ("澶栬鎯呭喌".equals(label)) {
+                    markOptionCells(cells, i + 1, appearanceStatus);
+                    continue;
+                }
+                if ("杩愯鏄惁姝e父".equals(label)) {
+                    markOptionCells(cells, i + 1, runningStatus);
+                    continue;
+                }
+                if (label.isEmpty() || i + 1 >= cells.size()) {
+                    continue;
+                }
+                XWPFTableCell valueCell = cells.get(i + 1);
+                switch (label) {
+                    case "浠櫒鍚嶇О":
+                        setCellText(valueCell, planDetails == null ? recordDto.getDeviceName() : planDetails.getDeviceName(), true);
+                        break;
+                    case "绠$悊缂栧彿":
+                    case "璁惧缂栧彿":
+                        setCellText(valueCell, planDetails == null ? recordDto.getDeviceNumber() : planDetails.getDeviceNumber(), true);
+                        break;
+                    case "璁惧鍨嬪彿":
+                        setCellText(valueCell, planDetails == null ? "" : planDetails.getDeviceModel(), true);
+                        break;
+                    case "妫�鏌ユ棩鏈�":
+                        setCellText(valueCell, checkDate == null ? "" : checkDate, true);
+                        break;
+                    case "鏍告煡浜�":
+                        setCellText(valueCell, checkerName == null ? "" : checkerName, true);
+                        break;
+                    case "鐜娓╂箍搴�":
+                        setCellText(valueCell, planDetails == null ? "" : planDetails.getEnvironment(), true);
+                        break;
+                    case "鏍告煡渚濇嵁":
+                        setCellText(valueCell, planDetails == null ? null : planDetails.getCheckBasis(), false);
+                        break;
+                    case "鏍告煡鏂瑰紡":
+                        setCellText(valueCell, planDetails == null ? null : planDetails.getCheckMethod(), false);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        fillEvaluationCell(table, reviewerName, checkDate, appearanceStatus, runningStatus);
+    }
+
+    /**
+     * 浠櫒鐜扮姸涓夐�変竴锛氶�変腑椤规樉绀衡槕锛屽叾浣欎繚鎸佲枴
+     */
+    private void markOptionCells(List<XWPFTableCell> cells, int startIndex, String selected) {
+        for (int i = startIndex; i < cells.size() && i <= startIndex + 2; i++) {
+            String option = normalizeCellLabel(cells.get(i).getText());
+            if (!"鑹ソ".equals(option) && !"涓�鑸�".equals(option) && !"鎹熷潖".equals(option)
+                    && !"姝e父".equals(option) && !"寮傚父".equals(option) && !"鍋滄満".equals(option)) {
+                continue;
+            }
+            setCellText(cells.get(i), option + (option.equals(selected) ? "鈽�" : "鈻�"), true);
+        }
+    }
+
+    /**
+     * 鏍告煡缁撴灉璇勪环鍕鹃�� + 瀹℃牳浜�/鏃堕棿濉啓
+     */
+    private void fillEvaluationCell(XWPFTable table, String reviewerName, String checkDate,
+                                    String appearanceStatus, String runningStatus) {
+        boolean hasStatus = StringUtils.isNotEmpty(appearanceStatus) || StringUtils.isNotEmpty(runningStatus);
+        boolean satisfied = hasStatus && !"鎹熷潖".equals(appearanceStatus)
+                && !"寮傚父".equals(runningStatus) && !"鍋滄満".equals(runningStatus);
+        for (XWPFTableRow row : table.getRows()) {
+            for (XWPFTableCell cell : row.getTableCells()) {
+                for (XWPFParagraph paragraph : cell.getParagraphs()) {
+                    String text = paragraph.getText();
+                    if (text == null) {
+                        continue;
+                    }
+                    if (text.contains("鏍告煡缁撴灉璇勪环") && text.contains("婊¤冻") && hasStatus) {
+                        // 妯℃澘閲屼袱涓閫夋渚濇鏄�滄弧瓒斥�濃�滀笉婊¤冻鈥�
+                        markParagraphSymbol(paragraph, satisfied ? 0 : 1);
+                    }
+                    if (text.contains("瀹℃牳浜�")) {
+                        rebuildReviewerParagraph(paragraph, reviewerName, checkDate);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 淇敼娈佃惤閲岀 targetIndex 涓� Wingdings 2 澶嶉�夋涓哄嬀閫夌姸鎬侊紙0x52=鈽戯紝0xA3=鈻★級
+     */
+    private void markParagraphSymbol(XWPFParagraph paragraph, int targetIndex) {
+        int index = 0;
+        for (XWPFRun run : paragraph.getRuns()) {
+            Node runNode = run.getCTR().getDomNode();
+            for (Node child = runNode.getFirstChild(); child != null; child = child.getNextSibling()) {
+                if (child.getNodeType() == Node.ELEMENT_NODE && "sym".equals(child.getLocalName())) {
+                    ((Element) child).setAttributeNS(WORD_NS, "w:char", index == targetIndex ? "0052" : "00A3");
+                    index++;
+                }
+            }
+        }
+    }
+
+    /**
+     * 閲嶅啓鈥滃鏍镐汉/鏃堕棿鈥濊锛屽~鍏ュ鏍镐汉鍜屾棩鏈�
+     */
+    private void rebuildReviewerParagraph(XWPFParagraph paragraph, String reviewerName, String checkDate) {
+        String text = paragraph.getText();
+        int labelIndex = text == null ? -1 : text.indexOf("瀹℃牳浜�");
+        if (labelIndex < 0) {
+            return;
+        }
+        String prefix = text.substring(0, labelIndex);
+        String dateLabel = text.contains("鏃ユ湡") ? "鏃ユ湡" : "鏃堕棿";
+        String newText = prefix + "瀹℃牳浜猴細" + (reviewerName == null ? "" : reviewerName)
+                + "                   " + dateLabel + "锛�" + (checkDate == null ? "" : checkDate);
+        List<XWPFRun> runs = paragraph.getRuns();
+        if (runs.isEmpty()) {
+            XWPFRun run = paragraph.createRun();
+            run.setFontFamily("瀹嬩綋");
+            run.setFontSize(12);
+            run.setText(newText);
+            return;
+        }
+        runs.get(0).setText(newText, 0);
+        for (int i = runs.size() - 1; i >= 1; i--) {
+            paragraph.removeRun(i);
+        }
+    }
+
+    /**
+     * 鍐欏叆鍗曞厓鏍兼枃鏈紱overwriteBlank=false 鏃朵繚鐣欐ā鏉块噷鐨勫浐瀹氭枃瀛�
+     */
+    private void setCellText(XWPFTableCell cell, String text, boolean overwriteBlank) {
+        String value = text == null ? "" : text;
+        if (!overwriteBlank && value.trim().isEmpty()) {
+            return;
+        }
+        List<XWPFParagraph> paragraphs = cell.getParagraphs();
+        XWPFParagraph paragraph = paragraphs.isEmpty() ? cell.addParagraph() : paragraphs.get(0);
+        for (int i = paragraphs.size() - 1; i >= 1; i--) {
+            cell.removeParagraph(i);
+        }
+        List<XWPFRun> runs = paragraph.getRuns();
+        if (runs.isEmpty()) {
+            XWPFRun run = paragraph.createRun();
+            run.setFontFamily("瀹嬩綋");
+            run.setFontSize(12);
+            run.setText(value);
+            return;
+        }
+        runs.get(0).setText(value, 0);
+        for (int i = runs.size() - 1; i >= 1; i--) {
+            paragraph.removeRun(i);
+        }
+    }
+
+    private String normalizeCellLabel(String text) {
+        if (text == null) {
+            return "";
+        }
+        return text.replace(" ", "").replace("\u00A0", "").replace("鈻�", "").replace("鈽�", "")
+                .replace("\n", "").replace("\r", "").trim();
+    }
+
+    private String resolveCheckerName(DeviceExaminePlanDetails planDetails, DeviceExamineRecordDto recordDto) {
+        if (planDetails != null && StringUtils.isNotBlank(planDetails.getCheckChargerUser())) {
+            return planDetails.getCheckChargerUser();
+        }
+        if (StringUtils.isNotBlank(recordDto.getCheckerUser())) {
+            return recordDto.getCheckerUser();
+        }
+        Integer userId = planDetails != null && planDetails.getCheckChargerUserId() != null
+                ? planDetails.getCheckChargerUserId() : recordDto.getCheckerUserId();
+        return resolveUserName(userId, null);
+    }
+
+    private String resolveUserName(Integer userId, String userName) {
+        if (StringUtils.isNotBlank(userName)) {
+            return userName;
+        }
+        if (userId == null) {
+            return null;
+        }
+        User user = userMapper.selectById(userId);
+        return user == null ? null : user.getName();
+    }
 }

--
Gitblit v1.9.3