Files
baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java
T

188 lines
12 KiB
Java
Raw Normal View History

2026-08-19 16:24:02 +08:00
package com.baiyue.lastleg.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.lastleg.dto.LastlegOrderDetailResponse;
import com.baiyue.lastleg.dto.LastlegOrderRequests;
import com.baiyue.lastleg.dto.LastlegOrderSummary;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.entity.LastlegOrderDetail;
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
2026-08-22 12:35:32 +08:00
import com.baiyue.lastleg.model.LastlegOrderType;
2026-08-19 16:24:02 +08:00
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Service
public class LastlegOrderService {
public static final String PENDING_ASSIGNMENT = "PENDING_ASSIGNMENT";
public static final String DESTINATION_ASSIGNED = "DESTINATION_ASSIGNED";
public static final String INBOUND_PLANNED = "INBOUND_PLANNED";
@Resource private LastlegOrderMapper orderMapper;
@Resource private LastlegOrderDetailMapper detailMapper;
@Resource private LastlegOrderStatusHistoryMapper historyMapper;
public PageResult<LastlegOrderSummary> page(String customerId, LastlegOrderRequests.Query query) {
Page<LastlegOrder> page = orderMapper.selectPage(
new Page<>(query.page(), query.size()), filter(customerId, query).orderByDesc(LastlegOrder::getCreatedAt));
return new PageResult<>(page.getRecords().stream().map(this::summary).toList(),
page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
}
public LastlegOrderDetailResponse detail(String customerId, long orderId) {
LastlegOrder order = findOwned(customerId, orderId);
List<LastlegOrderDetail> cargo = detailMapper.selectList(new LambdaQueryWrapper<LastlegOrderDetail>()
2026-08-24 11:55:50 +08:00
.eq(LastlegOrderDetail::getCustomerId, order.getCustomerId())
2026-08-19 16:24:02 +08:00
.eq(LastlegOrderDetail::getOrderId, orderId).orderByAsc(LastlegOrderDetail::getId));
List<LastlegOrderStatusHistory> history = historyMapper.selectList(
new LambdaQueryWrapper<LastlegOrderStatusHistory>()
2026-08-24 11:55:50 +08:00
.eq(LastlegOrderStatusHistory::getCustomerId, order.getCustomerId())
2026-08-19 16:24:02 +08:00
.eq(LastlegOrderStatusHistory::getOrderId, orderId)
.orderByAsc(LastlegOrderStatusHistory::getCreatedAt));
2026-08-20 18:02:38 +08:00
return new LastlegOrderDetailResponse(summary(order), cargo, history.stream()
.map(value -> new LastlegOrderDetailResponse.History(value.getId(), value.getFromStatus(),
value.getToStatus(), value.getRemark(), value.getCreatedAt())).toList());
2026-08-19 16:24:02 +08:00
}
@Transactional
public int transferDestination(String customerId, LastlegOrderRequests.TransferDestination request) {
List<Long> ids = request.orderIds().stream().filter(Objects::nonNull).distinct().toList();
if (ids.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least one order is required");
}
List<LastlegOrder> orders = orderMapper.selectList(new LambdaQueryWrapper<LastlegOrder>()
2026-08-21 10:43:25 +08:00
.eq(StringUtils.hasText(customerId), LastlegOrder::getCustomerId, customerId)
.in(LastlegOrder::getId, ids).last("FOR UPDATE"));
2026-08-19 16:24:02 +08:00
if (orders.size() != ids.size()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "One or more tail-leg orders were not found");
}
for (LastlegOrder order : orders) {
ensureTransferable(order);
}
LocalDateTime now = LocalDateTime.now();
for (LastlegOrder order : orders) {
String fromStatus = order.getCurrentStatus();
order.setTransferWarehouseId(request.warehouseId());
order.setTransferWarehouseCode(request.warehouseCode());
order.setTransferWarehouseName(request.warehouseName());
order.setCurrentStatus(DESTINATION_ASSIGNED);
order.setUpdatedAt(now);
orderMapper.updateById(order);
LastlegOrderDetail detailUpdate = new LastlegOrderDetail();
detailUpdate.setDestinationWarehouseId(request.warehouseId());
detailUpdate.setDestinationWarehouseCode(request.warehouseCode());
detailUpdate.setDestinationWarehouseName(request.warehouseName());
detailMapper.update(detailUpdate, new LambdaQueryWrapper<LastlegOrderDetail>()
.eq(LastlegOrderDetail::getOrderId, order.getId()));
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory();
2026-08-24 11:55:50 +08:00
history.setCustomerId(order.getCustomerId());
2026-08-19 16:24:02 +08:00
history.setOrderId(order.getId());
history.setFromStatus(fromStatus);
history.setToStatus(DESTINATION_ASSIGNED);
history.setRemark(StringUtils.hasText(request.remark())
? request.remark() : "Destination warehouse assigned: " + request.warehouseCode());
history.setCreatedAt(now);
historyMapper.insert(history);
}
return orders.size();
}
public byte[] exportCsv(String customerId, LastlegOrderRequests.Query query) {
List<LastlegOrder> orders = orderMapper.selectList(filter(customerId, query)
.orderByDesc(LastlegOrder::getCreatedAt).last("LIMIT 10000"));
List<String> lines = new ArrayList<>();
2026-08-20 18:02:38 +08:00
lines.add("ID,尾程单号,订单类型,入仓码,头程单号,货主,海外仓,转仓,转仓编码,FBA仓库,集装箱号,货柜状态,是否异常,是否暂扣,托数,箱数,体积(m3),数量,体积重量,预计到港时间,实际到港时间,预计到仓,入库单号,换标单号,换标状态,合托标记,主流程状态,子单状态,备注(仅自己),备注(客户),备注(仓库)");
2026-08-19 16:24:02 +08:00
for (LastlegOrder order : orders) {
lines.add(String.join(",", List.of(
2026-08-22 12:35:32 +08:00
csv(order.getId()), csv(order.getOrderNo()), csv(LastlegOrderType.labelOf(order.getOrderType())),
csv(order.getInboundCode()),
2026-08-19 16:24:02 +08:00
csv(order.getSourceOrderNo()), csv(order.getCustomerName()), csv(order.getOverseasWarehouseName()),
csv(order.getTransferWarehouseName()), csv(order.getTransferWarehouseCode()), csv(order.getFbaWarehouseCode()),
csv(order.getContainerNo()), csv(order.getContainerStatus()), csv(order.getAbnormal()), csv(order.getDetained()),
2026-08-20 18:02:38 +08:00
csv(order.getPalletCount()), csv(order.getPackageCount()), csv(order.getVolumeCbm()), csv(order.getProductQuantity()),
2026-08-19 16:24:02 +08:00
csv(order.getVolumeWeightKg()), csv(order.getEstimatedArrivalAt()), csv(order.getActualArrivalAt()),
2026-08-20 18:02:38 +08:00
csv(order.getEstimatedWarehouseAt()), csv(order.getInboundOrderNo()), csv(order.getRelabelOrderNo()), csv(order.getRelabelStatus()),
2026-08-19 16:24:02 +08:00
csv(order.getPalletMergeFlag()), csv(order.getCurrentStatus()), csv(order.getSubOrderStatus()), csv(order.getPrivateRemark()),
csv(order.getCustomerRemark()), csv(order.getWarehouseRemark()))));
}
return ("\uFEFF" + String.join("\r\n", lines)).getBytes(StandardCharsets.UTF_8);
}
private LambdaQueryWrapper<LastlegOrder> filter(String customerId, LastlegOrderRequests.Query query) {
if (query.createdFrom() != null && query.createdTo() != null && query.createdFrom().isAfter(query.createdTo())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "createdFrom must not be after createdTo");
}
2026-08-21 14:42:40 +08:00
String currentStatus = StringUtils.hasText(query.currentStatus()) ? query.currentStatus() : query.status();
2026-08-19 16:24:02 +08:00
return new LambdaQueryWrapper<LastlegOrder>()
2026-08-21 10:43:25 +08:00
.eq(StringUtils.hasText(customerId), LastlegOrder::getCustomerId, customerId)
2026-08-19 16:24:02 +08:00
.like(StringUtils.hasText(query.orderNo()), LastlegOrder::getOrderNo, query.orderNo())
2026-08-21 14:42:40 +08:00
.eq(StringUtils.hasText(query.orderType()), LastlegOrder::getOrderType, query.orderType())
2026-08-19 16:24:02 +08:00
.like(StringUtils.hasText(query.inboundCode()), LastlegOrder::getInboundCode, query.inboundCode())
.like(StringUtils.hasText(query.sourceOrderNo()), LastlegOrder::getSourceOrderNo, query.sourceOrderNo())
.like(StringUtils.hasText(query.containerNo()), LastlegOrder::getContainerNo, query.containerNo())
2026-08-21 14:42:40 +08:00
.eq(StringUtils.hasText(currentStatus), LastlegOrder::getCurrentStatus, currentStatus)
.and(StringUtils.hasText(query.warehouse()), wrapper -> wrapper
.like(LastlegOrder::getOverseasWarehouseId, query.warehouse())
.or().like(LastlegOrder::getOverseasWarehouseName, query.warehouse())
.or().like(LastlegOrder::getTransferWarehouseCode, query.warehouse())
.or().like(LastlegOrder::getTransferWarehouseName, query.warehouse()))
2026-08-19 16:24:02 +08:00
.eq(StringUtils.hasText(query.transferWarehouseCode()), LastlegOrder::getTransferWarehouseCode,
query.transferWarehouseCode())
.eq(query.abnormal() != null, LastlegOrder::getAbnormal, query.abnormal())
.eq(query.detained() != null, LastlegOrder::getDetained, query.detained())
.ge(query.createdFrom() != null, LastlegOrder::getCreatedAt, query.createdFrom())
.le(query.createdTo() != null, LastlegOrder::getCreatedAt, query.createdTo());
}
private LastlegOrder findOwned(String customerId, long orderId) {
LastlegOrder order = orderMapper.selectOne(new LambdaQueryWrapper<LastlegOrder>()
2026-08-21 10:43:25 +08:00
.eq(StringUtils.hasText(customerId), LastlegOrder::getCustomerId, customerId)
.eq(LastlegOrder::getId, orderId));
2026-08-19 16:24:02 +08:00
if (order == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tail-leg order not found");
return order;
}
private void ensureTransferable(LastlegOrder order) {
if (StringUtils.hasText(order.getInboundOrderNo()) || INBOUND_PLANNED.equals(order.getCurrentStatus())) {
throw new ResponseStatusException(HttpStatus.CONFLICT,
"Destination warehouse cannot be changed after inbound plan creation: " + order.getId());
}
}
private LastlegOrderSummary summary(LastlegOrder o) {
2026-08-22 12:35:32 +08:00
return new LastlegOrderSummary(o.getId(), o.getOrderNo(), o.getOrderType(),
LastlegOrderType.labelOf(o.getOrderType()), o.getSourceInboundId(),
2026-08-21 10:43:25 +08:00
o.getInboundCode(), o.getSourceOrderNo(), o.getCustomerId(), o.getCustomerName(), o.getOverseasWarehouseId(),
2026-08-19 16:24:02 +08:00
o.getOverseasWarehouseName(), o.getTransferWarehouseId(), o.getTransferWarehouseCode(),
o.getTransferWarehouseName(), o.getFbaWarehouseCode(), o.getContainerNo(), o.getContainerStatus(),
2026-08-20 18:02:38 +08:00
o.getAbnormal(), o.getDetained(), o.getPalletCount(), o.getPackageCount(), o.getVolumeCbm(),
2026-08-19 16:24:02 +08:00
o.getProductQuantity(), o.getVolumeWeightKg(), o.getEstimatedArrivalAt(), o.getActualArrivalAt(),
2026-08-20 18:02:38 +08:00
o.getEstimatedWarehouseAt(), o.getInboundOrderNo(), o.getRelabelOrderNo(), o.getRelabelStatus(),
o.getPalletMergeFlag(), o.getCurrentStatus(), o.getSubOrderStatus(), o.getPrivateRemark(),
o.getCustomerRemark(), o.getWarehouseRemark(), o.getCreatedAt(), o.getUpdatedAt());
2026-08-19 16:24:02 +08:00
}
private static String csv(Object value) {
if (value == null) return "";
String text = String.valueOf(value);
if (!text.isEmpty() && "=+-@".indexOf(text.charAt(0)) >= 0) text = "'" + text;
return '"' + text.replace("\"", "\"\"") + '"';
}
}