diff --git a/ruoyi-modules/ruoyi-oa/pom.xml b/ruoyi-modules/ruoyi-oa/pom.xml
index 3ff2a153..22566120 100644
--- a/ruoyi-modules/ruoyi-oa/pom.xml
+++ b/ruoyi-modules/ruoyi-oa/pom.xml
@@ -115,6 +115,10 @@
2.5.0
compile
+
+ org.dromara
+ ruoyi-common-word
+
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpContractInfoController.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpContractInfoController.java
index d168c722..e14b98fc 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpContractInfoController.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpContractInfoController.java
@@ -1,26 +1,30 @@
package org.dromara.oa.erp.controller;
-import java.util.List;
-
-import lombok.RequiredArgsConstructor;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.validation.annotation.Validated;
-import org.dromara.common.idempotent.annotation.RepeatSubmit;
-import org.dromara.common.log.annotation.Log;
-import org.dromara.common.web.core.BaseController;
-import org.dromara.common.mybatis.core.page.PageQuery;
+import com.deepoove.poi.config.Configure;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
-import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
-import org.dromara.oa.erp.domain.vo.ErpContractInfoVo;
-import org.dromara.oa.erp.domain.bo.ErpContractInfoBo;
-import org.dromara.oa.erp.service.IErpContractInfoService;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.word.util.WordTemplateUtil;
+import org.dromara.oa.erp.domain.bo.ErpContractInfoBo;
+import org.dromara.oa.erp.domain.vo.ErpContractInfoVo;
+import org.dromara.oa.erp.service.IErpContractInfoService;
+import org.dromara.oa.erp.word.ErpContractApprovalTablePolicy;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
/**
* 合同信息
@@ -57,6 +61,31 @@ public class ErpContractInfoController extends BaseController {
ExcelUtil.exportExcel(list, "合同信息", ErpContractInfoVo.class, response);
}
+ /**
+ * 导出合同审批单Word文档
+ *
+ * @param contractId 合同ID
+ * @param response HttpServletResponse
+ */
+ @SaCheckPermission("oa/erp:contractInfo:export")
+ @Log(title = "合同审批单Word导出", businessType = BusinessType.EXPORT)
+ @GetMapping("/exportApprovalWord/{contractId}")
+ public void exportApprovalWord(@NotNull(message = "合同ID不能为空")
+ @PathVariable("contractId") Long contractId,
+ HttpServletResponse response) {
+ // 组装模板数据(合同基本信息 + 审批记录)
+ java.util.Map data = erpContractInfoService.buildApprovalWordExportData(contractId);
+ // 生成文件名(优先使用合同编号)
+ ErpContractInfoVo vo = erpContractInfoService.queryById(contractId);
+ String fileName = "合同审批单_" + (vo != null && vo.getContractCode() != null ? vo.getContractCode() : contractId);
+ // 绑定审批记录动态表格策略
+ Configure config = Configure.builder()
+ .bind("审批记录表", new ErpContractApprovalTablePolicy())
+ .build();
+ // 渲染并输出Word文档
+ WordTemplateUtil.renderToResponse("合同审批单模板.docx", fileName, data, config, response);
+ }
+
/**
* 获取合同信息详细信息
*
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectChangeMapper.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectChangeMapper.java
index 398380a2..a07d1ae1 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectChangeMapper.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/mapper/ErpProjectChangeMapper.java
@@ -33,7 +33,7 @@ public interface ErpProjectChangeMapper extends BaseMapperPlus selectCustomErpProjectChangeVoList(@Param("page") Page page, @Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper);
+ Page selectCustomErpProjectChangeVoList(@Param("page") Page page, @Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper);
/**
* 查询项目变更申请列表
@@ -45,7 +45,7 @@ public interface ErpProjectChangeMapper extends BaseMapperPlus selectCustomErpProjectChangeVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper);
+ List selectCustomErpProjectChangeVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper);
/**
* 根据ID查询项目变更申请详情
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractInfoService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractInfoService.java
index d30ab633..4c6d7008 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractInfoService.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpContractInfoService.java
@@ -1,10 +1,9 @@
package org.dromara.oa.erp.service;
-import org.dromara.oa.erp.domain.ErpContractInfo;
-import org.dromara.oa.erp.domain.vo.ErpContractInfoVo;
-import org.dromara.oa.erp.domain.bo.ErpContractInfoBo;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.oa.erp.domain.bo.ErpContractInfoBo;
+import org.dromara.oa.erp.domain.vo.ErpContractInfoVo;
import java.util.Collection;
import java.util.List;
@@ -73,4 +72,12 @@ public interface IErpContractInfoService {
* @return
*/
ErpContractInfoVo contractSubmitAndFlowStart(ErpContractInfoBo bo);
+
+ /**
+ * 组装合同审批单Word导出数据
+ *
+ * @param contractId 合同ID
+ * @return Word模板数据Map
+ */
+ java.util.Map buildApprovalWordExportData(Long contractId);
}
diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractInfoServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractInfoServiceImpl.java
index 723e0518..41e7ba96 100644
--- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractInfoServiceImpl.java
+++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpContractInfoServiceImpl.java
@@ -1,44 +1,49 @@
package org.dromara.oa.erp.service.impl;
+import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.convert.NumberChineseFormatter;
import cn.hutool.core.map.MapUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.deepoove.poi.data.RowRenderData;
+import com.deepoove.poi.data.Rows;
+import com.github.yulichang.toolkit.JoinWrappers;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.enums.OAStatusEnum;
import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.github.yulichang.toolkit.JoinWrappers;
-import com.github.yulichang.wrapper.MPJLambdaWrapper;
-import lombok.RequiredArgsConstructor;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.tenant.helper.TenantHelper;
-import org.dromara.oa.base.domain.vo.BaseRelationMaterialVo;
-import org.dromara.oa.erp.domain.ErpContractMaterial;
-import org.dromara.oa.erp.domain.ErpContractPaymentMethod;
-import org.dromara.oa.erp.domain.vo.ErpContractMaterialVo;
-import org.dromara.oa.erp.domain.vo.ErpContractPaymentMethodVo;
-import org.dromara.oa.erp.mapper.ErpContractMaterialMapper;
-import org.dromara.oa.erp.mapper.ErpContractPaymentMethodMapper;
import org.dromara.oa.base.domain.bo.BaseRelationMaterialBo;
+import org.dromara.oa.base.domain.vo.BaseRelationMaterialVo;
import org.dromara.oa.base.service.IBaseRelationMaterialService;
import org.dromara.oa.crm.domain.vo.CrmPaymentAccountVo;
import org.dromara.oa.crm.service.ICrmPaymentAccountService;
+import org.dromara.oa.erp.domain.ErpContractInfo;
+import org.dromara.oa.erp.domain.ErpContractMaterial;
+import org.dromara.oa.erp.domain.ErpContractPaymentMethod;
+import org.dromara.oa.erp.domain.bo.ErpContractInfoBo;
+import org.dromara.oa.erp.domain.vo.ErpContractInfoVo;
+import org.dromara.oa.erp.domain.vo.ErpContractMaterialVo;
+import org.dromara.oa.erp.domain.vo.ErpContractPaymentMethodVo;
+import org.dromara.oa.erp.mapper.ErpContractInfoMapper;
+import org.dromara.oa.erp.mapper.ErpContractMaterialMapper;
+import org.dromara.oa.erp.mapper.ErpContractPaymentMethodMapper;
+import org.dromara.oa.erp.service.IErpContractInfoService;
+import org.dromara.system.api.RemoteUserService;
import org.dromara.workflow.api.RemoteWorkflowService;
import org.dromara.workflow.api.domain.RemoteStartProcess;
import org.dromara.workflow.api.event.ProcessEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
-import org.dromara.oa.erp.domain.bo.ErpContractInfoBo;
-import org.dromara.oa.erp.domain.vo.ErpContractInfoVo;
-import org.dromara.oa.erp.domain.ErpContractInfo;
-import org.dromara.oa.erp.mapper.ErpContractInfoMapper;
-import org.dromara.oa.erp.service.IErpContractInfoService;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@@ -55,6 +60,12 @@ import java.util.stream.Collectors;
@Slf4j
public class ErpContractInfoServiceImpl implements IErpContractInfoService {
+ private static final String WORD_DATE_FORMAT = "yyyy年MM月dd日";
+
+ private static final String BUSINESS_DIRECTION_LEADER_NODE_NAME = "业务方向负责人";
+
+ private static final String FINANCE_MANAGER_NODE_NAME = "财务经理";
+
private final ErpContractInfoMapper baseMapper;
private final ErpContractMaterialMapper contractMaterialMapper;
@@ -65,6 +76,8 @@ public class ErpContractInfoServiceImpl implements IErpContractInfoService {
private final ICrmPaymentAccountService crmPaymentAccountService;
+ private final RemoteUserService remoteUserService;
+
@DubboReference(timeout = 30000)
private RemoteWorkflowService remoteWorkflowService;
@@ -395,6 +408,215 @@ public class ErpContractInfoServiceImpl implements IErpContractInfoService {
return baseMapper.deleteByIds(ids) > 0;
}
+ /**
+ * 组装合同审批单Word导出数据
+ *
+ * @param contractId 合同ID
+ * @return Word模板数据Map
+ */
+ @Override
+ public Map buildApprovalWordExportData(Long contractId) {
+ ErpContractInfoVo contractInfo = queryById(contractId);
+ if (contractInfo == null) {
+ throw new ServiceException("合同不存在,ID:" + contractId);
+ }
+ if (!OAStatusEnum.COMPLETED.getStatus().equals(contractInfo.getContractStatus())) {
+ throw new ServiceException("仅合同状态为“可用”时允许导出审批单");
+ }
+
+ Map data = new HashMap<>(64);
+ // 审批单导出优先保障“合同基础信息”完整,避免审批记录正确但主表关键信息缺失导致单据不可用
+ data.put("合同编号", strVal(contractInfo.getContractCode()));
+ data.put("合同名称", strVal(contractInfo.getContractName()));
+ data.put("客户合同编号", strVal(contractInfo.getCustomerContractCode()));
+ data.put("合同签订日期", formatWordDate(contractInfo.getContractDate()));
+ data.put("合同总价", contractInfo.getTotalPrice() == null ? "" : contractInfo.getTotalPrice().toPlainString());
+ data.put("甲方公司", strVal(contractInfo.getOneCustomerName()));
+ data.put("乙方公司", strVal(contractInfo.getTwoCustomerName()));
+ data.put("合同负责人", strVal(contractInfo.getContractManagerName()));
+ data.put("所属部门", strVal(contractInfo.getDeptName()));
+ data.put("付款方式", strVal(contractInfo.getPaymentMethod()));
+ data.put("付款账户信息", strVal(contractInfo.getPaymentAccountInfo()));
+ data.put("合同类型", strVal(contractInfo.getContractType()));
+ data.put("合同大类", strVal(contractInfo.getContractCategory()));
+ data.put("业务方向", strVal(contractInfo.getBusinessDirection()));
+ data.put("签订地点", strVal(contractInfo.getSigningPlace()));
+ data.put("备注", strVal(contractInfo.getRemark()));
+ data.put("内部合同号", strVal(contractInfo.getInternalContractCode()));
+ data.put("外部合同号", strVal(contractInfo.getExternalContractCode()));
+ data.put("订单号", strVal(contractInfo.getOrderContractCode()));
+ data.put("项目号", strVal(contractInfo.getProjectContractCode()));
+ // Why:当前业务已明确“交付启动期限”与“交货期”同义,双键同时输出可兼容不同模板占位符命名
+ String deliveryPeriod = strVal(contractInfo.getDeliveryStart());
+ data.put("交货期", deliveryPeriod);
+ data.put("交付启动期限", deliveryPeriod);
+ data.put("质保期", strVal(contractInfo.getWarrantyPeriod()));
+ data.put("质保期描述", strVal(contractInfo.getWarrantyPeriodDescription()));
+
+ // 按需求先置空的字段
+ data.put("行业/大区", "");
+ data.put("是否为销售合同标准文本", "");
+ data.put("是否经法务部备案的销售合同文本", "");
+ data.put("销售产品是否出口", "");
+ data.put("盖章要求", "");
+ data.put("合同争议解决方式", "");
+
+ // 审批单只展示“已办节点”,避免把待办节点(无审批时间/意见)混入导出文档
+ Map flowData = remoteWorkflowService.flowHisTaskList(String.valueOf(contractId));
+ List