From d7a536d6b995f0d57bdbe5c6a07eb7a310c925c0 Mon Sep 17 00:00:00 2001 From: yinq Date: Tue, 19 May 2026 11:13:48 +0800 Subject: [PATCH] =?UTF-8?q?1.1.45=20=E5=AF=BC=E5=87=BA=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E5=8D=95=20PDF=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ErpProjectPurchaseController.java | 27 +++ .../vo/ErpProjectPurchaseMaterialVo.java | 5 + .../service/IErpProjectPurchaseService.java | 9 + .../impl/ErpProjectPurchaseServiceImpl.java | 207 ++++++++++++++++++ ...pProjectPurchaseMaterialLoopRowPolicy.java | 15 ++ 5 files changed, 263 insertions(+) create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpProjectPurchaseMaterialLoopRowPolicy.java diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpProjectPurchaseController.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpProjectPurchaseController.java index ed0ad7d2..6e86dea7 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpProjectPurchaseController.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/controller/ErpProjectPurchaseController.java @@ -1,6 +1,7 @@ package org.dromara.oa.erp.controller; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; @@ -13,10 +14,14 @@ import org.dromara.common.log.annotation.Log; import org.dromara.common.web.core.BaseController; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.core.domain.R; +import org.dromara.common.core.utils.StringUtils; 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.common.word.util.WordTemplateUtil; +import com.deepoove.poi.config.Configure; +import org.dromara.oa.erp.word.ErpProjectPurchaseMaterialLoopRowPolicy; import org.dromara.oa.erp.domain.vo.ErpProjectPurchaseVo; import org.dromara.oa.erp.domain.vo.ErpProjectPurchaseMaterialVo; import org.dromara.oa.erp.domain.vo.ErpProjectMaterialsSourceVo; @@ -77,6 +82,28 @@ public class ErpProjectPurchaseController extends BaseController { ExcelUtil.exportExcel(list, "项目采购单明细", ErpProjectPurchaseMaterialVo.class, response); } + /** + * 导出采购审批单 PDF(基于 Word 模板渲染后转换) + * + * @param projectPurchaseId 项目采购ID + */ + @SaCheckPermission("oa/erp:projectPurchase:export") + @Log(title = "采购审批单PDF导出", businessType = BusinessType.EXPORT) + @GetMapping("/exportApprovalPdf/{projectPurchaseId}") + public void exportApprovalPdf(@NotNull(message = "项目采购ID不能为空") + @PathVariable("projectPurchaseId") Long projectPurchaseId, + HttpServletResponse response) { + Map data = erpProjectPurchaseService.buildApprovalWordExportData(projectPurchaseId); + ErpProjectPurchaseVo vo = erpProjectPurchaseService.queryById(projectPurchaseId); + String fileName = "采购审批单_" + (vo != null && StringUtils.isNotBlank(vo.getProjectCode()) + ? vo.getProjectCode() : projectPurchaseId); + Configure config = Configure.builder() + .bind("标准物料", new ErpProjectPurchaseMaterialLoopRowPolicy()) + .bind("非标准物料", new ErpProjectPurchaseMaterialLoopRowPolicy()) + .build(); + WordTemplateUtil.renderToPdfResponse("采购审批单模板.docx", fileName, data, config, response); + } + /** * 获取项目采购信息详细信息 * diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpProjectPurchaseMaterialVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpProjectPurchaseMaterialVo.java index ad901f70..aa050466 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpProjectPurchaseMaterialVo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/domain/vo/ErpProjectPurchaseMaterialVo.java @@ -118,6 +118,11 @@ public class ErpProjectPurchaseMaterialVo implements Serializable { // @ExcelProperty(value = "单位ID") private Long unitId; + /** + * 单位名称 + */ + private String unitName; + /** * 需采购总数量 */ diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPurchaseService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPurchaseService.java index 956b78b1..23630b78 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPurchaseService.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/IErpProjectPurchaseService.java @@ -10,6 +10,7 @@ import org.dromara.common.mybatis.core.page.PageQuery; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 项目采购信息Service接口 @@ -90,4 +91,12 @@ public interface IErpProjectPurchaseService { * @param contractId 合同ID(仅备件类可传;传入优先) */ ErpProjectMaterialsSourceVo getProjectMaterialsSourceByProjectId(Long projectId, String spareFlag, Long contractId); + + /** + * 组装采购审批单 Word 导出数据 + * + * @param projectPurchaseId 项目采购ID + * @return 模板数据 Map + */ + Map buildApprovalWordExportData(Long projectPurchaseId); } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java index f39996e8..c85f6aca 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/service/impl/ErpProjectPurchaseServiceImpl.java @@ -1,5 +1,6 @@ package org.dromara.oa.erp.service.impl; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.github.yulichang.toolkit.JoinWrappers; @@ -11,6 +12,7 @@ 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.PageQuery; @@ -34,6 +36,7 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; @@ -48,6 +51,18 @@ import java.util.stream.Collectors; @Slf4j public class ErpProjectPurchaseServiceImpl implements IErpProjectPurchaseService { + private static final String MATERIAL_DATE_FORMAT = "yyyy-MM-dd"; + + private static final String NODE_PURCHASE_MANAGER = "采购经理"; + + private static final String NODE_PROJECT_MANAGER = "项目经理"; + + private static final String NODE_CHARGE = "部门经理"; + + private static final String NODE_DEPUTY = "副总审核"; + + private static final String NODE_GENERAL_MANAGER = "总经理"; + private final ErpProjectPurchaseMapper baseMapper; private final ErpProjectPurchaseMaterialMapper purchaseMaterialMapper; @@ -327,6 +342,198 @@ public class ErpProjectPurchaseServiceImpl implements IErpProjectPurchaseService return result; } + @Override + public Map buildApprovalWordExportData(Long projectPurchaseId) { + ErpProjectPurchaseVo purchase = queryById(projectPurchaseId); + if (purchase == null) { + throw new ServiceException("项目采购单不存在,ID:" + projectPurchaseId); + } + if (!OAStatusEnum.COMPLETED.getStatus().equals(purchase.getProjectPurchaseStatus())) { + throw new ServiceException("仅项目采购状态为“审批完成”时允许导出采购审批单"); + } + + Map data = new HashMap<>(32); + data.put("采购编号", strVal(purchase.getPurchaseCode())); + data.put("项目号", strVal(purchase.getProjectCode())); + data.put("项目名称", strVal(purchase.getProjectName())); + data.put("项目经理", strVal(purchase.getManagerName())); + data.put("部门负责人", strVal(purchase.getChargeName())); + data.put("分管副总", strVal(purchase.getDeputyName())); + data.put("收货人", strVal(purchase.getConsigneeUser())); + data.put("收货地址", strVal(purchase.getConsigneeAddress())); + data.put("收货人联系方式", strVal(purchase.getConsigneeContact())); + + List materials = purchase.getPurchaseMaterialList(); + if (materials == null) { + materials = Collections.emptyList(); + } + data.put("标准物料", buildMaterialLoopData(materials, "1")); + data.put("非标准物料", buildMaterialLoopData(materials, "2")); + + Map flowData = remoteWorkflowService.flowHisTaskList(String.valueOf(projectPurchaseId)); + List> handledTasks = extractHandledTasks(flowData == null ? null : flowData.get("list")); + data.put("采购经理意见", resolveApprovalOpinion(findLatestTaskByNodeName(handledTasks, NODE_PURCHASE_MANAGER))); + data.put("项目经理审批意见", resolveApprovalOpinion(findLatestTaskByNodeName(handledTasks, NODE_PROJECT_MANAGER))); + data.put("部门负责人审批意见", resolveApprovalOpinion(findLatestTaskByNodeName(handledTasks, NODE_CHARGE))); + data.put("分管副总审批意见", resolveApprovalOpinion(findLatestTaskByNodeName(handledTasks, NODE_DEPUTY))); + data.put("总经理意见", resolveApprovalOpinion(findLatestTaskByNodeName(handledTasks, NODE_GENERAL_MANAGER))); + data.put("是否需要总经理审批", resolveNeedGeneralManagerApproval(handledTasks, flowData)); + return data; + } + + private List> buildMaterialLoopData(List materials, String materialFlag) { + List> rows = materials.stream() + .filter(m -> materialFlag.equals(m.getMaterialFlag())) + .map(this::toMaterialLoopRow) + .collect(Collectors.toCollection(ArrayList::new)); + if (rows.isEmpty()) { + rows.add(toEmptyMaterialLoopRow()); + } + return rows; + } + + private Map toMaterialLoopRow(ErpProjectPurchaseMaterialVo material) { + String name = strVal(material.getMaterialName()); + if (StringUtils.isBlank(name)) { + name = strVal(material.getPurchaseMaterialName()); + } + String qty = ""; + if (material.getPurchaseAmount() != null) { + qty = material.getPurchaseAmount().stripTrailingZeros().toPlainString(); + } + Map row = new HashMap<>(8); + row.put("materialName", name); + row.put("materialCode", strVal(material.getMaterialCode())); + row.put("specificationDescription", strVal(material.getSpecificationDescription())); + row.put("unitName", strVal(material.getUnitName())); + row.put("quantity", qty); + row.put("arrivalTime", formatMaterialDate(material.getArrivalTime())); + return row; + } + + private Map toEmptyMaterialLoopRow() { + Map row = new HashMap<>(8); + row.put("materialName", ""); + row.put("materialCode", ""); + row.put("specificationDescription", ""); + row.put("unitName", ""); + row.put("quantity", ""); + row.put("arrivalTime", ""); + return row; + } + + private String resolveNeedGeneralManagerApproval(List> handledTasks, Map flowData) { + if (findLatestTaskByNodeName(handledTasks, NODE_GENERAL_MANAGER) != null) { + return "是"; + } + Object instanceId = flowData == null ? null : flowData.get("instanceId"); + if (instanceId != null) { + try { + Map variables = remoteWorkflowService.instanceVariable(Convert.toLong(instanceId)); + if (variables != null) { + for (String key : List.of("needGmApproval", "needGeneralManager", "generalManagerApproval", "gmApproval")) { + Object val = variables.get(key); + if (val != null) { + return Boolean.TRUE.equals(val) || "1".equals(String.valueOf(val)) || "true".equalsIgnoreCase(String.valueOf(val)) ? "是" : "否"; + } + } + } + } catch (Exception e) { + log.warn("读取项目采购流程变量失败, instanceId={}", instanceId, e); + } + } + return "否"; + } + + private String resolveApprovalOpinion(Map task) { + if (task == null) { + return ""; + } + String message = strVal(task.get("message")); + return StringUtils.isNotBlank(message) ? message : "同意"; + } + + private List> extractHandledTasks(Object listObj) { + if (!(listObj instanceof List taskList) || taskList.isEmpty()) { + return Collections.emptyList(); + } + List> handledTasks = new ArrayList<>(); + for (Object taskObj : taskList) { + Map taskMap = toMap(taskObj); + if (!taskMap.isEmpty() && taskMap.get("updateTime") != null) { + handledTasks.add(taskMap); + } + } + handledTasks.sort(Comparator.comparing(task -> { + Date updateTime = parseDate(task.get("updateTime")); + return updateTime == null ? new Date(0L) : updateTime; + })); + return handledTasks; + } + + private Map findLatestTaskByNodeName(List> handledTasks, String nodeName) { + if (StringUtils.isBlank(nodeName) || handledTasks == null || handledTasks.isEmpty()) { + return null; + } + for (int i = handledTasks.size() - 1; i >= 0; i--) { + Map task = handledTasks.get(i); + String taskNodeName = strVal(task.get("nodeName")); + String targetNodeName = strVal(task.get("targetNodeName")); + if (nodeName.equals(taskNodeName) || nodeName.equals(targetNodeName)) { + return task; + } + } + return null; + } + + private Map toMap(Object obj) { + if (obj == null) { + return Collections.emptyMap(); + } + if (obj instanceof Map rawMap) { + Map result = new HashMap<>(rawMap.size()); + for (Map.Entry entry : rawMap.entrySet()) { + result.put(String.valueOf(entry.getKey()), entry.getValue()); + } + return result; + } + return BeanUtil.beanToMap(obj); + } + + private Date parseDate(Object rawDate) { + if (rawDate == null) { + return null; + } + if (rawDate instanceof Date date) { + return date; + } + if (rawDate instanceof Number number) { + long millis = number.longValue(); + if (String.valueOf(millis).length() == 10) { + millis = millis * 1000; + } + return new Date(millis); + } + return DateUtils.parseDate(rawDate); + } + + private String formatMaterialDate(Date date) { + if (date == null) { + return ""; + } + return DateUtils.parseDateToStr(MATERIAL_DATE_FORMAT, date); + } + + private String strVal(Object value) { + if (value == null) { + return ""; + } + if (value instanceof BigDecimal decimal) { + return decimal.stripTrailingZeros().toPlainString(); + } + return String.valueOf(value); + } + /** * 项目采购流程事件监听 * diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpProjectPurchaseMaterialLoopRowPolicy.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpProjectPurchaseMaterialLoopRowPolicy.java new file mode 100644 index 00000000..549005fb --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpProjectPurchaseMaterialLoopRowPolicy.java @@ -0,0 +1,15 @@ +package org.dromara.oa.erp.word; + +import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; + +/** + * 采购审批单整表内物料行循环(poi-tl LoopRowTableRenderPolicy) + *

+ * onSameLine=true:循环标签与 [materialName] 等字段在同一行,避免标签独占一行导出为空行。 + */ +public class ErpProjectPurchaseMaterialLoopRowPolicy extends LoopRowTableRenderPolicy { + + public ErpProjectPurchaseMaterialLoopRowPolicy() { + super(true); + } +}