From c8f7e94c8effd7980b1e0c9048811d2b9d64ede2 Mon Sep 17 00:00:00 2001 From: syruan <321359594@qq.com> Date: Wed, 3 Dec 2025 19:53:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96Excel=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E6=B1=87=E6=80=BB=E5=8C=BA=E5=9F=9F=E5=8F=8A=E9=87=91=E9=A2=9D?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E4=B8=BA=E4=B8=AD=E6=96=87=E5=A4=A7=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/projectCost/calculation_detail.jsp | 18 +- .../views/projectCost/calculation_form.jsp | 4 +- .../static/js/projectCost/projectCost.js | Bin 141764 -> 141788 bytes resources/mybatis/cost/ProjectCostMapper.xml | 14 +- .../cost/service/ProjectCostServiceImpl.java | 363 +++++++++++++++++- 5 files changed, 377 insertions(+), 22 deletions(-) diff --git a/WebContent/WEB-INF/views/projectCost/calculation_detail.jsp b/WebContent/WEB-INF/views/projectCost/calculation_detail.jsp index 55e133e..f4a6399 100644 --- a/WebContent/WEB-INF/views/projectCost/calculation_detail.jsp +++ b/WebContent/WEB-INF/views/projectCost/calculation_detail.jsp @@ -246,23 +246,23 @@ elem: '#calculation-table', data: details, cols: [[ - {field: 'machineTypeName', title: '物资名称', width: 180}, - {field: 'machineModel', title: '规格型号', width: 150}, - {field: 'machineUnit', title: '单位', width: 100}, - {field: 'currentCount', title: '未退还数量', width: 120}, - {field: 'price', title: '单价(元/天)', width: 120, templet: function(d){ + {field: 'machineTypeName', title: '物资名称', minWidth: 150}, + {field: 'machineModel', title: '规格型号', minWidth: 120}, + {field: 'machineUnit', title: '单位', width: 80}, + {field: 'currentCount', title: '未退还数量', width: 110}, + {field: 'price', title: '单价(元/天)', width: 110, templet: function(d){ return d.price ? d.price.toFixed(2) : '0.00'; }}, - {field: 'firstLeaseTime', title: '首次领料时间', width: 180, templet: function(d){ + {field: 'firstLeaseTime', title: '首次领料时间', minWidth: 150, templet: function(d){ return d.firstLeaseTime || '--'; }}, - {field: 'lastReturnTime', title: '最后退料时间', width: 180, templet: function(d){ + {field: 'lastReturnTime', title: '最后退料时间', minWidth: 150, templet: function(d){ return d.lastReturnTime || '--'; }}, - {field: 'amount', title: '金额(元)', width: 120, style: 'color: #FF5722; font-weight: bold;', templet: function(d){ + {field: 'amount', title: '金额(元)', width: 110, style: 'color: #FF5722; font-weight: bold;', templet: function(d){ return d.amount ? d.amount.toFixed(2) : '0.00'; }}, - {title: '操作', width: 100, templet: function(d){ + {title: '操作', width: 100, fixed: 'right', templet: function(d){ return '查看明细'; }} ]], diff --git a/WebContent/WEB-INF/views/projectCost/calculation_form.jsp b/WebContent/WEB-INF/views/projectCost/calculation_form.jsp index 79e3993..a5192bb 100644 --- a/WebContent/WEB-INF/views/projectCost/calculation_form.jsp +++ b/WebContent/WEB-INF/views/projectCost/calculation_form.jsp @@ -89,8 +89,8 @@
- - + +
diff --git a/WebContent/static/js/projectCost/projectCost.js b/WebContent/static/js/projectCost/projectCost.js index e8c15203251484c4a51385146f71b73134e8b5ce..f022e6ef768fcad0f87916440917d5adaa74ce8a 100644 GIT binary patch delta 110 zcmX?dnd8o7j)pCaLadX28H+F)O`p%fls^3e3nSn3f?h^F@mz*XhCGIFAeq8Y!jQpW z#h}1o$Y3#jAqP_`L|H&TqXuK{WO-}x>HpZ7lBPdlW3*$3YMcJ?9OLr#Q|ye}Pq8!o GQUm~5 and wir.MODEL_ID = #{param.machineTypeId} - - and left(wtr.OPERATION_TIME,10) between #{param.startTime} and #{param.endTime} + + + and left(wtr.OPERATION_TIME,10) <= #{param.endTime} AND( @@ -88,7 +89,10 @@ and wir.MODEL_ID = #{param.machineTypeId} - and left(wir.TIME,10) BETWEEN #{param.startTime} and #{param.endTime} + + + and left(wir.TIME,10) <= #{param.endTime} + and ( bu.`NAME` like concat('%',#{param.keyWord},'%') OR diff --git a/src/com/bonus/cost/service/ProjectCostServiceImpl.java b/src/com/bonus/cost/service/ProjectCostServiceImpl.java index ea1d8e8..cb1c6c0 100644 --- a/src/com/bonus/cost/service/ProjectCostServiceImpl.java +++ b/src/com/bonus/cost/service/ProjectCostServiceImpl.java @@ -165,6 +165,92 @@ public class ProjectCostServiceImpl implements ProjectCostService { } } + /** + * 将数字金额转换为中文大写金额 + * + * @param amount 金额数字 + * @return 中文大写金额字符串 + */ + private String convertToChineseAmount(double amount) { + if (amount == 0) { + return "零元整"; + } + + // 中文数字 + String[] chineseNumbers = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; + // 中文单位 + String[] units = {"", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿"}; + + // 处理负数 + boolean isNegative = amount < 0; + amount = Math.abs(amount); + + // 分离整数和小数部分 + long integerPart = (long) amount; + int decimalPart = (int) Math.round((amount - integerPart) * 100); + + StringBuilder result = new StringBuilder(); + + if (isNegative) { + result.append("负"); + } + + // 处理整数部分 + if (integerPart == 0) { + result.append("零"); + } else { + String integerStr = String.valueOf(integerPart); + int length = integerStr.length(); + boolean hasZero = false; // 标记是否有零 + + for (int i = 0; i < length; i++) { + int digit = integerStr.charAt(i) - '0'; + int unitIndex = length - i - 1; + + if (digit == 0) { + // 遇到0的处理 + if (unitIndex == 4 || unitIndex == 8) { + // 万位或亿位,即使是0也要加单位 + if (!hasZero) { + result.append(units[unitIndex]); + } + } + hasZero = true; + } else { + // 非0的处理 + if (hasZero) { + result.append(chineseNumbers[0]); // 补零 + } + result.append(chineseNumbers[digit]); + result.append(units[unitIndex]); + hasZero = false; + } + } + } + + result.append("元"); + + // 处理小数部分 + if (decimalPart == 0) { + result.append("整"); + } else { + int jiao = decimalPart / 10; + int fen = decimalPart % 10; + + if (jiao > 0) { + result.append(chineseNumbers[jiao]).append("角"); + } else if (fen > 0) { + result.append("零"); + } + + if (fen > 0) { + result.append(chineseNumbers[fen]).append("分"); + } + } + + return result.toString(); + } + @Override public List queryProjectLeaseDetails(ProjectLeaseCostDetail o) { return projectCostDao.queryProjectLeaseDetails(o); @@ -356,16 +442,87 @@ public class ProjectCostServiceImpl implements ProjectCostService { LocalDateTime firstLeaseTime = null; LocalDateTime lastReturnTime = null; + // 第一步:处理统计期间开始之前的所有操作,计算期初在用数量 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); for (ProjectLeaseCostDetail item : items) { if (item == null || item.getOperateTime() == null) { continue; } - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime operateTime = LocalDateTime.parse(item.getOperateTime(), formatter); + + // 只处理统计期间开始之前的操作 + if (operateTime.isBefore(startDate)) { + boolean isLease = (item.getOperateType() == null || item.getOperateType() != 2); + if (isLease) { + int quantity = item.getLeaseNum() != null ? item.getLeaseNum() : 0; + currentCount += quantity; + System.out.println("期初领料: 物资ID=" + machineTypeId + ", 时间=" + item.getOperateTime() + ", 数量=" + quantity + ", 累计=" + currentCount); + } else { + int quantity = item.getReturnNum() != null ? item.getReturnNum() : 0; + currentCount -= quantity; + System.out.println("期初退料: 物资ID=" + machineTypeId + ", 时间=" + item.getOperateTime() + ", 数量=" + quantity + ", 累计=" + currentCount); + } + } + } + + System.out.println("物资ID=" + machineTypeId + " 期初在用数量: " + currentCount); + + // 如果期初在用数量大于0,需要计算从统计开始时间到第一个操作时间(或统计结束时间)的费用 + if (currentCount > 0) { + // 找到统计期间内的第一个操作时间 + LocalDateTime firstOperateTimeInPeriod = null; + for (ProjectLeaseCostDetail item : items) { + if (item == null || item.getOperateTime() == null) { + continue; + } + LocalDateTime operateTime = LocalDateTime.parse(item.getOperateTime(), formatter); + if (!operateTime.isBefore(startDate) && !operateTime.isAfter(endDate)) { + if (firstOperateTimeInPeriod == null || operateTime.isBefore(firstOperateTimeInPeriod)) { + firstOperateTimeInPeriod = operateTime; + } + } + } + + // 如果统计期间内有操作,计算从开始时间到第一个操作时间的费用 + // 如果统计期间内没有操作,计算从开始时间到结束时间的费用 + LocalDateTime endTimeForInitialSegment = firstOperateTimeInPeriod != null ? firstOperateTimeInPeriod : endDate; + long daysBetween = java.time.Duration.between(startDate, endTimeForInitialSegment).toDays(); + + if (daysBetween > 0) { + double segmentAmount = currentCount * unitPrice * daysBetween; + totalItemAmount += segmentAmount; + + Map segment = new HashMap<>(); + segment.put("startTime", startDate.toString()); + segment.put("endTime", endTimeForInitialSegment.toString()); + segment.put("days", daysBetween); + segment.put("count", currentCount); + segment.put("amount", segmentAmount); + segments.add(segment); + + System.out.println("期初在用费用: 物资ID=" + machineTypeId + ", 时段=" + startDate + " 至 " + endTimeForInitialSegment + + ", 天数=" + daysBetween + ", 数量=" + currentCount + ", 单价=" + unitPrice + ", 段金额=" + segmentAmount); + } + + // 如果统计期间内有操作,更新previousTime为第一个操作时间 + if (firstOperateTimeInPeriod != null) { + previousTime = firstOperateTimeInPeriod; + } else { + // 如果统计期间内没有操作,直接返回结果 + previousTime = endDate; + } + } + + // 第二步:处理统计期间内的所有操作 + for (ProjectLeaseCostDetail item : items) { + if (item == null || item.getOperateTime() == null) { + continue; + } LocalDateTime operateTime = LocalDateTime.parse(item.getOperateTime(), formatter); - // 如果操作时间超出统计范围,跳过 + // 只处理统计期间内的操作(包括开始和结束时间) if (operateTime.isBefore(startDate) || operateTime.isAfter(endDate)) { continue; } @@ -999,9 +1156,12 @@ public class ProjectCostServiceImpl implements ProjectCostService { } } - // 调用带签名区域的导出方法,传递工程名称和制单时间 + // 计算总金额 + double totalAmount = calculation.getTotalAmount() != null ? calculation.getTotalAmount() : 0.0; + + // 调用带签名区域的导出方法,传递工程名称、制单时间和总金额 exportSegmentExcelWithSignature(fileName, sheetName, headers, widths, formats, dataList, - calculation.getProjectName(), calculation.getCreateTime(), response); + calculation.getProjectName(), calculation.getCreateTime(), totalAmount, response); } /** @@ -1015,11 +1175,12 @@ public class ProjectCostServiceImpl implements ProjectCostService { * @param dataList 数据列表 * @param projectName 工程名称 * @param createTime 制单时间 + * @param totalAmount 总金额 * @param response HttpServletResponse对象 * @throws Exception 导出异常 */ private void exportSegmentExcelWithSignature(String fileName, String sheetName, String[] headers, int[] widths, int[] formats, - List> dataList, String projectName, String createTime, javax.servlet.http.HttpServletResponse response) throws Exception { + List> dataList, String projectName, String createTime, double totalAmount, javax.servlet.http.HttpServletResponse response) throws Exception { try { // 设置文件名 String filename = ""; @@ -1229,6 +1390,9 @@ public class ProjectCostServiceImpl implements ProjectCostService { } } + // 添加费用汇总区域(在签名区域之前) + currentRowIndex = addSummaryArea(wb, sheet, currentRowIndex, headers.length, totalAmount); + // 添加签名区域 addSignatureArea(wb, sheet, currentRowIndex, headers.length); @@ -1242,9 +1406,196 @@ public class ProjectCostServiceImpl implements ProjectCostService { } } + /** + * 添加费用汇总区域到Excel表格(在签名区域之前) + * + * @param wb 工作簿 + * @param sheet 工作表 + * @param startRowIndex 开始行索引 + * @param totalColumns 总列数 + * @param totalAmount 总金额 + * @return 新的行索引 + */ + private int addSummaryArea(HSSFWorkbook wb, HSSFSheet sheet, int startRowIndex, int totalColumns, double totalAmount) { + // 创建样式 - 标签样式(居中对齐) + HSSFCellStyle summaryStyle = wb.createCellStyle(); + HSSFFont summaryFont = wb.createFont(); + summaryFont.setFontName("微软雅黑"); + summaryFont.setFontHeightInPoints((short) 11); + summaryFont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); + summaryStyle.setFont(summaryFont); + summaryStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 改为居中对齐 + summaryStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); + summaryStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); + summaryStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN); + summaryStyle.setBorderRight(HSSFCellStyle.BORDER_THIN); + summaryStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); + + // 金额样式(居中对齐) + HSSFCellStyle amountStyle = wb.createCellStyle(); + HSSFFont amountFont = wb.createFont(); + amountFont.setFontName("微软雅黑"); + amountFont.setFontHeightInPoints((short) 11); + amountStyle.setFont(amountFont); + amountStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 改为居中对齐 + amountStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); + amountStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); + amountStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN); + amountStyle.setBorderRight(HSSFCellStyle.BORDER_THIN); + amountStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); + amountStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("#,##0.00")); + + // 空一行 + startRowIndex += 1; + + // 计算各项费用 + double transportFee = 0.0; // 运输费 + double craneFee = 0.0; // 吊车费 + double compensationFee = 0.0; // 赔偿费 + double grandTotal = totalAmount + transportFee + craneFee + compensationFee; // 汇总金额 + + // 计算列宽分配(左侧标签占30%,右侧金额占70%) + int labelColumns = Math.max(1, totalColumns * 3 / 10); + + // 第一行:合计金额 + HSSFRow row1 = sheet.createRow(startRowIndex); + row1.setHeight((short) 450); + HSSFCell cell1_1 = row1.createCell(0); + cell1_1.setCellValue("合计金额:"); + cell1_1.setCellStyle(summaryStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, 0, labelColumns - 1)); + + HSSFCell cell1_2 = row1.createCell(labelColumns); + cell1_2.setCellValue(totalAmount); + cell1_2.setCellStyle(amountStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, labelColumns, totalColumns - 1)); + + // 填充合并区域的所有单元格 + for (int i = 0; i < totalColumns; i++) { + if (row1.getCell(i) == null) { + row1.createCell(i).setCellStyle(i < labelColumns ? summaryStyle : amountStyle); + } + } + startRowIndex++; + + // 第二行:运输费 + HSSFRow row2 = sheet.createRow(startRowIndex); + row2.setHeight((short) 450); + HSSFCell cell2_1 = row2.createCell(0); + cell2_1.setCellValue("运输费:"); + cell2_1.setCellStyle(summaryStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, 0, labelColumns - 1)); + + HSSFCell cell2_2 = row2.createCell(labelColumns); + cell2_2.setCellValue(transportFee); + cell2_2.setCellStyle(amountStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, labelColumns, totalColumns - 1)); + + for (int i = 0; i < totalColumns; i++) { + if (row2.getCell(i) == null) { + row2.createCell(i).setCellStyle(i < labelColumns ? summaryStyle : amountStyle); + } + } + startRowIndex++; + + // 第三行:吊车费 + HSSFRow row3 = sheet.createRow(startRowIndex); + row3.setHeight((short) 450); + HSSFCell cell3_1 = row3.createCell(0); + cell3_1.setCellValue("吊车费:"); + cell3_1.setCellStyle(summaryStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, 0, labelColumns - 1)); + + HSSFCell cell3_2 = row3.createCell(labelColumns); + cell3_2.setCellValue(craneFee); + cell3_2.setCellStyle(amountStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, labelColumns, totalColumns - 1)); + + for (int i = 0; i < totalColumns; i++) { + if (row3.getCell(i) == null) { + row3.createCell(i).setCellStyle(i < labelColumns ? summaryStyle : amountStyle); + } + } + startRowIndex++; + + // 第四行:赔偿费 + HSSFRow row4 = sheet.createRow(startRowIndex); + row4.setHeight((short) 450); + HSSFCell cell4_1 = row4.createCell(0); + cell4_1.setCellValue("赔偿费:"); + cell4_1.setCellStyle(summaryStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, 0, labelColumns - 1)); + + HSSFCell cell4_2 = row4.createCell(labelColumns); + cell4_2.setCellValue(compensationFee); + cell4_2.setCellStyle(amountStyle); + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, labelColumns, totalColumns - 1)); + + for (int i = 0; i < totalColumns; i++) { + if (row4.getCell(i) == null) { + row4.createCell(i).setCellStyle(i < labelColumns ? summaryStyle : amountStyle); + } + } + startRowIndex++; + + // 第五行:汇总金额(分成4列:大写标签、大写内容、小写标签、小写内容) + HSSFRow row5 = sheet.createRow(startRowIndex); + row5.setHeight((short) 600); // 稍高一些 + + // 计算每列的宽度(平均分成4列) + int colWidth = totalColumns / 4; + + // 第1列:汇总金额大写 + HSSFCell cell5_1 = row5.createCell(0); + cell5_1.setCellValue("汇总金额大写:"); + cell5_1.setCellStyle(summaryStyle); + if (colWidth > 1) { + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, 0, colWidth - 1)); + } + + // 第2列:大写内容 + String chineseAmount = convertToChineseAmount(grandTotal); + HSSFCell cell5_2 = row5.createCell(colWidth); + cell5_2.setCellValue(chineseAmount); + cell5_2.setCellStyle(amountStyle); + if (colWidth > 1) { + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, colWidth, colWidth * 2 - 1)); + } + + // 第3列:汇总金额小写 + HSSFCell cell5_3 = row5.createCell(colWidth * 2); + cell5_3.setCellValue("汇总金额小写:"); + cell5_3.setCellStyle(summaryStyle); + if (colWidth > 1) { + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, colWidth * 2, colWidth * 3 - 1)); + } + + // 第4列:小写内容 + HSSFCell cell5_4 = row5.createCell(colWidth * 3); + cell5_4.setCellValue(String.format("%.2f 元", grandTotal)); + cell5_4.setCellStyle(amountStyle); + if (colWidth > 1) { + sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(startRowIndex, startRowIndex, colWidth * 3, totalColumns - 1)); + } + + // 填充合并区域的所有单元格 + for (int i = 0; i < totalColumns; i++) { + if (row5.getCell(i) == null) { + if (i < colWidth || (i >= colWidth * 2 && i < colWidth * 3)) { + row5.createCell(i).setCellStyle(summaryStyle); // 标签列 + } else { + row5.createCell(i).setCellStyle(amountStyle); // 内容列 + } + } + } + startRowIndex++; + + return startRowIndex; + } + /** * 添加签名区域到Excel表格 - * + * * @param wb 工作簿 * @param sheet 工作表 * @param startRowIndex 开始行索引