|
|
@@ -7,6 +7,8 @@ import com.alibaba.excel.ExcelWriter;
|
|
|
import com.alibaba.excel.converters.longconverter.LongStringConverter;
|
|
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
|
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.yc.ship.framework.common.pojo.PageResult;
|
|
|
import com.yc.ship.framework.excel.core.util.ExcelUtils;
|
|
|
import com.yc.ship.module.resource.api.ship.dto.RoomModelFloorNumDTO;
|
|
|
@@ -17,6 +19,7 @@ import com.yc.ship.module.trade.dal.mysql.order.TradeVisitorMapper;
|
|
|
import com.yc.ship.module.trade.dal.mysql.report.VoyageStockBoardMapper;
|
|
|
import com.yc.ship.module.trade.service.report.VoyageStockBoardService;
|
|
|
import com.yc.ship.module.trade.utils.IdCardProvinceUtil;
|
|
|
+import com.yc.ship.module.trade.utils.excel.AllVoyageStockBoardExportStyleHandler;
|
|
|
import com.yc.ship.module.trade.utils.excel.VoyageStockBoardExportStyleHandler;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
@@ -24,6 +27,7 @@ import org.springframework.stereotype.Service;
|
|
|
import javax.annotation.Resource;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
import java.io.IOException;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.net.URLEncoder;
|
|
|
@@ -217,9 +221,278 @@ public class VoyageStockBoardServiceImpl implements VoyageStockBoardService {
|
|
|
|
|
|
@Override
|
|
|
public PageResult<AllVoyageStockBoardRespVO> getAllVoyageStockBoardPage(AllVoyageStockBoardReqVO reqVO) {
|
|
|
- return null;
|
|
|
+ IPage<AllVoyageStockBoardRespVO> page = voyageStockBoardMapper.selectAllVoyageStockPage(new Page<>(reqVO.getPageNo(), reqVO.getPageSize()), reqVO);
|
|
|
+ List<AllVoyageStockBoardRespVO> list = page.getRecords();
|
|
|
+ list.forEach(item -> {
|
|
|
+ // 根据航次id查询实团计划、占位计划、切位计划、总计划
|
|
|
+ Map<String,Object> numMap = voyageStockBoardMapper.selectVoyageStockNum(item.getVoyageId());
|
|
|
+ if(numMap != null){
|
|
|
+ item.setTotalPlanSum(numMap.get("totalPlanSum")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("totalPlanSum").toString()));
|
|
|
+ item.setRealGroupTotal(numMap.get("realGroupTotal")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("realGroupTotal").toString()));
|
|
|
+ item.setOccupyTotal(numMap.get("occupyTotal")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("occupyTotal").toString()));
|
|
|
+ item.setSwitchTotal(numMap.get("switchTotal")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("switchTotal").toString()));
|
|
|
+ }
|
|
|
+ // 根据航次id查询动态房型数据
|
|
|
+ List<Map<String, Object>> l = voyageStockBoardMapper.selectVoyageStockNumGruopByRoomModelIdAndFloor(item.getVoyageId());
|
|
|
+ Map<String, BigDecimal> map = new HashMap<>();
|
|
|
+ if (CollUtil.isNotEmpty(l)) {
|
|
|
+ for(Map<String, Object> m : l){
|
|
|
+ // map的key为:已确定数:room_roomModelId_floor_confirmed 虚占位:room_roomModelId_floor_virtual,
|
|
|
+ // 家庭套房或家庭套房PRO的key为:家庭套房:已确定数:family_floor_confirmed 虚占位:family_floor_virtual
|
|
|
+ // 家庭套房PRO:已确定数: family_pro_floor_confirmed 虚占位:family_pro_floor_virtual
|
|
|
+ String roomModelId = String.valueOf(m.get("room_model_id"));
|
|
|
+ String floor = String.valueOf(m.get("floor"));
|
|
|
+ String roomModelName = String.valueOf(m.get("room_model_name"));
|
|
|
+
|
|
|
+ BigDecimal confirmed = m.get("confirmed") != null ? new BigDecimal(String.valueOf(m.get("confirmed"))) : BigDecimal.ZERO;
|
|
|
+ BigDecimal virtualNum = m.get("virtualNum") != null ? new BigDecimal(String.valueOf(m.get("virtualNum"))) : BigDecimal.ZERO;
|
|
|
+
|
|
|
+ // 根据房型名称判断前缀和key格式
|
|
|
+ String prefix;
|
|
|
+ String confirmedKey;
|
|
|
+ String virtualKey;
|
|
|
+
|
|
|
+ if (roomModelName != null) {
|
|
|
+ if (roomModelName.contains("家庭套房PRO")) {
|
|
|
+ // 家庭套房PRO:不包含roomModelId
|
|
|
+ prefix = "family_pro";
|
|
|
+ confirmedKey = prefix + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = prefix + "_" + floor + "_virtual";
|
|
|
+ } else if (roomModelName.contains("家庭套房")) {
|
|
|
+ // 家庭套房:不包含roomModelId
|
|
|
+ prefix = "family";
|
|
|
+ confirmedKey = prefix + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = prefix + "_" + floor + "_virtual";
|
|
|
+ } else {
|
|
|
+ // 普通房型:包含roomModelId
|
|
|
+ prefix = "room";
|
|
|
+ confirmedKey = prefix + "_" + roomModelId + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = prefix + "_" + roomModelId + "_" + floor + "_virtual";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 默认情况:普通房型
|
|
|
+ confirmedKey = "room_" + roomModelId + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = "room_" + roomModelId + "_" + floor + "_virtual";
|
|
|
+ }
|
|
|
+
|
|
|
+ map.put(confirmedKey, confirmed);
|
|
|
+ map.put(virtualKey, virtualNum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ item.setRoomData( map);
|
|
|
+ });
|
|
|
+ return new PageResult<>(list, page.getTotal());
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public void exportAllVoyageStockBoardExcel(AllVoyageStockBoardReqVO reqVO, HttpServletResponse response) throws IOException {
|
|
|
+ // 1. 查询所有数据
|
|
|
+ IPage<AllVoyageStockBoardRespVO> page = voyageStockBoardMapper.selectAllVoyageStockPage(
|
|
|
+ new Page<>(1, Integer.MAX_VALUE), reqVO);
|
|
|
+ List<AllVoyageStockBoardRespVO> list = page.getRecords();
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(list)) {
|
|
|
+ throw new RuntimeException("没有可导出的数据");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取表头配置
|
|
|
+ List<AllVoyageStockBoardTableHeadRespVO> tableHeads = getAllVoyageStockBoardTableHead(reqVO);
|
|
|
+ if (CollUtil.isEmpty(tableHeads)) {
|
|
|
+ throw new RuntimeException("没有可导出的房型数据");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 填充数据
|
|
|
+ list.forEach(item -> {
|
|
|
+ Map<String, Object> numMap = voyageStockBoardMapper.selectVoyageStockNum(item.getVoyageId());
|
|
|
+ if (numMap != null) {
|
|
|
+ item.setTotalPlanSum(numMap.get("totalPlanSum")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("totalPlanSum").toString()));
|
|
|
+ item.setRealGroupTotal(numMap.get("realGroupTotal")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("realGroupTotal").toString()));
|
|
|
+ item.setOccupyTotal(numMap.get("occupyTotal")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("occupyTotal").toString()));
|
|
|
+ item.setSwitchTotal(numMap.get("switchTotal")==null?BigDecimal.ZERO: new BigDecimal(numMap.get("switchTotal").toString()));
|
|
|
+ }
|
|
|
+ // 根据航次id查询动态房型数据
|
|
|
+ List<Map<String, Object>> l = voyageStockBoardMapper.selectVoyageStockNumGruopByRoomModelIdAndFloor(item.getVoyageId());
|
|
|
+ Map<String, BigDecimal> map = new HashMap<>();
|
|
|
+ if (CollUtil.isNotEmpty(l)) {
|
|
|
+ for (Map<String, Object> m : l) {
|
|
|
+ String roomModelId = String.valueOf(m.get("room_model_id"));
|
|
|
+ String floor = String.valueOf(m.get("floor"));
|
|
|
+ String roomModelName = String.valueOf(m.get("room_model_name"));
|
|
|
+
|
|
|
+ BigDecimal confirmed = m.get("confirmed") != null ? new BigDecimal(String.valueOf(m.get("confirmed"))) : BigDecimal.ZERO;
|
|
|
+ BigDecimal virtualNum = m.get("virtualNum") != null ? new BigDecimal(String.valueOf(m.get("virtualNum"))) : BigDecimal.ZERO;
|
|
|
+ // 根据房型名称判断前缀和key格式
|
|
|
+ String prefix;
|
|
|
+ String confirmedKey;
|
|
|
+ String virtualKey;
|
|
|
+ if (roomModelName != null) {
|
|
|
+ if (roomModelName.contains("家庭套房PRO")) {
|
|
|
+ prefix = "family_pro";
|
|
|
+ confirmedKey = prefix + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = prefix + "_" + floor + "_virtual";
|
|
|
+ } else if (roomModelName.contains("家庭套房")) {
|
|
|
+ prefix = "family";
|
|
|
+ confirmedKey = prefix + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = prefix + "_" + floor + "_virtual";
|
|
|
+ } else {
|
|
|
+ prefix = "room";
|
|
|
+ confirmedKey = prefix + "_" + roomModelId + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = prefix + "_" + roomModelId + "_" + floor + "_virtual";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ confirmedKey = "room_" + roomModelId + "_" + floor + "_confirmed";
|
|
|
+ virtualKey = "room_" + roomModelId + "_" + floor + "_virtual";
|
|
|
+
|
|
|
+ }
|
|
|
+ map.put(confirmedKey, confirmed);
|
|
|
+ map.put(virtualKey, virtualNum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ item.setRoomData(map);
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ // 4. 构建动态表头
|
|
|
+ List<List<String>> headers = buildDynamicExportHeadersFromTableHead(tableHeads);
|
|
|
+ log.info("headers: {}", headers);
|
|
|
+ // 5. 转换导出数据(使用表头配置的key)
|
|
|
+ List<List<Object>> data = transformDynamicExportDataFromTableHead(list, tableHeads);
|
|
|
+ log.info("data: {}", data);
|
|
|
+ // 6. 设置响应头(必须在获取OutputStream之前)
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
|
|
|
+ response.setCharacterEncoding("UTF-8");
|
|
|
+ String fileName = URLEncoder.encode("全航次库存看板.xlsx", StandardCharsets.UTF_8.name());
|
|
|
+ response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
|
|
|
+
|
|
|
+ // 7. 使用EasyExcel导出
|
|
|
+ try {
|
|
|
+ EasyExcel.write(response.getOutputStream())
|
|
|
+ .head(headers)
|
|
|
+ .autoCloseStream(false)
|
|
|
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
|
+ .registerWriteHandler(new AllVoyageStockBoardExportStyleHandler(tableHeads))
|
|
|
+ .registerConverter(new LongStringConverter())
|
|
|
+ .sheet("全航次库存看板")
|
|
|
+ .doWrite(data);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("导出Excel失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建导出表头
|
|
|
+ */
|
|
|
+ private List<List<String>> buildDynamicExportHeadersFromTableHead(List<AllVoyageStockBoardTableHeadRespVO> tableHeads) {
|
|
|
+ List<List<String>> head = new ArrayList<>();
|
|
|
+
|
|
|
+ // 固定列
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("航次")));
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("倒计时(天)")));
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("实团计划合计")));
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("占位计划合计")));
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("切位计划合计")));
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("总计划(含虚占位计划合计)")));
|
|
|
+
|
|
|
+ // 动态房型列
|
|
|
+ for (AllVoyageStockBoardTableHeadRespVO tableHead : tableHeads) {
|
|
|
+ String roomLabel = tableHead.getLabel() + "(" + tableHead.getSumRooms() + "间)";
|
|
|
+
|
|
|
+ // 为每个楼层添加4列:总计划、已确定、虚占位、剩余
|
|
|
+ for (AllVoyageStockBoardTableHeadRespVO.FloorInfo floor : tableHead.getFloors()) {
|
|
|
+ String floorLabel = floor.getName() + " " + floor.getFloors() + "F(" + floor.getTotalRooms() + "间)";
|
|
|
+
|
|
|
+ // 总计划
|
|
|
+ List<String> header1 = new ArrayList<>();
|
|
|
+ header1.add(roomLabel);
|
|
|
+ header1.add(floorLabel);
|
|
|
+ header1.add("总计划(含虚占位)");
|
|
|
+ head.add(header1);
|
|
|
+
|
|
|
+ // 已确定
|
|
|
+ List<String> header2 = new ArrayList<>();
|
|
|
+ header2.add(roomLabel);
|
|
|
+ header2.add(floorLabel);
|
|
|
+ header2.add("其中已确定计划");
|
|
|
+ head.add(header2);
|
|
|
+
|
|
|
+ // 虚占位
|
|
|
+ List<String> header3 = new ArrayList<>();
|
|
|
+ header3.add(roomLabel);
|
|
|
+ header3.add(floorLabel);
|
|
|
+ header3.add("其中虚占位计划");
|
|
|
+ head.add(header3);
|
|
|
+
|
|
|
+ // 剩余
|
|
|
+ List<String> header4 = new ArrayList<>();
|
|
|
+ header4.add(roomLabel);
|
|
|
+ header4.add(floorLabel);
|
|
|
+ header4.add("剩余");
|
|
|
+ head.add(header4);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 房型余量列
|
|
|
+ List<String> remainHeader = new ArrayList<>();
|
|
|
+ remainHeader.add(roomLabel);
|
|
|
+ remainHeader.add("");
|
|
|
+ remainHeader.add("余量");
|
|
|
+ head.add(remainHeader);
|
|
|
+ }
|
|
|
+
|
|
|
+ return head;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 基于表头配置转换导出数据
|
|
|
+ */
|
|
|
+ private List<List<Object>> transformDynamicExportDataFromTableHead(List<AllVoyageStockBoardRespVO> list,
|
|
|
+ List<AllVoyageStockBoardTableHeadRespVO> tableHeads) {
|
|
|
+ List<List<Object>> result = new ArrayList<>();
|
|
|
+
|
|
|
+ for (AllVoyageStockBoardRespVO item : list) {
|
|
|
+ List<Object> rowData = new ArrayList<>();
|
|
|
+
|
|
|
+ // 固定列数据
|
|
|
+ rowData.add(item.getVoyage() != null ? item.getVoyage() : "");
|
|
|
+ rowData.add(item.getCountDown() != null ? item.getCountDown() : "");
|
|
|
+ rowData.add(formatDecimal(item.getRealGroupTotal()));
|
|
|
+ rowData.add(formatDecimal(item.getOccupyTotal()));
|
|
|
+ rowData.add(formatDecimal(item.getSwitchTotal()));
|
|
|
+ rowData.add(formatDecimal(item.getTotalPlanSum()));
|
|
|
+
|
|
|
+ // 动态房型列数据(按照表头配置的key顺序)
|
|
|
+ Map<String, BigDecimal> roomData = item.getRoomData();
|
|
|
+ BigDecimal totalRemain = BigDecimal.ZERO;
|
|
|
+ for (AllVoyageStockBoardTableHeadRespVO tableHead : tableHeads) {
|
|
|
+ for (AllVoyageStockBoardTableHeadRespVO.FloorInfo floor : tableHead.getFloors()) {
|
|
|
+ String floorKey = floor.getKey();
|
|
|
+ BigDecimal totalRooms = new BigDecimal(floor.getTotalRooms());
|
|
|
+ // 已确定
|
|
|
+ BigDecimal confirmed = roomData != null ? (roomData.get(floorKey + "_confirmed")==null ? BigDecimal.ZERO : roomData.get(floorKey + "_confirmed")) : BigDecimal.ZERO;
|
|
|
+ rowData.add(formatDecimal(confirmed));
|
|
|
+ // 虚占位
|
|
|
+ BigDecimal virtual = roomData != null ? (roomData.get(floorKey + "_virtual")==null ? BigDecimal.ZERO : roomData.get(floorKey + "_virtual")) : BigDecimal.ZERO;
|
|
|
+ rowData.add(formatDecimal(virtual));
|
|
|
+ // 总计划
|
|
|
+ BigDecimal total = confirmed.add(virtual);
|
|
|
+ rowData.add(formatDecimal(total));
|
|
|
+ // 剩余
|
|
|
+ BigDecimal remain = totalRooms.subtract(total);
|
|
|
+ totalRemain = totalRemain.add( remain);
|
|
|
+ rowData.add(formatDecimal(remain));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 房型余量
|
|
|
+ rowData.add(totalRemain);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.add(rowData);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* 填充同比环比数据
|
|
|
*/
|