Showing
4 changed files
with
271 additions
and
5 deletions
| @@ -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 | } |
No preview for this file type
No preview for this file type
No preview for this file type