feat: complete tail order controls and terminal drayage
This commit is contained in:
+12
-7
@@ -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")
|
||||
|
||||
-10
@@ -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
|
||||
) {
|
||||
}
|
||||
+10
-2
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
+25
@@ -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;
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+13
-2
@@ -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,
|
||||
|
||||
+16
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+146
-10
@@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user