From adec098966186d615abca6437bea608efca68cac Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Tue, 10 Mar 2026 15:03:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(oa):=20=E6=96=B0=E5=A2=9E=E6=8A=A5?= =?UTF-8?q?=E4=BB=B7=E5=8D=95=E6=A8=A1=E6=9D=BF=E5=8F=98=E9=87=8F=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增报价单模板变量赋值服务实现 - 添加报价单特殊变量处理(金额大小写转换) - 实现报价单数据源取值逻辑 - 重构报价单查询方法以支持按ID精确查询 - 移除旧版后端直出导出接口与逻辑 - 更新模板变量赋值请求对象支持报价单类型 - 优化物料明细排序逻辑确保导出一致性 - 添加报价单金额大小写转换算法实现 --- .../bo/TemplateVariableAssignRequest.java | 16 +- .../TemplateVariableAssignServiceImpl.java | 247 +++++++++++++++++- .../controller/CrmQuoteInfoController.java | 216 +-------------- .../oa/crm/domain/vo/CrmQuoteMaterialVo.java | 5 + .../service/impl/CrmQuoteInfoServiceImpl.java | 7 +- .../impl/CrmQuoteMaterialServiceImpl.java | 5 +- .../word/ErpContractApprovalTablePolicy.java | 76 ++++++ 7 files changed, 344 insertions(+), 228 deletions(-) create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpContractApprovalTablePolicy.java diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/domain/bo/TemplateVariableAssignRequest.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/domain/bo/TemplateVariableAssignRequest.java index 15ac9e6c..e5e75ba5 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/domain/bo/TemplateVariableAssignRequest.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/domain/bo/TemplateVariableAssignRequest.java @@ -1,12 +1,10 @@ package org.dromara.oa.base.domain.bo; +import jakarta.validation.constraints.NotBlank; import lombok.Data; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; -import java.util.List; /** * 模板变量赋值请求对象 @@ -21,15 +19,19 @@ public class TemplateVariableAssignRequest implements Serializable { private static final long serialVersionUID = 1L; /** - * 模板类型(1合同 2发货单) + * 模板类型(1合同 2发货单 4报价单) */ - @NotNull(message = "模板类型不能为空") + @NotBlank(message = "模板类型不能为空") private String templateType; /** - * 合同ID + * 合同ID(templateType=1时必填) */ - @NotNull(message = "合同ID不能为空") private Long contractId; + + /** + * 报价单ID(templateType=4时必填) + */ + private Long quoteId; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/service/impl/TemplateVariableAssignServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/service/impl/TemplateVariableAssignServiceImpl.java index af9bd89d..2d3fc62c 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/service/impl/TemplateVariableAssignServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/base/service/impl/TemplateVariableAssignServiceImpl.java @@ -1,5 +1,6 @@ package org.dromara.oa.base.service.impl; +import lombok.RequiredArgsConstructor; import org.dromara.common.core.enums.FormatsType; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.DateUtils; @@ -10,23 +11,24 @@ import org.dromara.oa.base.domain.vo.BaseTemplateVariableVo; import org.dromara.oa.base.domain.vo.TemplateVariableAssignVo; import org.dromara.oa.base.service.IBaseTemplateVariableService; import org.dromara.oa.base.service.ITemplateVariableAssignService; +import org.dromara.oa.crm.domain.bo.CrmQuoteMaterialBo; +import org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo; +import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo; +import org.dromara.oa.crm.service.ICrmQuoteInfoService; +import org.dromara.oa.crm.service.ICrmQuoteMaterialService; 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.service.IErpContractInfoService; import org.dromara.oa.erp.service.IErpContractMaterialService; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -43,6 +45,10 @@ public class TemplateVariableAssignServiceImpl implements ITemplateVariableAssig private final IErpContractInfoService erpContractInfoService; private final IErpContractMaterialService erpContractMaterialService; + // 报价单 + private final ICrmQuoteInfoService crmQuoteInfoService; + private final ICrmQuoteMaterialService crmQuoteMaterialService; + /** * 根据模板类型为模板变量赋值 * 模板类型:1=合同,2=发货单,3=项目变更,4=报价单 @@ -64,8 +70,11 @@ public class TemplateVariableAssignServiceImpl implements ITemplateVariableAssig // 项目变更类型 return assignDeliveryTemplateVariables(request); } else if ("4".equals(templateType)) { + if (request.getQuoteId() == null) { + throw new ServiceException("报价单ID不能为空"); + } // 报价单类型 - return assignDeliveryTemplateVariables(request); + return assignQuoteTemplateVariables(request); } else { throw new ServiceException("不支持的模板类型:" + templateType); } @@ -443,5 +452,227 @@ public class TemplateVariableAssignServiceImpl implements ITemplateVariableAssig } + /** + * 为报价单模板变量赋值 + */ + private List assignQuoteTemplateVariables(TemplateVariableAssignRequest request) { + // 1) 按模板类型查询所有变量配置,确保字段/数据源信息齐备 + BaseTemplateVariableBo queryBo = new BaseTemplateVariableBo(); + queryBo.setTemplateType(request.getTemplateType()); + List allTemplateVariables = baseTemplateVariableService.queryList(queryBo); + if (allTemplateVariables == null) { + allTemplateVariables = new ArrayList<>(); + } + + // 2) 查询报价单主表信息,缺失则直接报错 + CrmQuoteInfoVo quoteInfo = crmQuoteInfoService.queryById(request.getQuoteId()); + if (StringUtils.isNull(quoteInfo)) { + throw new ServiceException("报价单信息不存在,报价单ID:" + request.getQuoteId()); + } + + // 3) 如存在物料数据源需求,则查询物料并补充序号 + List quoteMaterials = null; + boolean needMaterialData = allTemplateVariables.stream() + .anyMatch(var -> "crm_quote_material".equalsIgnoreCase(var.getDataSource())); + if (needMaterialData) { + CrmQuoteMaterialBo materialBo = new CrmQuoteMaterialBo(); + materialBo.setQuoteId(request.getQuoteId()); + quoteMaterials = crmQuoteMaterialService.queryList(materialBo); + if (quoteMaterials == null) { + quoteMaterials = new ArrayList<>(); + } + for (int i = 0; i < quoteMaterials.size(); i++) { + CrmQuoteMaterialVo material = quoteMaterials.get(i); + material.setSeq(material.getItemNo() != null ? material.getItemNo() : (i + 1L)); + } + } + + return buildAssignResultListForQuote(allTemplateVariables, quoteInfo, quoteMaterials); + } + + /** + * 构建报价单赋值结果列表 + */ + private List buildAssignResultListForQuote( + List allTemplateVariables, + CrmQuoteInfoVo quoteInfo, + List quoteMaterials) { + + List resultList = new ArrayList<>(); + for (BaseTemplateVariableVo variable : allTemplateVariables) { + if (variable == null || StringUtils.isBlank(variable.getVarName())) { + continue; + } + + // 1) 先处理报价单特殊变量(金额大小写、质保期等) + Object value = resolveSpecialQuoteValue(variable, quoteInfo); + + // 2) 根据变量数据源从主表或明细表取值 + if (value == null) { + value = getValueFromQuoteDataSource(variable, quoteInfo, quoteMaterials); + } + + // 3) 若取不到值则回退默认值 + if (value == null && StringUtils.isNotBlank(variable.getDefaultValue())) { + value = variable.getDefaultValue(); + } + + // 4) 按变量类型(数组/时间/文本)转换输出格式 + Object finalValue = convertValueToTargetType(value, variable); + + TemplateVariableAssignVo assignVo = new TemplateVariableAssignVo(); + assignVo.setVarName(variable.getVarName()); + assignVo.setVarLabel(variable.getVarLabel()); + assignVo.setVarValue(finalValue); + assignVo.setVarType(variable.getVarType()); + resultList.add(assignVo); + } + + return resultList; + } + + /** + * 报价单特殊变量处理(优先于 data_field 反射) + * 兼容已上线模板占位符,不要求每次都改变量配置即可输出大写/小写金额与质保期 + */ + private Object resolveSpecialQuoteValue(BaseTemplateVariableVo variable, CrmQuoteInfoVo quoteInfo) { + if (variable == null || quoteInfo == null) { + return null; + } + if (!"crm_quote_info".equalsIgnoreCase(variable.getDataSource())) { + return null; + } + + String normalizedVarName = normalizeVariableName(variable.getVarName()); + String dataField = variable.getDataField(); + BigDecimal totalAmount = quoteInfo.getTotalIncludingTax() != null + ? quoteInfo.getTotalIncludingTax() + : quoteInfo.getTotalPrice(); + + if ("总价大写".equals(normalizedVarName) + || "totalAmountUppercase".equalsIgnoreCase(dataField) + || "capitalizedAmount".equalsIgnoreCase(dataField)) { + return toChineseUpperMoney(totalAmount); + } + + if ("总价小写".equals(normalizedVarName) + || "totalAmountLowercase".equalsIgnoreCase(dataField)) { + return formatMoney(totalAmount); + } + + if ("质保期".equals(normalizedVarName) + || "warrantyPeriodDescription".equalsIgnoreCase(dataField)) { + return StringUtils.isNotBlank(quoteInfo.getRemark()) ? quoteInfo.getRemark() : ""; + } + + return null; + } + + private String formatMoney(BigDecimal amount) { + if (amount == null) { + return ""; + } + return amount.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } + + private String toChineseUpperMoney(BigDecimal amount) { + if (amount == null) { + return ""; + } + BigDecimal normalized = amount.setScale(2, RoundingMode.HALF_UP); + if (normalized.compareTo(BigDecimal.ZERO) == 0) { + return "零元整"; + } + // 超出本实现支持上限时返回数值字符串,避免导出中断 + if (normalized.abs().compareTo(new BigDecimal("9999999999999999.99")) > 0) { + return normalized.toPlainString(); + } + + final String[] digits = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; + final String[] units = {"分", "角", "元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆"}; + + long value = normalized.movePointRight(2).abs().longValue(); + StringBuilder sb = new StringBuilder(); + int unitIndex = 0; + boolean zeroPending = false; + + while (value > 0) { + int digit = (int) (value % 10); + if (digit == 0) { + if ((unitIndex == 2 || unitIndex == 6 || unitIndex == 10 || unitIndex == 14) + && sb.length() > 0 + && sb.indexOf(units[unitIndex]) < 0) { + sb.insert(0, units[unitIndex]); + } + if (!zeroPending && unitIndex > 1 && sb.length() > 0) { + sb.insert(0, "零"); + zeroPending = true; + } + } else { + sb.insert(0, units[unitIndex]); + sb.insert(0, digits[digit]); + zeroPending = false; + } + value /= 10; + unitIndex++; + } + + String result = sb.toString() + .replaceAll("零{2,}", "零") + .replaceAll("零(万|亿|兆)", "$1") + .replace("亿万", "亿") + .replaceAll("零角零分$", "整") + .replaceAll("零分$", "整") + .replaceAll("零角", "零"); + + if (!result.contains("元")) { + result = "零元" + result; + } + if (!result.endsWith("整") && !result.endsWith("分")) { + result = result + "整"; + } + if (normalized.signum() < 0) { + result = "负" + result; + } + return result; + } + + /** + * 报价单数据源取值 + */ + private Object getValueFromQuoteDataSource(BaseTemplateVariableVo variable, + CrmQuoteInfoVo quoteInfo, + List quoteMaterials) { + if (StringUtils.isBlank(variable.getDataField())) { + return null; + } + + String dataSource = variable.getDataSource(); + // 明细数组数据源:取子表字段列表 + if ("crm_quote_material".equalsIgnoreCase(dataSource)) { + return getFieldValueFromQuoteMaterialList(quoteMaterials, variable.getDataField()); + // 主表数据源:直接取报价单字段 + } else if ("crm_quote_info".equalsIgnoreCase(dataSource)) { + return getFieldValueFromObject(quoteInfo, variable.getDataField()); + } else { + return null; + } + } + + private List getFieldValueFromQuoteMaterialList(List quoteMaterials, String fieldName) { + if (quoteMaterials == null || quoteMaterials.isEmpty() || StringUtils.isBlank(fieldName)) { + return new ArrayList<>(); + } + + List values = new ArrayList<>(); + for (CrmQuoteMaterialVo material : quoteMaterials) { + // 逐条提取字段值,空值用空字符串占位,保证长度对齐 + Object value = getFieldValueFromObject(material, fieldName); + values.add(value != null ? value : ""); + } + return values; + } + + } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmQuoteInfoController.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmQuoteInfoController.java index f032e705..8206d94b 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmQuoteInfoController.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmQuoteInfoController.java @@ -1,21 +1,14 @@ package org.dromara.oa.crm.controller; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.NumberChineseFormatter; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.resource.ClassPathResource; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; -import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.service.DictService; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; -import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; @@ -23,15 +16,12 @@ 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.oa.crm.domain.bo.CrmQuoteInfoBo; -import org.dromara.oa.crm.domain.dto.QuoteTemplateMaterialDto; import org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo; -import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo; import org.dromara.oa.crm.service.ICrmQuoteInfoService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.math.BigDecimal; -import java.util.*; +import java.util.List; /** * 报价单信息 @@ -60,15 +50,15 @@ public class CrmQuoteInfoController extends BaseController { } /** - * 导出报价单信息列表 + * 导出报价单信息列表(Excel导出-已停用) */ - @SaCheckPermission("oa/crm:crmQuoteInfo:export") + /*@SaCheckPermission("oa/crm:crmQuoteInfo:export") @Log(title = "报价单信息", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(CrmQuoteInfoBo bo, HttpServletResponse response) { List list = crmQuoteInfoService.queryList(bo); ExcelUtil.exportExcel(list, "报价单信息", CrmQuoteInfoVo.class, response); - } + }*/ /** * 获取报价单信息详细信息 @@ -153,202 +143,10 @@ public class CrmQuoteInfoController extends BaseController { return R.ok(crmQuoteInfoService.recalcTotals(quoteId)); } - /** - * 导出报价单模板 - * - * @param quoteId 报价单ID + /* + * 旧版后端直出导出接口与逻辑已停用。 + * 当前统一走:前端调用 /oa/base/templateVariable/assign 组装变量后,在模板预览页导出 PDF。 */ - @SaCheckPermission("oa/crm:crmQuoteInfo:export") - @Log(title = "导出报价单模板", businessType = BusinessType.EXPORT) - @GetMapping("/exportTemplate/{quoteId}") - public void exportQuoteTemplate(@PathVariable("quoteId") Long quoteId, HttpServletResponse response) { - // 获取报价单详细信息 - CrmQuoteInfoVo quoteInfo = crmQuoteInfoService.queryById(quoteId); - if (quoteInfo == null) { - throw new ServiceException("报价单不存在"); - } - - // 准备Map形式的主表数据(对应模板中的{key}占位符,注意不是{map.key}) - Map templateData = new HashMap<>(); - templateData.put("customerContactName", quoteInfo.getCustomerContactName() != null ? quoteInfo.getCustomerContactName() : ""); - templateData.put("title", quoteInfo.getQuoteName() != null ? quoteInfo.getQuoteName() : ""); - templateData.put("quoteDate", quoteInfo.getQuoteDate() != null ? DateUtil.format(quoteInfo.getQuoteDate(), "yyyy年MM月dd日") : ""); - templateData.put("taxIncludedInfo", quoteInfo.getTaxIncludedInfo() != null ? quoteInfo.getTaxIncludedInfo() : ""); - templateData.put("deliveryPeriod", quoteInfo.getDeliveryPeriod() != null ? quoteInfo.getDeliveryPeriod() + "天" : ""); - templateData.put("deliveryMethod", quoteInfo.getDeliveryMethod() != null ? quoteInfo.getDeliveryMethod() : ""); - templateData.put("quoteValidity", quoteInfo.getValidDays() != null ? quoteInfo.getValidDays() + "天" : ""); - //付款方式字典 - String paymentMethodLabel = ""; - if (quoteInfo.getPaymentMethod() != null) { - String dictLabel = dictService.getDictLabel("payment_method", quoteInfo.getPaymentMethod()); - paymentMethodLabel = dictLabel != null ? dictLabel : ""; - } - templateData.put("paymentMethod", paymentMethodLabel); - //币种字典 - String currencyLabel = "人民币"; - if (quoteInfo.getCurrencyType() != null) { - String dictLabel = dictService.getDictLabel("currency_type", quoteInfo.getCurrencyType()); - if (dictLabel != null) { - currencyLabel = dictLabel; - } - } - templateData.put("currencyType", currencyLabel); - - // 价格单位,根据币种名称解析 - String priceUnit = resolvePriceUnit(currencyLabel); - templateData.put("priceUnit", priceUnit); - - // 金额相关 - BigDecimal totalAmount = quoteInfo.getTotalIncludingTax() != null ? quoteInfo.getTotalIncludingTax() : BigDecimal.ZERO; - templateData.put("total", totalAmount.toString()); - templateData.put("totalLower", totalAmount.toString()); - templateData.put("totalUpper", NumberChineseFormatter.format(totalAmount.doubleValue(), true, true)); - - // 供货方信息 - templateData.put("supplierContactName", quoteInfo.getSupplierContactName() != null ? quoteInfo.getSupplierContactName() : ""); - templateData.put("supplierContactPhone", quoteInfo.getSupplierContactPhone() != null ? quoteInfo.getSupplierContactPhone() : ""); - templateData.put("supplierContactEmail", quoteInfo.getSupplierContactEmail() != null ? quoteInfo.getSupplierContactEmail() : ""); - - //总备注 - templateData.put("totalRemark", quoteInfo.getRemark() != null ? quoteInfo.getRemark() : ""); - - // 准备明细数据列表(对应模板中的{.key}占位符) - List materialList = new ArrayList<>(); - if (CollUtil.isNotEmpty(quoteInfo.getItemsVo())) { - for (CrmQuoteMaterialVo material : quoteInfo.getItemsVo()) { - QuoteTemplateMaterialDto materialDto = new QuoteTemplateMaterialDto(); - materialDto.setSeq(material.getItemNo() != null ? material.getItemNo().toString() : ""); - materialDto.setProductName(material.getProductName() != null ? material.getProductName() : ""); - materialDto.setModelDesc(material.getSpecificationDescription() != null ? material.getSpecificationDescription() : ""); - materialDto.setQuantity(material.getAmount() != null ? material.getAmount().toString() : "0"); - materialDto.setUnit(material.getUnitName() != null ? material.getUnitName() : ""); - // 使用含税单价作为单价 - materialDto.setPrice(material.getIncludingPrice() != null ? material.getIncludingPrice().toString() : "0"); - materialDto.setSubtotal(material.getSubtotal() != null ? material.getSubtotal().toString() : "0"); - materialDto.setRemark(material.getRemark() != null ? material.getRemark() : ""); - materialList.add(materialDto); - } - } - - // 如果没有明细,添加一个空行,避免导出异常 - if (materialList.isEmpty()) { - log.warn("报价单 {} 没有明细数据,添加空行", quoteId); - materialList.add(new QuoteTemplateMaterialDto()); - } - - // 主表数据直接放入,占位符用{key} - Map exportData = new LinkedHashMap<>(); - // 将主表数据作为一个整体Map放入,这样ExcelUtil遍历时会调用fill(map),从而匹配模板中的{key} - exportData.put("mainInfo", templateData); - exportData.put("data", materialList); // 明细数据使用"data"作为key - - // 导出Excel模板 - String templatePath = "报价单模板.xlsx"; - String fileName = "报价单_" + quoteInfo.getQuoteCode() + ".xlsx"; - - // 打印实际加载的资源路径 - ClassPathResource resource = new ClassPathResource(templatePath); - try { - // 使用 ExcelUtil 封装的混合模式导出:主表(Map)原位填充,占位符 {key};明细(List)按 {data.key} 展开 - ExcelUtil.exportTemplateMixed(exportData, fileName, templatePath, response); - } catch (Exception e) { - throw new ServiceException("导出报价单失败: " + e.getMessage()); - } - } - - /** - * 根据币种名称解析价格单位 - * 例如:币种包含“人民币”时,价格单位为“元” - * 其他币种默认返回币种名称本身 - * - * @param currencyLabel 币种名称 - * @return 价格单位 - */ - private String resolvePriceUnit(String currencyLabel) { - if (currencyLabel == null || currencyLabel.isBlank()) { - return "元"; - } - String label = currencyLabel.trim(); - - // 人民币:使用 if 单独处理 - if (label.contains("人民币") || "CNY".equalsIgnoreCase(label) || "RMB".equalsIgnoreCase(label)) { - return "元"; - } - - // 其他币种:使用 switch,按中英文名称和常见代码匹配 - switch (label) { - case "美元": - case "美金": - case "USD": - return "美元"; - case "欧元": - case "EUR": - return "欧元"; - case "日元": - case "日币": - case "JPY": - return "日元"; - case "英镑": - case "GBP": - return "英镑"; - case "港币": - case "HKD": - return "港币"; - case "韩元": - case "韩币": - case "KRW": - return "韩元"; - case "加元": - case "CAD": - return "加元"; - case "澳元": - case "AUD": - return "澳元"; - case "瑞士法郎": - case "CHF": - return "瑞士法郎"; - case "新西兰元": - case "NZD": - return "新西兰元"; - case "新加坡元": - case "新币": - case "SGD": - return "新加坡元"; - case "泰铢": - case "THB": - return "泰铢"; - case "林吉特": - case "马来西亚林吉特": - case "MYR": - return "林吉特"; - case "印尼盾": - case "印尼卢比": - case "IDR": - return "印尼盾"; - case "菲律宾比索": - case "菲币": - case "PHP": - return "菲律宾比索"; - case "越南盾": - case "VND": - return "越南盾"; - case "新台币": - case "台币": - case "TWD": - return "新台币"; - case "卢比": - case "印度卢比": - case "INR": - return "卢比"; - case "柬埔寨瑞尔": - case "瑞尔": - case "KHR": - return "瑞尔"; - default: - // 默认直接返回币种名称 - return label; - } - } } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmQuoteMaterialVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmQuoteMaterialVo.java index 3ed89d13..389fdcf3 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmQuoteMaterialVo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmQuoteMaterialVo.java @@ -46,6 +46,11 @@ public class CrmQuoteMaterialVo implements Serializable { @ExcelProperty(value = "序号(ERP风格)") private Long itemNo; + /** + * 模板导出序号(优先取itemNo,缺失时按行号补齐) + */ + private Long seq; + /** * 标准物料标识(1标准物料 2非标物料) */ diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteInfoServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteInfoServiceImpl.java index f91c2add..1fa86ee6 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteInfoServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteInfoServiceImpl.java @@ -63,7 +63,7 @@ public class CrmQuoteInfoServiceImpl implements ICrmQuoteInfoService { @DubboReference(timeout = 30000) private RemoteWorkflowService remoteWorkflowService; - @DubboReference() + @DubboReference(timeout = 30000) private RemoteCodeRuleService remoteCodeRuleService; /** @@ -74,7 +74,9 @@ public class CrmQuoteInfoServiceImpl implements ICrmQuoteInfoService { */ @Override public CrmQuoteInfoVo queryById(Long quoteId){ - CrmQuoteInfoVo vo = baseMapper.selectVoById(quoteId); + CrmQuoteInfoBo queryBo = new CrmQuoteInfoBo(); + queryBo.setQuoteId(quoteId); + CrmQuoteInfoVo vo = this.queryList(queryBo).stream().findFirst().orElse(null); if (vo != null) { // 关联查询子表明细 MPJLambdaWrapper lqw = JoinWrappers.lambda(CrmQuoteMaterial.class) @@ -128,6 +130,7 @@ public class CrmQuoteInfoServiceImpl implements ICrmQuoteInfoService { // 主表全部字段 .selectAll(CrmQuoteInfo.class) .eq(CrmQuoteInfo::getDelFlag, "0") + .eq(bo.getQuoteId() != null, CrmQuoteInfo::getQuoteId, bo.getQuoteId()) // 客户方联系人(CrmCustomerContact,别名:CustomerContact) .selectAs("CustomerContact", CrmCustomerContact::getContactName, CrmQuoteInfo::getCustomerContactRealName) .leftJoin(CrmCustomerContact.class, "CustomerContact", CrmCustomerContact::getContactId, CrmQuoteInfo::getCustomerContactId) diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteMaterialServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteMaterialServiceImpl.java index 7c5f7800..a34ac142 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteMaterialServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmQuoteMaterialServiceImpl.java @@ -19,7 +19,6 @@ import org.springframework.stereotype.Service; import java.util.Collection; import java.util.List; -import java.util.Map; /** * 报价单物料明细Service业务层处理 @@ -71,7 +70,6 @@ public class CrmQuoteMaterialServiceImpl implements ICrmQuoteMaterialService { } private MPJLambdaWrapper buildQueryWrapper(CrmQuoteMaterialBo bo) { - Map params = bo.getParams(); MPJLambdaWrapper lqw = JoinWrappers.lambda(CrmQuoteMaterial.class) .selectAll(CrmQuoteMaterial.class) .eq(CrmQuoteMaterial::getDelFlag, "0") @@ -97,6 +95,9 @@ public class CrmQuoteMaterialServiceImpl implements ICrmQuoteMaterialService { .eq(bo.getIncludingPrice() != null, CrmQuoteMaterial::getIncludingPrice, bo.getIncludingPrice()) .eq(bo.getSubtotal() != null, CrmQuoteMaterial::getSubtotal, bo.getSubtotal()) .eq(StringUtils.isNotBlank(bo.getActiveFlag()), CrmQuoteMaterial::getActiveFlag, bo.getActiveFlag()) + // 导出/展示都按业务序号稳定排序,避免数组变量错位 + .orderByAsc(CrmQuoteMaterial::getItemNo) + .orderByAsc(CrmQuoteMaterial::getQuoteMaterialId) ; return lqw; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpContractApprovalTablePolicy.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpContractApprovalTablePolicy.java new file mode 100644 index 00000000..7ed6b11e --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/erp/word/ErpContractApprovalTablePolicy.java @@ -0,0 +1,76 @@ +package org.dromara.oa.erp.word; + +import cn.hutool.core.collection.CollUtil; +import com.deepoove.poi.data.RowRenderData; +import com.deepoove.poi.policy.DynamicTableRenderPolicy; +import com.deepoove.poi.policy.TableRenderPolicy; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.xwpf.usermodel.XWPFTable; +import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.apache.poi.xwpf.usermodel.XWPFTableRow; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcBorders; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder; + +import java.util.List; + +/** + * 合同审批记录表格渲染策略 + */ +@Slf4j +public class ErpContractApprovalTablePolicy extends DynamicTableRenderPolicy { + + /** + * 数据行起始索引 + */ + private static final int DETAIL_START_ROW = 1; + + /** + * 审批记录列数:序号、审批人、审批意见、审批时间 + */ + private static final int COLUMN_COUNT = 4; + + @Override + public void render(XWPFTable table, Object data) throws Exception { + if (data == null) { + log.warn("合同审批记录为空,跳过表格渲染"); + if (table.getNumberOfRows() > DETAIL_START_ROW) { + table.removeRow(DETAIL_START_ROW); + } + return; + } + + @SuppressWarnings("unchecked") + List rows = (List) data; + if (CollUtil.isEmpty(rows)) { + log.warn("合同审批记录列表为空,跳过表格渲染"); + if (table.getNumberOfRows() > DETAIL_START_ROW) { + table.removeRow(DETAIL_START_ROW); + } + return; + } + + table.removeRow(DETAIL_START_ROW); + for (int i = 0; i < rows.size(); i++) { + RowRenderData rowData = rows.get(i); + int rowIndex = DETAIL_START_ROW + i; + XWPFTableRow newRow = table.insertNewTableRow(rowIndex); + for (int j = 0; j < COLUMN_COUNT; j++) { + XWPFTableCell cell = newRow.createCell(); + cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); + setCellBorder(cell); + } + TableRenderPolicy.Helper.renderRow(table.getRow(rowIndex), rowData); + } + } + + private void setCellBorder(XWPFTableCell cell) { + if (cell.getCTTc().getTcPr() == null) { + cell.getCTTc().addNewTcPr(); + } + CTTcBorders borders = cell.getCTTc().getTcPr().addNewTcBorders(); + borders.addNewTop().setVal(STBorder.SINGLE); + borders.addNewBottom().setVal(STBorder.SINGLE); + borders.addNewLeft().setVal(STBorder.SINGLE); + borders.addNewRight().setVal(STBorder.SINGLE); + } +}