Commit d64a40c989a9fedbcea686498f11cfd0e6323d56

Authored by 房远帅
1 parent f43b9c8c

楚江ERP:合同打印改为pdf

@@ -59,12 +59,12 @@ import org.springframework.validation.annotation.Validated; @@ -59,12 +59,12 @@ import org.springframework.validation.annotation.Validated;
59 import org.springframework.web.bind.annotation.*; 59 import org.springframework.web.bind.annotation.*;
60 60
61 import javax.annotation.Resource; 61 import javax.annotation.Resource;
  62 +import java.io.*;
  63 +import java.nio.file.Files;
  64 +import java.util.concurrent.TimeUnit;
62 import javax.servlet.http.HttpServletResponse; 65 import javax.servlet.http.HttpServletResponse;
63 import javax.validation.Valid; 66 import javax.validation.Valid;
64 import javax.validation.constraints.NotBlank; 67 import javax.validation.constraints.NotBlank;
65 -import java.io.FileNotFoundException;  
66 -import java.io.IOException;  
67 -import java.io.InputStream;  
68 import java.math.BigDecimal; 68 import java.math.BigDecimal;
69 import java.math.RoundingMode; 69 import java.math.RoundingMode;
70 import java.net.URLEncoder; 70 import java.net.URLEncoder;
@@ -750,7 +750,7 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -750,7 +750,7 @@ public class ContractDistributorStandardController extends DefaultBaseController
750 /** 750 /**
751 * 标准合同模版打印 751 * 标准合同模版打印
752 */ 752 */
753 - @ApiOperation("标准合同模版打印") 753 + /* @ApiOperation("标准合同模版打印")
754 @HasPermission({ 754 @HasPermission({
755 "contract-manage:distribution-standard-contract:query", 755 "contract-manage:distribution-standard-contract:query",
756 "contract-manage:distribution-inventory-contract:query", 756 "contract-manage:distribution-inventory-contract:query",
@@ -958,7 +958,7 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -958,7 +958,7 @@ public class ContractDistributorStandardController extends DefaultBaseController
958 log.error("标准合同模版打印: {}", e.getMessage(), e); 958 log.error("标准合同模版打印: {}", e.getMessage(), e);
959 throw e; 959 throw e;
960 } 960 }
961 - } 961 + }*/
962 962
963 private void processTemplate(Workbook workbook, Map<String, Object> dataMap) { 963 private void processTemplate(Workbook workbook, Map<String, Object> dataMap) {
964 Sheet sheet = workbook.getSheetAt(0); 964 Sheet sheet = workbook.getSheetAt(0);
@@ -1043,4 +1043,270 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -1043,4 +1043,270 @@ public class ContractDistributorStandardController extends DefaultBaseController
1043 cell.setCellValue(((LocalDate) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 1043 cell.setCellValue(((LocalDate) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
1044 } 1044 }
1045 } 1045 }
  1046 +
  1047 + /**
  1048 + * 标准合同模版打印
  1049 + */
  1050 + @ApiOperation("标准合同模版打印")
  1051 + @HasPermission({
  1052 + "contract-manage:distribution-standard-contract:query",
  1053 + "contract-manage:distribution-inventory-contract:query",
  1054 + "contract-manage:distribution-unlocked-contract:query",
  1055 + "contract-manage:foreign-trade-standard-contract:query",
  1056 + "contract-manage:foreign-trade-inventory-contract:query",
  1057 + "contract-manage:foreign-trade-unlocked-contract:query",
  1058 + "contract-manage:processed-standard-contract:query"
  1059 + })
  1060 + @GetMapping("/printStandardContract")
  1061 + public void printStandardContract(@NotBlank(message = "合同不可为空") String id, HttpServletResponse response) throws IOException {
  1062 + InvokeResult<GetContractDistributorStandardBo> result = get(id);
  1063 + GetContractDistributorStandardBo data = result.getData();
  1064 +
  1065 + // === 1. 确定模板路径(不变)===
  1066 + String templatePath = "templates/standardContractTemplate.xls";
  1067 + if ("INTL_STD_CONTRACT".equals(data.getType())
  1068 + || "INTL_INVENTORY_AGMT".equals(data.getType())
  1069 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())) {
  1070 + templatePath = "templates/foreignStandardContractTemplate.xls";
  1071 + } else if ("PROCESS_STD_AGMT".equals(data.getType())) {
  1072 + templatePath = "templates/processStdAgmtTemplate.xls";
  1073 + }
  1074 +
  1075 + // === 2. 创建临时 .xls 文件 ===
  1076 + File tempExcel = null;
  1077 + File pdfFile = null;
  1078 +
  1079 + try {
  1080 + // === 3. 加载模板并填充数据(核心逻辑完全保留)===
  1081 + ClassPathResource templateResource = new ClassPathResource(templatePath);
  1082 + try (InputStream inputStream = templateResource.getStream()) {
  1083 + HSSFWorkbook workbook = new HSSFWorkbook(inputStream);
  1084 +
  1085 + try {
  1086 + Sheet sheet = workbook.getSheetAt(0);
  1087 + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  1088 + int startRow = 6;
  1089 + String contractTitle = "销售订单(经销)";
  1090 + if ("INTL_STD_CONTRACT".equals(data.getType())
  1091 + || "INTL_INVENTORY_AGMT".equals(data.getType())
  1092 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())) {
  1093 + contractTitle = "销售订单(外贸)";
  1094 + }
  1095 +
  1096 + List<String> productNameList = contractFrameworkService.getCustomerFrameworkProductNameList(data.getBuyer(), data.getSupplier());
  1097 +
  1098 + if (CollectionUtils.isNotEmpty(data.getContractDistributorLineList())) {
  1099 + for (GetContractDistributorLineBo line : data.getContractDistributorLineList()) {
  1100 + if (StringUtils.isNotBlank(line.getProductName())
  1101 + && !productNameList.contains(line.getProductName().substring(0, 2))) {
  1102 + contractTitle = "销售合同(经销)";
  1103 + if ("INTL_STD_CONTRACT".equals(data.getType())
  1104 + || "INTL_INVENTORY_AGMT".equals(data.getType())
  1105 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())) {
  1106 + contractTitle = "销售合同(外贸)";
  1107 + }
  1108 + }
  1109 +
  1110 + setCellValue(sheet, startRow, 1, line.getProductName());
  1111 + setCellValue(sheet, startRow, 4, line.getBrand());
  1112 +
  1113 + // --- 构建 LaTeX 并插入图片 ---
  1114 + List<LatexFormulaExcelExporterUtil.FormulaComponent> formulaComponentList = new ArrayList<>(3);
  1115 + addFormulaComponent(formulaComponentList, line.getThickness(), line.getThicknessTolPos(), line.getThicknessTolNeg());
  1116 + addFormulaComponent(formulaComponentList, line.getWidth(), line.getWidthTolPos(), line.getWidthTolNeg());
  1117 + addFormulaComponent(formulaComponentList, line.getLength(), line.getLengthTolPos(), line.getLengthTolNeg());
  1118 +
  1119 + String latex = LatexFormulaExcelExporterUtil.convertToLatex(formulaComponentList);
  1120 + if (StringUtils.isNotBlank(latex)) {
  1121 + LatexFormulaExcelExporterUtil.insertLatexImageToCell(workbook, sheet, latex, startRow, 5);
  1122 + }
  1123 +
  1124 + setCellValue(sheet, startRow, 6, line.getStatus());
  1125 + setCellValue(sheet, startRow, 7, line.getQuantity());
  1126 +
  1127 + if ("INTL_STD_CONTRACT".equals(data.getType())
  1128 + || "INTL_INVENTORY_AGMT".equals(data.getType())
  1129 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())) {
  1130 + setCellValue(sheet, startRow, 9, line.getProcessingFee());
  1131 + setCellValue(sheet, startRow, 10, line.getUnitPrice());
  1132 + } else {
  1133 + setCellValue(sheet, startRow, 9, line.getUnitPrice());
  1134 + setCellValue(sheet, startRow, 10, line.getAmountExcludingTax());
  1135 + }
  1136 +
  1137 + setCellValue(sheet, startRow, 11, line.getTotalAmount());
  1138 + setCellValue(sheet, startRow, 12,
  1139 + line.getDeliveryDate() != null ? line.getDeliveryDate().format(dateFormatter) : "");
  1140 + startRow++;
  1141 + }
  1142 + }
  1143 +
  1144 + if (CollectionUtils.isNotEmpty(data.getContractStdProcessingLineList())) {
  1145 + startRow++;
  1146 + for (GetContractStdProcessingLineBo line : data.getContractStdProcessingLineList()) {
  1147 + setCellValue(sheet, startRow, 1, line.getRawProductName() + "、" + line.getRawProductGrade());
  1148 + setCellValue(sheet, startRow, 4, line.getSupplyTime());
  1149 + setCellValue(sheet, startRow, 5, line.getMaterialProductRatioName());
  1150 + setCellValue(sheet, startRow, 6, line.getProductName());
  1151 + setCellValue(sheet, startRow, 7, line.getProductGrade());
  1152 + setCellValue(sheet, startRow, 8, line.getProductQuantity());
  1153 +
  1154 + List<LatexFormulaExcelExporterUtil.FormulaComponent> formulaComponentList = new ArrayList<>(3);
  1155 + addFormulaComponent(formulaComponentList, line.getThickness(), line.getThicknessTolPos(), line.getThicknessTolNeg());
  1156 + addFormulaComponent(formulaComponentList, line.getWidth(), line.getWidthTolPos(), line.getWidthTolNeg());
  1157 + addFormulaComponent(formulaComponentList, line.getLength(), line.getLengthTolPos(), line.getLengthTolNeg());
  1158 +
  1159 + String latex = LatexFormulaExcelExporterUtil.convertToLatex(formulaComponentList);
  1160 + if (StringUtils.isNotBlank(latex)) {
  1161 + LatexFormulaExcelExporterUtil.insertLatexImageToCell(workbook, sheet, latex, startRow, 9);
  1162 + }
  1163 +
  1164 + setCellValue(sheet, startRow, 10, line.getProductStatus());
  1165 + setCellValue(sheet, startRow, 11, line.getUnitPrice());
  1166 + setCellValue(sheet, startRow, 12, line.getAmountExcludingTax());
  1167 + setCellValue(sheet, startRow, 13, line.getTotalAmount());
  1168 + setCellValue(sheet, startRow, 14,
  1169 + line.getDeliveryDate() != null ? line.getDeliveryDate().format(dateFormatter) : "");
  1170 + }
  1171 + }
  1172 +
  1173 + // --- 填充全局变量 ---
  1174 + Map<String, Object> dataMap = buildDataMap(data, contractTitle, dateFormatter);
  1175 + processTemplate(workbook, dataMap);
  1176 +
  1177 + // === 4. 写入临时 .xls 文件 ===
  1178 + tempExcel = File.createTempFile("contract_" + data.getCode(), ".xls");
  1179 + try (FileOutputStream fos = new FileOutputStream(tempExcel)) {
  1180 + workbook.write(fos);
  1181 + }
  1182 +
  1183 + } finally {
  1184 + workbook.close(); // HSSFWorkbook 实现了 Closeable
  1185 + }
  1186 + }
  1187 +
  1188 + // === 5. 调用 LibreOffice 转 PDF ===
  1189 + pdfFile = convertExcelToPdf(tempExcel);
  1190 +
  1191 + // === 6. 设置 PDF 响应头 ===
  1192 + String fileName = data.getCode() + "_" + data.getBuyerName() + "-合同打印.pdf";
  1193 + String encodedFileName;
  1194 + try {
  1195 + encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
  1196 + } catch (UnsupportedEncodingException e) {
  1197 + // UTF-8 是标准编码,理论上不会抛出,但 Java 8 要求处理
  1198 + throw new RuntimeException("UTF-8 encoding not supported", e);
  1199 + }
  1200 + response.setContentType("application/pdf");
  1201 + response.setCharacterEncoding("UTF-8");
  1202 + response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
  1203 + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
  1204 +
  1205 + // === 7. 输出 PDF 到浏览器 ===
  1206 + try (InputStream pdfIn = new FileInputStream(pdfFile)) {
  1207 + byte[] buffer = new byte[8192];
  1208 + int len;
  1209 + while ((len = pdfIn.read(buffer)) != -1) {
  1210 + response.getOutputStream().write(buffer, 0, len);
  1211 + }
  1212 + response.getOutputStream().flush();
  1213 + }
  1214 +
  1215 + } catch (Exception e) {
  1216 + log.error("标准合同导出 PDF 失败: {}", e.getMessage(), e);
  1217 + throw new RuntimeException("合同导出失败,请联系管理员", e);
  1218 + } finally {
  1219 + // === 8. 清理临时文件 ===
  1220 + deleteQuietly(tempExcel);
  1221 + deleteQuietly(pdfFile);
  1222 + }
  1223 + }
  1224 +
  1225 + private void addFormulaComponent(List<LatexFormulaExcelExporterUtil.FormulaComponent> list,
  1226 + BigDecimal base, BigDecimal sup, BigDecimal sub) {
  1227 + if (base != null) {
  1228 + LatexFormulaExcelExporterUtil.FormulaComponent comp = new LatexFormulaExcelExporterUtil.FormulaComponent();
  1229 + comp.setBase(base);
  1230 + comp.setSup(sup);
  1231 + comp.setSub(sub);
  1232 + list.add(comp);
  1233 + }
  1234 + }
  1235 +
  1236 + private Map<String, Object> buildDataMap(GetContractDistributorStandardBo data, String contractTitle, DateTimeFormatter dateFormatter) {
  1237 + Map<String, Object> dataMap = new HashMap<>();
  1238 + dataMap.put("title", contractTitle);
  1239 + dataMap.put("code", data.getCode());
  1240 + dataMap.put("orderDate", data.getOrderDate().format(dateFormatter));
  1241 + dataMap.put("supplierName", data.getSupplierName());
  1242 + dataMap.put("buyerName", data.getBuyerName());
  1243 + dataMap.put("unitName", data.getUnit());
  1244 + dataMap.put("totalQuantity", data.getTotalQuantity().setScale(0, RoundingMode.HALF_UP).toString());
  1245 + dataMap.put("totalAmountExcludingTax", data.getTotalAmountExcludingTax().setScale(2, RoundingMode.HALF_UP).toString());
  1246 + dataMap.put("totalAmountIncludingTax", data.getTotalAmountIncludingTax().setScale(2, RoundingMode.HALF_UP).toString());
  1247 + dataMap.put("totalAmountCapital", data.getTotalAmountCapital());
  1248 + dataMap.put("specialInstructions", data.getSpecialInstructions());
  1249 + dataMap.put("depositInfo", data.getDepositInfo());
  1250 + dataMap.put("paymentTerms", data.getPaymentTerms());
  1251 + dataMap.put("packagingRequirements", data.getPackagingRequirements());
  1252 + dataMap.put("transportMode", data.getTransportMode());
  1253 +
  1254 + if (BooleanUtils.isTrue(data.getIncludesPackagingFee()) && BooleanUtils.isTrue(data.getIncludesTransportFee())) {
  1255 + dataMap.put("includesTransportOrPackaging", "单价中已包含包装费用与运费");
  1256 + } else if (BooleanUtils.isTrue(data.getIncludesPackagingFee()) && BooleanUtils.isNotTrue(data.getIncludesTransportFee())) {
  1257 + dataMap.put("includesTransportOrPackaging", "单价中已包含包装费用,不包含运费");
  1258 + } else if (BooleanUtils.isNotTrue(data.getIncludesPackagingFee()) && BooleanUtils.isTrue(data.getIncludesTransportFee())) {
  1259 + dataMap.put("includesTransportOrPackaging", "单价中不包含包装费用,包含运费");
  1260 + } else {
  1261 + dataMap.put("includesTransportOrPackaging", "单价中不包含包装费用与运费");
  1262 + }
  1263 +
  1264 + dataMap.put("remarks", data.getRemarks());
  1265 + return dataMap;
  1266 + }
  1267 +
  1268 + private File convertExcelToPdf(File excelFile) throws IOException, InterruptedException {
  1269 + if (!excelFile.exists()) {
  1270 + throw new IllegalArgumentException("Excel 文件不存在: " + excelFile.getAbsolutePath());
  1271 + }
  1272 +
  1273 + String pdfPath = excelFile.getAbsolutePath().replaceAll("\\.xls$", ".pdf");
  1274 + File pdfFile = new File(pdfPath);
  1275 +
  1276 + // 使用绝对路径 /usr/bin/libreoffice(CentOS 7 默认位置)
  1277 + String command = String.format(
  1278 + "/usr/bin/libreoffice --headless --convert-to pdf --outdir %s %s",
  1279 + excelFile.getParent(),
  1280 + excelFile.getAbsolutePath()
  1281 + );
  1282 +
  1283 + Process process = Runtime.getRuntime().exec(command);
  1284 + boolean finished = process.waitFor(30, TimeUnit.SECONDS); // 超时 30 秒
  1285 +
  1286 + if (!finished) {
  1287 + process.destroyForcibly();
  1288 + throw new RuntimeException("LibreOffice 转换超时(>30秒)");
  1289 + }
  1290 +
  1291 + int exitCode = process.exitValue();
  1292 + if (exitCode != 0) {
  1293 + throw new RuntimeException("LibreOffice 转换失败,退出码: " + exitCode);
  1294 + }
  1295 +
  1296 + if (!pdfFile.exists()) {
  1297 + throw new RuntimeException("PDF 未生成: " + pdfPath);
  1298 + }
  1299 +
  1300 + return pdfFile;
  1301 + }
  1302 +
  1303 + private void deleteQuietly(File file) {
  1304 + if (file != null && file.exists()) {
  1305 + try {
  1306 + Files.delete(file.toPath());
  1307 + } catch (IOException e) {
  1308 + log.warn("无法删除临时文件: {}", file.getAbsolutePath(), e);
  1309 + }
  1310 + }
  1311 + }
1046 } 1312 }