fix:订单审批通过时生成订单信息pdf

dev_1.0.2
jiangpeng 2026-04-23 02:00:18 +08:00
parent 515abca80f
commit ae727d0fdd
4 changed files with 454 additions and 1 deletions

View File

@ -53,6 +53,11 @@
<artifactId>javax.mail</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.23</version>
</dependency>
</dependencies>
</project>

View File

@ -19,6 +19,22 @@ import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Chunk;
import com.lowagie.text.Image;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.ColumnText;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.pdf.draw.LineSeparator;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
@ -35,7 +51,9 @@ import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.dto.ApiDataQueryDto;
import com.ruoyi.sip.flowable.domain.Todo;
@ -69,9 +87,11 @@ import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.ProjectOrderInfoMapper;
import com.ruoyi.common.core.text.Convert;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.*;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import static com.ruoyi.common.utils.ShiroUtils.getSysUser;
@ -119,6 +139,7 @@ public class ProjectOrderInfoServiceImpl implements IProjectOrderInfoService, To
private static final List<String> N_VIDIA_LIST = Arrays.asList("0504A14F", "0504A14G", "0504A1JX");
private Boolean useMcpExportExcelFormat = Boolean.TRUE;
private static final List<String> FILE_INFO_LIST = Arrays.asList( "(请上传商务折扣审批邮件信息).pdf/.jpg/.png","(请上传未盖章合同信息).pdf/.jpg/.png", "(补充附件).zip/.rar/.jpg/.png","(请上传已盖章合同信息).pdf/.jpg/.png");
private static final String ORDER_CONTENT_FILE_SORT = "5";
@Autowired
private TaskService taskService;
@ -1741,6 +1762,7 @@ public class ProjectOrderInfoServiceImpl implements IProjectOrderInfoService, To
handleApproveOrder(businessKey);
// 订单审批通过时推送项目信息到CRM
projectInfoService.scheduleOpportunityUpdateByProjectId(dbProjectOrderInfo.getProjectId());
saveOrderContentPdf(dbProjectOrderInfo.getId());
}
@Override
@ -1839,6 +1861,428 @@ public class ProjectOrderInfoServiceImpl implements IProjectOrderInfoService, To
}
}
private void saveOrderContentPdf(Long orderId) {
ProjectOrderInfo orderInfo = selectProjectOrderInfoById(orderId);
if (orderInfo == null) {
throw new ServiceException("订单不存在无法生成订单内容PDF");
}
try {
deleteExistingOrderContentPdf(orderId, orderInfo.getVersionCode());
byte[] pdfBytes = buildOrderContentPdf(orderInfo);
String fileName = buildOrderContentFileName(orderInfo);
String filePath = uploadGeneratedPdf(fileName, pdfBytes);
ProjectOrderFileLog fileLog = new ProjectOrderFileLog();
fileLog.setOrderId(orderId);
fileLog.setFileName(fileName);
fileLog.setFilePath(filePath);
fileLog.setFileType(ProjectOrderFileLog.FileTypeEnum.CONTRACT_BAK.getCode());
fileLog.setFileSort(ORDER_CONTENT_FILE_SORT);
fileLog.setFileVersionCode(orderInfo.getVersionCode());
fileLog.setUploadTime(DateUtils.getNowDate());
fileLog.setUploadUser(resolveCurrentUserId());
fileLog.setUploadUserName(resolveCurrentUserName());
fileLogService.insertProjectOrderFileLog(fileLog);
} catch (Exception e) {
log.error("生成订单内容PDF失败订单ID{}", orderId, e);
throw new ServiceException("生成订单内容PDF失败: " + e.getMessage());
}
}
private void deleteExistingOrderContentPdf(Long orderId, String versionCode) {
ProjectOrderFileLog query = new ProjectOrderFileLog();
query.setOrderId(orderId);
query.setFileType(ProjectOrderFileLog.FileTypeEnum.CONTRACT_BAK.getCode());
query.setFileVersionCode(versionCode);
query.setFileSort(ORDER_CONTENT_FILE_SORT);
List<ProjectOrderFileLog> existList = fileLogService.selectProjectOrderFileLogList(query);
if (CollUtil.isNotEmpty(existList)) {
String ids = existList.stream().map(v -> String.valueOf(v.getId())).collect(Collectors.joining(","));
fileLogService.deleteProjectOrderFileLogByIds(ids);
}
}
private String buildOrderContentFileName(ProjectOrderInfo orderInfo) {
String safeProjectName = StringUtils.isEmpty(orderInfo.getProjectName()) ? "订单内容"
: orderInfo.getProjectName().replaceAll("[\\\\/:*?\"<>|]", "_");
return safeProjectName + "_Rev." + safeText(orderInfo.getVersionCode()) + "_订单内容.pdf";
}
private String uploadGeneratedPdf(String fileName, byte[] pdfBytes) throws IOException {
MultipartFile multipartFile = new ByteArrayMultipartFile(fileName, fileName, "application/pdf", pdfBytes);
return FileUploadUtils.upload(multipartFile);
}
private byte[] buildOrderContentPdf(ProjectOrderInfo orderInfo) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Rectangle pageSize = PageSize.A4.rotate();
Document document = new Document(pageSize, 22, 22, 20, 20);
BaseFont baseFont = createPdfBaseFont();
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
writer.setPageEvent(new OrderContentPdfPageEvent(baseFont, buildPdfFooter(orderInfo)));
document.open();
addPdfHeader(document, baseFont, orderInfo);
addOrderInfoSection(document, baseFont, orderInfo);
document.close();
return outputStream.toByteArray();
}
private String buildPdfFooter(ProjectOrderInfo orderInfo) {
return safeEmpty(orderInfo.getProjectCode()) + "-"
+ safeEmpty(orderInfo.getOrderCode()) + "-Rev."
+ safeEmpty(orderInfo.getVersionCode());
}
private BaseFont createPdfBaseFont() throws Exception {
List<String> embeddedFontCandidates = Arrays.asList(
"C:\\Windows\\Fonts\\msyh.ttc,0",
"C:\\Windows\\Fonts\\simsun.ttc,0",
"C:\\Windows\\Fonts\\simhei.ttf"
);
for (String fontPath : embeddedFontCandidates) {
String actualPath = fontPath.contains(",") ? fontPath.substring(0, fontPath.indexOf(',')) : fontPath;
if (Files.exists(Paths.get(actualPath))) {
return BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}
}
return BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
}
private void addPdfHeader(Document document, BaseFont baseFont, ProjectOrderInfo orderInfo) throws DocumentException {
PdfPTable headerTable = new PdfPTable(2);
headerTable.setWidthPercentage(100);
headerTable.setWidths(new float[]{4.5f, 1.5f});
headerTable.setSpacingAfter(4);
headerTable.getDefaultCell().setBorder(Rectangle.NO_BORDER);
PdfPCell companyCell = createBorderlessCell("紫光汇智信息技术有限公司", baseFont, 13, Element.ALIGN_LEFT,
new java.awt.Color(125, 125, 125), true);
companyCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
companyCell.setPaddingTop(4);
headerTable.addCell(companyCell);
Image logoImage = loadOrderPdfLogo();
if (logoImage != null) {
logoImage.scaleToFit(110f, 30f);
PdfPCell logoCell = new PdfPCell(logoImage, false);
logoCell.setBorder(Rectangle.NO_BORDER);
logoCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
logoCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
logoCell.setPadding(0);
headerTable.addCell(logoCell);
} else {
PdfPCell brandCell = createBorderlessCell("紫光汇智 UNIS SENSE", baseFont, 10, Element.ALIGN_RIGHT,
new java.awt.Color(118, 38, 137), true);
brandCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
brandCell.setPaddingTop(4);
headerTable.addCell(brandCell);
}
document.add(headerTable);
LineSeparator separator = new LineSeparator();
separator.setLineWidth(0.6f);
separator.setLineColor(new java.awt.Color(200, 200, 200));
document.add(new Chunk(separator));
Paragraph subTitle = new Paragraph(buildPdfSubtitle(orderInfo),
new com.lowagie.text.Font(baseFont, 11, com.lowagie.text.Font.BOLD, new java.awt.Color(85, 85, 85)));
subTitle.setAlignment(Element.ALIGN_CENTER);
subTitle.setSpacingBefore(3);
subTitle.setSpacingAfter(8);
document.add(subTitle);
}
private Image loadOrderPdfLogo() {
try (InputStream inputStream = SpringUtils.getResource("classpath:static/img/companyLogoHD.jpg").getInputStream()) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
return Image.getInstance(outputStream.toByteArray());
} catch (Exception e) {
log.warn("读取订单PDF Logo失败", e);
return null;
}
}
private String buildPdfSubtitle(ProjectOrderInfo orderInfo) {
return safeEmpty(orderInfo.getProjectName()) + "Rev." + safeEmpty(orderInfo.getVersionCode());
}
private static class OrderContentPdfPageEvent extends PdfPageEventHelper {
private final BaseFont baseFont;
private final String footerText;
private OrderContentPdfPageEvent(BaseFont baseFont, String footerText) {
this.baseFont = baseFont;
this.footerText = footerText;
}
@Override
public void onEndPage(PdfWriter writer, Document document) {
com.lowagie.text.Font footerFont = new com.lowagie.text.Font(baseFont, 9,
com.lowagie.text.Font.NORMAL, new java.awt.Color(120, 120, 120));
float y = document.bottom() - 10;
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
new Phrase(footerText, footerFont), document.left(), y, 0);
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT,
new Phrase("第" + writer.getPageNumber() + "页", footerFont), document.right(), y, 0);
}
}
private void addOrderInfoSection(Document document, BaseFont baseFont, ProjectOrderInfo orderInfo) throws DocumentException {
Paragraph sectionTitle = new Paragraph("订单信息",
new com.lowagie.text.Font(baseFont, 14, com.lowagie.text.Font.BOLD, new java.awt.Color(32, 126, 214)));
sectionTitle.setSpacingAfter(5);
document.add(sectionTitle);
PdfPTable table = new PdfPTable(6);
table.setWidthPercentage(100);
table.setWidths(new float[]{1.3f, 2.5f, 1.3f, 2.5f, 1.4f, 2.5f});
table.setSpacingAfter(6);
addSixColumnRow(table, baseFont, "项目名称", orderInfo.getProjectName(), 3, "版本号", orderInfo.getVersionCode(), 1);
addSixColumnRow(table, baseFont, "项目编号", orderInfo.getProjectCode(), 1, "最终客户", orderInfo.getCustomerName(), 3);
addSixColumnRow(table, baseFont, "BG",
firstNonEmpty(orderInfo.getBgPropertyDesc(), DictUtils.getDictLabel(BG_TYPE_DICT_TYPE, orderInfo.getBgProperty())), 1,
"行业", firstNonEmpty(orderInfo.getIndustryTypeDesc(),
DictUtils.getDictLabel("YYS".equals(orderInfo.getBgProperty()) ? "bg_yys" : "bg_hysy", orderInfo.getIndustryType())), 1,
"代表处", orderInfo.getAgentName(), 1);
addSixColumnRow(table, baseFont, "进货商接口人", orderInfo.getBusinessPerson(), 1, "Email", orderInfo.getBusinessEmail(), 1,
"联系方式", orderInfo.getBusinessPhone(), 1);
addSixColumnRow(table, baseFont, "合同编号", orderInfo.getOrderCode(), 1, "执行单有效截止时间", formatDate(orderInfo.getOrderEndTime()), 3);
addSixColumnRow(table, baseFont, "币种", DictUtils.getDictLabel("currency_type", orderInfo.getCurrencyType()), 1,
"总代出货金额", formatMoney(orderInfo.getShipmentAmount()), 3);
addSixColumnRow(table, baseFont, "要求到货时间", formatDate(orderInfo.getDeliveryTime()), 1,
"公司直发", firstNonEmpty(orderInfo.getCompanyDeliveryDesc(), DictUtils.getDictLabel("company_delivery", orderInfo.getCompanyDelivery())), 3);
addSixColumnRow(table, baseFont, "下单通路", firstNonEmpty(orderInfo.getOrderChannelDesc(), resolveOrderChannel(orderInfo.getOrderChannel())), 1,
"供货商", orderInfo.getSupplier(), 3);
addSixColumnRow(table, baseFont, "汇智责任人", orderInfo.getDutyName(), 1, "Email", orderInfo.getDutyEmail(), 1,
"联系方式", orderInfo.getDutyPhone(), 1);
addSixColumnRow(table, baseFont, "进货商", orderInfo.getPartnerName(), 3, "进货商类型",
firstNonEmpty(orderInfo.getLevelDesc(), DictUtils.getDictLabel("identify_level", orderInfo.getLevel())), 1);
addSixColumnRow(table, baseFont, "进货商联系人", orderInfo.getPartnerUserName(), 1, "Email", orderInfo.getPartnerEmail(), 1,
"联系方式", orderInfo.getPartnerPhone(), 1);
addSingleFieldRow(table, baseFont, "其他特别说明", orderInfo.getRemark());
document.add(table);
}
private void addSixColumnRow(PdfPTable table, BaseFont baseFont, String label1, String value1, int value1Colspan,
String label2, String value2, int value2Colspan) {
table.addCell(createLabelCell(label1, baseFont));
PdfPCell valueCell1 = createValueCell(value1, baseFont);
valueCell1.setColspan(value1Colspan);
table.addCell(valueCell1);
table.addCell(createLabelCell(label2, baseFont));
PdfPCell valueCell2 = createValueCell(value2, baseFont);
valueCell2.setColspan(value2Colspan);
table.addCell(valueCell2);
}
private void addSixColumnRow(PdfPTable table, BaseFont baseFont, String label1, String value1, int value1Colspan,
String label2, String value2, int value2Colspan,
String label3, String value3, int value3Colspan) {
table.addCell(createLabelCell(label1, baseFont));
PdfPCell valueCell1 = createValueCell(value1, baseFont);
valueCell1.setColspan(value1Colspan);
table.addCell(valueCell1);
table.addCell(createLabelCell(label2, baseFont));
PdfPCell valueCell2 = createValueCell(value2, baseFont);
valueCell2.setColspan(value2Colspan);
table.addCell(valueCell2);
table.addCell(createLabelCell(label3, baseFont));
PdfPCell valueCell3 = createValueCell(value3, baseFont);
valueCell3.setColspan(value3Colspan);
table.addCell(valueCell3);
}
private void addSingleFieldRow(PdfPTable table, BaseFont baseFont, String label, String value) {
table.addCell(createLabelCell(label, baseFont));
PdfPCell valueCell = createValueCell(value, baseFont);
valueCell.setColspan(5);
table.addCell(valueCell);
}
private PdfPCell createLabelCell(String value, BaseFont baseFont) {
return createTableCell(value, baseFont, 10, Element.ALIGN_LEFT, new java.awt.Color(248, 248, 248), true, false);
}
private PdfPCell createValueCell(String value, BaseFont baseFont) {
return createTableCell(value, baseFont, 10, Element.ALIGN_LEFT, java.awt.Color.WHITE, false, true);
}
private PdfPCell createTableCell(String value, BaseFont baseFont, int fontSize, int alignment, java.awt.Color backgroundColor,
boolean bold, boolean emptyAsBlank) {
com.lowagie.text.Font font = new com.lowagie.text.Font(baseFont, fontSize,
bold ? com.lowagie.text.Font.BOLD : com.lowagie.text.Font.NORMAL);
String displayValue = emptyAsBlank ? safeEmptyValue(value) : safeText(value);
PdfPCell cell = new PdfPCell(new Phrase(displayValue, font));
cell.setHorizontalAlignment(alignment);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setPadding(4);
cell.setMinimumHeight(26f);
cell.setBackgroundColor(backgroundColor);
cell.setBorderColor(new java.awt.Color(220, 220, 220));
cell.setBorderWidth(0.7f);
return cell;
}
private PdfPCell createBorderlessCell(String value, BaseFont baseFont, int fontSize, int alignment,
java.awt.Color fontColor, boolean bold) {
com.lowagie.text.Font font = new com.lowagie.text.Font(baseFont, fontSize,
bold ? com.lowagie.text.Font.BOLD : com.lowagie.text.Font.NORMAL, fontColor);
PdfPCell cell = new PdfPCell(new Phrase(safeText(value), font));
cell.setHorizontalAlignment(alignment);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setBorder(Rectangle.NO_BORDER);
cell.setPadding(0);
return cell;
}
private String resolveOrderChannel(String orderChannel) {
if (ProjectOrderInfo.OrderChannelEnum.TOTAL_GENERATION.getCode().equals(orderChannel)) {
return "总代";
}
if (ProjectOrderInfo.OrderChannelEnum.DIRECT_SIGNING.getCode().equals(orderChannel)) {
return "直签";
}
return "";
}
private String resolvePaymentMethodLabel(ProjectOrderInfo orderInfo) {
if (StringUtils.isEmpty(orderInfo.getPaymentMethod())) {
return "";
}
if (ProjectOrderInfo.OrderChannelEnum.TOTAL_GENERATION.getCode().equals(orderInfo.getOrderChannel())) {
if ("1-1".equals(orderInfo.getPaymentMethod())) {
return "全款支付,无需预付款";
}
if ("1-2".equals(orderInfo.getPaymentMethod())) {
return "全款支付,需单独备货生产,预付订单总货款百分比";
}
}
if (ProjectOrderInfo.OrderChannelEnum.DIRECT_SIGNING.getCode().equals(orderInfo.getOrderChannel())) {
if ("2-1".equals(orderInfo.getPaymentMethod())) {
return "全款支付";
}
if ("2-2".equals(orderInfo.getPaymentMethod())) {
return "全款支付,需单独备货生产,预付订单总货款百分比";
}
if ("2-3".equals(orderInfo.getPaymentMethod())) {
return "商业汇票支付,预付订单总货款百分比";
}
}
return orderInfo.getPaymentMethod();
}
private String resolveCurrentUserId() {
try {
Long userId = ShiroUtils.getUserId();
return userId == null ? "" : String.valueOf(userId);
} catch (Exception e) {
return "";
}
}
private String resolveCurrentUserName() {
try {
SysUser sysUser = getSysUser();
return sysUser == null ? "" : sysUser.getUserName();
} catch (Exception e) {
return "";
}
}
private String formatMoney(BigDecimal value) {
if (value == null) {
return "";
}
return new DecimalFormat("#,##0.00").format(value);
}
private String formatDate(Date date) {
return date == null ? "-" : DateUtil.format(date, "yyyy-MM-dd");
}
private String safeText(Object value) {
if (value == null) {
return "-";
}
String text = String.valueOf(value).trim();
return StringUtils.isEmpty(text) ? "-" : text;
}
private String firstNonEmpty(String first, String second) {
return StringUtils.isNotEmpty(first) ? first : second;
}
private String safeEmpty(String value) {
return value == null ? "" : value.trim();
}
private String safeEmptyValue(String value) {
return value == null ? "" : value.trim();
}
private static class ByteArrayMultipartFile implements MultipartFile {
private final String name;
private final String originalFilename;
private final String contentType;
private final byte[] content;
private ByteArrayMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
this.name = name;
this.originalFilename = originalFilename;
this.contentType = contentType;
this.content = content == null ? new byte[0] : content;
}
@Override
public String getName() {
return name;
}
@Override
public String getOriginalFilename() {
return originalFilename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return content.length == 0;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] getBytes() {
return content;
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(content);
}
@Override
public void transferTo(File dest) throws IOException {
Files.write(dest.toPath(), content);
}
}
@Override
public byte[] exportContractTemplate( ProjectOrderInfo orderInfo) {
try {

View File

@ -12,6 +12,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="uploadTime" column="upload_time" />
<result property="filePath" column="file_path" />
<result property="fileType" column="file_type" />
<result property="fileSort" column="file_sort" />
<result property="fileVersionCode" column="file_version_code" />
</resultMap>
<sql id="selectProjectOrderFileLogVo">
@ -30,6 +32,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="uploadTime != null "> andt1. upload_time = #{uploadTime}</if>
<if test="filePath != null and filePath != ''"> and t1.file_path = #{filePath}</if>
<if test="fileType != null and fileType != ''"> and t1.file_type = #{fileType}</if>
<if test="fileSort != null and fileSort != ''"> and t1.file_sort = #{fileSort}</if>
<if test="fileVersionCode != null and fileVersionCode != ''"> and t1.file_version_code = #{fileVersionCode}</if>
</where>
order by t1.id
</select>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB