优化Excel导出功能,增加费用汇总区域及金额转换为中文大写

This commit is contained in:
syruan 2025-12-03 19:53:45 +08:00 committed by syruan
parent 3bdf7bdc12
commit c8f7e94c8e
5 changed files with 377 additions and 22 deletions

View File

@ -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 '<a class="layui-btn layui-btn-xs" lay-event="viewDetail">查看明细</a>';
}}
]],

View File

@ -89,8 +89,8 @@
</div>
</div>
<div class="filter-action">
<button class="layui-btn" lay-submit lay-filter="calculation-generate">生成</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="calculation-save">保存</button>
<button class="layui-btn" lay-submit lay-filter="calculation-generate">开始计算</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="calculation-save">保存结算单</button>
</div>
</div>

View File

@ -26,7 +26,7 @@
LEFT JOIN wf_task_record wtr1 ON wtr1.ID = wtr.SUP_ID
LEFT JOIN wf_task_record wtr2 ON wtr2.ID = wtr1.SUP_ID
LEFT JOIN wf_collar_apply wca ON wca.APPLY_NUMBER = wtr2.NUMBER
LEFT JOIN bm_subcontractors bs ON wca.SUBCONTRACTORS_ID = bs.ID
LEFT JOIN bm_subcontractors bs ON wca.SUBCONTRACTORS_ID = bs.ID
LEFT JOIN wf_agreement_task wat ON wtr.SUP_ID = wat.TASK_ID
LEFT JOIN wf_lease_agreement wla ON wat.AGREEMENT_ID = wla.ID
LEFT JOIN mm_type mt ON wir.MODEL_ID = mt.ID
@ -35,7 +35,7 @@
LEFT JOIN bm_project bp ON wla.PROJECT = bp.ID
LEFT JOIN pm_organization pmo ON pmo.id = wtr.ORG_ID
LEFT JOIN wf_ma_outstock wmo ON wir.SUP_ID = wmo.TASK_ID
LEFT JOIN pm_user pu ON wmo.OUT_PERSON = pu.ID
LEFT JOIN pm_user pu ON wmo.OUT_PERSON = pu.ID
LEFT JOIN mm_machines mm ON mm.ID = wir.MA_ID
WHERE wir.TYPE = 2
and wtr.IS_ACTIVE = 1
@ -43,8 +43,9 @@
<if test="param.machineTypeId != null">
and wir.MODEL_ID = #{param.machineTypeId}
</if>
<if test="param.startTime != null and param.endTime != null ">
and left(wtr.OPERATION_TIME,10) between #{param.startTime} and #{param.endTime}
<if test="param.endTime != null">
<!-- 查询所有在统计结束时间之前的领料记录(包括统计期间开始之前的领料) -->
and left(wtr.OPERATION_TIME,10) &lt;= #{param.endTime}
</if>
<if test="param.keyWord != null and param.keyWord != ''">
AND(
@ -88,7 +89,10 @@
<if test="param.machineTypeId != null">
and wir.MODEL_ID = #{param.machineTypeId}
</if>
and left(wir.TIME,10) BETWEEN #{param.startTime} and #{param.endTime}
<if test="param.endTime != null">
<!-- 查询所有在统计结束时间之前的退料记录(包括统计期间开始之前的退料) -->
and left(wir.TIME,10) &lt;= #{param.endTime}
</if>
<if test="param.keyWord != null and param.keyWord != ''">
and (
bu.`NAME` like concat('%',#{param.keyWord},'%') OR

View File

@ -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<ProjectLeaseCostDetail> 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<String, Object> 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<Map<String, Object>> dataList, String projectName, String createTime, javax.servlet.http.HttpServletResponse response) throws Exception {
List<Map<String, Object>> 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 开始行索引