feat: complete tail order controls and terminal drayage

This commit is contained in:
2026-09-07 23:58:18 +08:00
parent f018ca85a9
commit e32fe65fa8
53 changed files with 2566 additions and 87 deletions
@@ -3,8 +3,8 @@ package com.baiyue.lastleg.inboundplan.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.inboundplan.dto.ConvertInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.dto.ConvertContainerInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.dto.ManualInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.dto.BatchFbaAppointmentRequest;
import com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanResponse;
@@ -44,14 +44,19 @@ public class InboundPlanController {
return ApiResponse.success(service.page(scope(user, customerId), query));
}
@PostMapping("/convert")
@PostMapping("/manual")
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_CREATE + "')")
/** 由平台人工将待分配尾程订单转换为入仓计划。 */
@Operation(summary = "人工创建入仓计划", description = "头程装柜完成只生成待分配尾程订单;平台确认后调用本接口创建入仓计划。Required authority: "
@Operation(summary = "手工填写并创建单票入仓计划", description = "要求订单版本和 Idempotency-Key;仓库主档、入仓码及客户归属均由服务端校验。Required authority: "
+ OverseasPermissions.INBOUND_ORDER_CREATE)
public ApiResponse<InboundPlanResponse> convert(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ConvertInboundPlanRequest request) {
return ApiResponse.success(service.convert(convertScope(user, request.customerId()), request.orderId()));
public ApiResponse<InboundPlanResponse> manual(@AuthenticationPrincipal AuthenticatedUser user,
@RequestHeader("Idempotency-Key") String idempotencyKey,
@Valid @RequestBody ManualInboundPlanRequest request) {
if (user == null || user.tenantId() == null || user.tenantId() != 0L || !"PLATFORM".equals(user.appCode())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "仅平台账号可手工创建入仓计划");
}
convertScope(user, request.customerId());
String operatorId = user.userId() == null ? user.username() : String.valueOf(user.userId());
return ApiResponse.success(service.convertManual(request, idempotencyKey, operatorId));
}
@PostMapping("/convert-container")
@@ -1,10 +0,0 @@
package com.baiyue.lastleg.inboundplan.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public record ConvertInboundPlanRequest(
@NotNull Long orderId,
@Size(max = 128) String customerId
) {
}
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.inboundplan.dto;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.List;
public record InboundPlanResponse(
@@ -21,7 +22,14 @@ public record InboundPlanResponse(
List<Line> lines,
List<Pallet> pallets,
List<Allocation> allocations,
int version
int version,
LocalDate plannedArrivalDate,
String palletType,
boolean assigned,
boolean mergeRequired,
boolean urgent,
String creationMode,
String remark
) {
public record Line(Long id, Long orderId, String inboundCode, String fbaWarehouseCode, String destinationWarehouseId,
String destinationWarehouseCode, String destinationWarehouseName,
@@ -32,7 +40,7 @@ public record InboundPlanResponse(
String destinationWarehouseCode, String destinationWarehouseName,
BigDecimal capacityCbm, BigDecimal allocatedVolumeCbm, int sequenceNo,
Long warehousePalletId, Long warehouseLocationId, Long warehouseResourceLockId,
String assignmentStatus, LocalDateTime lockedAt, BigDecimal actualVolumeCbm,
String palletType, String assignmentStatus, LocalDateTime lockedAt, BigDecimal actualVolumeCbm,
int actualPackageCount) {
}
@@ -0,0 +1,25 @@
package com.baiyue.lastleg.inboundplan.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
public record ManualInboundPlanRequest(
@NotBlank @Size(max = 128) String customerId,
@NotNull Long orderId,
@NotNull @Min(0) Integer expectedOrderVersion,
@NotNull Long plannedWarehouseId,
@NotBlank @Size(max = 128) String plannedWarehouseCode,
@NotBlank @Size(max = 255) String plannedWarehouseName,
@NotNull LocalDate plannedArrivalDate,
@NotBlank @Size(max = 128) String inboundCode,
@NotNull @Min(1) @Max(999) Integer palletCount,
@NotBlank @Size(max = 32) String palletType,
@NotNull Boolean assigned,
@NotNull Boolean mergeRequired,
@NotNull Boolean urgent,
@Size(max = 1000) String remark
) { }
@@ -8,6 +8,7 @@ import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDate;
@Data
@TableName("ll_inbound_plan")
@@ -19,6 +20,13 @@ public class InboundPlan {
private Long sourceOrderId;
private String containerNo;
private Long overseasWarehouseId;
private LocalDate plannedArrivalDate;
private String palletType;
private Boolean assignedFlag;
private Boolean mergeRequired;
private Boolean urgentFlag;
private String creationMode;
private String planRemark;
private String status;
private Integer lineCount;
private Integer palletCount;
@@ -17,6 +17,7 @@ public class InboundPlanPallet {
private Long planId;
private String palletNo;
private String sourcePalletNo;
private String palletType;
private String destinationWarehouseId;
private String destinationWarehouseCode;
private String destinationWarehouseName;
@@ -36,12 +36,12 @@ public interface InboundPlanOrderMapper {
"COALESCE(NULLIF(o.transfer_warehouse_name,''),MIN(NULLIF(d.destination_warehouse_name,''))) AS destinationWarehouseName," +
"CASE WHEN COALESCE(NULLIF(o.transfer_warehouse_id,''),NULLIF(o.transfer_warehouse_code,'')) IS NOT NULL THEN 1 " +
"ELSE COUNT(DISTINCT COALESCE(NULLIF(d.destination_warehouse_id,''),NULLIF(d.destination_warehouse_code,''),'#EMPTY#')) END AS destinationCount," +
"0 AS destinationMismatchCount " +
"0 AS destinationMismatchCount,o.detained AS detained,o.version AS version " +
"FROM ll_order o LEFT JOIN ll_order_detail d ON d.order_id=o.id " +
"WHERE o.customer_id=#{customerId} AND o.id=#{orderId} " +
"GROUP BY o.id,o.customer_id,o.container_no,o.inbound_code,o.current_status,o.inbound_order_no,o.volume_cbm,o.package_count,o.pallet_count," +
"o.overseas_warehouse_id,o.overseas_warehouse_code,o.overseas_warehouse_name,o.fba_warehouse_code," +
"o.transfer_warehouse_id,o.transfer_warehouse_code,o.transfer_warehouse_name " +
"o.transfer_warehouse_id,o.transfer_warehouse_code,o.transfer_warehouse_name,o.detained,o.version " +
"ORDER BY o.id FOR UPDATE")
List<SourceOrderSnapshot> lockOrder(@Param("customerId") String customerId,
@Param("orderId") Long orderId);
@@ -59,6 +59,17 @@ public interface InboundPlanOrderMapper {
@Param("planNo") String planNo, @Param("palletCount") int palletCount,
@Param("updatedAt") LocalDateTime updatedAt);
@Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," +
"pallet_merge_flag=CASE WHEN #{mergeRequired}=1 THEN 'MERGE_REQUIRED' ELSE NULL END," +
"pallet_count=#{palletCount},version=version+1,updated_at=#{updatedAt} " +
"WHERE customer_id=#{customerId} AND id=#{orderId} AND version=#{expectedVersion} AND detained=0 " +
"AND current_status IN ('PENDING_ASSIGNMENT','DESTINATION_ASSIGNED') AND inbound_order_no IS NULL")
int markPlannedManual(@Param("customerId") String customerId, @Param("orderId") Long orderId,
@Param("planNo") String planNo, @Param("palletCount") int palletCount,
@Param("mergeRequired") boolean mergeRequired,
@Param("expectedVersion") int expectedVersion,
@Param("updatedAt") LocalDateTime updatedAt);
@Insert("INSERT INTO ll_loading_order_status_history(customer_id,order_id,from_status,to_status,remark,created_at) " +
"VALUES(#{customerId},#{orderId},#{fromStatus},'INBOUND_PLANNED',#{remark},#{createdAt})")
int insertHistory(@Param("customerId") String customerId, @Param("orderId") Long orderId, @Param("fromStatus") String fromStatus,
@@ -20,5 +20,19 @@ public record SourceOrderSnapshot(
String destinationWarehouseCode,
String destinationWarehouseName,
int destinationCount,
int destinationMismatchCount
) { }
int destinationMismatchCount,
boolean detained,
int version
) {
public SourceOrderSnapshot(Long orderId, String customerId, String containerNo, String inboundCode,
String currentStatus, String inboundOrderNo, BigDecimal volumeCbm,
int packageCount, int recommendedPalletCount, String overseasWarehouseId,
String overseasWarehouseCode, String overseasWarehouseName, String fbaWarehouseCode,
String destinationWarehouseId, String destinationWarehouseCode,
String destinationWarehouseName, int destinationCount, int destinationMismatchCount) {
this(orderId, customerId, containerNo, inboundCode, currentStatus, inboundOrderNo, volumeCbm,
packageCount, recommendedPalletCount, overseasWarehouseId, overseasWarehouseCode, overseasWarehouseName,
fbaWarehouseCode, destinationWarehouseId, destinationWarehouseCode, destinationWarehouseName,
destinationCount, destinationMismatchCount, false, 0);
}
}
@@ -6,6 +6,7 @@ import com.baiyue.common.core.api.PageResult;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanListResponse;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanRequests;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanResponse;
import com.baiyue.lastleg.inboundplan.dto.ManualInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.dto.SupplementPalletRequest;
import com.baiyue.lastleg.inboundplan.dto.UpdateInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.entity.InboundPlan;
@@ -23,6 +24,11 @@ import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
import com.baiyue.common.event.ContainerNumbers;
import com.baiyue.lastleg.entity.LastlegOrderCommandRecord;
import com.baiyue.lastleg.mapper.LastlegOrderCommandRecordMapper;
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@@ -31,6 +37,10 @@ import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -50,6 +60,64 @@ public class InboundPlanService {
@Resource private InboundPlanQueryMapper queryMapper;
@Resource private InventoryBalanceMapper inventoryBalanceMapper;
@Resource private DropshipOrderService dropshipOrders;
@Resource private WarehouseResourceGateway warehouseGateway;
@Resource private LastlegOrderCommandRecordMapper commandMapper;
@Resource private ObjectMapper objectMapper;
@Transactional
public InboundPlanResponse convertManual(ManualInboundPlanRequest request, String idempotencyKey,
String operatorId) {
String customerId = trimToNull(request.customerId());
if (customerId == null || "0".equals(customerId)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "平台人工计划必须指定客户");
}
String key = trimToNull(idempotencyKey);
if (key == null || key.length() > 128) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Idempotency-Key 必须为1至128个字符");
}
List<SourceOrderSnapshot> snapshots = orderMapper.lockOrder(customerId, request.orderId());
if (snapshots == null || snapshots.size() != 1) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "尾程订单不存在");
}
SourceOrderSnapshot order = snapshots.get(0);
String requestHash = sha256(request);
LastlegOrderCommandRecord replay = commandMapper.find(customerId, request.orderId(), "MANUAL_PLAN", operatorId, key);
if (replay != null) {
if (!MessageDigest.isEqual(replay.getRequestHash().getBytes(StandardCharsets.UTF_8),
requestHash.getBytes(StandardCharsets.UTF_8))) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "同一幂等键不能用于不同请求");
}
return detailByPlanNo(customerId, replay.getResultRef());
}
if (planMapper.findBySourceOrder(customerId, request.orderId()) != null) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "尾程订单已生成入库计划");
}
if (order.version() != request.expectedOrderVersion()) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "尾程订单版本已变化,请刷新后重试");
}
if (order.detained()) throw new ResponseStatusException(HttpStatus.CONFLICT, "暂扣尾程订单不能生成入库计划");
if (!Objects.equals(trimToNull(order.inboundCode()), trimToNull(request.inboundCode()))) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "入仓码与尾程订单冻结事实不一致");
}
WarehouseResourceGateway.WarehouseReference warehouse =
warehouseGateway.requireActiveSharedWarehouse(request.plannedWarehouseId());
if (!Objects.equals(String.valueOf(warehouse.warehouseId()), trimToNull(order.overseasWarehouseId()))
|| !Objects.equals(warehouse.warehouseCode(), request.plannedWarehouseCode().trim())
|| !Objects.equals(warehouse.warehouseName(), request.plannedWarehouseName().trim())
|| !Objects.equals(warehouse.warehouseCode(), trimToNull(order.overseasWarehouseCode()))
|| !Objects.equals(warehouse.warehouseName(), trimToNull(order.overseasWarehouseName()))) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "计划入仓仓库不存在、已停用或与订单预分配事实不一致");
}
validateOrders(snapshots);
InboundPlanResponse response = createPlan(customerId, request.orderId(), snapshots, List.of(), null, request);
LastlegOrderCommandRecord command = new LastlegOrderCommandRecord();
command.setId(IdWorker.getId()); command.setCustomerId(customerId); command.setOrderId(request.orderId());
command.setAction("MANUAL_PLAN"); command.setOperatorId(operatorId); command.setIdempotencyKey(key);
command.setRequestHash(requestHash); command.setResultRef(response.planNo());
command.setResultJson(json(response)); command.setResultVersion(request.expectedOrderVersion() + 1);
command.setCreatedAt(LocalDateTime.now()); commandMapper.insert(command);
return response;
}
@Transactional(readOnly = true)
public PageResult<InboundPlanListResponse> page(String customerId, InboundPlanRequests.Query query) {
@@ -112,12 +180,12 @@ public class InboundPlanService {
return detail(customerId, existing);
}
validateOrders(orders);
return createPlan(customerId, selectedOrderId, orders, sourcePalletNos, wholePallets);
return createPlan(customerId, selectedOrderId, orders, sourcePalletNos, wholePallets, null);
}
private InboundPlanResponse createPlan(String customerId, Long selectedOrderId,
List<SourceOrderSnapshot> orders, List<String> sourcePalletNos,
List<WholePalletInput> wholePallets) {
List<WholePalletInput> wholePallets, ManualInboundPlanRequest manual) {
String containerNo = requiredContainerNo(orders.get(0).containerNo());
LocalDateTime now = LocalDateTime.now();
InboundPlan plan = new InboundPlan();
@@ -136,6 +204,13 @@ public class InboundPlanService {
throw new ResponseStatusException(HttpStatus.CONFLICT, "接柜仓ID格式无效", exception);
}
plan.setStatus("PENDING_ASSIGNMENT");
plan.setPlannedArrivalDate(manual == null ? null : manual.plannedArrivalDate());
plan.setPalletType(manual == null ? null : manual.palletType().trim().toUpperCase());
plan.setAssignedFlag(manual != null && manual.assigned());
plan.setMergeRequired(manual != null && manual.mergeRequired());
plan.setUrgentFlag(manual != null && manual.urgent());
plan.setCreationMode(manual == null ? "AUTO" : "MANUAL");
plan.setPlanRemark(manual == null ? null : trimToNull(manual.remark()));
plan.setLineCount(orders.size());
plan.setTotalVolumeCbm(orders.stream().map(SourceOrderSnapshot::volumeCbm)
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
@@ -145,16 +220,20 @@ public class InboundPlanService {
Map<Long, SourceOrderSnapshot> orderById = orders.stream()
.collect(Collectors.toMap(SourceOrderSnapshot::orderId, value -> value));
Map<Long, InboundPlanLine> lineByOrder = createLines(plan, orders);
PalletAllocator.Result allocation = wholePallets == null ? PalletAllocator.allocate(orders.stream()
PalletAllocator.Result allocation = wholePallets == null && manual == null ? PalletAllocator.allocate(orders.stream()
.map(order -> new PalletAllocator.LineInput(order.orderId(), warehouseKey(order), volume(order),
order.packageCount(), order.recommendedPalletCount())).toList()) : null;
if (wholePallets != null) validateWholePallets(orders, wholePallets);
plan.setPalletCount(wholePallets == null ? allocation.pallets().size() : wholePallets.size());
plan.setPalletCount(manual != null ? manual.palletCount()
: wholePallets == null ? allocation.pallets().size() : wholePallets.size());
planMapper.insert(plan);
lineByOrder.values().forEach(lineMapper::insert);
Map<Long, Integer> palletCounts;
if (wholePallets == null) {
if (manual != null) {
createManualPallets(plan, orders.get(0), lineByOrder.get(orders.get(0).orderId()), manual);
palletCounts = Map.of(orders.get(0).orderId(), manual.palletCount());
} else if (wholePallets == null) {
Map<PalletAllocator.PalletDraft, InboundPlanPallet> persistedPallets = createPallets(plan, allocation,
orderById, sourcePalletNos == null ? List.of() : sourcePalletNos);
createAllocations(plan, allocation, lineByOrder, persistedPallets);
@@ -171,7 +250,11 @@ public class InboundPlanService {
InboundPlanLine line = lineByOrder.get(order.orderId());
line.setPlannedPalletCount(count);
lineMapper.updateById(line);
if (orderMapper.markPlanned(customerId, order.orderId(), plan.getPlanNo(), count, now) != 1) {
int updated = manual == null
? orderMapper.markPlanned(customerId, order.orderId(), plan.getPlanNo(), count, now)
: orderMapper.markPlannedManual(customerId, order.orderId(), plan.getPlanNo(), count,
manual.mergeRequired(), manual.expectedOrderVersion(), now);
if (updated != 1) {
throw new IllegalStateException("Failed to update order " + order.orderId());
}
inventoryBalanceMapper.bindInboundPlan(customerId, order.orderId(), order.inboundCode(),
@@ -248,7 +331,7 @@ public class InboundPlanService {
for (int index = 0; index < refs.size(); index++) {
InboundPlanOrderMapper.ContainerOrderRef ref = refs.get(index);
created.add(createPlan(ref.customerId(), ref.orderId(), snapshots.get(index), List.of(),
wholePallets.get(index)));
wholePallets.get(index), null));
}
return List.copyOf(created);
}
@@ -257,6 +340,43 @@ public class InboundPlanService {
return new WholePalletInput(pallet.sourcePalletNo(), Math.toIntExact(pallet.packageCount()));
}
private void createManualPallets(InboundPlan plan, SourceOrderSnapshot order, InboundPlanLine line,
ManualInboundPlanRequest request) {
if (order.packageCount() > 0 && request.palletCount() > order.packageCount()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "人工计划托盘数不能大于订单箱数");
}
BigDecimal remainingVolume = volume(order);
int remainingPackages = order.packageCount();
for (int index = 0; index < request.palletCount(); index++) {
int slots = request.palletCount() - index;
int packages = slots == 1 ? remainingPackages : remainingPackages / slots;
BigDecimal palletVolume = slots == 1 ? remainingVolume
: remainingVolume.divide(BigDecimal.valueOf(slots), 4, java.math.RoundingMode.HALF_UP);
InboundPlanPallet pallet = new InboundPlanPallet();
pallet.setId(IdWorker.getId()); pallet.setCustomerId(plan.getCustomerId()); pallet.setPlanId(plan.getId());
pallet.setPalletNo(plan.getPlanNo() + "-P" + String.format("%03d", index + 1));
pallet.setPalletType(request.palletType().trim().toUpperCase());
pallet.setDestinationWarehouseId(order.destinationWarehouseId());
pallet.setDestinationWarehouseCode(order.destinationWarehouseCode());
pallet.setDestinationWarehouseName(order.destinationWarehouseName());
pallet.setCapacityCbm(PalletAllocator.PALLET_CAPACITY_CBM); pallet.setAllocatedVolumeCbm(palletVolume);
pallet.setSequenceNo(index + 1); pallet.setAssignmentStatus("UNASSIGNED");
pallet.setActualVolumeCbm(BigDecimal.ZERO); pallet.setActualPackageCount(0); palletMapper.insert(pallet);
InboundPlanPalletAllocation allocation = new InboundPlanPalletAllocation();
allocation.setId(IdWorker.getId()); allocation.setCustomerId(plan.getCustomerId()); allocation.setPlanId(plan.getId());
allocation.setLineId(line.getId()); allocation.setPalletId(pallet.getId());
allocation.setAllocatedVolumeCbm(palletVolume); allocation.setPlannedPackageCount(packages);
allocation.setCountedPackageCount(0); allocation.setPickedPackageCount(0);
allocation.setActualVolumeCbm(BigDecimal.ZERO); allocation.setStatus("PENDING_COUNT"); allocation.setVersion(0);
allocationMapper.insert(allocation);
remainingPackages -= packages; remainingVolume = remainingVolume.subtract(palletVolume);
}
if (remainingPackages != 0 || remainingVolume.signum() != 0) {
throw new IllegalStateException("人工计划托盘分配未保持箱数或体积守恒");
}
}
private String requiredContainerNo(String value) {
try {
return ContainerNumbers.requireCanonical(value);
@@ -395,7 +515,7 @@ public class InboundPlanService {
plan.setPalletCount(plan.getPalletCount() + 1); plan.setUpdatedAt(LocalDateTime.now()); planMapper.updateById(plan);
return new InboundPlanResponse.Pallet(pallet.getId(), pallet.getPalletNo(), pallet.getDestinationWarehouseId(),
pallet.getDestinationWarehouseCode(), pallet.getDestinationWarehouseName(), pallet.getCapacityCbm(),
pallet.getAllocatedVolumeCbm(), pallet.getSequenceNo(), null, null, null, pallet.getAssignmentStatus(),
pallet.getAllocatedVolumeCbm(), pallet.getSequenceNo(), null, null, null, pallet.getPalletType(), pallet.getAssignmentStatus(),
null, pallet.getActualVolumeCbm(), pallet.getActualPackageCount());
}
@@ -533,12 +653,14 @@ public class InboundPlanService {
pallet.getDestinationWarehouseId(), pallet.getDestinationWarehouseCode(), pallet.getDestinationWarehouseName(),
pallet.getCapacityCbm(), pallet.getAllocatedVolumeCbm(), pallet.getSequenceNo(),
pallet.getWarehousePalletId(), pallet.getWarehouseLocationId(), pallet.getWarehouseResourceLockId(),
pallet.getAssignmentStatus(), pallet.getLockedAt(), pallet.getActualVolumeCbm(),
pallet.getPalletType(), pallet.getAssignmentStatus(), pallet.getLockedAt(), pallet.getActualVolumeCbm(),
pallet.getActualPackageCount())).toList(),
allocations.stream().map(item -> new InboundPlanResponse.Allocation(item.getLineId(), item.getPalletId(),
item.getAllocatedVolumeCbm(), item.getPlannedPackageCount(), item.getCountedPackageCount(),
item.getPickedPackageCount(), item.getActualVolumeCbm(), item.getStatus(), item.getVersion())).toList(),
plan.getVersion() == null ? 0 : plan.getVersion());
plan.getVersion() == null ? 0 : plan.getVersion(), plan.getPlannedArrivalDate(), plan.getPalletType(),
Boolean.TRUE.equals(plan.getAssignedFlag()), Boolean.TRUE.equals(plan.getMergeRequired()),
Boolean.TRUE.equals(plan.getUrgentFlag()), plan.getCreationMode(), plan.getPlanRemark());
}
private BigDecimal volume(SourceOrderSnapshot order) {
@@ -554,4 +676,18 @@ public class InboundPlanService {
private static String trimToNull(String value) {
return value == null || value.isBlank() ? null : value.trim();
}
private String sha256(Object value) {
try {
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
.digest(objectMapper.writeValueAsBytes(value)));
} catch (NoSuchAlgorithmException | JsonProcessingException exception) {
throw new IllegalStateException("无法生成命令摘要", exception);
}
}
private String json(Object value) {
try { return objectMapper.writeValueAsString(value); }
catch (JsonProcessingException exception) { throw new IllegalStateException("无法保存命令结果", exception); }
}
}