zhuyifan

Merge branch 'feature/2025-10-23' into 'master'

Feature/2025 10 23



See merge request !11
......@@ -57,6 +57,8 @@ public class LoginContextHolder {
private Integer enterpriseId;
private Integer clientId;
private String name;
@Builder.Default
......
......@@ -48,8 +48,8 @@ public class KdsController {
@ApiOperation(value = "kds获取餐别详情")
@PostMapping("/kds/mealDetails")
@ResponseStatus(HttpStatus.OK)
public List<KdsMealDetailDto> getMealDetail() {
return kdsService.getMealDetail();
public List<KdsMealDetailDto> getMealDetail(@Valid @RequestBody KdsDTO.KdsMealDetailQueryDto queryDto) {
return kdsService.getMealDetail(queryDto);
}
}
......
......@@ -8,6 +8,7 @@ import lombok.Getter;
public enum SettlementDimensionEnum {
BY_SKU(1, "菜品"),
BY_MEAL(2, "餐别"),
BY_ORDER(3, "订单")
;
/**
......
......@@ -93,7 +93,7 @@ public class LoginInterceptor implements HandlerInterceptor {
ResponseUtil.write(response, ResponseResult.failed(ErrorCodeEnum.FORBIDDEN));
return false;
}
builder.id(loginId).enterpriseId(enterpriseId).name(customer.getResponse().getName()).openId(openId).loginSource(LoginSourceEnum.CUSTOMER);
builder.id(loginId).enterpriseId(enterpriseId).clientId(customer.getResponse().getClientId()).name(customer.getResponse().getName()).openId(openId).loginSource(LoginSourceEnum.CUSTOMER);
return true;
} else if (loginSource.equals(LoginSourceEnum.OPERATOR.getValue())){
final var loginId = StpUtil.getLoginIdAsInt();
......@@ -103,7 +103,7 @@ public class LoginInterceptor implements HandlerInterceptor {
ResponseUtil.write(response, ResponseResult.failed(ErrorCodeEnum.FORBIDDEN));
return false;
}
builder.id(loginId).enterpriseId(enterpriseId).name(opeartor.getName()).loginSource(LoginSourceEnum.OPERATOR);
builder.id(loginId).enterpriseId(enterpriseId).clientId(opeartor.getClientId()).name(opeartor.getName()).loginSource(LoginSourceEnum.OPERATOR);
return true;
} else if (loginSource.equals(LoginSourceEnum.KDS.getValue())) {
final var loginId = StpUtil.getLoginIdAsInt();
......
......@@ -30,6 +30,7 @@ public class KdsDTO {
private Integer price;
private String roomNo;
private String remark;
private String avoidRemark;
private CustomerNotice customerNotice;
private String adjustSkuRemark;
private String mealTime;
......@@ -38,6 +39,8 @@ public class KdsDTO {
private ClientDto client;
private ClientDto stall;
private CustomerDetailDto customer;
private Integer menuId;
private String menuName;
}
@Data
......@@ -77,6 +80,7 @@ public class KdsDTO {
private String tableCode;
private Integer totalCount;
private String remark;
private String avoidRemark;
private String mealTime;
private CustomerNotice customerNotice;
private Integer totalPrice;
......@@ -87,6 +91,8 @@ public class KdsDTO {
private ClientDto client;
private CustomerDetailDto customer;
private List<KdsOrderLocationDetailDto> orderDetailList;
private Integer menuId;
private String menuName;
}
@Data
......@@ -97,6 +103,7 @@ public class KdsDTO {
public static class KdsOrderSkuQueryDto {
private ServeStatusEnum serveStatus;
private Integer mealId;
private String mealTime;
}
@Data
......@@ -107,12 +114,22 @@ public class KdsDTO {
public static class KdsOrderLocationQueryDto {
private OrderStatusEnum orderStatus;
private Integer mealId;
private String mealTime;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "KdsMealDetailQueryDto")
public static class KdsMealDetailQueryDto {
private String mealTime;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "KdsMealDetailDto")
public static class KdsMealDetailDto {
private Integer mealId;
......
......@@ -64,6 +64,16 @@ public class OrderDbDTO {
}
@Data
@ApiModel(description = "查询订单Dto")
public static class QueryOrderDto {
private Integer enterpriseId;
private Integer clientId;
private OrderStatusEnum status;
private String keyword;
private List<Integer> stallIds;
}
@Data
@ApiModel(description = "查询已撤销订单Dto")
public static class QueryRefundOrderByPaginationDto {
private Integer enterpriseId;
......@@ -155,6 +165,7 @@ public class OrderDbDTO {
private String tableCode;
private Integer updatedBy;
private OrderSourceEnum updatedSource;
private String avoidRemark;
}
......@@ -440,6 +451,9 @@ public class OrderDbDTO {
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "忌口备注")
private String avoidRemark;
@ApiModelProperty(value = "下单客户当时的忌口、医嘱")
private CustomerNotice customerNoticeJson;
......
......@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModel;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
......@@ -75,7 +76,55 @@ public class SettlementDTO {
private Map<String, Map<String, Map<String, List<SettlementMenuMealDto>>>> classifyOrders;
}
}
@Data
@Builder
@ApiModel(description = "SettlementDataByOrderDto")
public static class SettlementDataByOrderDto {
private List<SettlementOrderDetail> classifyOrders;
}
@Data
@Builder
@ApiModel(description = "SettlementOrderDetail")
public static class SettlementOrderDetail {
private String mealTime;
private String code;
private String mealName;
private String roomNo;
private String customerName;
private String HISCustomerNo;
private List<String> avoids;
private List<String> doctors;
private String menuName;
private String skuName;
private Integer count;
private BigDecimal price;
private BigDecimal totalPrice;
private String remark;
private String avoidRemark;
private String onlinePay;
}
@Data
@Builder
@ApiModel(description = "SeparateMealDetail")
public static class SeparateMealDetail {
private Integer orderId;
private String mealName;
private String roomNo;
private String customerName;
private String menuName;
private String skuName;
private Integer count;
private String avoids;
private String remark;
}
\ No newline at end of file
@Data
@Builder
@ApiModel(description = "SeparateMealDataDto")
public static class SeparateMealDataDto {
private List<SeparateMealDetail> mealDetails;
}
}
......
package com.infoloop.tianting.model.enums.desc;
import com.infoloop.tianting.clientcustomerorderservice.OnlinePayEnum;
public enum OnlinePayDescEnum implements BaseProtoDescEnum<OnlinePayEnum> {
UNKNOWN(OnlinePayEnum.UNKNOWN, ""),
ONLINE(OnlinePayEnum.ONLINE, "线上支付"),
OFFLINE(OnlinePayEnum.OFFLINE, "线下支付"),
LEAVE_HOSPITAL(OnlinePayEnum.LEAVE_HOSPITAL, "出院支付"),
EMPLOYEE_CARD(OnlinePayEnum.EMPLOYEE_CARD, "员工卡支付"),
PAPER_VERIFICATION(OnlinePayEnum.PAPER_VERIFICATION, "纸质核销"),
CASH(OnlinePayEnum.CASH, "现金支付"),
MEAL_CARD(OnlinePayEnum.MEAL_CARD, "餐卡结算"),
BAND_CARD(OnlinePayEnum.BAND_CARD, "POS-银行卡"),
ALIPAY(OnlinePayEnum.ALIPAY, "POS-支付宝"),
WECHATPAY(OnlinePayEnum.WECHATPAY, "POS-微信");
private final OnlinePayEnum protoEnum;
private final String desc;
OnlinePayDescEnum(OnlinePayEnum protoEnum, String desc) {
this.protoEnum = protoEnum;
this.desc = desc;
}
@Override
public OnlinePayEnum getProtoEnum() {
return protoEnum;
}
@Override
public String getDesc() {
return desc;
}
}
package com.infoloop.tianting.service;
import com.infoloop.tianting.model.dto.KdsDTO;
import com.infoloop.tianting.model.dto.KdsDTO.KdsMealDetailDto;
import com.infoloop.tianting.model.dto.KdsDTO.KdsOrderLocationDto;
import com.infoloop.tianting.model.dto.KdsDTO.KdsOrderLocationQueryDto;
......@@ -14,5 +15,5 @@ public interface KdsService {
List<KdsOrderLocationDto> getOrderDetailByLocation(KdsOrderLocationQueryDto queryDto);
List<KdsMealDetailDto> getMealDetail();
List<KdsMealDetailDto> getMealDetail(KdsDTO.KdsMealDetailQueryDto queryDto);
}
......
package com.infoloop.tianting.service;
import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderDto;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementRequestDto;
import java.io.ByteArrayOutputStream;
public interface SettlementService {
ByteArrayOutputStream generateOrderSettlementExcel(SettlementRequestDto settlementRequest);
ByteArrayOutputStream exportSeparateMealsExcel(QueryOrderDto queryOrderDto);
}
......
......@@ -31,6 +31,7 @@ import com.infoloop.tianting.clientcustomerorderservice.OrderStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.OrderTypeEnum;
import com.infoloop.tianting.clientcustomerorderservice.PayStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.QueryClientCustomerOrdersByConditionRpcRequest;
import com.infoloop.tianting.clientcustomerorderservice.QueryClientCustomerOrdersByConditionRpcResponse;
import com.infoloop.tianting.clientcustomerorderservice.QueryClientCustomerOrdersByPaginationRpcRequest;
import com.infoloop.tianting.clientcustomerorderservice.QueryClientCustomerOrdersByPaginationRpcResponse;
import com.infoloop.tianting.clientcustomerorderservice.QuerySkuSellQuantitiesByStallAndSkuIdsRpcRequest;
......@@ -57,6 +58,7 @@ import com.infoloop.tianting.model.bo.AddSkusSellQuantityBO;
import com.infoloop.tianting.model.bo.SkuSellQuantityBO;
import com.infoloop.tianting.model.dto.OperationDTO.BatchOperationDto;
import com.infoloop.tianting.model.dto.OperationDTO.SingleOperationDto;
import com.infoloop.tianting.model.dto.OrderDbDTO;
import com.infoloop.tianting.model.dto.OrderDbDTO.CreateOrderDetailDto;
import com.infoloop.tianting.model.dto.OrderDbDTO.CreateOrderDto;
import com.infoloop.tianting.model.dto.OrderDbDTO.CreateOrderResponse;
......@@ -180,6 +182,8 @@ public class OrderServiceRpcClient {
.setTableCode(tableCode)
.setShouldUpdateRoomNo(updateOrderDto.isShouldUpdateRoomNo())
.setRoomNo(roomNo)
.setShouldUpdateAvoidRemark(updateOrderDto.getAvoidRemark() != null)
.setAvoidRemark(updateOrderDto.getAvoidRemark() == null ? "" : updateOrderDto.getAvoidRemark())
.build();
final var request = UpdateClientCustomerOrderRpcRequest.newBuilder()
.setEnterpriseId(updateOrderDto.getEnterpriseId())
......@@ -188,7 +192,7 @@ public class OrderServiceRpcClient {
.setModification(modification)
.build();
final UpdateClientCustomerOrderRpcResponse updateClientCustomerOrderRpcResponse = orderServiceRpcBlockingStub.updateClientCustomerOrder(request);
if (updateClientCustomerOrderRpcResponse.getIsUpdated() && updateOrderDto.getStatus() == OrderStatusEnum.ORDER_CANCELED) {
if (updateClientCustomerOrderRpcResponse.getIsUpdated() && updateOrderDto.getStatus() != null && updateOrderDto.getStatus() == OrderStatusEnum.ORDER_CANCELED) {
final var orderById = getOrderById(updateOrderDto.getId());
if (orderById != null && orderById.getCloseTimeType() == CloseTimeType.IMMEDIATELY) {
final var orderDetails = getOrderDetailsByOrderId(orderById.getId());
......@@ -484,6 +488,17 @@ public class OrderServiceRpcClient {
return orderServiceRpcBlockingStub.queryClientCustomerOrdersByPagination(request);
}
public QueryClientCustomerOrdersByConditionRpcResponse queryClientCustomerOrdersByCondition(OrderDbDTO.QueryOrderDto queryOrderDto) {
final var request = QueryClientCustomerOrdersByConditionRpcRequest.newBuilder()
.setShouldFilterStatus(true)
.setStatus(queryOrderDto.getStatus())
.setEnterpriseId(queryOrderDto.getEnterpriseId())
.setShouldFilterClientIds(true).addClientIds(queryOrderDto.getClientId())
.setShouldFilterStallIds(true).addAllStallIds(queryOrderDto.getStallIds())
.build();
return orderServiceRpcBlockingStub.queryClientCustomerOrdersByCondition(request);
}
public QueryClientCustomerOrdersByPaginationRpcResponse queryRefundClientCustomerOrdersByPagination(
QueryRefundOrderByPaginationDto queryOrderDto) {
final var request = QueryClientCustomerOrdersByPaginationRpcRequest.newBuilder()
......
......@@ -6,7 +6,9 @@ import com.infoloop.tianting.clientcustomerorderservice.ServeStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.SingleClientCustomerOrderDetailRpcResponse;
import com.infoloop.tianting.clientcustomerorderservice.SingleClientCustomerOrderRpcResponse;
import com.infoloop.tianting.context.LoginContextHolder;
import com.infoloop.tianting.menuservice.SingleMenuRpcResponse;
import com.infoloop.tianting.model.dto.ClientInventoryDbDTO.ClientDto;
import com.infoloop.tianting.model.dto.KdsDTO;
import com.infoloop.tianting.model.dto.KdsDTO.KdsMealDetailDto;
import com.infoloop.tianting.model.dto.KdsDTO.KdsOrderLocationDetailDto;
import com.infoloop.tianting.model.dto.KdsDTO.KdsOrderLocationDto;
......@@ -22,6 +24,7 @@ import com.infoloop.tianting.service.client.ClientCustomerServiceRpcClient;
import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
import com.infoloop.tianting.service.client.EnterpriseResourceServiceRpcClient;
import com.infoloop.tianting.service.client.MealServiceRpcClient;
import com.infoloop.tianting.service.client.MenuServiceRpcClient;
import com.infoloop.tianting.service.client.OrderServiceRpcClient;
import com.infoloop.tianting.service.client.SkuServiceRpcClient;
import com.infoloop.tianting.service.client.WOperatorServiceRpcClient;
......@@ -33,8 +36,10 @@ import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
......@@ -49,14 +54,14 @@ public class KdsServiceImpl implements KdsService {
private final SkuServiceRpcClient skuServiceRpcClient;
private final ClientInventoryServiceRpcClient clientInventoryServiceRpcClient;
private final ClientCustomerServiceRpcClient clientCustomerServiceRpcClient;
private final MenuServiceRpcClient menuServiceRpcClient;
@Override
public List<KdsOrderSkuDto> getOrderDetailBySku(KdsOrderSkuQueryDto queryDto) {
// 获取当日所有的订单 和 订单明细
LocalDate today = LocalDate.now();
LocalDate today = LocalDate.parse(queryDto.getMealTime(), DateUtil.YYYY_MM_DD);
LocalDateTime todayZero = today.atStartOfDay();
LocalDate tomorrow = today.plusDays(1);
LocalDateTime tomorrowZero = tomorrow.atStartOfDay();
LocalDateTime todayEnd = today.atTime(LocalTime.MAX);
final var operatorLoginInfo = LoginContextHolder.getLoginInfo();
boolean shouldFilterStallIds = false;
List<Integer> stallIds = new ArrayList<>();
......@@ -73,7 +78,7 @@ public class KdsServiceImpl implements KdsService {
.shouldFilterMealTimeStart(true)
.mealTimeStart(todayZero)
.shouldFilterMealTimeEnd(true)
.mealTimeEnd(tomorrowZero)
.mealTimeEnd(todayEnd)
.shouldFilterPayStatus(true)
.payStatus(PayStatusEnum.SUCCEED)
.build();
......@@ -118,6 +123,9 @@ public class KdsServiceImpl implements KdsService {
.collect(Collectors.toList());
final var skus = skuServiceRpcClient.getDishSkusByIds(skuIds).getResponseList();
final var menuIds = orderList.stream().map(SingleClientCustomerOrderRpcResponse::getMenuId).distinct().toList();
final var menuMap = menuServiceRpcClient.getMenusByIds(operator.getEnterpriseId(), menuIds).getResponsesList().stream().collect(Collectors.toMap(SingleMenuRpcResponse::getId, Function.identity()));
List<KdsOrderSkuDto> kdsOrderSkuList = new ArrayList<>();
for (Integer menuDetailId : menuDetailIds) {
// 找出该menuDetailId 的所有orderDetail
......@@ -168,8 +176,11 @@ public class KdsServiceImpl implements KdsService {
.count(detail.getCount())
.orderDetailId(detail.getId())
.remark(order.get().getRemark())
.avoidRemark(order.get().getAvoidRemark())
.orderId(order.get().getId())
.roomNo(order.get().getSyncRoomNo().isEmpty() ? order.get().getRoomNo() : order.get().getSyncRoomNo())
.menuId(order.get().getMenuId())
.menuName(menuMap.get(order.get().getMenuId()) == null ? "" : menuMap.get(order.get().getMenuId()).getName())
.tableCode(order.get().getTableCode())
.customerNotice(CustomerNotice.builder()
.doctors(order.get().getCustomerNoticeJson().getDoctorsList())
......@@ -208,9 +219,9 @@ public class KdsServiceImpl implements KdsService {
}
@Override
public List<KdsMealDetailDto> getMealDetail() {
public List<KdsMealDetailDto> getMealDetail(KdsDTO.KdsMealDetailQueryDto queryDto) {
// 获取当日用餐的所有的订单 和 订单明细
LocalDate today = LocalDate.now();
LocalDate today = LocalDate.parse(queryDto.getMealTime(), DateUtil.YYYY_MM_DD);
LocalDateTime todayZero = today.atStartOfDay();
LocalDate tomorrow = today.plusDays(1);
LocalDateTime tomorrowZero = tomorrow.atStartOfDay().minusMinutes(1);
......@@ -235,6 +246,8 @@ public class KdsServiceImpl implements KdsService {
.mealTimeStart(todayZero)
.shouldFilterMealTimeEnd(true)
.mealTimeEnd(tomorrowZero)
.shouldFilterPayStatus(true)
.payStatus(PayStatusEnum.SUCCEED)
.build();
final var orderList = orderServiceRpcClient.getOrdersByCondition(queryOrderDto);
......@@ -281,10 +294,9 @@ public class KdsServiceImpl implements KdsService {
@Override
public List<KdsOrderLocationDto> getOrderDetailByLocation(KdsOrderLocationQueryDto queryDto) {
// 获取当日用餐的所有的订单 和 订单明细
LocalDate today = LocalDate.now();
LocalDate today = LocalDate.parse(queryDto.getMealTime(), DateUtil.YYYY_MM_DD);
LocalDateTime todayZero = today.atStartOfDay();
LocalDate tomorrow = today.plusDays(1);
LocalDateTime tomorrowZero = tomorrow.atStartOfDay().minusMinutes(1);
LocalDateTime todayEnd = today.atTime(LocalTime.MAX);
final var operatorLoginInfo = LoginContextHolder.getLoginInfo();
......@@ -305,7 +317,7 @@ public class KdsServiceImpl implements KdsService {
.shouldFilterMealTimeStart(true)
.mealTimeStart(todayZero)
.shouldFilterMealTimeEnd(true)
.mealTimeEnd(tomorrowZero)
.mealTimeEnd(todayEnd)
.shouldFilterStatus(true)
.status(queryDto.getOrderStatus())
.shouldFilterPayStatus(true)
......@@ -339,6 +351,12 @@ public class KdsServiceImpl implements KdsService {
final var filteredOrderDetailList = orderDetailList.stream()
.filter(od -> od.getMealId() == queryDto.getMealId() && od.getServeStatus() != ServeStatusEnum.SERVE_REJECT)
.collect(Collectors.toList());
final var menuIds = orderList.stream().map(SingleClientCustomerOrderRpcResponse::getMenuId).distinct().toList();
final var menuMap = menuServiceRpcClient.getMenusByIds(operator.getEnterpriseId(), menuIds).getResponsesList().stream().collect(Collectors.toMap(SingleMenuRpcResponse::getId, Function.identity()));
List<KdsOrderLocationDto> kdsOrderLocationList = new ArrayList<>();
for (SingleClientCustomerOrderRpcResponse order : orderList) {
......@@ -395,9 +413,12 @@ public class KdsServiceImpl implements KdsService {
final var kdsOrder = KdsOrderLocationDto.builder()
.totalCount(order.getTotalNum())
.remark(order.getRemark())
.avoidRemark(order.getAvoidRemark())
.orderId(order.getId())
.roomNo(order.getSyncRoomNo().isEmpty() ? order.getRoomNo() : order.getSyncRoomNo())
.tableCode(order.getTableCode())
.menuId(order.getMenuId())
.menuName(menuMap.get(order.getMenuId()) == null ? "" : menuMap.get(order.getMenuId()).getName())
.customerNotice(CustomerNotice.builder()
.doctors(order.getCustomerNoticeJson().getDoctorsList())
.avoids(order.getCustomerNoticeJson().getAvoidsList())
......
......@@ -487,6 +487,7 @@ public class OrderServiceImpl implements OrderService {
.mealTime(DateUtil.formatDate(r.getMealTime()))
.orderType(r.getOrderTypeValue())
.remark(r.getRemark())
.avoidRemark(r.getAvoidRemark())
.isDeleted(r.getIsDeleted())
.isRead(r.getIsRead())
.creationSource(r.getCreationSource().getNumber())
......
package com.infoloop.tianting.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.infoloop.tianting.SingleDishSkuByIdsRpcResponse;
import com.infoloop.tianting.clientcustomerorderservice.OnlinePayEnum;
import com.infoloop.tianting.clientcustomerorderservice.OrderStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.PayStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.ServeStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.SingleClientCustomerOrderDetailRpcResponse;
import com.infoloop.tianting.clientcustomerorderservice.SingleClientCustomerOrderRpcResponse;
import com.infoloop.tianting.clientcustomerservice.SingleClientCustomerRpcResponse;
import com.infoloop.tianting.clientinventoryservice.SingleClientRpcResponse;
import com.infoloop.tianting.context.LoginContextHolder;
import com.infoloop.tianting.enums.SettlementDimensionEnum;
......@@ -12,35 +18,55 @@ import com.infoloop.tianting.exception.ErrorCodeEnum;
import com.infoloop.tianting.menuservice.SingleMenuDetailRpcResponse;
import com.infoloop.tianting.menuservice.SingleMenuRpcResponse;
import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderByConditionDto;
import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderDto;
import com.infoloop.tianting.model.dto.SettlementDTO;
import com.infoloop.tianting.model.dto.SettlementDTO.SeparateMealDataDto;
import com.infoloop.tianting.model.dto.SettlementDTO.SeparateMealDetail;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementDataByMealDto;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementDataDto;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementMenuMealDto;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementMenuSkuDto;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementRequestDto;
import com.infoloop.tianting.model.enums.desc.BaseProtoDescEnum;
import com.infoloop.tianting.model.enums.desc.OnlinePayDescEnum;
import com.infoloop.tianting.service.SettlementService;
import com.infoloop.tianting.service.client.ClientCustomerServiceRpcClient;
import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
import com.infoloop.tianting.service.client.ClientResourceTagServiceRpcClient;
import com.infoloop.tianting.service.client.MealServiceRpcClient;
import com.infoloop.tianting.service.client.MenuServiceRpcClient;
import com.infoloop.tianting.service.client.OrderServiceRpcClient;
import com.infoloop.tianting.service.client.SkuServiceRpcClient;
import com.infoloop.tianting.setmealscheduleservice.SingleMealRpcResponse;
import com.infoloop.tianting.utils.DateUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
......@@ -57,6 +83,8 @@ public class SettlementServiceImpl implements SettlementService {
private final SkuServiceRpcClient skuServiceRpcClient;
private final ClientInventoryServiceRpcClient clientInventoryServiceRpcClient;
private final MealServiceRpcClient mealServiceRpcClient;
private final ClientCustomerServiceRpcClient clientCustomerServiceRpcClient;
private final ClientResourceTagServiceRpcClient clientResourceTagServiceRpcClient;
public static ByteArrayOutputStream writeToExcelBySku(SettlementDataDto settlementDataDto)
throws IOException {
......@@ -272,9 +300,567 @@ public class SettlementServiceImpl implements SettlementService {
public ByteArrayOutputStream generateOrderSettlementExcel(SettlementRequestDto settlementRequest) {
if (settlementRequest.getDimension().equals(SettlementDimensionEnum.BY_SKU)) {
return generateOrderSettlementExcelBySku(settlementRequest);
} else {
} else if (settlementRequest.getDimension().equals(SettlementDimensionEnum.BY_MEAL)) {
return generateOrderSettlementExcelByMeal(settlementRequest);
} else if (settlementRequest.getDimension().equals(SettlementDimensionEnum.BY_ORDER)) {
return generateOrderSettlementExcelByOrder(settlementRequest);
}
return null;
}
private ByteArrayOutputStream generateOrderSettlementExcelByOrder(SettlementRequestDto settlementRequest) {
// 参数校验
if (settlementRequest == null || settlementRequest.getTimeStart() == null || settlementRequest.getTimeEnd() == null) {
throw BusinessException.build(ErrorCodeEnum.GENERATE_EXCEL_ERROR);
}
final var loginInfo = LoginContextHolder.getLoginInfo();
final var userResources = clientResourceTagServiceRpcClient.getUserResources(loginInfo.getEnterpriseId(), loginInfo.getClientId(), loginInfo.getId());
// 计算日期范围
final var startDate = settlementRequest.getTimeStart().plusHours(8).toLocalDate().atStartOfDay();
final var endDate = settlementRequest.getTimeEnd()
.plusHours(8)
.toLocalDate()
.atTime(23, 59, 59, 999_000_000);
// 获取企业ID
final var operatorLoginInfo = LoginContextHolder.getLoginInfo();
final int enterpriseId = LoginContextHolder.hasLogin() ? operatorLoginInfo.getEnterpriseId() : settlementRequest.getEnterpriseId();
final var stallIds = new ArrayList<Integer>();
if (CollectionUtil.isEmpty(settlementRequest.getStallIds())) {
stallIds.addAll(userResources.getStallIdsList());
} else {
stallIds.addAll(settlementRequest.getStallIds());
}
// 构建查询条件
final var queryOrderDto = QueryOrderByConditionDto.builder()
.enterpriseId(enterpriseId)
.shouldFilterMealTimeStart(true)
.mealTimeStart(startDate)
.shouldFilterMealTimeEnd(true)
.mealTimeEnd(endDate)
.shouldFilterStallIds(true)
.stallIds(stallIds)
.build();
// 查询订单列表
final var orderList = orderServiceRpcClient.getOrdersByCondition(queryOrderDto);
if (orderList == null || orderList.isEmpty()) {
log.warn("未查询到订单数据,enterpriseId: {}, startDate: {}, endDate: {}", enterpriseId, startDate, endDate);
return createEmptyExcelByOrder();
}
// 筛选有效订单ID
List<Integer> orderIds = orderList.stream()
.filter(Objects::nonNull)
.filter(o -> {
// 在线支付订单需要支付成功
if (o.getOnlinePay() == OnlinePayEnum.ONLINE) {
return o.getPayStatus() == PayStatusEnum.SUCCEED;
} else {
// 非在线支付订单需要未取消
return o.getStatus() != OrderStatusEnum.ORDER_CANCELED;
}
})
.map(SingleClientCustomerOrderRpcResponse::getId)
.distinct()
.collect(Collectors.toList());
if (orderIds.isEmpty()) {
log.warn("筛选后无有效订单,enterpriseId: {}", enterpriseId);
return createEmptyExcelByOrder();
}
// 构建订单映射
final var orderMap = orderList.stream()
.filter(o -> o.getId() != 0)
.collect(Collectors.toMap(SingleClientCustomerOrderRpcResponse::getId, o -> o, (o1, o2) -> o1));
// 查询订单详情
final var orderDetailList = orderServiceRpcClient.getOrderDetailsByOrderIds(orderIds);
if (orderDetailList == null || orderDetailList.isEmpty()) {
log.warn("未查询到订单详情数据,orderIds: {}", orderIds);
return createEmptyExcelByOrder();
}
// 订单详情按订单ID分组
final var orderDetailGroupByOrderId = orderDetailList.stream()
.filter(Objects::nonNull)
.filter(d -> !d.getServeStatus().equals(ServeStatusEnum.SERVE_REJECT))
.collect(Collectors.groupingBy(SingleClientCustomerOrderDetailRpcResponse::getOrderId));
// 获取SKU数据
List<Integer> skuIds = orderDetailList.stream()
.filter(Objects::nonNull)
.filter(d -> !d.getServeStatus().equals(ServeStatusEnum.SERVE_REJECT))
.map(SingleClientCustomerOrderDetailRpcResponse::getSkuId)
.distinct()
.collect(Collectors.toList());
final var skuResponse = skuServiceRpcClient.getDishSkusByIds(skuIds);
final List<SingleDishSkuByIdsRpcResponse> skus = skuResponse != null ? skuResponse.getResponseList() : new ArrayList<>();
final var skuMap = skus.stream()
.filter(Objects::nonNull)
.filter(s -> s.getId() != 0)
.collect(Collectors.toMap(SingleDishSkuByIdsRpcResponse::getId, s -> s, (s1, s2) -> s1));
// 获取餐别数据
final var mealResponse = mealServiceRpcClient.getAllMeals(enterpriseId);
final List<SingleMealRpcResponse> meals = mealResponse != null ? mealResponse.getResponsesList() : new ArrayList<>();
final var mealMap = meals.stream()
.filter(Objects::nonNull)
.filter(m -> m.getId() != 0)
.collect(Collectors.toMap(SingleMealRpcResponse::getId, m -> m, (m1, m2) -> m1));
// 获取菜单数据
List<Integer> menuIds = orderList.stream()
.filter(Objects::nonNull)
.map(SingleClientCustomerOrderRpcResponse::getMenuId)
.filter(id -> id != 0)
.distinct()
.collect(Collectors.toList());
final var menuResponse = menuServiceRpcClient.getMenusByIds(enterpriseId, menuIds);
final List<SingleMenuRpcResponse> menuList = menuResponse != null ? menuResponse.getResponsesList() : new ArrayList<>();
final var menuMap = menuList.stream()
.filter(Objects::nonNull)
.filter(m -> m.getId() != 0)
.collect(Collectors.toMap(SingleMenuRpcResponse::getId, m -> m, (m1, m2) -> m1));
// 获取菜单详情数据
final var menuDetailResponse = menuServiceRpcClient.queryMenuDetailsByMenuIds(enterpriseId, menuIds);
final List<SingleMenuDetailRpcResponse> menuDetailList = menuDetailResponse != null ? menuDetailResponse.getResponsesList() : new ArrayList<>();
final var menuDetailMap = menuDetailList.stream()
.filter(Objects::nonNull)
.filter(m -> m.getId() != 0)
.collect(Collectors.toMap(SingleMenuDetailRpcResponse::getId, m -> m, (m1, m2) -> m1));
// 获取客户数据
List<Integer> customerIds = orderList.stream()
.filter(Objects::nonNull)
.map(SingleClientCustomerOrderRpcResponse::getCustomerId)
.filter(id -> id != 0)
.distinct()
.collect(Collectors.toList());
final var customerResponse = clientCustomerServiceRpcClient.getClientCustomersByIds(enterpriseId, customerIds);
final List<SingleClientCustomerRpcResponse> customers = customerResponse != null ? customerResponse.getResponsesList() : new ArrayList<>();
final var customerMap = customers.stream()
.filter(Objects::nonNull)
.filter(c -> c.getId() != 0)
.collect(Collectors.toMap(SingleClientCustomerRpcResponse::getId, c -> c, (c1, c2) -> c1));
// 构建结算订单明细
final var classifyOrders = new ArrayList<SettlementDTO.SettlementOrderDetail>();
for (Integer orderId : orderIds) {
final var orderDetails = orderDetailGroupByOrderId.getOrDefault(orderId, Collections.emptyList());
final var order = orderMap.get(orderId);
// 订单为空则跳过
if (order == null) {
log.warn("订单不存在,orderId: {}", orderId);
continue;
}
// 获取菜单信息
final var menu = menuMap.get(order.getMenuId());
final String menuName = menu != null ? menu.getName() : "";
// 获取客户信息
final var customer = customerMap.get(order.getCustomerId());
final String customerName = customer != null ? customer.getName() : "";
final String hisCustomerNo = customer != null ? customer.getHISCustomerNo() : "";
// 处理每个订单详情
for (SingleClientCustomerOrderDetailRpcResponse orderDetail : orderDetails) {
if (orderDetail == null) {
continue;
}
// 安全获取餐别名称
String mealName = getMealNameSafely(orderDetail, menuDetailMap, mealMap);
// 安全获取SKU名称
String skuName = getSkuNameSafely(orderDetail, skuMap);
// 安全获取房间号
String roomNo = getRoomNoSafely(order);
// 安全获取忌口和医嘱
List<String> avoids = getAvoidsSafely(order);
List<String> doctors = getDoctorsSafely(order);
// 安全获取在线支付描述
String onlinePayDesc = getOnlinePayDescSafely(order);
// 构建订单详情
order.getOrderCode();
order.getRemark();
order.getAvoidRemark();
SettlementDTO.SettlementOrderDetail settlementOrderDetail = SettlementDTO.SettlementOrderDetail.builder()
.mealTime(order.getMealTime() != 0L ? LocalDateTimeUtil.of(order.getMealTime()).format(DateUtil.YYYY_MM_DD_LEFT_SYMBOL) : "")
.code(order.getOrderCode())
.mealName(mealName)
.roomNo(roomNo)
.customerName(customerName)
.HISCustomerNo(hisCustomerNo)
.avoids(avoids)
.doctors(doctors)
.menuName(menuName)
.skuName(skuName)
.count(orderDetail.getCount() != 0 ? orderDetail.getCount() : 0)
.price(calculatePrice(orderDetail.getPrice()))
.totalPrice(calculateTotalPrice(orderDetail.getPrice(), orderDetail.getCount()))
.remark(order.getRemark())
.avoidRemark(order.getAvoidRemark())
.onlinePay(onlinePayDesc)
.build();
classifyOrders.add(settlementOrderDetail);
}
}
// 构建结算数据
final var settlementDataByOrderDto = SettlementDTO.SettlementDataByOrderDto.builder()
.classifyOrders(classifyOrders)
.build();
// 生成Excel
try {
return writeToExcelByOrder(settlementDataByOrderDto);
} catch (IOException e) {
log.error("生成按订单结算Excel失败", e);
throw BusinessException.build(ErrorCodeEnum.GENERATE_EXCEL_ERROR);
}
}
/**
* 安全获取餐别名称
*/
private String getMealNameSafely(SingleClientCustomerOrderDetailRpcResponse orderDetail,
Map<Integer, SingleMenuDetailRpcResponse> menuDetailMap,
Map<Integer, SingleMealRpcResponse> mealMap) {
if (orderDetail == null || orderDetail.getMenuDetailId() == 0) {
return "";
}
final var menuDetail = menuDetailMap.get(orderDetail.getMenuDetailId());
if (menuDetail == null || menuDetail.getMealId() == 0) {
return "";
}
final var meal = mealMap.get(menuDetail.getMealId());
return meal != null ? meal.getName() : "";
}
/**
* 安全获取SKU名称
*/
private String getSkuNameSafely(SingleClientCustomerOrderDetailRpcResponse orderDetail,
Map<Integer, SingleDishSkuByIdsRpcResponse> skuMap) {
if (orderDetail == null || orderDetail.getSkuId() == 0) {
return "";
}
final var sku = skuMap.get(orderDetail.getSkuId());
return sku != null ? sku.getName() : "";
}
/**
* 安全获取房间号
*/
private String getRoomNoSafely(SingleClientCustomerOrderRpcResponse order) {
if (order == null) {
return "";
}
String syncRoomNo = order.getSyncRoomNo();
if (!syncRoomNo.isEmpty()) {
return syncRoomNo;
}
return order.getRoomNo();
}
/**
* 安全获取忌口列表
*/
private List<String> getAvoidsSafely(SingleClientCustomerOrderRpcResponse order) {
if (order == null) {
return Collections.emptyList();
}
return order.getCustomerNoticeJson().getAvoidsList();
}
/**
* 安全获取医嘱列表
*/
private List<String> getDoctorsSafely(SingleClientCustomerOrderRpcResponse order) {
if (order == null) {
return Collections.emptyList();
}
return order.getCustomerNoticeJson().getDoctorsList();
}
/**
* 安全获取在线支付描述
*/
private String getOnlinePayDescSafely(SingleClientCustomerOrderRpcResponse order) {
if (order == null) {
return "";
}
try {
String desc = BaseProtoDescEnum.getDescByProtoEnum(order.getOnlinePay(), OnlinePayDescEnum.class);
return desc != null ? desc : "";
} catch (Exception e) {
log.warn("获取在线支付描述失败,orderId: {}", order.getId(), e);
return "";
}
}
/**
* 计算单价
*/
private BigDecimal calculatePrice(Integer price) {
if (price == null) {
return BigDecimal.ZERO;
}
return new BigDecimal(price).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
}
/**
* 计算总价
*/
private BigDecimal calculateTotalPrice(Integer price, Integer count) {
if (price == null || count == null) {
return BigDecimal.ZERO;
}
return new BigDecimal(price)
.multiply(new BigDecimal(count))
.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
}
/**
* 创建空的订单Excel
*/
private ByteArrayOutputStream createEmptyExcelByOrder() {
try {
final var emptyData = SettlementDTO.SettlementDataByOrderDto.builder()
.classifyOrders(Collections.emptyList())
.build();
return writeToExcelByOrder(emptyData);
} catch (IOException e) {
log.error("创建空Excel失败", e);
throw BusinessException.build(ErrorCodeEnum.GENERATE_EXCEL_ERROR);
}
}
/**
* 按订单维度生成Excel
*/
private static ByteArrayOutputStream writeToExcelByOrder(SettlementDTO.SettlementDataByOrderDto settlementDataDto)
throws IOException {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("客人结算");
// 创建表头样式(黑色边框)
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 创建数据行样式(浅灰色边框)
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
dataStyle.setTopBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
dataStyle.setBottomBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
dataStyle.setLeftBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
dataStyle.setRightBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
int rowNum = 0;
// 创建表头
Row titleRow = sheet.createRow(rowNum++);
int columnNum = 0;
Cell cell0 = titleRow.createCell(columnNum++);
cell0.setCellValue("用餐时间");
cell0.setCellStyle(headerStyle);
Cell cell1 = titleRow.createCell(columnNum++);
cell1.setCellValue("订单号");
cell1.setCellStyle(headerStyle);
Cell cell2 = titleRow.createCell(columnNum++);
cell2.setCellValue("餐别");
cell2.setCellStyle(headerStyle);
Cell cell3 = titleRow.createCell(columnNum++);
cell3.setCellValue("房间号");
cell3.setCellStyle(headerStyle);
Cell cell4 = titleRow.createCell(columnNum++);
cell4.setCellValue("客户姓名");
cell4.setCellStyle(headerStyle);
Cell cell5 = titleRow.createCell(columnNum++);
cell5.setCellValue("客户腕带号");
cell5.setCellStyle(headerStyle);
Cell cell6 = titleRow.createCell(columnNum++);
cell6.setCellValue("忌口信息");
cell6.setCellStyle(headerStyle);
Cell cell7 = titleRow.createCell(columnNum++);
cell7.setCellValue("医嘱信息");
cell7.setCellStyle(headerStyle);
Cell cell8 = titleRow.createCell(columnNum++);
cell8.setCellValue("餐单名称");
cell8.setCellStyle(headerStyle);
Cell cell9 = titleRow.createCell(columnNum++);
cell9.setCellValue("菜品名称");
cell9.setCellStyle(headerStyle);
Cell cell10 = titleRow.createCell(columnNum++);
cell10.setCellValue("菜品份数");
cell10.setCellStyle(headerStyle);
Cell cell11 = titleRow.createCell(columnNum++);
cell11.setCellValue("菜品单价");
cell11.setCellStyle(headerStyle);
Cell cell12 = titleRow.createCell(columnNum++);
cell12.setCellValue("菜品金额");
cell12.setCellStyle(headerStyle);
Cell cell13 = titleRow.createCell(columnNum++);
cell13.setCellValue("备注");
cell13.setCellStyle(headerStyle);
Cell cell14 = titleRow.createCell(columnNum++);
cell14.setCellValue("支付方式");
cell14.setCellStyle(headerStyle);
// 填充数据
final var classifyOrders = settlementDataDto.getClassifyOrders();
if (classifyOrders != null) {
for (SettlementDTO.SettlementOrderDetail orderDetail : classifyOrders) {
Row dataRow = sheet.createRow(rowNum++);
columnNum = 0;
Cell dataCell0 = dataRow.createCell(columnNum++);
dataCell0.setCellValue(orderDetail.getMealTime() != null ? orderDetail.getMealTime() : "");
dataCell0.setCellStyle(dataStyle);
Cell dataCell1 = dataRow.createCell(columnNum++);
dataCell1.setCellValue(orderDetail.getCode() != null ? orderDetail.getCode() : "");
dataCell1.setCellStyle(dataStyle);
Cell dataCell2 = dataRow.createCell(columnNum++);
dataCell2.setCellValue(orderDetail.getMealName() != null ? orderDetail.getMealName() : "");
dataCell2.setCellStyle(dataStyle);
Cell dataCell3 = dataRow.createCell(columnNum++);
dataCell3.setCellValue(orderDetail.getRoomNo() != null ? orderDetail.getRoomNo() : "");
dataCell3.setCellStyle(dataStyle);
Cell dataCell4 = dataRow.createCell(columnNum++);
dataCell4.setCellValue(orderDetail.getCustomerName() != null ? orderDetail.getCustomerName() : "");
dataCell4.setCellStyle(dataStyle);
Cell dataCell5 = dataRow.createCell(columnNum++);
dataCell5.setCellValue(orderDetail.getHISCustomerNo() != null ? orderDetail.getHISCustomerNo() : "");
dataCell5.setCellStyle(dataStyle);
// 忌口列表转字符串
String avoidsStr = orderDetail.getAvoids() != null ? String.join(", ", orderDetail.getAvoids()) : "";
Cell dataCell6 = dataRow.createCell(columnNum++);
dataCell6.setCellValue(avoidsStr);
dataCell6.setCellStyle(dataStyle);
// 医嘱列表转字符串
String doctorsStr = orderDetail.getDoctors() != null ? String.join(", ", orderDetail.getDoctors()) : "";
Cell dataCell7 = dataRow.createCell(columnNum++);
dataCell7.setCellValue(doctorsStr);
dataCell7.setCellStyle(dataStyle);
Cell dataCell8 = dataRow.createCell(columnNum++);
dataCell8.setCellValue(orderDetail.getMenuName() != null ? orderDetail.getMenuName() : "");
dataCell8.setCellStyle(dataStyle);
Cell dataCell9 = dataRow.createCell(columnNum++);
dataCell9.setCellValue(orderDetail.getSkuName() != null ? orderDetail.getSkuName() : "");
dataCell9.setCellStyle(dataStyle);
Cell dataCell10 = dataRow.createCell(columnNum++);
dataCell10.setCellValue(orderDetail.getCount() != null ? orderDetail.getCount() : 0);
dataCell10.setCellStyle(dataStyle);
// 价格
Cell dataCell11 = dataRow.createCell(columnNum++);
if (orderDetail.getPrice() != null) {
dataCell11.setCellValue(orderDetail.getPrice().doubleValue());
} else {
dataCell11.setCellValue(0.0);
}
dataCell11.setCellStyle(dataStyle);
// 总价
Cell dataCell12 = dataRow.createCell(columnNum++);
if (orderDetail.getTotalPrice() != null) {
dataCell12.setCellValue(orderDetail.getTotalPrice().doubleValue());
} else {
dataCell12.setCellValue(0.0);
}
dataCell12.setCellStyle(dataStyle);
// 合并备注和忌口备注,用分号分割,备注在前
String remark = orderDetail.getRemark() != null ? orderDetail.getRemark() : "";
String avoidRemark = orderDetail.getAvoidRemark() != null ? orderDetail.getAvoidRemark() : "";
String combinedRemark = "";
if (!remark.isEmpty() && !avoidRemark.isEmpty()) {
combinedRemark = remark + ";" + avoidRemark;
} else if (!remark.isEmpty()) {
combinedRemark = remark;
} else if (!avoidRemark.isEmpty()) {
combinedRemark = avoidRemark;
}
Cell dataCell13 = dataRow.createCell(columnNum++);
dataCell13.setCellValue(combinedRemark);
dataCell13.setCellStyle(dataStyle);
Cell dataCell14 = dataRow.createCell(columnNum++);
dataCell14.setCellValue(orderDetail.getOnlinePay() != null ? orderDetail.getOnlinePay() : "");
dataCell14.setCellStyle(dataStyle);
}
}
// 自动调整列宽
for (int i = 0; i < 15; i++) {
sheet.autoSizeColumn(i);
}
// 将工作簿内容写入字节数组输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
workbook.close();
return outputStream;
}
public ByteArrayOutputStream generateOrderSettlementExcelBySku(SettlementRequestDto settlementRequest) {
......@@ -541,4 +1127,364 @@ public class SettlementServiceImpl implements SettlementService {
}
@Override
public ByteArrayOutputStream exportSeparateMealsExcel(QueryOrderDto queryOrderDto) {
// 参数校验
if (queryOrderDto == null) {
throw BusinessException.build(ErrorCodeEnum.GENERATE_EXCEL_ERROR);
}
final int enterpriseId = queryOrderDto.getEnterpriseId();
// 查询订单列表
final var queryClientCustomerOrdersByConditionRpcResponse = orderServiceRpcClient.queryClientCustomerOrdersByCondition(queryOrderDto);
// 今天开始时间的时间戳(毫秒)
final long todayStartTimestamp = LocalDate.now().atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
final var orderList = queryClientCustomerOrdersByConditionRpcResponse.getResponsesList()
.stream()
.filter(e -> {
boolean payStatusMatch = e.getPayStatus() == PayStatusEnum.SUCCEED|| e.getPayStatus() == PayStatusEnum.PAY_CANCELED;
// 过滤时间:只查询今天和以后的订单
boolean timeMatch = e.getMealTime() >= todayStartTimestamp;
return payStatusMatch && timeMatch;
})
.toList();
if (orderList.isEmpty()) {
log.warn("未查询到订单数据,enterpriseId: {}", enterpriseId);
return createEmptyExcelBySeparateMeal();
}
// 获取订单IDs
List<Integer> orderIds = orderList.stream()
.map(SingleClientCustomerOrderRpcResponse::getId)
.collect(Collectors.toList());
// 构建订单映射
final var orderMap = orderList.stream()
.filter(Objects::nonNull)
.filter(o -> o.getId() != 0)
.collect(Collectors.toMap(SingleClientCustomerOrderRpcResponse::getId, o -> o, (o1, o2) -> o1));
// 查询订单详情
final var orderDetailList = orderServiceRpcClient.getOrderDetailsByOrderIds(orderIds);
if (orderDetailList == null || orderDetailList.isEmpty()) {
log.warn("未查询到订单详情数据,orderIds: {}", orderIds);
return createEmptyExcelBySeparateMeal();
}
// 订单详情按订单ID分组
final var orderDetailGroupByOrderId = orderDetailList.stream()
.filter(Objects::nonNull)
.filter(d -> !d.getServeStatus().equals(ServeStatusEnum.SERVE_REJECT))
.collect(Collectors.groupingBy(SingleClientCustomerOrderDetailRpcResponse::getOrderId));
// 获取SKU数据
List<Integer> skuIds = orderDetailList.stream()
.filter(Objects::nonNull)
.filter(d -> !d.getServeStatus().equals(ServeStatusEnum.SERVE_REJECT))
.map(SingleClientCustomerOrderDetailRpcResponse::getSkuId)
.distinct()
.collect(Collectors.toList());
final var skuResponse = skuServiceRpcClient.getDishSkusByIds(skuIds);
final List<SingleDishSkuByIdsRpcResponse> skus = skuResponse != null ? skuResponse.getResponseList() : new ArrayList<>();
final var skuMap = skus.stream()
.collect(Collectors.toMap(SingleDishSkuByIdsRpcResponse::getId, s -> s, (s1, s2) -> s1));
// 获取餐别数据
final var mealResponse = mealServiceRpcClient.getAllMeals(enterpriseId);
final List<SingleMealRpcResponse> meals = mealResponse != null ? mealResponse.getResponsesList() : new ArrayList<>();
final var mealMap = meals.stream()
.filter(Objects::nonNull)
.filter(m -> m.getId() != 0)
.collect(Collectors.toMap(SingleMealRpcResponse::getId, m -> m, (m1, m2) -> m1));
// 获取菜单数据
List<Integer> menuIds = orderList.stream()
.map(SingleClientCustomerOrderRpcResponse::getMenuId)
.distinct()
.collect(Collectors.toList());
final var menuResponse = menuServiceRpcClient.getMenusByIds(enterpriseId, menuIds);
final List<SingleMenuRpcResponse> menuList = menuResponse != null ? menuResponse.getResponsesList() : new ArrayList<>();
final var menuMap = menuList.stream()
.collect(Collectors.toMap(SingleMenuRpcResponse::getId, m -> m, (m1, m2) -> m1));
// 获取菜单详情数据
final var menuDetailResponse = menuServiceRpcClient.queryMenuDetailsByMenuIds(enterpriseId, menuIds);
final List<SingleMenuDetailRpcResponse> menuDetailList = menuDetailResponse != null ? menuDetailResponse.getResponsesList() : new ArrayList<>();
final var menuDetailMap = menuDetailList.stream()
.collect(Collectors.toMap(SingleMenuDetailRpcResponse::getId, m -> m, (m1, m2) -> m1));
// 获取客户数据
List<Integer> customerIds = orderList.stream()
.map(SingleClientCustomerOrderRpcResponse::getCustomerId)
.filter(id -> id != 0)
.distinct()
.collect(Collectors.toList());
final var customerResponse = clientCustomerServiceRpcClient.getClientCustomersByIds(enterpriseId, customerIds);
final List<SingleClientCustomerRpcResponse> customers = customerResponse != null ? customerResponse.getResponsesList() : new ArrayList<>();
final var customerMap = customers.stream()
.collect(Collectors.toMap(SingleClientCustomerRpcResponse::getId, c -> c, (c1, c2) -> c1));
// 构建分餐表明细
final var mealDetails = new ArrayList<SeparateMealDetail>();
for (Integer orderId : orderIds) {
final var orderDetails = orderDetailGroupByOrderId.getOrDefault(orderId, Collections.emptyList());
final var order = orderMap.get(orderId);
// 订单为空则跳过
if (order == null) {
log.warn("订单不存在,orderId: {}", orderId);
continue;
}
// 获取菜单信息
final var menu = menuMap.get(order.getMenuId());
final String menuName = menu != null ? menu.getName() : "";
// 获取客户信息
final var customer = customerMap.get(order.getCustomerId());
final String customerName = customer != null ? customer.getName() : "";
// 安全获取房间号
String roomNo = getRoomNoSafely(order);
// 处理每个订单详情
for (SingleClientCustomerOrderDetailRpcResponse orderDetail : orderDetails) {
if (orderDetail == null) {
continue;
}
// 安全获取餐别名称
String mealName = getMealNameSafely(orderDetail, menuDetailMap, mealMap);
// 安全获取SKU名称
String skuName = getSkuNameSafely(orderDetail, skuMap);
// 安全获取忌口信息(列表转字符串)
List<String> avoidsList = getAvoidsSafely(order);
String avoidsStr = String.join(", ", avoidsList);
String remark = order.getRemark();
String avoidRemark = order.getAvoidRemark();
String combinedRemark = "";
if (!remark.isEmpty() && !avoidRemark.isEmpty()) {
combinedRemark = remark + ";" + avoidRemark;
} else if (!remark.isEmpty()) {
combinedRemark = remark;
} else if (!avoidRemark.isEmpty()) {
combinedRemark = avoidRemark;
}
// 构建分餐表明细
SeparateMealDetail mealDetail = SeparateMealDetail.builder()
.orderId(orderId)
.mealName(mealName)
.roomNo(roomNo)
.customerName(customerName)
.menuName(menuName)
.skuName(skuName)
.count(orderDetail.getCount() != 0 ? orderDetail.getCount() : 0)
.avoids(avoidsStr)
.remark(combinedRemark)
.build();
mealDetails.add(mealDetail);
}
}
// 构建分餐表数据
final var separateMealDataDto = SeparateMealDataDto.builder()
.mealDetails(mealDetails)
.build();
// 生成Excel
try {
return writeToExcelBySeparateMeal(separateMealDataDto);
} catch (IOException e) {
log.error("生成分餐表Excel失败", e);
throw BusinessException.build(ErrorCodeEnum.GENERATE_EXCEL_ERROR);
}
}
/**
* 生成分餐表Excel
*/
private static ByteArrayOutputStream writeToExcelBySeparateMeal(SeparateMealDataDto separateMealDataDto)
throws IOException {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("分餐表");
// 创建标题样式(合并单元格标题)
CellStyle titleStyle = workbook.createCellStyle();
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderBottom(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
titleStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
titleStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
titleStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 16);
titleStyle.setFont(titleFont);
// 创建表头样式(黑色边框)
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 创建数据行样式(浅灰色边框)
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
dataStyle.setTopBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
dataStyle.setBottomBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
dataStyle.setLeftBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
dataStyle.setRightBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
int rowNum = 0;
// 第一行:创建合并单元格标题
Row mainTitleRow = sheet.createRow(rowNum++);
Cell mainTitleCell = mainTitleRow.createCell(0);
mainTitleCell.setCellValue("住院客户分餐表");
mainTitleCell.setCellStyle(titleStyle);
// 合并第一行的8列
CellRangeAddress titleRegion = new CellRangeAddress(0, 0, 0, 7);
sheet.addMergedRegion(titleRegion);
// 为合并区域设置边框
RegionUtil.setBorderTop(BorderStyle.THIN, titleRegion, sheet);
RegionUtil.setBorderBottom(BorderStyle.THIN, titleRegion, sheet);
RegionUtil.setBorderLeft(BorderStyle.THIN, titleRegion, sheet);
RegionUtil.setBorderRight(BorderStyle.THIN, titleRegion, sheet);
RegionUtil.setTopBorderColor(IndexedColors.BLACK.getIndex(), titleRegion, sheet);
RegionUtil.setBottomBorderColor(IndexedColors.BLACK.getIndex(), titleRegion, sheet);
RegionUtil.setLeftBorderColor(IndexedColors.BLACK.getIndex(), titleRegion, sheet);
RegionUtil.setRightBorderColor(IndexedColors.BLACK.getIndex(), titleRegion, sheet);
// 第二行:创建表头
Row titleRow = sheet.createRow(rowNum++);
int columnNum = 0;
Cell cell0 = titleRow.createCell(columnNum++);
cell0.setCellValue("餐别");
cell0.setCellStyle(headerStyle);
Cell cell1 = titleRow.createCell(columnNum++);
cell1.setCellValue("房间号");
cell1.setCellStyle(headerStyle);
Cell cell2 = titleRow.createCell(columnNum++);
cell2.setCellValue("客户姓名");
cell2.setCellStyle(headerStyle);
Cell cell3 = titleRow.createCell(columnNum++);
cell3.setCellValue("餐单名称");
cell3.setCellStyle(headerStyle);
Cell cell4 = titleRow.createCell(columnNum++);
cell4.setCellValue("菜品名称");
cell4.setCellStyle(headerStyle);
Cell cell5 = titleRow.createCell(columnNum++);
cell5.setCellValue("菜品数量");
cell5.setCellStyle(headerStyle);
Cell cell6 = titleRow.createCell(columnNum++);
cell6.setCellValue("忌口信息");
cell6.setCellStyle(headerStyle);
Cell cell7 = titleRow.createCell(columnNum++);
cell7.setCellValue("备注");
cell7.setCellStyle(headerStyle);
// 填充数据
final var mealDetails = separateMealDataDto.getMealDetails();
if (mealDetails != null) {
for (SeparateMealDetail mealDetail : mealDetails) {
Row dataRow = sheet.createRow(rowNum++);
columnNum = 0;
Cell dataCell0 = dataRow.createCell(columnNum++);
dataCell0.setCellValue(mealDetail.getMealName() != null ? mealDetail.getMealName() : "");
dataCell0.setCellStyle(dataStyle);
Cell dataCell1 = dataRow.createCell(columnNum++);
dataCell1.setCellValue(mealDetail.getRoomNo() != null ? mealDetail.getRoomNo() : "");
dataCell1.setCellStyle(dataStyle);
Cell dataCell2 = dataRow.createCell(columnNum++);
dataCell2.setCellValue(mealDetail.getCustomerName() != null ? mealDetail.getCustomerName() : "");
dataCell2.setCellStyle(dataStyle);
Cell dataCell3 = dataRow.createCell(columnNum++);
dataCell3.setCellValue(mealDetail.getMenuName() != null ? mealDetail.getMenuName() : "");
dataCell3.setCellStyle(dataStyle);
Cell dataCell4 = dataRow.createCell(columnNum++);
dataCell4.setCellValue(mealDetail.getSkuName() != null ? mealDetail.getSkuName() : "");
dataCell4.setCellStyle(dataStyle);
Cell dataCell5 = dataRow.createCell(columnNum++);
dataCell5.setCellValue(mealDetail.getCount() != null ? mealDetail.getCount() : 0);
dataCell5.setCellStyle(dataStyle);
Cell dataCell6 = dataRow.createCell(columnNum++);
dataCell6.setCellValue(mealDetail.getAvoids() != null ? mealDetail.getAvoids() : "");
dataCell6.setCellStyle(dataStyle);
Cell dataCell7 = dataRow.createCell(columnNum++);
dataCell7.setCellValue(mealDetail.getRemark() != null ? mealDetail.getRemark() : "");
dataCell7.setCellStyle(dataStyle);
}
}
// 自动调整列宽
for (int i = 0; i < 8; i++) {
sheet.autoSizeColumn(i);
}
// 将工作簿内容写入字节数组输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
workbook.close();
return outputStream;
}
/**
* 创建空的分餐表Excel(无数据)
*/
private static ByteArrayOutputStream createEmptyExcelBySeparateMeal() {
try {
SeparateMealDataDto emptyData = SeparateMealDataDto.builder()
.mealDetails(new ArrayList<>())
.build();
return writeToExcelBySeparateMeal(emptyData);
} catch (IOException e) {
log.error("创建空分餐表Excel失败", e);
throw BusinessException.build(ErrorCodeEnum.GENERATE_EXCEL_ERROR);
}
}
}
......
......@@ -2,6 +2,8 @@ package com.infoloop.tianting.streamController;
import cn.dev33.satoken.annotation.SaIgnore;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.infoloop.tianting.enums.SettlementDimensionEnum;
import com.infoloop.tianting.model.dto.OrderDbDTO;
import com.infoloop.tianting.model.dto.SettlementDTO.SettlementRequestDto;
import com.infoloop.tianting.service.SettlementService;
import io.swagger.annotations.Api;
......@@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.io.ByteArrayOutputStream;
import java.time.format.DateTimeFormatter;
@Api(tags = "结算")
@ApiSupport(order = 0)
......@@ -29,35 +32,59 @@ import java.io.ByteArrayOutputStream;
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SettlementController {
private final SettlementService settlementService;
@SaIgnore
@ApiOperation(value = "企业端结算账单生成excel")
@PostMapping("/enterprise/settlement/generate-excel")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<byte[]> enterpriseSettlementGenerateExcel(@Valid @RequestBody SettlementRequestDto settlementRequestDto) {
ByteArrayOutputStream outputStream = settlementService.generateOrderSettlementExcel(settlementRequestDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "订单数据.xlsx");
return ResponseEntity.ok()
.headers(headers)
.body(outputStream.toByteArray());
}
private final SettlementService settlementService;
@SaIgnore
@ApiOperation(value = "企业端结算账单生成excel")
@PostMapping("/enterprise/settlement/generate-excel")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<byte[]> enterpriseSettlementGenerateExcel(@Valid @RequestBody SettlementRequestDto settlementRequestDto) {
ByteArrayOutputStream outputStream = settlementService.generateOrderSettlementExcel(settlementRequestDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "订单数据.xlsx");
return ResponseEntity.ok()
.headers(headers)
.body(outputStream.toByteArray());
}
@ApiOperation(value = "结算账单生成excel")
@PostMapping("/settlement/generate-excel")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<byte[]> settlementGenerateExcel(
@Valid @RequestBody SettlementRequestDto settlementRequestDto) {
ByteArrayOutputStream outputStream = settlementService.generateOrderSettlementExcel(
settlementRequestDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "订单数据.xlsx");
return ResponseEntity.ok()
.headers(headers)
.body(outputStream.toByteArray());
public ResponseEntity<byte[]> settlementGenerateExcel(@Valid @RequestBody SettlementRequestDto settlementRequestDto) {
ByteArrayOutputStream outputStream = settlementService.generateOrderSettlementExcel(
settlementRequestDto);
String fileName;
if (settlementRequestDto.getDimension() == SettlementDimensionEnum.BY_ORDER) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String startDate = settlementRequestDto.getTimeStart().format(formatter);
String endDate = settlementRequestDto.getTimeEnd().format(formatter);
fileName = String.format("结算报表_%s_%s.xlsx", startDate, endDate);
} else {
fileName = "订单数据.xlsx";
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", fileName);
return ResponseEntity.ok()
.headers(headers)
.body(outputStream.toByteArray());
}
@ApiOperation(value = "院区端订单导出")
@PostMapping("/separateMeals/export")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<byte[]> exportSeparateMeals(@Valid @RequestBody OrderDbDTO.QueryOrderDto queryOrderDto) {
ByteArrayOutputStream outputStream = settlementService.exportSeparateMealsExcel(queryOrderDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "分餐表.xlsx");
return ResponseEntity.ok()
.headers(headers)
.body(outputStream.toByteArray());
}
}
......
......@@ -89,6 +89,11 @@ enum OnlinePayEnum {
LEAVE_HOSPITAL = 3;//出院
EMPLOYEE_CARD = 4; //员工卡
PAPER_VERIFICATION = 5; //纸质核销
CASH = 6;//现金
MEAL_CARD = 7;//餐卡
BAND_CARD = 8;//银行卡
ALIPAY = 9;//支付宝
WECHATPAY = 10;//微信支付
}
enum ServeStatusEnum {
......@@ -141,6 +146,7 @@ message SingleClientCustomerOrderRpcResponse {
bool isAllocation = 35;
string syncRoomNo = 36;
bool isCanceled = 37;
string avoidRemark = 38;
}
message GetClientCustomerOrderByIdRpcRequest {
......@@ -385,6 +391,8 @@ message ClientCustomerOrderModification {
string syncRoomNo = 55;
bool shouldUpdateIsCanceled = 56;
bool isCanceled = 57;
bool shouldUpdateAvoidRemark = 58;
string avoidRemark = 59;
}
message UpdateClientCustomerOrderRpcRequest {
......