diff --git a/doc/中铜技术文档/Seata分布式事务集成参考.md b/doc/中铜技术文档/Seata分布式事务集成参考.md
new file mode 100644
index 0000000..f847a51
--- /dev/null
+++ b/doc/中铜技术文档/Seata分布式事务集成参考.md
@@ -0,0 +1,158 @@
+[Seata分布式事务集成参考.md](Seata%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E9%9B%86%E6%88%90%E5%8F%82%E8%80%83.md)# Seata 分布式事务集成参考
+
+## 一、架构说明
+
+### 多环境 Seata 配置管理
+
+所有环境的 Seata 配置统一通过 Nacos 管理,本地配置文件不包含 Seata 配置。
+
+```
+├── dev 环境: base-server-dev.yaml (dev_tx_group, namespace: dev)
+├── test 环境: base-server-test.yaml (test_tx_group, namespace: test)
+└── prod 环境: base-server-prod.yaml (prod_tx_group, namespace: prod)
+
+Seata Server: 172.16.46.63:30088
+```
+
+### 为什么使用 IP 直连?
+
+Nacos 的 namespace 隔离机制导致不同命名空间的应用无法跨 namespace 发现服务。Seata Server 通过 IP 直连(`registry.type=file`)避免此限制,所有环境共享同一个 Seata Server,通过不同的 `tx-service-group` 实现逻辑隔离。
+
+---
+
+## 二、Nacos 配置
+
+在对应环境的 Nacos 配置中心创建配置文件。以 `base-server-dev.yaml` 为例(其他环境只需修改 `tx-service-group`):
+
+```yaml
+seata:
+ enabled: true
+ application-id: base-server
+ tx-service-group: dev_tx_group
+ enable-auto-data-source-proxy: true
+ data-source-proxy-mode: AT
+ registry:
+ type: file
+ config:
+ type: file
+ service:
+ vgroupMapping:
+ default_tx_group: default
+ dev_tx_group: default
+ test_tx_group: default
+ prod_tx_group: default
+ default:
+ grouplist: 172.16.46.63:30088
+ client:
+ tm:
+ defaultGlobalTransactionTimeout: 60000
+ undo:
+ logTable: undo_log
+ dataValidation: true
+ logSerialization: jackson
+```
+
+---
+
+## 三、业务集成步骤
+
+### 步骤 1:在 pom.xml 中添加依赖
+
+Seata 2.4.0 版本已在 `zt-dependencies` 中统一管理。业务模块只需在 `pom.xml` 中添加:
+
+```xml
+
+ io.seata
+ seata-spring-boot-starter
+
+```
+
+版本号会从 `zt-dependencies` 继承。
+
+### 步骤 2:创建 undo_log 表
+
+在业务数据库执行:
+
+```sql
+CREATE TABLE "UNDO_LOG" (
+ "BRANCH_ID" BIGINT NOT NULL,
+ "XID" VARCHAR(128) NOT NULL,
+ "CONTEXT" VARCHAR(128) NOT NULL,
+ "ROLLBACK_INFO" BLOB NOT NULL,
+ "LOG_STATUS" INT NOT NULL,
+ "LOG_CREATED" DATETIME DEFAULT SYSDATE,
+ "LOG_MODIFIED" DATETIME DEFAULT SYSDATE,
+ PRIMARY KEY ("BRANCH_ID")
+);
+
+CREATE UNIQUE INDEX "UX_UNDO_LOG" ON "UNDO_LOG" ("XID", "BRANCH_ID");
+```
+
+### 步骤 3:在 Service 方法上添加 @GlobalTransactional 注解
+
+```java
+import io.seata.spring.annotation.GlobalTransactional;
+
+@Service
+public class OrderServiceImpl implements OrderService {
+
+ @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
+ @Override
+ public Long createOrder(OrderCreateReqVO req) {
+ // 本地操作
+ orderMapper.insert(order);
+
+ // 跨服务调用自动参与分布式事务
+ inventoryApi.deduct(productId, quantity);
+
+ return order.getId();
+ }
+}
+```
+
+**说明**:
+- 只在事务发起方添加注解
+- 被调用的其他服务自动参与,无需额外配置
+- Seata 自动通过 HTTP Header 传递事务 ID
+
+---
+
+## 四、配置汇总
+
+| 配置项 | 开发环境 | 测试环境 | 生产环境 |
+|--------|---------|---------|---------|
+| **tx-service-group** | `dev_tx_group` | `test_tx_group` | `prod_tx_group` |
+| **Nacos 命名空间** | `hwc` | `test` | `prod` |
+| **Seata Server** | `172.16.46.63:30088` | `172.16.46.63:30088` | `172.16.46.63:30088` |
+
+---
+
+## 五、其他事务模式
+
+当前配置默认使用 **AT 模式**(自动事务模式)。Seata 还支持其他事务模式,需要业务自己实现:
+
+### TCC 模式
+
+需要实现 Try、Confirm、Cancel 三个业务方法,使用 `@TwoPhaseBusinessAction` 注解标记。详见官方文档。
+
+### Saga 模式
+
+需要定义 Saga 流程和状态机。详见官方文档。
+
+### XA 模式
+
+需要数据库支持 XA 事务,配置中修改 `data-source-proxy-mode: XA`。
+
+**详见**: https://seata.apache.org/zh-cn/docs/overview/what-is-seata
+
+---
+
+## 六、Seata 控制台
+
+**地址**: `http://172.16.46.63:30087`
+
+可查看全局事务、分支事务、全局锁等监控信息。
+
+---
+
+**官方文档**: https://seata.apache.org/zh-cn/docs/overview/what-is-seata
diff --git a/doc/中铜技术文档/计量单位转换使用文档.md b/doc/中铜技术文档/计量单位转换使用文档.md
new file mode 100644
index 0000000..afee935
--- /dev/null
+++ b/doc/中铜技术文档/计量单位转换使用文档.md
@@ -0,0 +1,212 @@
+# 计量单位转换业务使用文档
+
+## 一、系统概述
+
+计量单位转换提供统一的计量单位转换服务,支持同一量纲内的单位自动转换。采用**单向配置、双向生效**机制,只需配置"非基准单位 → 基准单位"的转换规则,自动推导反向和间接转换。
+
+**核心特性**:
+- 单向配置、双向生效的转换机制
+- 支持按单位ID、符号、名称进行转换
+- 高精度计算,支持批量操作
+- 跨模块统一服务
+
+## 二、内容配置
+
+### 2.1 管理菜单路径
+后台管理 → 基础管理 → 计量单位 → 计量单位管理
+
+### 2.2 配置功能
+
+#### 计量量纲管理
+- **功能**:创建和管理不同的量纲类型(如重量、长度、体积等)
+- **操作**:新增量纲、编辑量纲信息、删除量纲
+- **每个量纲只能设置一个基准单位**
+
+#### 计量单位管理
+- **功能**:创建和管理具体的计量单位
+- **操作**:新增单位、编辑单位信息、删除单位
+- **关联量纲**:将单位归属到具体的量纲下
+
+#### 转换规则配置
+- **功能**:配置单位间的转换规则
+- **配置原则**:只需配置"非基准单位 → 基准单位"
+- **自动推导**:系统自动推导反向转换和间接转换
+
+### 2.3 配置建议
+
+1. **量纲规划**:提前规划好业务需要的量纲类型
+2. **基准单位选择**:选择业务中最常用、最稳定的单位作为基准
+3. **转换规则**:优先使用整数转换系数,提高计算精度
+4. **定期校验**:使用转换路径校验功能确保配置正确性
+5. **转换完整性校验**:系统提供同一量纲内所有单位能否互相转换的校验功能,确保转换配置的完整性
+
+## 三、API接口清单
+
+### 3.1 单位管理接口
+
+| 接口 | 方法 | 路径 | 说明 | API文档 |
+|------|------|------|------|---------|
+| 获取量纲树 | GET | `/admin-api/base/unit-management/unit-quantity/tree` | 获取量纲和单位树形结构 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E8%AE%A1%E9%87%8F%E5%8D%95%E4%BD%8D%E9%87%8F/getUnitQuantityTree) |
+| 获取单位列表 | GET | `/admin-api/base/unit-management/unt-info/page` | 获取单位列表(用于下拉选择) | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E8%AE%A1%E9%87%8F%E5%8D%95%E4%BD%8D/getUntInfoPage) |
+
+### 3.2 单位转换接口
+
+| 接口 | 方法 | 路径 | 说明 | API文档 |
+|------|------|------|------|---------|
+| 按ID转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert` | 通过单位ID转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convert) |
+| 按符号转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert-by-symbol` | 通过单位符号转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convertBySymbol) |
+| 按名称转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert-by-name` | 通过单位名称转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convertByName) |
+| 批量ID转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert` | 按ID批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvert) |
+| 批量符号转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert-by-symbol` | 按符号批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvertBySymbol) |
+| 批量名称转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert-by-name` | 按名称批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvertByName) |
+
+
+---
+
+## 三、后端业务调用示例
+
+### base模块内使用
+
+```java
+@Service
+public class PurchaseOrderServiceImpl {
+
+ @Resource
+ private UnitConversionService unitConversionService;
+
+ /**
+ * 处理采购订单,统一转换为千克计算
+ */
+ public void processPurchaseOrder(PurchaseOrderSaveReqVO orderVO) {
+ for (PurchaseOrderDetailVO detail : orderVO.getDetails()) {
+ // 方式1:按符号转换
+ UnitConvertBySymbolReqVO convertReq = new UnitConvertBySymbolReqVO();
+ convertReq.setSrcUnitSymbol(detail.getUnt());
+ convertReq.setTgtUnitSymbol("kg");
+ convertReq.setValue(detail.getQty());
+ convertReq.setPrecision(6);
+
+ UnitConvertRespVO result = unitConversionService.convertBySymbol(convertReq);
+ BigDecimal standardQuantity = result.getConvertedValue();
+
+ // 方式2:按ID转换(如果有单位ID)
+ // UnitConvertReqVO convertReq = new UnitConvertReqVO();
+ // convertReq.setSrcUntId(detail.getUntId());
+ // convertReq.setTgtUntId(kgUnitId);
+ // ...
+ }
+ }
+}
+```
+
+---
+
+## 四、跨模块调用
+
+### 4.1 直接Service调用(推荐)
+
+在同一服务内直接注入使用:
+```java
+@Resource
+private UnitConversionService unitConversionService;
+```
+
+### 4.2 跨服务调用(按需使用)
+
+**1. 在 API 模块中定义 Feign 接口:**
+
+```java
+package com.zt.plat.module.base.api;
+
+import com.zt.plat.framework.common.pojo.CommonResult;
+import com.zt.plat.module.base.enums.ApiConstants;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@FeignClient(name = ApiConstants.NAME)
+@Tag(name = "RPC 服务 - 单位转换")
+public interface UnitConversionApi {
+
+ String PREFIX = ApiConstants.PREFIX + "/unit-conversion";
+
+ @PostMapping(PREFIX + "/convert")
+ @Operation(summary = "按ID转换单位")
+ CommonResult convert(@RequestBody UnitConvertReqVO reqVO);
+
+ @PostMapping(PREFIX + "/convert-by-symbol")
+ @Operation(summary = "按符号转换单位")
+ CommonResult convertBySymbol(@RequestBody UnitConvertBySymbolReqVO reqVO);
+}
+```
+
+**2. 在其他服务中调用:**
+
+```java
+@Service
+public class PurchaseServiceImpl {
+
+ @Resource
+ private UnitConversionApi unitConversionApi;
+
+ public void processPurchase(PurchaseVO purchase) {
+ UnitConvertBySymbolReqVO convertReq = new UnitConvertBySymbolReqVO();
+ convertReq.setSrcUnitSymbol(purchase.getUnit());
+ convertReq.setTgtUnitSymbol("kg");
+ convertReq.setValue(purchase.getQuantity());
+ convertReq.setPrecision(6);
+
+ CommonResult result = unitConversionApi.convertBySymbol(convertReq);
+ if (result.isSuccess()) {
+ BigDecimal standardQty = result.getData().getConvertedValue();
+ // 业务处理
+ }
+ }
+}
+```
+
+---
+
+## 五、前端使用
+
+### 5.1 基本API调用
+
+```typescript
+// 获取量纲树
+export const getUnitQuantityTree = () => {
+ return request.get('/admin-api/base/unit-management/unit-quantity/tree')
+}
+
+// 获取单位列表
+export const getUntInfoPage = (params: any) => {
+ return request.get('/admin-api/base/unit-management/unt-info/page', { params })
+}
+
+// 单位转换
+export const convertUnitBySymbol = (data: any) => {
+ return request.post('/admin-api/base/unit-management/unit-conversion/convert-by-symbol', data)
+}
+```
+
+---
+
+## 六、常见问题
+
+**Q1: 前端如何获取单位选项?**
+
+A: 使用 `/admin-api/base/unit-management/unit-quantity/tree` 获取量纲树,然后根据选择的量纲调用 `/admin-api/base/unit-management/unt-info/page` 获取单位列表。
+
+**Q2: 按ID转换和按符号转换哪个更好?**
+
+A: 按ID转换更稳定,因为数据库ID不会变化。建议在前端保存单位ID,在业务转换时使用ID调用。
+
+**Q3: 跨服务调用需要特殊配置吗?**
+
+A: 不需要,项目已经统一配置好。所有Feign客户端都使用 `name = "base-server"`,路径使用 `/rpc-api` 前缀。
+
+**Q4: 批量转换性能问题?**
+
+A: 使用批量接口,设置ignoreErrors=true。
+
diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java
index 7c39b56..b9b3a0a 100644
--- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java
+++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java
@@ -14,8 +14,10 @@ import com.zt.plat.framework.excel.core.util.ExcelUtils;
import com.zt.plat.module.qms.business.config.dal.dataobject.ConfigUserSignatureDO;
import com.zt.plat.module.qms.business.config.service.ConfigUserSignatureService;
import com.zt.plat.module.qms.business.reportdoc.controller.vo.*;
+import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentDataDO;
import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentMainDO;
import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentTypeDO;
+import com.zt.plat.module.qms.business.reportdoc.service.ReportDocumentDataService;
import com.zt.plat.module.qms.business.reportdoc.service.ReportDocumentMainService;
import com.zt.plat.module.qms.business.reportdoc.service.ReportDocumentTypeService;
import com.zt.plat.module.qms.enums.QmsCommonConstant;
@@ -32,7 +34,9 @@ import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.zt.plat.framework.common.pojo.CommonResult.error;
@@ -54,6 +58,7 @@ public class ReportDocumentMainController extends AbstractFileUploadController i
}
@Resource private ReportDocumentMainService reportDocumentMainService;
+ @Resource private ReportDocumentDataService reportDocumentDataService;
@Resource private ReportDocumentTypeService reportDocumentTypeService;
@Resource private ConfigUserSignatureService configUserSignatureService;
@@ -149,7 +154,7 @@ public class ReportDocumentMainController extends AbstractFileUploadController i
@Operation(summary = "获得检测报告业务")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
//@PreAuthorize("@ss.hasPermission('qms:report-document-main:query')")
- public CommonResult getReportDocumentMain(@RequestParam("id") Long id) {
+ public CommonResult getReportDocumentMain(@RequestParam("id") Long id, @RequestParam(value = "editFlag", required = false, defaultValue = "false") String editFlag) {
ReportDocumentMainDO reportDocumentMain = reportDocumentMainService.getReportDocumentMain(id);
ReportDocumentMainRespVO vo = BeanUtils.toBean(reportDocumentMain, ReportDocumentMainRespVO.class);
@@ -183,9 +188,58 @@ public class ReportDocumentMainController extends AbstractFileUploadController i
vo.setDocumentSignature(docSigJson.toJSONString());
}
+ //处理抬头数据
+
+ if("true".equals(editFlag)){
+ String formData = vo.getFormData();
+ JSONObject formDataJson = new JSONObject();
+ if(!ObjectUtils.isEmpty(formData))
+ formDataJson = JSONObject.parseObject(formData);
+ List dataList = reportDocumentDataService.listByMainDataId(id).getData();
+ ReportDocumentTypeDO typeDO = reportDocumentTypeService.getReportDocumentType(reportDocumentMain.getReportDocumentTypeId());
+ String customConfig = typeDO.getCustomConfig();
+ String defaultConclusion = "";
+ if(!ObjectUtils.isEmpty(customConfig)){
+ JSONObject config = JSONObject.parseObject(customConfig);
+ defaultConclusion = config.getString("defaultConclusion");
+ }
+ formDataJson.put("conclusion", defaultConclusion);
+ if(!dataList.isEmpty())
+ formDataJson.put("sampleName", dataList.get(0).getSampleName());
+ //处理检测标准
+ String standard = assembleStandard(dataList);
+ formDataJson.put("standard", standard);
+ vo.setFormData(formDataJson.toJSONString());
+// ReportDocumentMainSaveReqVO updateVO = new ReportDocumentMainSaveReqVO();
+// updateVO.setId(reportDocumentMain.getId());
+// updateVO.setFormData(formDataJson.toJSONString());
+// reportDocumentMainService.updateReportDocumentMain(updateVO);
+ }
return success(vo);
}
+ private String assembleStandard(List dataList){
+
+ Set standardSet = new HashSet<>();
+ for(ReportDocumentDataDO data : dataList){
+ String content = data.getDocumentContent();
+ if(ObjectUtils.isEmpty( content))
+ continue;
+ JSONObject json = JSONObject.parseObject(content);
+ for(String key : json.keySet()){
+ JSONObject obj = json.getJSONObject(key);
+ String methodName = obj.getString("methodName");
+ if(ObjectUtils.isEmpty(methodName))
+ continue;
+ standardSet.add(methodName);
+ }
+ }
+ if(standardSet.isEmpty())
+ return "";
+ return String.join(",", standardSet);
+ }
+
+
@GetMapping("/page")
@Operation(summary = "获得检测报告业务分页")
//@PreAuthorize("@ss.hasPermission('qms:report-document-main:query')")