feat(oa): 新增报价单模板变量赋值功能

- 新增报价单模板变量赋值服务实现
- 添加报价单特殊变量处理(金额大小写转换)
- 实现报价单数据源取值逻辑
- 重构报价单查询方法以支持按ID精确查询
- 移除旧版后端直出导出接口与逻辑
- 更新模板变量赋值请求对象支持报价单类型
- 优化物料明细排序逻辑确保导出一致性
- 添加报价单金额大小写转换算法实现
dev
zangch@mesnac.com 3 days ago
parent 11b89ab75d
commit adec098966

@ -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
* IDtemplateType=1
*/
@NotNull(message = "合同ID不能为空")
private Long contractId;
/**
* IDtemplateType=4
*/
private Long quoteId;
}

@ -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<TemplateVariableAssignVo> assignQuoteTemplateVariables(TemplateVariableAssignRequest request) {
// 1) 按模板类型查询所有变量配置,确保字段/数据源信息齐备
BaseTemplateVariableBo queryBo = new BaseTemplateVariableBo();
queryBo.setTemplateType(request.getTemplateType());
List<BaseTemplateVariableVo> 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<CrmQuoteMaterialVo> 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<TemplateVariableAssignVo> buildAssignResultListForQuote(
List<BaseTemplateVariableVo> allTemplateVariables,
CrmQuoteInfoVo quoteInfo,
List<CrmQuoteMaterialVo> quoteMaterials) {
List<TemplateVariableAssignVo> 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<CrmQuoteMaterialVo> 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<Object> getFieldValueFromQuoteMaterialList(List<CrmQuoteMaterialVo> quoteMaterials, String fieldName) {
if (quoteMaterials == null || quoteMaterials.isEmpty() || StringUtils.isBlank(fieldName)) {
return new ArrayList<>();
}
List<Object> values = new ArrayList<>();
for (CrmQuoteMaterialVo material : quoteMaterials) {
// 逐条提取字段值,空值用空字符串占位,保证长度对齐
Object value = getFieldValueFromObject(material, fieldName);
values.add(value != null ? value : "");
}
return values;
}
}

@ -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<CrmQuoteInfoVo> 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<String, String> 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<QuoteTemplateMaterialDto> 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<String, Object> 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;
}
}
}

@ -46,6 +46,11 @@ public class CrmQuoteMaterialVo implements Serializable {
@ExcelProperty(value = "序号(ERP风格)")
private Long itemNo;
/**
* itemNo
*/
private Long seq;
/**
* 1 2
*/

@ -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<CrmQuoteMaterial> 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)

@ -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<CrmQuoteMaterial> buildQueryWrapper(CrmQuoteMaterialBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<CrmQuoteMaterial> 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;
}

@ -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<RowRenderData> rows = (List<RowRenderData>) 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);
}
}
Loading…
Cancel
Save