|
|
|
@ -1,11 +1,17 @@
|
|
|
|
package org.dromara.oa.crm.controller;
|
|
|
|
package org.dromara.oa.crm.controller;
|
|
|
|
|
|
|
|
|
|
|
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
|
|
|
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.servlet.http.HttpServletResponse;
|
|
|
|
import jakarta.validation.constraints.NotEmpty;
|
|
|
|
import jakarta.validation.constraints.NotEmpty;
|
|
|
|
import jakarta.validation.constraints.NotNull;
|
|
|
|
import jakarta.validation.constraints.NotNull;
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import org.dromara.common.core.domain.R;
|
|
|
|
import org.dromara.common.core.domain.R;
|
|
|
|
|
|
|
|
import org.dromara.common.core.exception.ServiceException;
|
|
|
|
import org.dromara.common.core.validate.AddGroup;
|
|
|
|
import org.dromara.common.core.validate.AddGroup;
|
|
|
|
import org.dromara.common.core.validate.EditGroup;
|
|
|
|
import org.dromara.common.core.validate.EditGroup;
|
|
|
|
import org.dromara.common.excel.utils.ExcelUtil;
|
|
|
|
import org.dromara.common.excel.utils.ExcelUtil;
|
|
|
|
@ -16,12 +22,15 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
|
|
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
|
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
|
|
import org.dromara.common.web.core.BaseController;
|
|
|
|
import org.dromara.common.web.core.BaseController;
|
|
|
|
import org.dromara.oa.crm.domain.bo.CrmQuoteInfoBo;
|
|
|
|
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.CrmQuoteInfoVo;
|
|
|
|
|
|
|
|
import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo;
|
|
|
|
import org.dromara.oa.crm.service.ICrmQuoteInfoService;
|
|
|
|
import org.dromara.oa.crm.service.ICrmQuoteInfoService;
|
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* 报价单信息
|
|
|
|
* 报价单信息
|
|
|
|
@ -30,6 +39,7 @@ import java.util.List;
|
|
|
|
* @author Yinq
|
|
|
|
* @author Yinq
|
|
|
|
* @date 2025-10-28
|
|
|
|
* @date 2025-10-28
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
@Validated
|
|
|
|
@Validated
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
@RestController
|
|
|
|
@RestController
|
|
|
|
@ -130,4 +140,89 @@ public class CrmQuoteInfoController extends BaseController {
|
|
|
|
return R.ok(crmQuoteInfoService.recalcTotals(quoteId));
|
|
|
|
return R.ok(crmQuoteInfoService.recalcTotals(quoteId));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 导出报价单模板
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param quoteId 报价单ID
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@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("paymentMethod", quoteInfo.getPaymentMethod() != null ? quoteInfo.getPaymentMethod() : "");
|
|
|
|
|
|
|
|
templateData.put("deliveryPeriod", quoteInfo.getDeliveryPeriod() != null ? quoteInfo.getDeliveryPeriod() + "天" : "");
|
|
|
|
|
|
|
|
templateData.put("deliveryMethod", quoteInfo.getDeliveryMethod() != null ? quoteInfo.getDeliveryMethod() : "");
|
|
|
|
|
|
|
|
templateData.put("quoteValidity", quoteInfo.getValidDays() != null ? quoteInfo.getValidDays() + "天" : "");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 币种
|
|
|
|
|
|
|
|
templateData.put("currencyType", quoteInfo.getCurrencyType() != null ? quoteInfo.getCurrencyType() : "人民币");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 金额相关
|
|
|
|
|
|
|
|
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() : "");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 准备明细数据列表(对应模板中的{.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());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 方案A: 主表数据直接放入,占位符用{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());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|