11
This commit is contained in:
1 parent
c6a6251c42
commit
a859b0ab19
3 files changed
+131
-19
No files matched your search
+2
-2
@@ -189,9 +189,9 @@ public class HardCanteenApptTotalController {
|
||||
* 导出用户在指定用餐日期范围内每天的预约情况。
|
||||
*
|
||||
* @param query 用户、日期范围、食堂、餐段和状态筛选条件;携带 userIds 时仅导出勾选用户
|
||||
* @return 用户和日期维度的 Excel 数据,包含每天未预约用户
|
||||
* @return 用户和日期维度的 Excel 数据,包含每天未预约用户,最后一行为餐段及当日金额合计
|
||||
*/
|
||||
@Operation(summary = "用户预约查询导出", description = "逐日导出用户预约及核销情况,包含未预约用户,最多查询两个月")
|
||||
@Operation(summary = "用户预约查询导出", description = "逐日导出用户预约及核销情况,包含未预约用户和末尾金额合计,最多查询两个月")
|
||||
@SysLog("用户预约查询导出")
|
||||
@PreAuthorize("@pms.hasPermission('hard_canteenUserApptTotal_export')")
|
||||
@ResponseExcel(name = "用户预约查询")
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ public interface HardCanteenApptTotalService {
|
||||
* 查询并转换用户预约导出数据,包含没有预约订单的权限范围用户。
|
||||
*
|
||||
* @param query 用户、日期范围和预约状态筛选条件
|
||||
* @return 用户和日期维度的 Excel 导出行
|
||||
* @return 用户和日期维度的 Excel 导出行,最后一行为餐段及当日金额合计
|
||||
*/
|
||||
List<HardCanteenUserApptExcelVo> userReservationExport(HardCanteenUserApptQueryDto query);
|
||||
}
|
||||
+128
-16
@@ -96,9 +96,13 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
/** 用户预约导出的二级表头行数,第一行是业务分组,第二行是具体字段。 */
|
||||
private static final int USER_RESERVATION_EXPORT_HEADER_ROW_COUNT = 2;
|
||||
|
||||
/** 用户预约导出明细之后固定追加的合计行数量。 */
|
||||
private static final int USER_RESERVATION_EXPORT_SUMMARY_ROW_COUNT = 1;
|
||||
|
||||
/** 用户预约导出工作表二级表头之后允许写入的最大明细行数。 */
|
||||
private static final int MAX_USER_RESERVATION_EXPORT_ROWS =
|
||||
BatchExcelExportTemplate.calculateMaxDetailRowCount(USER_RESERVATION_EXPORT_HEADER_ROW_COUNT, 0);
|
||||
BatchExcelExportTemplate.calculateMaxDetailRowCount(USER_RESERVATION_EXPORT_HEADER_ROW_COUNT,
|
||||
USER_RESERVATION_EXPORT_SUMMARY_ROW_COUNT);
|
||||
|
||||
/** 用户预约导出工作表名称。 */
|
||||
private static final String USER_RESERVATION_EXPORT_SHEET_NAME = "用户预约查询";
|
||||
@@ -281,7 +285,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
* 查询用户预约 Excel 数据。
|
||||
*
|
||||
* @param query 用户、日期范围和预约状态筛选条件
|
||||
* @return 用户和日期维度的导出行
|
||||
* @return 用户和日期维度的导出行,最后一行为餐段及当日金额合计
|
||||
*/
|
||||
@Override
|
||||
public List<HardCanteenUserApptExcelVo> userReservationExport(HardCanteenUserApptQueryDto query) {
|
||||
@@ -291,8 +295,19 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
return List.of();
|
||||
}
|
||||
Map<String, String> mealSegmentLabelMap = fetchMealSegmentLabelMap();
|
||||
return listAllFilteredExportRows(exportQuery, getQueryDates(exportQuery)).stream()
|
||||
.map(row -> toExcelRow(row, exportQuery, mealSegmentLabelMap)).toList();
|
||||
List<HardCanteenUserApptVo> reservationRows =
|
||||
listAllFilteredExportRows(exportQuery, getQueryDates(exportQuery));
|
||||
if (reservationRows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
UserReservationExportSummary summary =
|
||||
new UserReservationExportSummary(exportQuery, mealSegmentLabelMap);
|
||||
summary.addRows(reservationRows);
|
||||
List<HardCanteenUserApptExcelVo> excelRows = new ArrayList<>(reservationRows.size() + 1);
|
||||
reservationRows.stream().map(row -> toExcelRow(row, exportQuery, mealSegmentLabelMap))
|
||||
.forEach(excelRows::add);
|
||||
excelRows.add(toExcelSummaryRow(summary, mealSegmentLabelMap));
|
||||
return excelRows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +371,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
long processedUserTotal = 0L;
|
||||
long exportedRowTotal = 0L;
|
||||
long current = 1L;
|
||||
UserReservationExportSummary summary = new UserReservationExportSummary(query, mealSegmentLabelMap);
|
||||
|
||||
try (ExcelWriter excelWriter = EasyExcel.write(outputPath.toFile(), HardCanteenUserApptExcelVo.class).build()) {
|
||||
WriteSheet writeSheet = EasyExcel.writerSheet(USER_RESERVATION_EXPORT_SHEET_NAME).build();
|
||||
@@ -367,16 +383,18 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
}
|
||||
int remainingUserCount = Math.toIntExact(Math.min(pageRecords.size(), userTotal - processedUserTotal));
|
||||
List<UserVO> batchUsers = pageRecords.subList(0, remainingUserCount);
|
||||
List<HardCanteenUserApptExcelVo> batchRows = filterRows(
|
||||
buildRows(batchUsers, query, queryDates), query).stream()
|
||||
.map(row -> toExcelRow(row, query, mealSegmentLabelMap)).toList();
|
||||
long nextExportedRowTotal = exportedRowTotal + batchRows.size();
|
||||
List<HardCanteenUserApptVo> batchReservationRows =
|
||||
filterRows(buildRows(batchUsers, query, queryDates), query);
|
||||
long nextExportedRowTotal = exportedRowTotal + batchReservationRows.size();
|
||||
if (nextExportedRowTotal > MAX_USER_RESERVATION_EXPORT_ROWS) {
|
||||
throw new IllegalStateException("导出数据超过单个 Excel 工作表支持的 "
|
||||
+ MAX_USER_RESERVATION_EXPORT_ROWS + " 条,请缩小查询范围后分批导出");
|
||||
}
|
||||
List<HardCanteenUserApptExcelVo> batchRows = batchReservationRows.stream()
|
||||
.map(row -> toExcelRow(row, query, mealSegmentLabelMap)).toList();
|
||||
if (!batchRows.isEmpty()) {
|
||||
excelWriter.write(batchRows, writeSheet);
|
||||
summary.addRows(batchReservationRows);
|
||||
}
|
||||
exportedRowTotal = nextExportedRowTotal;
|
||||
processedUserTotal += batchUsers.size();
|
||||
@@ -387,14 +405,14 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
}
|
||||
userPage = fetchExportUserPage(++current, USER_RESERVATION_EXPORT_BATCH_SIZE, query);
|
||||
}
|
||||
}
|
||||
|
||||
if (processedUserTotal != userTotal) {
|
||||
throw new IllegalStateException("导出用户数据在执行期间发生变化,预期 " + userTotal + " 位,实际处理 "
|
||||
+ processedUserTotal + " 位");
|
||||
}
|
||||
if (exportedRowTotal == 0L) {
|
||||
throw new IllegalStateException("没有符合当前筛选条件的用户预约记录");
|
||||
if (processedUserTotal != userTotal) {
|
||||
throw new IllegalStateException("导出用户数据在执行期间发生变化,预期 " + userTotal
|
||||
+ " 位,实际处理 " + processedUserTotal + " 位");
|
||||
}
|
||||
if (exportedRowTotal == 0L) {
|
||||
throw new IllegalStateException("没有符合当前筛选条件的用户预约记录");
|
||||
}
|
||||
excelWriter.write(List.of(toExcelSummaryRow(summary, mealSegmentLabelMap)), writeSheet);
|
||||
}
|
||||
return exportedRowTotal;
|
||||
}
|
||||
@@ -1146,6 +1164,26 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
return excel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户预约导出文件末尾的金额合计行。
|
||||
*
|
||||
* <p>合计仍使用与明细相同的导出模型,姓名列标记“合计”,餐段金额和当日金额分别写入对应统计列,
|
||||
* 其余人员及预约字段保持为空。</p>
|
||||
*
|
||||
* @param summary 已累计全部导出分片的金额汇总
|
||||
* @param mealSegmentLabelMap 餐段编码到字典标签的映射
|
||||
* @return 可直接追加到 EasyExcel 工作表末尾的合计行
|
||||
*/
|
||||
private HardCanteenUserApptExcelVo toExcelSummaryRow(UserReservationExportSummary summary,
|
||||
Map<String, String> mealSegmentLabelMap) {
|
||||
HardCanteenUserApptExcelVo excel = new HardCanteenUserApptExcelVo();
|
||||
excel.setUserName("合计");
|
||||
excel.setMealSegmentTotalPrices(buildExportSummaryMealSegmentTotalPrices(summary,
|
||||
mealSegmentLabelMap));
|
||||
excel.setTotalPrice(centsToYuan(summary.totalPrice));
|
||||
return excel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用餐时间段字典并保留字典排序;远程调用失败时由实际订单编码继续完成导出。
|
||||
*
|
||||
@@ -1225,6 +1263,24 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
}).collect(Collectors.joining(";"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化全部导出明细累计后的各餐段金额。
|
||||
*
|
||||
* @param summary 跨分片累计的金额汇总
|
||||
* @param mealSegmentLabelMap 餐段编码到字典标签的映射
|
||||
* @return 例如“早餐:60.80元;午餐:119.00元”的餐段合计
|
||||
*/
|
||||
private String buildExportSummaryMealSegmentTotalPrices(UserReservationExportSummary summary,
|
||||
Map<String, String> mealSegmentLabelMap) {
|
||||
if (summary.mealSegmentTotalPrices.isEmpty()) {
|
||||
return "全部餐段:0.00元";
|
||||
}
|
||||
return summary.mealSegmentTotalPrices.entrySet().stream()
|
||||
.map(entry -> resolveMealSegmentLabel(entry.getKey(), mealSegmentLabelMap)
|
||||
+ ":" + formatYuan(entry.getValue()))
|
||||
.collect(Collectors.joining(";"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并当前字典餐段和订单中的历史餐段编码,供预约数量及金额摘要复用。
|
||||
*
|
||||
@@ -1330,4 +1386,60 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
return "无需核销";
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户预约导出跨分片金额汇总器。
|
||||
*
|
||||
* <p>所有金额均以分为单位累计,生成最终合计行时才统一转换为元,避免分片导出过程中出现精度损失。</p>
|
||||
*/
|
||||
private static final class UserReservationExportSummary {
|
||||
|
||||
/** 各餐段累计金额,键为订单餐段协议编码,值为商品总价,单位为分。 */
|
||||
private final Map<String, Long> mealSegmentTotalPrices = new LinkedHashMap<>();
|
||||
|
||||
/** 全部导出明细的当日金额累计值,单位为分。 */
|
||||
private long totalPrice;
|
||||
|
||||
/**
|
||||
* 创建跨分片汇总器,并按查询餐段或餐段字典顺序预置零金额。
|
||||
*
|
||||
* @param query 本次导出的餐段筛选条件
|
||||
* @param mealSegmentLabelMap 餐段编码到字典标签的有序映射
|
||||
*/
|
||||
private UserReservationExportSummary(HardCanteenUserApptQueryDto query,
|
||||
Map<String, String> mealSegmentLabelMap) {
|
||||
if (StrUtil.isNotBlank(query.getHourType())) {
|
||||
mealSegmentTotalPrices.put(query.getHourType(), 0L);
|
||||
} else {
|
||||
mealSegmentLabelMap.keySet().forEach(hourType ->
|
||||
mealSegmentTotalPrices.put(hourType, 0L));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 累计一批已经通过全部查询条件筛选的用户日期明细。
|
||||
*
|
||||
* @param rows 当前分片即将写入 Excel 的用户日期明细
|
||||
*/
|
||||
private void addRows(List<HardCanteenUserApptVo> rows) {
|
||||
rows.forEach(this::addRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 累计单个用户日期明细的当日总金额和各餐段金额。
|
||||
*
|
||||
* @param row 当前导出的一条用户日期明细
|
||||
*/
|
||||
private void addRow(HardCanteenUserApptVo row) {
|
||||
totalPrice = Math.addExact(totalPrice, row.getTotalPrice() == null ? 0L : row.getTotalPrice());
|
||||
if (row.getOrders() == null || row.getOrders().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
row.getOrders().stream().filter(Objects::nonNull)
|
||||
.filter(order -> StrUtil.isNotBlank(order.getHourType()))
|
||||
.forEach(order -> mealSegmentTotalPrices.merge(order.getHourType(),
|
||||
order.getTotalPrice() == null ? 0L : order.getTotalPrice().longValue(),
|
||||
Math::addExact));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user