From f02d0df2f1dfc7a8b775cf50d45375e57628a5ef Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 10 十一月 2026 11:26:22 +0800
Subject: [PATCH] feat(ncr): 完善NCR处置联动逻辑与关闭校验

---
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractDocumentRenderer.java |  130 ++++++++++++++++++++++++++++++++++---------
 1 files changed, 103 insertions(+), 27 deletions(-)

diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractDocumentRenderer.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractDocumentRenderer.java
index cd05bb6..c3a7102 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractDocumentRenderer.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractDocumentRenderer.java
@@ -4,6 +4,7 @@
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
 import org.springframework.core.io.ClassPathResource;
 import org.w3c.dom.*;
+
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.transform.TransformerFactory;
@@ -16,7 +17,7 @@
 import java.util.*;
 import java.util.zip.*;
 
-/** 淇濈暀鐢ㄦ埛鍚堝悓妯℃澘鐨� OOXML 鍖咃紝浠呭~鍐欎笟鍔℃Ы浣嶅苟鎵╁睍浜у搧鏄庣粏琛屻�� */
+/** 鍦ㄩ攢鍞悎鍚屾ā鏉夸腑濉啓鍚堝悓淇℃伅锛屼繚鐣欐ā鏉跨殑鍏朵粬鍐呭鍙婃牸寮忋�� */
 public final class CrmContractDocumentRenderer {
     private static final String W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
     private CrmContractDocumentRenderer() { }
@@ -49,14 +50,38 @@
             factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
             factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
             var document = factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml));
-            Node rowTemplate = null;
-            var rows = document.getElementsByTagNameNS(W, "tr");
-            for (int i = 0; i < rows.getLength(); i++) {
-                if (rows.item(i).getTextContent().contains("${itemName}")) { rowTemplate = rows.item(i); break; }
+            var paragraphs = document.getElementsByTagNameNS(W, "p");
+            boolean numberFilled = false, dateFilled = false, buyerFilled = false;
+            for (int i = 0; i < paragraphs.getLength(); i++) {
+                Node p = paragraphs.item(i);
+                String value = p.getTextContent();
+                if (value.contains("鍚堝悓缂栧彿锛歋HHT20260102902-F")) {
+                    setParagraphText(p, "鍚堝悓缂栧彿锛�" + text(contract.getNo()));
+                    alignRight(p);
+                    numberFilled = true;
+                } else if (value.trim().equals("绛捐鏃ユ湡锛�")) {
+                    setParagraphText(p, "绛捐鏃ユ湡锛�" + date(contract.getOrderDate()));
+                    alignRight(p);
+                    dateFilled = true;
+                } else if (value.contains("涔版柟锛堢敳鏂癸級锛�") && value.contains("绛捐鍦扮偣锛氭睙鑻忓崡閫�")) {
+                    fillBuyer(p, text(contract.getCustomerName()));
+                    buyerFilled = true;
+                } else if (value.equals("涔欐柟锛堢洊绔狅級锛�")) {
+                    // 鍘熸ā鏉跨殑绌虹櫧涔版柟绛剧珷妗嗚鍐欎负涔欐柟锛涘悓鏃跺鐞� DrawingML 鍜� VML 涓や釜鍏煎鍒嗘敮銆�
+                    setParagraphText(p, "鐢叉柟锛堢洊绔狅級锛�" + text(contract.getCustomerName()));
+                }
             }
-            if (rowTemplate == null) throw new IOException("鍚堝悓妯℃澘缂哄皯浜у搧鏄庣粏妲戒綅");
-            int index = 0;
+            if (!numberFilled || !dateFilled || !buyerFilled) throw new IOException("閿�鍞悎鍚屾ā鏉胯〃澶翠笉瀹屾暣");
+
+            Node table = document.getElementsByTagNameNS(W, "tbl").item(0);
+            List<Node> rows = children(table, "tr");
+            if (rows.size() != 4 || children(rows.get(1), "tc").size() != 7
+                    || !rows.get(3).getTextContent().contains("鍚堣锛堝惈13%澧炲�肩◣锛�")) {
+                throw new IOException("閿�鍞悎鍚屾ā鏉夸骇鍝佽〃鏍间笉瀹屾暣");
+            }
+            Node rowTemplate = rows.get(1);
             BigDecimal productTotal = BigDecimal.ZERO;
+            int index = 0;
             for (var product : contract.getProducts()) {
                 Node row = rowTemplate.cloneNode(true);
                 BigDecimal price = Objects.requireNonNull(product.getContractPrice(), "鍚堝悓鍗曚环涓嶈兘涓虹┖");
@@ -66,25 +91,32 @@
                 productTotal = productTotal.add(amount);
                 String name = text(product.getItemName());
                 if (name.isBlank()) name = text(product.getItemCode());
-                String specification = text(product.getItemSpecification());
-                if (!specification.isBlank()) name += "锛�" + specification + "锛�";
-                replace(row, Map.of("index", String.valueOf(++index), "itemName", name,
-                        "unitName", text(product.getItemUnitName()), "count", count.stripTrailingZeros().toPlainString(),
-                        "price", money(price), "amount", money(amount)));
+                List<Node> cells = children(row, "tc");
+                String[] values = {String.valueOf(++index), name, text(product.getItemSpecification()),
+                        text(product.getItemUnitName()), count.stripTrailingZeros().toPlainString(), money(price), money(amount)};
+                for (int i = 0; i < values.length; i++) {
+                    Node cell = cells.get(i);
+                    Node properties = firstChild(cell, "tcPr");
+                    Node noWrap = firstChild(properties, "noWrap");
+                    if (noWrap != null) properties.removeChild(noWrap);
+                    setParagraphText(firstChild(cell, "p"), values[i]);
+                }
+                Node rowProperties = firstChild(row, "trPr");
+                if (firstChild(rowProperties, "cantSplit") == null) {
+                    rowProperties.appendChild(document.createElementNS(W, "w:cantSplit"));
+                }
                 rowTemplate.getParentNode().insertBefore(row, rowTemplate);
             }
             rowTemplate.getParentNode().removeChild(rowTemplate);
             BigDecimal total = Objects.requireNonNull(contract.getTotalPrice(), "鍚堝悓鎬婚噾棰濅笉鑳戒负绌�");
             BigDecimal discount = productTotal.subtract(total);
-            replace(document, Map.of("customerName", text(contract.getCustomerName()),
-                    "contractNo", text(contract.getNo()),
-                    "contractName", text(contract.getName()),
-                    "contractDate", contract.getOrderDate() == null ? "____________" : contract.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")),
-                    "startDate", date(contract.getStartTime()),
-                    "endDate", date(contract.getEndTime()),
-                    "totalPrice", money(total), "totalUpper", Convert.digitToChinese(total),
-                    "discountNote", discount.signum() == 0 ? "" : "锛堟槑缁嗗悎璁� " + money(productTotal) + " 鍏冿紝涓庡悎鍚岄噾棰濈殑宸 " + money(discount) + " 鍏冿級"));
-            if (document.getDocumentElement().getTextContent().contains("${")) throw new IOException("鍚堝悓妯℃澘瀛樺湪鏈~鍐欐Ы浣�");
+            String summary = "鍚堣锛堝惈13%澧炲�肩◣锛�  浜烘皯甯侊細" + money(total) + "鍏�  澶у啓锛�" + Convert.digitToChinese(total);
+            if (discount.signum() != 0) summary += "锛堟槑缁嗗悎璁� " + money(productTotal) + " 鍏冿紝涓庡悎鍚岄噾棰濈殑宸 " + money(discount) + " 鍏冿級";
+            Node totalCell = children(rows.get(3), "tc").get(0);
+            Node totalProperties = firstChild(totalCell, "tcPr");
+            Node noWrap = firstChild(totalProperties, "noWrap");
+            if (noWrap != null) totalProperties.removeChild(noWrap);
+            setParagraphText(firstChild(totalCell, "p"), summary);
             var transformerFactory = TransformerFactory.newInstance();
             transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
             transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
@@ -95,16 +127,60 @@
         } catch (Exception exception) { throw new IOException("鐢熸垚鍚堝悓 Word 鏂囦欢澶辫触", exception); }
     }
 
-    private static void replace(Node node, Map<String, String> values) {
-        if (node.getNodeType() == Node.ELEMENT_NODE && "t".equals(node.getLocalName()) && W.equals(node.getNamespaceURI())) {
+    private static void fillBuyer(Node paragraph, String buyer) {
+        var nodes = ((Element) paragraph).getElementsByTagNameNS(W, "t");
+        boolean afterColon = false, inserted = false;
+        for (int i = 0; i < nodes.getLength(); i++) {
+            Node node = nodes.item(i);
             String value = node.getTextContent();
-            for (var entry : values.entrySet()) value = value.replace("${" + entry.getKey() + "}", entry.getValue());
-            node.setTextContent(value);
-            return;
+            if (value.contains("绛捐鍦扮偣锛�")) break;
+            if (afterColon) {
+                node.setTextContent(inserted ? "" : buyer + "  ");
+                inserted = true;
+            } else if (value.equals("锛�")) afterColon = true;
         }
-        for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) replace(child, values);
+        if (!inserted) throw new IllegalStateException("鍚堝悓妯℃澘缂哄皯涔版柟濉啓浣嶇疆");
     }
 
+    private static void alignRight(Node paragraph) {
+        Node properties = firstChild(paragraph, "pPr");
+        Node indent = firstChild(properties, "ind");
+        if (indent != null) properties.removeChild(indent);
+        Node alignment = firstChild(properties, "jc");
+        if (alignment == null) {
+            alignment = paragraph.getOwnerDocument().createElementNS(W, "w:jc");
+            properties.appendChild(alignment);
+        }
+        ((Element) alignment).setAttributeNS(W, "w:val", "right");
+    }
+
+    private static void setParagraphText(Node paragraph, String value) {
+        var nodes = ((Element) paragraph).getElementsByTagNameNS(W, "t");
+        if (nodes.getLength() == 0) {
+            var document = paragraph.getOwnerDocument();
+            Node run = document.createElementNS(W, "w:r");
+            Node text = document.createElementNS(W, "w:t");
+            run.appendChild(text);
+            paragraph.appendChild(run);
+            text.setTextContent(value);
+        } else {
+            nodes.item(0).setTextContent(value);
+            for (int i = 1; i < nodes.getLength(); i++) nodes.item(i).setTextContent("");
+        }
+    }
+
+    private static List<Node> children(Node parent, String localName) {
+        List<Node> result = new ArrayList<>();
+        if (parent == null) return result;
+        for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) {
+            if (W.equals(node.getNamespaceURI()) && localName.equals(node.getLocalName())) result.add(node);
+        }
+        return result;
+    }
+    private static Node firstChild(Node parent, String localName) {
+        List<Node> result = children(parent, localName);
+        return result.isEmpty() ? null : result.get(0);
+    }
     private static String money(BigDecimal value) { return value.setScale(2, RoundingMode.HALF_UP).toPlainString(); }
     private static String date(java.time.LocalDateTime value) { return value == null ? "____________" : value.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")); }
     private static String text(String value) { return value == null ? "" : value; }

--
Gitblit v1.9.3