|
|
@@ -110,6 +110,7 @@ import com.yc.ship.module.trade.utils.AgencyAuthUtils;
|
|
|
import com.yc.ship.module.trade.utils.excel.ExcelStyleHandler;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.poi.hpsf.Decimal;
|
|
|
import org.apache.poi.ss.usermodel.*;
|
|
|
import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
import org.apache.poi.ss.util.RegionUtil;
|
|
|
@@ -422,7 +423,6 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
AtomicReference<BigDecimal> amount = new AtomicReference<>(BigDecimal.ZERO);
|
|
|
try {
|
|
|
List<OrderPolicyDO> orderPolicyList = orderPolicyMapper.selectList(OrderPolicyDO::getOrderId, orderId);
|
|
|
-
|
|
|
if (!orderPolicyList.isEmpty()) {
|
|
|
orderPolicyList.forEach(item -> {
|
|
|
amount.set(amount.get().add(item.getAmount()));
|
|
|
@@ -439,6 +439,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
.set(TradeOrderDO::getUpdateTime, LocalDateTime.now())
|
|
|
.set(TradeOrderDO::getDeposiStatus, 1)
|
|
|
.set(TradeOrderDO::getPayAmount, tradeOrderDO.getPayAmount().subtract(amount.get()))
|
|
|
+ .set(TradeOrderDO::getFreeAmount, amount.get())
|
|
|
.eq(TradeOrderDO::getId, orderId)
|
|
|
);
|
|
|
Map<String, Object> extMap = new HashMap<>();
|
|
|
@@ -571,15 +572,8 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
tradeOrderAuditMapper.insert(tradeOrderAuditDO);
|
|
|
|
|
|
if (tradeOrderDO.getAuditStatus() + 1 > tradeOrderDO.getAuditType()) {
|
|
|
- tradeOrderMapper.update(Wrappers.<TradeOrderDO>lambdaUpdate()
|
|
|
- .set(TradeOrderDO::getAuditStatus, tradeOrderDO.getAuditStatus() + 1)
|
|
|
- .set(TradeOrderDO::getUpdateTime, LocalDateTime.now())
|
|
|
- .set(TradeOrderDO::getDamagedStatus, 2)
|
|
|
- .set(TradeOrderDO::getDamaged, damaged)
|
|
|
- .eq(TradeOrderDO::getId, orderId)
|
|
|
- );
|
|
|
- tradeOrderPayService.cancelOrder(orderId);
|
|
|
-
|
|
|
+ BigDecimal supplementAmount = tradeOrderDO.getRealPayAmount().subtract(damaged);
|
|
|
+ int isSupplement = supplementAmount.compareTo(BigDecimal.ZERO) > 0 ? 0 : 1;
|
|
|
try {
|
|
|
Map map = new HashMap();
|
|
|
map.put("orderNo", tradeOrderDO.getOrderNo());
|
|
|
@@ -588,23 +582,9 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
ShipRespDTO shipRespDTO = shipApi.queryShip(tradeOrderDO.getShipId());
|
|
|
map.put("routeName", routeRespDTO.getName());
|
|
|
map.put("boatName", shipRespDTO.getName());
|
|
|
- BigDecimal deposi = tradeOrderDO.getDeposi();
|
|
|
- Integer deposiStatus = tradeOrderDO.getDeposiStatus();
|
|
|
- BigDecimal realPay = BigDecimal.ZERO;
|
|
|
- if (deposiStatus == 2) {
|
|
|
- realPay = deposi;
|
|
|
- } else if(tradeOrderDO.getPayStatus()==1){
|
|
|
- realPay = tradeOrderDO.getPayAmount();
|
|
|
- }
|
|
|
-
|
|
|
- BigDecimal payAmount = tradeOrderDO.getPayAmount();
|
|
|
- BigDecimal refundAmount = payAmount.multiply(damaged);
|
|
|
-
|
|
|
- BigDecimal multiply = refundAmount.multiply(realPay).compareTo(BigDecimal.ZERO) > 0 ? refundAmount.multiply(realPay):BigDecimal.ZERO;
|
|
|
-
|
|
|
map.put("wyAmount", damaged);
|
|
|
- map.put("wyRefundAmount", refundAmount);
|
|
|
- map.put("wyPayAmount", multiply);
|
|
|
+ map.put("wyRefundAmount", isSupplement==0?supplementAmount:BigDecimal.ZERO);
|
|
|
+ map.put("wyPayAmount", isSupplement==1?supplementAmount.abs():BigDecimal.ZERO);
|
|
|
map.put("startTime", DateUtil.formatDate(tradeOrderDO.getTravelDate()));
|
|
|
NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO();
|
|
|
reqDTO.setTemplateParams(map);
|
|
|
@@ -615,11 +595,28 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
reqDTO.setUserId(Long.parseLong(tradeOrderDO.getSellerId()));
|
|
|
notifyMessageSendApi.sendSingleMessageToAdmin(reqDTO);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("发送短信异常", e);
|
|
|
+ log.error("发送站内信异常", e);
|
|
|
}
|
|
|
|
|
|
+ tradeOrderMapper.update(Wrappers.<TradeOrderDO>lambdaUpdate()
|
|
|
+ .set(TradeOrderDO::getAuditStatus, tradeOrderDO.getAuditStatus() + 1)
|
|
|
+ .set(TradeOrderDO::getUpdateTime, LocalDateTime.now())
|
|
|
+ .set(TradeOrderDO::getDamagedStatus, 2)
|
|
|
+ .set(TradeOrderDO::getSupplementAmount, supplementAmount)
|
|
|
+ .set(TradeOrderDO::getDamaged, damaged)
|
|
|
+ //是否需要补缴费 1是 0 否
|
|
|
+ .set(TradeOrderDO::getIsSupplementary, isSupplement)
|
|
|
+ .eq(TradeOrderDO::getId, orderId)
|
|
|
+ );
|
|
|
+ tradeOrderPayService.cancelOrder(orderId);
|
|
|
Map<String, Object> extMap = new HashMap<>();
|
|
|
- extMap.put("result", "审核通过");
|
|
|
+ String result = "审核通过";
|
|
|
+ if(isSupplement==1){
|
|
|
+ result += "需要补缴"+supplementAmount;
|
|
|
+ }else{
|
|
|
+ result += "退押金"+supplementAmount;
|
|
|
+ }
|
|
|
+ extMap.put("result", result);
|
|
|
TradeOrderLogUtils.setOrderInfo(orderId, tradeOrderDO.getOrderStatus(), TradeOrderStatusEnum.UNPAID.getStatus(), extMap);
|
|
|
} else {
|
|
|
AuditUserDO auditUserDO = auditUserMapper.selectOne(new QueryWrapper<AuditUserDO>().eq("type", tradeOrderDO.getAuditType()).eq("audit_status", tradeOrderDO.getAuditStatus() + 1).last("limit 1"));
|
|
|
@@ -1713,7 +1710,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
for (TradeVistorReqVO tradeVistorReqVO : createVO.getTourist()) {
|
|
|
List<ShipTradeOrderCreateReqVO.Visitor> visitorList = new ArrayList<>();
|
|
|
ShipTradeOrderCreateReqVO.OrderDetail visitorDetailId = new ShipTradeOrderCreateReqVO.OrderDetail();
|
|
|
- BeanUtils.copyProperties(orderDetail, ShipTradeOrderCreateReqVO.OrderDetail.class);
|
|
|
+ //BeanUtils.copyProperties(orderDetail, ShipTradeOrderCreateReqVO.OrderDetail.class);
|
|
|
visitorDetailId.setPrice(tradeVistorReqVO.getActualPrice());
|
|
|
visitorDetailId.setOriginPrice(tradeVistorReqVO.getPrice());
|
|
|
visitorDetailId.setProductType(0);
|
|
|
@@ -2684,47 +2681,198 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
return CommonResult.success("修改成功");
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+ private String getPersonTypeAll(String type) {
|
|
|
+ String des;
|
|
|
+ switch (type) {
|
|
|
+ case "leader":
|
|
|
+ des = "领队";
|
|
|
+ break;
|
|
|
+ case "with":
|
|
|
+ des = "陪同";
|
|
|
+ break;
|
|
|
+ case "childTake":
|
|
|
+ case "childPlus":
|
|
|
+ case "childNonTake":
|
|
|
+ des = "儿童";
|
|
|
+ break;
|
|
|
+ case "babyTake":
|
|
|
+ case "babyPlus":
|
|
|
+ case "babyNonTake":
|
|
|
+ des = "婴儿";
|
|
|
+ break;
|
|
|
+ case "adultTake":
|
|
|
+ case "adultPlus":
|
|
|
+ default:
|
|
|
+ des = "成人";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return des;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出游客名单列表-计调
|
|
|
+ *
|
|
|
+ * 功能说明:
|
|
|
+ * 1. 查询订单基础信息(船名、航期、航向)
|
|
|
+ * 2. 查询游客列表(包含订单、房间、游客详细信息)
|
|
|
+ * 3. 使用Excel模板进行数据填充
|
|
|
+ * 4. 按订单和房间自动合并单元格
|
|
|
+ *
|
|
|
+ * 导出结构:
|
|
|
+ * - 订单详情(8列):代理商、订单号、团号、航向、航期、应收款、实收款、定金
|
|
|
+ * - 房间详情(3列):序号、房型、入住类型
|
|
|
+ * - 游客详情(8列):姓名、性别、证件类型、证件号、游客类型、国籍、增值服务、备注
|
|
|
+ *
|
|
|
+ * @param reqVO 查询条件
|
|
|
+ * @return 导出的Excel文件
|
|
|
+ */
|
|
|
@Override
|
|
|
public File exportTouristList(TradeOrderPageReqVO reqVO) {
|
|
|
+ InputStream template = getClass().getClassLoader().getResourceAsStream("templates/tourist_template_operator.xlsx");
|
|
|
+ return getFile(reqVO,template, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出游客名单列表-计调
|
|
|
+ *
|
|
|
+ * 功能说明:
|
|
|
+ * 1. 查询订单基础信息(船名、航期、航向)
|
|
|
+ * 2. 查询游客列表(包含订单、房间、游客详细信息)
|
|
|
+ * 3. 使用Excel模板进行数据填充
|
|
|
+ * 4. 按订单和房间自动合并单元格
|
|
|
+ *
|
|
|
+ * 导出结构:
|
|
|
+ * - 订单详情(8列):代理商、订单号、团号、航向、航期、应收款、实收款、定金
|
|
|
+ * - 房间详情(3列):序号、房型、入住类型
|
|
|
+ * - 游客详情(8列):姓名、性别、证件类型、证件号、游客类型、国籍、增值服务、备注
|
|
|
+ *
|
|
|
+ * @param reqVO 查询条件
|
|
|
+ * @return 导出的Excel文件
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public File exportTouristListToAgent(TradeOrderPageReqVO reqVO) {
|
|
|
+ InputStream template = getClass().getClassLoader().getResourceAsStream("templates/tourist_template_agent.xlsx");
|
|
|
+ return getFile(reqVO,template, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ private File getFile(TradeOrderPageReqVO reqVO,InputStream template, int fileType) {
|
|
|
+ List<Integer> orderStatus = reqVO.getOrderStatus();
|
|
|
+ // 排除状态为 -2 的订单
|
|
|
+ if (orderStatus != null && orderStatus.contains(-2)) {
|
|
|
+ orderStatus.remove(Integer.valueOf(-2));
|
|
|
+ reqVO.setOrderStatus(orderStatus.isEmpty() ? null : orderStatus);
|
|
|
+ }
|
|
|
+
|
|
|
// 1. 查询订单基础信息(船名、航期、航向)
|
|
|
Map<String, Object> baseInfo = tradeOrderMapper.selectTouristExportBase(reqVO);
|
|
|
-
|
|
|
- // 2. 查询游客列表
|
|
|
+ // 查询订单表头信息
|
|
|
+ Map<String, Object> headInfo = tradeOrderMapper.selectTouristExportHead(reqVO);
|
|
|
+ // 2. 查询游客列表(包含订单、房间、游客详细信息)
|
|
|
List<TouristExportVisitorVO> visitorList = tradeVisitorMapper.selectTouristExportVisitor(reqVO);
|
|
|
|
|
|
- // 3. 加载模板
|
|
|
- InputStream template = getClass().getClassLoader().getResourceAsStream("templates/tourist_template.xlsx");
|
|
|
- String fileName = String.valueOf(System.currentTimeMillis());
|
|
|
+ // 3. 加载Excel模板
|
|
|
+ String fileName = String.valueOf(System.currentTimeMillis()) + fileType;
|
|
|
String tmpFile = "/tmp/" + fileName + "_tourist.xlsx";
|
|
|
//String tmpFile = "D:/tmp/" + fileName + "_tourist.xlsx";
|
|
|
|
|
|
- // 先不合并,直接导出
|
|
|
+ // 4. 准备ExcelWriter,注册样式处理器和合并策略
|
|
|
+ // - ExcelStyleHandler: 设置单元格样式
|
|
|
+ // - TouristListMergeStrategy: 按订单和房间自动合并单元格
|
|
|
ExcelWriter excelWriter = EasyExcel.write(tmpFile).withTemplate(template).build();
|
|
|
- WriteSheet writeSheet = EasyExcelFactory.writerSheet().registerWriteHandler(new ExcelStyleHandler(9)).build();
|
|
|
+ WriteSheet writeSheet = EasyExcelFactory.writerSheet()
|
|
|
+ .registerWriteHandler(new ExcelStyleHandler(visitorList, fileType))
|
|
|
+ /* .registerWriteHandler(new TouristListMergeStrategy(visitorList))*/
|
|
|
+ .build();
|
|
|
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).autoStyle(true).build();
|
|
|
|
|
|
- // 4. 准备基础信息数据
|
|
|
+ // 5. 准备基础信息数据(填充到Excel表头区域)
|
|
|
Map<String, Object> baseData = new HashMap<>();
|
|
|
-
|
|
|
+ String personNum = "";
|
|
|
+ if(headInfo!=null){
|
|
|
+ // 总人数
|
|
|
+ String totalOrders = headInfo.get("totalOrders") != null ? headInfo.get("totalOrders").toString() :"0";
|
|
|
+ // 成人数
|
|
|
+ String adultNum = headInfo.get("adultNum") != null ? headInfo.get("adultNum").toString() :"0";
|
|
|
+ // 儿童数
|
|
|
+ String childBabyNum = headInfo.get("childBabyNum") != null ? headInfo.get("childBabyNum").toString() :"0";
|
|
|
+ // 陪同数
|
|
|
+ String withNum = headInfo.get("withNum") != null ? headInfo.get("withNum").toString() :"0";
|
|
|
+ // 领队数
|
|
|
+ String leaderNum = headInfo.get("leaderNum") != null ? headInfo.get("leaderNum").toString() :"0";
|
|
|
+ StringBuilder personNumBuilder = new StringBuilder();
|
|
|
+ if (StringUtils.isNotEmpty(totalOrders)) {
|
|
|
+ personNumBuilder.append(totalOrders);
|
|
|
+ if (StringUtils.isNotEmpty(adultNum) || StringUtils.isNotEmpty(childBabyNum)
|
|
|
+ || StringUtils.isNotEmpty(withNum) || StringUtils.isNotEmpty(leaderNum)) {
|
|
|
+ personNumBuilder.append("(");
|
|
|
+
|
|
|
+ boolean hasContent = false;
|
|
|
+ if (StringUtils.isNotEmpty(adultNum)) {
|
|
|
+ personNumBuilder.append(adultNum).append(" 成人");
|
|
|
+ hasContent = true;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(childBabyNum)) {
|
|
|
+ if (hasContent) {
|
|
|
+ personNumBuilder.append("/");
|
|
|
+ }
|
|
|
+ personNumBuilder.append(childBabyNum).append(" 儿童");
|
|
|
+ hasContent = true;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(leaderNum)) {
|
|
|
+ if (hasContent) {
|
|
|
+ personNumBuilder.append("/");
|
|
|
+ }
|
|
|
+ personNumBuilder.append(leaderNum).append(" 领队");
|
|
|
+ hasContent = true;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(withNum)) {
|
|
|
+ if (hasContent) {
|
|
|
+ personNumBuilder.append("/");
|
|
|
+ }
|
|
|
+ personNumBuilder.append(withNum).append(" 陪同");
|
|
|
+ hasContent = true;
|
|
|
+ }
|
|
|
+ personNumBuilder.append(")");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ personNum = personNumBuilder.toString();
|
|
|
+ }
|
|
|
// 船名
|
|
|
baseData.put("shipName", baseInfo != null && baseInfo.get("shipName") != null ? baseInfo.get("shipName") : "");
|
|
|
-
|
|
|
- // 航期
|
|
|
- // 航期
|
|
|
- baseData.put("travelDate", baseInfo != null && baseInfo.get("travelDate") != null
|
|
|
- ? ((LocalDateTime) baseInfo.get("travelDate")).format(DateTimeFormatter.ofPattern("yyyy.M.d"))
|
|
|
- : "");
|
|
|
-
|
|
|
-
|
|
|
- // 航向
|
|
|
+ // 人数/类型
|
|
|
+ baseData.put("personNum", personNum);
|
|
|
+ // 人数/ 国籍
|
|
|
+ baseData.put("nationalityStats", headInfo != null && headInfo.get("nationalityStats") != null ? headInfo.get("nationalityStats") : "");
|
|
|
+ // 房间总数
|
|
|
+ baseData.put("totalRooms", headInfo != null && headInfo.get("totalRooms") != null ? headInfo.get("totalRooms") : "");
|
|
|
+ // 房型合计
|
|
|
+ baseData.put("roomStats", headInfo != null && headInfo.get("roomStats") != null ? headInfo.get("roomStats") : "");
|
|
|
+ // 应收款
|
|
|
+ baseData.put("totalPayAmount", headInfo != null && headInfo.get("totalPayAmount") != null ? headInfo.get("totalPayAmount") : "");
|
|
|
+ // 实收款
|
|
|
+ baseData.put("totalActualAmount", headInfo != null && headInfo.get("totalActualAmount") != null ? headInfo.get("totalActualAmount") : "");
|
|
|
+ // 定金
|
|
|
+ baseData.put("deposi", headInfo != null && headInfo.get("deposi") != null ? headInfo.get("deposi") : "");
|
|
|
+ // 时间
|
|
|
+ LocalDateTime newDate = LocalDateTime.now();
|
|
|
+ baseData.put("newDate", newDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
|
|
+
|
|
|
+
|
|
|
+ // 航向(1=宜昌-重庆,其他=重庆-宜昌)
|
|
|
Integer direction = baseInfo != null && baseInfo.get("direction") != null ? (Integer) baseInfo.get("direction") : 0;
|
|
|
baseData.put("direction", direction != null && direction == 1 ? "宜昌-重庆" : "重庆-宜昌");
|
|
|
+ // 如果为上水(宜昌 - 重庆)则航期往后推一天
|
|
|
+ baseData.put("travelDate", formatTravelDate(baseInfo, direction));
|
|
|
|
|
|
// 总人数和国籍统计
|
|
|
int totalCount = visitorList != null ? visitorList.size() : 0;
|
|
|
StringBuilder countryStat = new StringBuilder();
|
|
|
|
|
|
if (visitorList != null && !visitorList.isEmpty()) {
|
|
|
+ // 按国籍分组统计人数
|
|
|
Map<String, Long> countryMap = visitorList.stream()
|
|
|
.filter(item -> ObjectUtil.isNotEmpty(item) && StringUtils.isNotEmpty(item.getNationalityName()))
|
|
|
.collect(Collectors.groupingBy(
|
|
|
@@ -2732,6 +2880,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
Collectors.counting()
|
|
|
));
|
|
|
|
|
|
+ // 拼接国籍统计字符串(格式:数量/国籍)
|
|
|
for (Map.Entry<String, Long> entry : countryMap.entrySet()) {
|
|
|
if (countryStat.length() > 0) {
|
|
|
countryStat.append(" ");
|
|
|
@@ -2742,41 +2891,251 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
|
|
|
baseData.put("totalCount", totalCount);
|
|
|
baseData.put("countryStat", countryStat.toString());
|
|
|
|
|
|
- // 5. 准备游客明细列表
|
|
|
+ // 6. 准备游客明细列表数据(按订单→房间→游客三级分组填充)
|
|
|
List<Map<String, Object>> touristData = new ArrayList<>();
|
|
|
|
|
|
- if (visitorList != null) {
|
|
|
- for (int i = 0; i < visitorList.size(); i++) {
|
|
|
- TouristExportVisitorVO visitor = visitorList.get(i);
|
|
|
- Map<String, Object> item = new HashMap<>();
|
|
|
- if (ObjectUtil.isEmpty(visitor)) {
|
|
|
- continue;
|
|
|
+ if (visitorList != null && !visitorList.isEmpty()) {
|
|
|
+ // 按订单号分组(使用 LinkedHashMap 保持 visitorList 的原始顺序)
|
|
|
+ Map<String, List<TouristExportVisitorVO>> orderGroupMap = visitorList.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ TouristExportVisitorVO::getOrderNo,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.toList()
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 先按 orderId 分组取每个订单的 payAmount(同一订单不重复计算)
|
|
|
+ // 再按 orderNo 分组累加 payAmount
|
|
|
+ Map<String, BigDecimal> orderPayAmountMap = visitorList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ TouristExportVisitorVO::getOrderId,
|
|
|
+ v -> v,
|
|
|
+ (v1, v2) -> v1, // orderId 相同时保留第一个
|
|
|
+ LinkedHashMap::new
|
|
|
+ ))
|
|
|
+ .values().stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ TouristExportVisitorVO::getOrderNo,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.reducing(
|
|
|
+ BigDecimal.ZERO,
|
|
|
+ v -> v.getPayAmount() != null ? v.getPayAmount() : BigDecimal.ZERO,
|
|
|
+ BigDecimal::add
|
|
|
+ )
|
|
|
+ ));
|
|
|
+
|
|
|
+
|
|
|
+ Map<String, BigDecimal> orderAmountMap = visitorList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ TouristExportVisitorVO::getOrderId,
|
|
|
+ v -> v,
|
|
|
+ (v1, v2) -> v1, // orderId 相同时保留第一个
|
|
|
+ LinkedHashMap::new
|
|
|
+ ))
|
|
|
+ .values().stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ TouristExportVisitorVO::getOrderNo,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.reducing(
|
|
|
+ BigDecimal.ZERO,
|
|
|
+ v -> v.getAmount() != null ? v.getAmount() : BigDecimal.ZERO,
|
|
|
+ BigDecimal::add
|
|
|
+ )
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 定金
|
|
|
+ Map<String, BigDecimal> orderDeposiMap = visitorList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ TouristExportVisitorVO::getOrderId,
|
|
|
+ v -> v,
|
|
|
+ (v1, v2) -> v1, // orderId 相同时保留第一个
|
|
|
+ LinkedHashMap::new
|
|
|
+ ))
|
|
|
+ .values().stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ TouristExportVisitorVO::getOrderNo,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.reducing(
|
|
|
+ BigDecimal.ZERO,
|
|
|
+ v -> v.getDeposi() != null ? v.getDeposi() : BigDecimal.ZERO,
|
|
|
+ BigDecimal::add
|
|
|
+ )
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 房间序号从1开始累加
|
|
|
+ int roomIndex = 1;
|
|
|
+ for (List<TouristExportVisitorVO> orderVisitors : orderGroupMap.values()) {
|
|
|
+ // 按房间索引分组(同一房间的游客合并显示房间详情)
|
|
|
+ // 使用 LinkedHashMap 保持房间在 visitorList 中的出现顺序
|
|
|
+ Map<String, List<TouristExportVisitorVO>> roomGroupMap = orderVisitors.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ v -> v.getRoomIndexId() != null ? v.getRoomIndexId() : "",
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.toList()
|
|
|
+ ));
|
|
|
+
|
|
|
+
|
|
|
+ for (List<TouristExportVisitorVO> roomVisitors : roomGroupMap.values()) {
|
|
|
+ // 准备房间入住类型描述(如:2个成人/1个儿童)
|
|
|
+ String roomDescription = prepareRoomDescription(roomVisitors);
|
|
|
+
|
|
|
+ // 为该房间的每个游客创建一行数据
|
|
|
+ for (TouristExportVisitorVO visitor : roomVisitors) {
|
|
|
+ Map<String, Object> item = new HashMap<>();
|
|
|
+
|
|
|
+ // 订单详情(8列):同一订单的游客合并显示这些列
|
|
|
+ item.put("sourceName", StringUtils.isEmpty(visitor.getSourceName()) ? "" : visitor.getSourceName()); // 代理商名称
|
|
|
+ item.put("orderNo", StringUtils.isEmpty(visitor.getOrderNo()) ? "" : visitor.getOrderNo()); // 订单号
|
|
|
+ item.put("groupNo", StringUtils.isEmpty(visitor.getGroupNo()) ? "" : visitor.getGroupNo()); // 团号
|
|
|
+ item.put("direction", StringUtils.isEmpty(visitor.getDirection()) ? "" : visitor.getDirection()); // 航向(宜昌-重庆/重庆-宜昌)
|
|
|
+
|
|
|
+ item.put("travelDate", formatTravelDate(baseInfo, direction));
|
|
|
+ item.put("amount", orderAmountMap.getOrDefault(visitor.getOrderNo(), BigDecimal.ZERO)); // 应收款
|
|
|
+
|
|
|
+
|
|
|
+ // 实收款计算逻辑:根据支付状态判断
|
|
|
+ //Integer payStatus = visitor.getPayStatus(); // 支付状态
|
|
|
+ //BigDecimal payAmount = visitor.getPayAmount() != null ? visitor.getPayAmount() : BigDecimal.ZERO; // 实际金额
|
|
|
+ BigDecimal deposi = visitor.getDeposi() != null ? visitor.getDeposi() : BigDecimal.ZERO; // 定金
|
|
|
+
|
|
|
+ item.put("payAmount", orderPayAmountMap.getOrDefault(visitor.getOrderNo(), BigDecimal.ZERO)); // 实收款
|
|
|
+
|
|
|
+ item.put("deposi", orderDeposiMap.getOrDefault(visitor.getOrderNo(), BigDecimal.ZERO)); // 定金
|
|
|
+
|
|
|
+ // 房间详情(3列):同一房间的游客合并显示这些列
|
|
|
+ item.put("floor", StringUtils.isEmpty(visitor.getFloor()) ? "" : visitor.getFloor()); // 楼层
|
|
|
+
|
|
|
+ item.put("roomIndex", roomIndex); // 序号(按订单内的房间顺序)
|
|
|
+ item.put("roomType", (StringUtils.isEmpty(visitor.getRoomType()) ? "" : visitor.getRoomType()) + (StringUtils.isEmpty(visitor.getFloor()) ? "" : (" (" + visitor.getFloor() + ")"))); // 房型(如:豪华标准间 (2F))
|
|
|
+ item.put("roomDescription", roomDescription); // 入住类型(如:2个成人/1个儿童)
|
|
|
+
|
|
|
+ // 游客详情(8列):每个游客独立显示
|
|
|
+ item.put("name", StringUtils.isEmpty(visitor.getName()) ? "" : visitor.getName()); // 游客姓名
|
|
|
+ item.put("gender", visitor.getGender() != null ? (visitor.getGender() == 1 ? "男" : "女") : ""); // 性别(1=男,其他=女)
|
|
|
+ item.put("credentialType", DictFrameworkUtils.getDictDataLabel(DictTypeConstants.VISITOR_CREDENTIAL_TYPE, visitor.getCredentialType())); // 证件类型(身份证/护照等)
|
|
|
+ item.put("credentialNo", StringUtils.isEmpty(visitor.getCredentialNo()) ? "" : visitor.getCredentialNo()); // 证件号
|
|
|
+ item.put("visitorType", StringUtils.isEmpty(visitor.getVisitorType()) ? "" : getPersonTypeAll(visitor.getVisitorType())); // 游客类型(成人/儿童/婴儿)
|
|
|
+ item.put("nationalityName", StringUtils.isEmpty(visitor.getNationalityName()) ? "" : visitor.getNationalityName()); // 国籍(如:中国、美国)
|
|
|
+ item.put("valueAddedService", StringUtils.isEmpty(visitor.getValueAddedService()) ? "" : formatPolicyName(visitor.getValueAddedService())); // 增值服务(如:接送站、保险等)
|
|
|
+ item.put("policyName", ""); // 优惠政策(如:早鸟优惠、团立减等)
|
|
|
+ item.put("remark", StringUtils.isEmpty(visitor.getRemark()) ? "" : visitor.getRemark()); // 备注信息
|
|
|
+
|
|
|
+
|
|
|
+ item.put("visitorHome", StringUtils.isEmpty(visitor.getVisitorType()) ? "" : DictFrameworkUtils.getDictDataLabel(DictTypeConstants.TOUR_TYPE, visitor.getVisitorType())); // 游客入住类型
|
|
|
+
|
|
|
+ item.put("birthday", StringUtils.isEmpty(visitor.getBirthday()) ? "" : visitor.getBirthday()); // 生日
|
|
|
+ item.put("mobile", StringUtils.isEmpty(visitor.getMobile()) ? "" : visitor.getMobile()); // 手机号
|
|
|
+ item.put("jz", StringUtils.isEmpty(visitor.getJz()) ? "" : visitor.getJz()); // 是否接站
|
|
|
+ item.put("orderStatus", StringUtils.isEmpty(visitor.getOrderStatus()) ? "" : TradeOrderStatusEnum.valueOf(Integer.valueOf(visitor.getOrderStatus())).getName()); // 订单状态
|
|
|
+ touristData.add(item);
|
|
|
+ }
|
|
|
+ roomIndex++;
|
|
|
}
|
|
|
- // 序号
|
|
|
- item.put("xh", i + 1);
|
|
|
- // 姓名
|
|
|
- item.put("name", StringUtils.isEmpty(visitor.getName()) ? "" : visitor.getName());
|
|
|
- // 国籍
|
|
|
- item.put("nationalityName", StringUtils.isEmpty(visitor.getNationalityName()) ? "" : visitor.getNationalityName());
|
|
|
- // 证件类型
|
|
|
- item.put("credentialType", DictFrameworkUtils.getDictDataLabel(DictTypeConstants.VISITOR_CREDENTIAL_TYPE, visitor.getCredentialType()));
|
|
|
- // 证件号
|
|
|
- item.put("credentialNo", StringUtils.isEmpty(visitor.getCredentialNo()) ? "" : visitor.getCredentialNo());
|
|
|
- // 出生年月
|
|
|
- item.put("birthday", StringUtils.isEmpty(visitor.getBirthday()) ? "" : visitor.getBirthday());
|
|
|
- // 备注
|
|
|
- item.put("remark", StringUtils.isEmpty(visitor.getRemark()) ? "" : visitor.getRemark());
|
|
|
-
|
|
|
- touristData.add(item);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 6. 填充数据
|
|
|
- excelWriter.fill(new FillWrapper("visitor", touristData),fillConfig, writeSheet);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 填充数据到Excel模板
|
|
|
+ // 先填充游客明细列表数据,再填充基础信息
|
|
|
+ excelWriter.fill(new FillWrapper("visitor", touristData), fillConfig, writeSheet);
|
|
|
excelWriter.fill(baseData, writeSheet);
|
|
|
excelWriter.finish();
|
|
|
|
|
|
return new File(tmpFile);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 格式化航期日期
|
|
|
+ *
|
|
|
+ * @param baseInfo 基础信息 Map
|
|
|
+ * @param direction 航向(1=宜昌 - 重庆,其他=重庆 - 宜昌)
|
|
|
+ * @return 格式化后的航期字符串(yyyy.M.d),上水时往前推一天
|
|
|
+ */
|
|
|
+ private String formatTravelDate(Map<String, Object> baseInfo, Integer direction) {
|
|
|
+ if (baseInfo == null || baseInfo.get("travelDate") == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime travelDateTime = (LocalDateTime) baseInfo.get("travelDate");
|
|
|
+ // 上水(宜昌 - 重庆)航期往前推一天
|
|
|
+ if (direction != null && direction == 1) {
|
|
|
+ travelDateTime = travelDateTime.plusDays(-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return travelDateTime.format(DateTimeFormatter.ofPattern("yyyy.M.d"));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 准备房间入住类型描述
|
|
|
+ *
|
|
|
+ * 功能说明:
|
|
|
+ * 统计房间内各类型游客的数量,生成入住类型描述字符串
|
|
|
+ *
|
|
|
+ * 示例:
|
|
|
+ * - 2个成人 → "2个成人"
|
|
|
+ * - 1个成人/1个儿童 → "1个成人/1个儿童"
|
|
|
+ * - 2个成人/1个儿童/1个婴儿 → "2个成人/1个儿童/1个婴儿"
|
|
|
+ *
|
|
|
+ * @param roomVisitors 同一房间的游客列表
|
|
|
+ * @return 入住类型描述字符串
|
|
|
+ */
|
|
|
+ private String prepareRoomDescription(List<TouristExportVisitorVO> roomVisitors) {
|
|
|
+ if (roomVisitors == null || roomVisitors.isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按游客类型统计数量
|
|
|
+ Map<String, Long> typeCountMap = roomVisitors.stream()
|
|
|
+ .filter(v -> StringUtils.isNotEmpty(v.getVisitorType()))
|
|
|
+ .collect(Collectors.groupingBy(TouristExportVisitorVO::getVisitorType, Collectors.counting()));
|
|
|
+
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (Map.Entry<String, Long> entry : typeCountMap.entrySet()) {
|
|
|
+ if (sb.length() > 0) {
|
|
|
+ sb.append("/");
|
|
|
+ }
|
|
|
+ sb.append(entry.getValue()).append("个").append(getPersonTypeDes1(entry.getKey()));
|
|
|
+ }
|
|
|
+
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化优惠政策名称
|
|
|
+ *
|
|
|
+ * 功能说明:
|
|
|
+ * 将优惠政策字符串按 "、" 分割后,添加序号并用逗号连接
|
|
|
+ *
|
|
|
+ * 示例:
|
|
|
+ * - 输入:null 或 "" → 输出:""
|
|
|
+ * - 输入:"升舱礼遇2升3" → 输出:"①升舱礼遇2升3"
|
|
|
+ * - 输入:"升舱礼遇2升3、3升4、5升6" → 输出:"①升舱礼遇2升3"
|
|
|
+ *
|
|
|
+ * @param policyName 原始优惠政策字符串
|
|
|
+ * @return 格式化后的优惠政策字符串
|
|
|
+ */
|
|
|
+ private String formatPolicyName(String policyName) {
|
|
|
+ if (StringUtils.isEmpty(policyName)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按 "," 分割
|
|
|
+ String[] policies = policyName.split(",");
|
|
|
+ if (policies.length == 0) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 序号字符:①②③④⑤⑥⑦⑧⑨⑩...
|
|
|
+ String[] numberSymbols = {"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩", "⑪", "⑫", "⑬", "⑭", "⑮"};
|
|
|
+
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < policies.length; i++) {
|
|
|
+ if (sb.length() > 0) {
|
|
|
+ sb.append("\n");
|
|
|
+ }
|
|
|
+ // 如果序号超过预定义范围,使用普通数字编号
|
|
|
+ String prefix = i < numberSymbols.length ? numberSymbols[i] : (i + 1) + ".";
|
|
|
+ sb.append(prefix).append(policies[i].trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
}
|