Merge remote-tracking branch 'origin/test' into dev
# Conflicts: # firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java
This commit is contained in:
@@ -74,6 +74,8 @@ public class LoadingListService {
|
|||||||
@Resource
|
@Resource
|
||||||
private LoadingCompletedOutboxService loadingCompletedOutboxService;
|
private LoadingCompletedOutboxService loadingCompletedOutboxService;
|
||||||
@Resource
|
@Resource
|
||||||
|
private FbaPricingService fbaPricingService;
|
||||||
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private LoadingDocumentFileClient documentFiles;
|
private LoadingDocumentFileClient documentFiles;
|
||||||
@@ -501,6 +503,7 @@ public class LoadingListService {
|
|||||||
|
|
||||||
AggregateRow before = requirePlan(planId);
|
AggregateRow before = requirePlan(planId);
|
||||||
List<Link> links = loadingPickService.requirePreloadConfirmationReady(planId);
|
List<Link> links = loadingPickService.requirePreloadConfirmationReady(planId);
|
||||||
|
requireContainerFeesReadyForPreloadConfirmation(links, operationId);
|
||||||
if (repository.touchForPreloadConfirmation(planId, request.expectedVersion(),
|
if (repository.touchForPreloadConfirmation(planId, request.expectedVersion(),
|
||||||
operatorId, trim(operatorName)) != 1) {
|
operatorId, trim(operatorName)) != 1) {
|
||||||
throw conflict("预装计划已被其他操作修改,请刷新后重试");
|
throw conflict("预装计划已被其他操作修改,请刷新后重试");
|
||||||
@@ -610,6 +613,30 @@ public class LoadingListService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-review/pre-measurement members may be reserved in an editing plan. Confirmation is the
|
||||||
|
* hard boundary: every customer group in the cabinet must have a ready, freshly recomputed fee.
|
||||||
|
*/
|
||||||
|
private void requireContainerFeesReadyForPreloadConfirmation(List<Link> links, String operationId) {
|
||||||
|
Map<Long, Set<Long>> tenantsByContainer = new LinkedHashMap<>();
|
||||||
|
for (Link link : links) {
|
||||||
|
if (link.containerBookingId() == null || link.tenantId() == null) {
|
||||||
|
throw conflict("预装关联缺少柜次或客户归属,不能确认预装");
|
||||||
|
}
|
||||||
|
tenantsByContainer.computeIfAbsent(link.containerBookingId(), ignored -> new java.util.LinkedHashSet<>())
|
||||||
|
.add(link.tenantId());
|
||||||
|
}
|
||||||
|
for (Map.Entry<Long, Set<Long>> entry : tenantsByContainer.entrySet()) {
|
||||||
|
for (Long tenantId : entry.getValue()) {
|
||||||
|
var result = fbaPricingService.recalculateUnlockedContainerCustomerFees(entry.getKey(), tenantId,
|
||||||
|
sha256("CONFIRM_PRELOAD_FEE|" + operationId + '|' + entry.getKey() + '|' + tenantId));
|
||||||
|
if (result.status() == FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY) {
|
||||||
|
throw conflict("柜内存在尚未完成初始费用的入仓单,不能确认预装");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void validateRange(java.time.LocalDateTime from, java.time.LocalDateTime to) {
|
private static void validateRange(java.time.LocalDateTime from, java.time.LocalDateTime to) {
|
||||||
if (from != null && to != null && from.isAfter(to)) {
|
if (from != null && to != null && from.isAfter(to)) {
|
||||||
throw badRequest("创建开始时间不能晚于结束时间");
|
throw badRequest("创建开始时间不能晚于结束时间");
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public class SmartLoadingService {
|
|||||||
@Resource private LoadingModelBridgeService loadingModelBridge;
|
@Resource private LoadingModelBridgeService loadingModelBridge;
|
||||||
@Resource private SplitPreloadService splitPreloadService;
|
@Resource private SplitPreloadService splitPreloadService;
|
||||||
@Resource private ColoadGroupAssignmentPort coloadGroups;
|
@Resource private ColoadGroupAssignmentPort coloadGroups;
|
||||||
|
@Resource private FbaPricingService fbaPricingService;
|
||||||
@Resource private PriceBookQueryUseCase priceBooks;
|
@Resource private PriceBookQueryUseCase priceBooks;
|
||||||
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
||||||
@Resource private ObjectMapper objectMapper;
|
@Resource private ObjectMapper objectMapper;
|
||||||
@@ -430,7 +431,13 @@ public class SmartLoadingService {
|
|||||||
@BusinessAudit(event = "SMART_LOADING_ITEM_ASSIGN", resourceType = "LOADING_PLAN",
|
@BusinessAudit(event = "SMART_LOADING_ITEM_ASSIGN", resourceType = "LOADING_PLAN",
|
||||||
resourceId = "#planId", description = "分配智能装柜明细")
|
resourceId = "#planId", description = "分配智能装柜明细")
|
||||||
public Plan assign(long planId, AssignRequest request, long operatorId, String operatorName) {
|
public Plan assign(long planId, AssignRequest request, long operatorId, String operatorName) {
|
||||||
return assign(planId, request, operatorId, operatorName, false, null, null, null, false, null);
|
return assign(planId, request, operatorId, operatorName, false, null, null, null, false, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Plan assign(long planId, AssignRequest request, long operatorId, String operatorName,
|
||||||
|
boolean wholeGroupAssignment) {
|
||||||
|
return assign(planId, request, operatorId, operatorName, wholeGroupAssignment,
|
||||||
|
null, null, null, false, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -463,19 +470,21 @@ public class SmartLoadingService {
|
|||||||
String childOperation = "PAL-" + sha256(operation + '|' + line.getInboundDetailId()).substring(0, 48);
|
String childOperation = "PAL-" + sha256(operation + '|' + line.getInboundDetailId()).substring(0, 48);
|
||||||
assign(planId, new AssignRequest(line.getInboundId(), line.getInboundDetailId(), line.getCartonCount(),
|
assign(planId, new AssignRequest(line.getInboundId(), line.getInboundDetailId(), line.getCartonCount(),
|
||||||
current.getVersion(), childOperation), operatorId, operatorName, true, request.palletId(),
|
current.getVersion(), childOperation), operatorId, operatorName, true, request.palletId(),
|
||||||
line.getPalletNo(), line.getActualSkuQuantity(), sequence++ == 0, candidate);
|
line.getPalletNo(), line.getActualSkuQuantity(), sequence++ == 0, candidate, false);
|
||||||
}
|
}
|
||||||
LoadingPlanEntity assigned = repository.plan(planId);
|
LoadingPlanEntity assigned = repository.plan(planId);
|
||||||
history(planId, null, "ADD_PALLET", original.getPlanStatus(), assigned.getPlanStatus(), operation,
|
history(planId, null, "ADD_PALLET", original.getPlanStatus(), assigned.getPlanStatus(), operation,
|
||||||
json(values("palletId", request.palletId(), "version", request.expectedVersion())),
|
json(values("palletId", request.palletId(), "version", request.expectedVersion())),
|
||||||
json(values("requestHash", requestHash, "palletNo", first.getPalletNo(), "lineCount", lines.size())),
|
json(values("requestHash", requestHash, "palletNo", first.getPalletNo(), "lineCount", lines.size())),
|
||||||
operatorId, operatorName);
|
operatorId, operatorName);
|
||||||
|
recalculateAfterRelationshipChange(assigned.getContainerBookingId(), Set.of(first.getAccountId()), operation);
|
||||||
return view(assigned, requireBooking(assigned.getContainerBookingId()));
|
return view(assigned, requireBooking(assigned.getContainerBookingId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Plan assign(long planId, AssignRequest request, long operatorId, String operatorName,
|
private Plan assign(long planId, AssignRequest request, long operatorId, String operatorName,
|
||||||
boolean wholeGroupAssignment, Long palletId, String palletNo,
|
boolean wholeGroupAssignment, Long palletId, String palletNo,
|
||||||
Integer actualSkuQuantity, boolean primaryPalletLine, Candidate frozenCandidate) {
|
Integer actualSkuQuantity, boolean primaryPalletLine, Candidate frozenCandidate,
|
||||||
|
boolean recalculateFees) {
|
||||||
String operation = operation(request.operationId());
|
String operation = operation(request.operationId());
|
||||||
String requestHash = sha256("ASSIGN|" + planId + '|'
|
String requestHash = sha256("ASSIGN|" + planId + '|'
|
||||||
+ request.inboundId() + '|' + request.inboundDetailId() + '|' + request.packageCount()
|
+ request.inboundId() + '|' + request.inboundDetailId() + '|' + request.packageCount()
|
||||||
@@ -544,6 +553,9 @@ public class SmartLoadingService {
|
|||||||
loadingModelBridge.bindIncrementalAllocation(planId, candidate.accountId(), candidate.inboundId(),
|
loadingModelBridge.bindIncrementalAllocation(planId, candidate.accountId(), candidate.inboundId(),
|
||||||
operatorId, operatorName, operation);
|
operatorId, operatorName, operation);
|
||||||
loadingPickService.sync(planId, changed);
|
loadingPickService.sync(planId, changed);
|
||||||
|
if (recalculateFees) {
|
||||||
|
recalculateAfterRelationshipChange(booking.getId(), Set.of(candidate.accountId()), operation);
|
||||||
|
}
|
||||||
return view(repository.plan(planId), booking);
|
return view(repository.plan(planId), booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +569,7 @@ public class SmartLoadingService {
|
|||||||
resourceId = "#planId", description = "按入仓码分配智能装柜箱数")
|
resourceId = "#planId", description = "按入仓码分配智能装柜箱数")
|
||||||
public Plan assignInbound(long planId, AssignInboundRequest request,
|
public Plan assignInbound(long planId, AssignInboundRequest request,
|
||||||
long operatorId, String operatorName) {
|
long operatorId, String operatorName) {
|
||||||
return assignInbound(planId, request, operatorId, operatorName, false);
|
return assignInbound(planId, request, operatorId, operatorName, false, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -637,17 +649,24 @@ public class SmartLoadingService {
|
|||||||
loadingModelBridge.bindIncrementalAllocation(planId, request.customerId(), inboundId,
|
loadingModelBridge.bindIncrementalAllocation(planId, request.customerId(), inboundId,
|
||||||
operatorId, operatorName, operation);
|
operatorId, operatorName, operation);
|
||||||
created.forEach(item -> loadingPickService.sync(planId, item));
|
created.forEach(item -> loadingPickService.sync(planId, item));
|
||||||
|
recalculateAfterRelationshipChange(booking.getId(), Set.of(request.customerId()), operation);
|
||||||
return view(repository.plan(planId), booking);
|
return view(repository.plan(planId), booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Plan assignInbound(long planId, AssignInboundRequest request,
|
private Plan assignInbound(long planId, AssignInboundRequest request,
|
||||||
long operatorId, String operatorName, boolean wholeGroupAssignment) {
|
long operatorId, String operatorName, boolean wholeGroupAssignment) {
|
||||||
return assignInbound(planId, request, operatorId, operatorName, wholeGroupAssignment, null);
|
return assignInbound(planId, request, operatorId, operatorName, wholeGroupAssignment, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Plan assignInbound(long planId, AssignInboundRequest request,
|
private Plan assignInbound(long planId, AssignInboundRequest request,
|
||||||
long operatorId, String operatorName, boolean wholeGroupAssignment,
|
long operatorId, String operatorName, boolean wholeGroupAssignment,
|
||||||
List<Candidate> frozenDetails) {
|
List<Candidate> frozenDetails) {
|
||||||
|
return assignInbound(planId, request, operatorId, operatorName, wholeGroupAssignment, frozenDetails, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Plan assignInbound(long planId, AssignInboundRequest request,
|
||||||
|
long operatorId, String operatorName, boolean wholeGroupAssignment,
|
||||||
|
List<Candidate> frozenDetails, boolean recalculateFees) {
|
||||||
String operation = operation(request.operationId());
|
String operation = operation(request.operationId());
|
||||||
String requestHash = sha256("ASSIGN_INBOUND|" + planId + '|' + request.inboundId() + '|'
|
String requestHash = sha256("ASSIGN_INBOUND|" + planId + '|' + request.inboundId() + '|'
|
||||||
+ request.packageCount() + '|' + request.expectedVersion());
|
+ request.packageCount() + '|' + request.expectedVersion());
|
||||||
@@ -661,8 +680,13 @@ public class SmartLoadingService {
|
|||||||
if (!wholeGroupAssignment && projection != null && Set.of("PREPARING", "WAIT_MEASURE",
|
if (!wholeGroupAssignment && projection != null && Set.of("PREPARING", "WAIT_MEASURE",
|
||||||
"MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED", "WAIT_PRICING",
|
"MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED", "WAIT_PRICING",
|
||||||
"FEE_CONFIRMING", "INBOUNDING").contains(projection.getProcessStatus())) {
|
"FEE_CONFIRMING", "INBOUNDING").contains(projection.getProcessStatus())) {
|
||||||
return assignDeclaredInbound(planId, request, projection, operation, requestHash,
|
Plan assigned = assignDeclaredInbound(planId, request, projection, operation, requestHash,
|
||||||
operatorId, operatorName);
|
operatorId, operatorName);
|
||||||
|
if (recalculateFees) {
|
||||||
|
recalculateAfterRelationshipChange(requireBooking(repository.plan(planId).getContainerBookingId()).getId(),
|
||||||
|
Set.of(projection.getTenantId()), operation);
|
||||||
|
}
|
||||||
|
return assigned;
|
||||||
}
|
}
|
||||||
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||||
@@ -686,7 +710,7 @@ public class SmartLoadingService {
|
|||||||
.substring(0, 48);
|
.substring(0, 48);
|
||||||
assign(planId, new AssignRequest(request.inboundId(), allocation.candidate().inboundDetailId(),
|
assign(planId, new AssignRequest(request.inboundId(), allocation.candidate().inboundDetailId(),
|
||||||
allocation.packageCount(), current.getVersion(), childOperation), operatorId, operatorName,
|
allocation.packageCount(), current.getVersion(), childOperation), operatorId, operatorName,
|
||||||
wholeGroupAssignment, null, null, null, false, null);
|
wholeGroupAssignment, null, null, null, false, null, false);
|
||||||
sequence++;
|
sequence++;
|
||||||
}
|
}
|
||||||
LoadingPlanEntity assigned = repository.plan(planId);
|
LoadingPlanEntity assigned = repository.plan(planId);
|
||||||
@@ -695,6 +719,9 @@ public class SmartLoadingService {
|
|||||||
json(values("requestHash", requestHash, "packageCount", request.packageCount(),
|
json(values("requestHash", requestHash, "packageCount", request.packageCount(),
|
||||||
"specificationLineCount", sequence, "cartonIdentityStatus", "UNVERIFIED")),
|
"specificationLineCount", sequence, "cartonIdentityStatus", "UNVERIFIED")),
|
||||||
operatorId, operatorName);
|
operatorId, operatorName);
|
||||||
|
if (recalculateFees) {
|
||||||
|
recalculateAfterRelationshipChange(booking.getId(), Set.of(first.accountId()), operation);
|
||||||
|
}
|
||||||
return view(assigned, booking);
|
return view(assigned, booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,6 +852,8 @@ public class SmartLoadingService {
|
|||||||
json(values("requestHash", requestHash, "groupId", locked.groupId(),
|
json(values("requestHash", requestHash, "groupId", locked.groupId(),
|
||||||
"memberCount", locked.members().size(), "groupTotalBoxes", locked.fullPackageCount(),
|
"memberCount", locked.members().size(), "groupTotalBoxes", locked.fullPackageCount(),
|
||||||
"candidateInboundCount", memberCandidates.size())), operatorId, operatorName);
|
"candidateInboundCount", memberCandidates.size())), operatorId, operatorName);
|
||||||
|
recalculateAfterRelationshipChange(booking.getId(), locked.members().stream()
|
||||||
|
.map(Member::tenantId).collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)), operation);
|
||||||
return view(assigned, booking);
|
return view(assigned, booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,6 +893,8 @@ public class SmartLoadingService {
|
|||||||
json(values("version", request.expectedPlanVersion(), "groupId", groupId)),
|
json(values("version", request.expectedPlanVersion(), "groupId", groupId)),
|
||||||
json(values("requestHash", requestHash, "groupId", groupId, "deleted", true)),
|
json(values("requestHash", requestHash, "groupId", groupId, "deleted", true)),
|
||||||
operatorId, operatorName);
|
operatorId, operatorName);
|
||||||
|
recalculateAfterRelationshipChange(booking.getId(), group.members().stream()
|
||||||
|
.map(Member::tenantId).collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)), operation);
|
||||||
return view(repository.plan(planId), booking);
|
return view(repository.plan(planId), booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,6 +1085,7 @@ public class SmartLoadingService {
|
|||||||
history(planId, itemId, "REMOVE_ITEM", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
history(planId, itemId, "REMOVE_ITEM", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
||||||
json(values("version", request.expectedPlanVersion(), "packageCount", item.getPackageCount())),
|
json(values("version", request.expectedPlanVersion(), "packageCount", item.getPackageCount())),
|
||||||
json(values("requestHash", requestHash, "deleted", true)), operatorId, operatorName);
|
json(values("requestHash", requestHash, "deleted", true)), operatorId, operatorName);
|
||||||
|
recalculateAfterRelationshipChange(plan.getContainerBookingId(), Set.of(item.getAccountId()), operation);
|
||||||
return view(repository.plan(planId), requireBooking(plan.getContainerBookingId()));
|
return view(repository.plan(planId), requireBooking(plan.getContainerBookingId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1160,6 +1192,10 @@ public class SmartLoadingService {
|
|||||||
json(values("version", request.expectedPlanVersion(), "itemCount", items.size())),
|
json(values("version", request.expectedPlanVersion(), "itemCount", items.size())),
|
||||||
json(values("requestHash", requestHash, "removedItemCount", removable.size(),
|
json(values("requestHash", requestHash, "removedItemCount", removable.size(),
|
||||||
"removedGroupCount", removableGroupIds.size())), operatorId, operatorName);
|
"removedGroupCount", removableGroupIds.size())), operatorId, operatorName);
|
||||||
|
if (!removable.isEmpty()) {
|
||||||
|
recalculateAfterRelationshipChange(booking.getId(), removable.stream().map(LoadingPlanItemEntity::getAccountId)
|
||||||
|
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)), operation);
|
||||||
|
}
|
||||||
return view(repository.plan(planId), booking);
|
return view(repository.plan(planId), booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1662,6 +1698,17 @@ public class SmartLoadingService {
|
|||||||
private static void requireSameCustomerScope(ContainerBookingEntity booking, Candidate candidate) {
|
private static void requireSameCustomerScope(ContainerBookingEntity booking, Candidate candidate) {
|
||||||
if (!sameCustomerScope(booking, candidate)) throw forbidden();
|
if (!sameCustomerScope(booking, candidate)) throw forbidden();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* A draft may legitimately contain pre-review or pre-measurement members without a fee snapshot.
|
||||||
|
* The pricing port reports that as NOT_READY, while any sent/customer-confirmed member fails and
|
||||||
|
* rolls this relation mutation back with the surrounding transaction.
|
||||||
|
*/
|
||||||
|
private void recalculateAfterRelationshipChange(long containerBookingId, Set<Long> tenantIds,
|
||||||
|
String operation) {
|
||||||
|
tenantIds.stream().filter(Objects::nonNull).filter(tenantId -> tenantId > 0).sorted()
|
||||||
|
.forEach(tenantId -> fbaPricingService.recalculateUnlockedContainerCustomerFees(
|
||||||
|
containerBookingId, tenantId, sha256("SMART_LOADING_RELATION|" + operation + '|' + tenantId)));
|
||||||
|
}
|
||||||
private void touch(LoadingPlanEntity p,long op,String name){if(repository.touch(p.getId(),p.getVersion(),op,name)!=1)throw conflict("计划已被并发修改");}
|
private void touch(LoadingPlanEntity p,long op,String name){if(repository.touch(p.getId(),p.getVersion(),op,name)!=1)throw conflict("计划已被并发修改");}
|
||||||
private Plan replay(String operation, long planId, String action, Long itemId, String requestHash) {
|
private Plan replay(String operation, long planId, String action, Long itemId, String requestHash) {
|
||||||
LoadingPlanHistoryEntity history = repository.operation(operation);
|
LoadingPlanHistoryEntity history = repository.operation(operation);
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ class LoadingListServiceTest {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private LoadingCompletedOutboxService loadingCompletedOutboxService;
|
private LoadingCompletedOutboxService loadingCompletedOutboxService;
|
||||||
@MockBean
|
@MockBean
|
||||||
|
private FbaPricingService fbaPricingService;
|
||||||
|
@MockBean
|
||||||
private InboundHoldGuard inboundHoldGuard;
|
private InboundHoldGuard inboundHoldGuard;
|
||||||
@Resource
|
@Resource
|
||||||
private LoadingListService service;
|
private LoadingListService service;
|
||||||
@@ -778,6 +780,9 @@ class LoadingListServiceTest {
|
|||||||
when(repository.history(anyString())).thenReturn(null);
|
when(repository.history(anyString())).thenReturn(null);
|
||||||
when(repository.aggregate(10L)).thenReturn(before, after);
|
when(repository.aggregate(10L)).thenReturn(before, after);
|
||||||
when(loadingPickService.requirePreloadConfirmationReady(10L)).thenReturn(links);
|
when(loadingPickService.requirePreloadConfirmationReady(10L)).thenReturn(links);
|
||||||
|
when(fbaPricingService.recalculateUnlockedContainerCustomerFees(eq(30L), anyLong(), anyString()))
|
||||||
|
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
|
||||||
|
FbaPricingService.ContainerFeeRecalculationStatus.RECALCULATED, 2));
|
||||||
when(repository.touchForPreloadConfirmation(10L, 3, 9L, "operator")).thenReturn(1);
|
when(repository.touchForPreloadConfirmation(10L, 3, 9L, "operator")).thenReturn(1);
|
||||||
when(repository.confirmLinkedPreload(301L, "PENDING", 2, 9L, "operator")).thenReturn(1);
|
when(repository.confirmLinkedPreload(301L, "PENDING", 2, 9L, "operator")).thenReturn(1);
|
||||||
when(repository.confirmLinkedPreload(302L, "CONFIRMING", 5, 9L, "operator")).thenReturn(1);
|
when(repository.confirmLinkedPreload(302L, "CONFIRMING", 5, 9L, "operator")).thenReturn(1);
|
||||||
@@ -791,12 +796,35 @@ class LoadingListServiceTest {
|
|||||||
assertThat(response.readiness().pdaTaskId()).isEqualTo(80L);
|
assertThat(response.readiness().pdaTaskId()).isEqualTo(80L);
|
||||||
verify(repository).confirmLinkedPreload(301L, "PENDING", 2, 9L, "operator");
|
verify(repository).confirmLinkedPreload(301L, "PENDING", 2, 9L, "operator");
|
||||||
verify(repository).confirmLinkedPreload(302L, "CONFIRMING", 5, 9L, "operator");
|
verify(repository).confirmLinkedPreload(302L, "CONFIRMING", 5, 9L, "operator");
|
||||||
|
verify(fbaPricingService).recalculateUnlockedContainerCustomerFees(eq(30L), eq(101L), anyString());
|
||||||
|
verify(fbaPricingService).recalculateUnlockedContainerCustomerFees(eq(30L), eq(102L), anyString());
|
||||||
verify(loadingPickService, times(1)).beginIfReady(30L, 9L, "operator");
|
verify(loadingPickService, times(1)).beginIfReady(30L, 9L, "operator");
|
||||||
verify(repository).appendHistory(eq(10L), anyString(), eq("CONFIRM_PRELOAD"),
|
verify(repository).appendHistory(eq(10L), anyString(), eq("CONFIRM_PRELOAD"),
|
||||||
eq("EDITING"), eq("EDITING"), anyString(), anyString(), eq(9L),
|
eq("EDITING"), eq("EDITING"), anyString(), anyString(), eq(9L),
|
||||||
eq("operator"), org.mockito.ArgumentMatchers.isNull());
|
eq("operator"), org.mockito.ArgumentMatchers.isNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void refusesConfirmationWhenAContainerCustomerFeeIsNotReadyWithoutTouchingPlan() {
|
||||||
|
AggregateRow before = row("EDITING", 3);
|
||||||
|
List<Link> links = List.of(new Link(1L, 101L, 201L, 301L, "PENDING", 2, 10L, 30L,
|
||||||
|
"MSCU-001", "EDITING", 3, 4, null));
|
||||||
|
when(repository.history(anyString())).thenReturn(null);
|
||||||
|
when(repository.aggregate(10L)).thenReturn(before);
|
||||||
|
when(loadingPickService.requirePreloadConfirmationReady(10L)).thenReturn(links);
|
||||||
|
when(fbaPricingService.recalculateUnlockedContainerCustomerFees(eq(30L), eq(101L), anyString()))
|
||||||
|
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
|
||||||
|
FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY, 0));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service.confirmPreload(
|
||||||
|
10L, 9L, "operator", "confirm-key", new ConfirmPreloadRequest(3)))
|
||||||
|
.isInstanceOf(BizException.class).hasMessageContaining("初始费用");
|
||||||
|
|
||||||
|
verify(repository, never()).touchForPreloadConfirmation(anyLong(), anyInt(), anyLong(), anyString());
|
||||||
|
verify(repository, never()).confirmLinkedPreload(anyLong(), anyString(), anyInt(), anyLong(), anyString());
|
||||||
|
verify(loadingPickService, never()).beginIfReady(anyLong(), anyLong(), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void exactConfirmationReplayReturnsFrozenResponseWithoutTouchingPlan() throws Exception {
|
void exactConfirmationReplayReturnsFrozenResponseWithoutTouchingPlan() throws Exception {
|
||||||
PreloadConfirmationResponse expected = new PreloadConfirmationResponse(
|
PreloadConfirmationResponse expected = new PreloadConfirmationResponse(
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class SmartLoadingServiceTest {
|
|||||||
@MockBean private LoadingModelBridgeService loadingModelBridge;
|
@MockBean private LoadingModelBridgeService loadingModelBridge;
|
||||||
@MockBean private SplitPreloadService splitPreloadService;
|
@MockBean private SplitPreloadService splitPreloadService;
|
||||||
@MockBean private ColoadGroupAssignmentPort coloadGroups;
|
@MockBean private ColoadGroupAssignmentPort coloadGroups;
|
||||||
|
@MockBean private FbaPricingService fbaPricingService;
|
||||||
@MockBean private PriceBookQueryUseCase priceBooks;
|
@MockBean private PriceBookQueryUseCase priceBooks;
|
||||||
@MockBean private FirstlegDocumentNumberGenerator documentNumbers;
|
@MockBean private FirstlegDocumentNumberGenerator documentNumbers;
|
||||||
@MockBean private DropshipPalletizationMapper dropshipPallets;
|
@MockBean private DropshipPalletizationMapper dropshipPallets;
|
||||||
@@ -114,6 +115,9 @@ class SmartLoadingServiceTest {
|
|||||||
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenReturn(
|
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenReturn(
|
||||||
ApiResponse.success(new OrderCargoClient.CargoBinding(20L, 30L, 1, "货物", 5,
|
ApiResponse.success(new OrderCargoClient.CargoBinding(20L, 30L, 1, "货物", 5,
|
||||||
new BigDecimal("10"), new BigDecimal("2"))));
|
new BigDecimal("10"), new BigDecimal("2"))));
|
||||||
|
when(fbaPricingService.recalculateUnlockedContainerCustomerFees(anyLong(), anyLong(), anyString()))
|
||||||
|
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
|
||||||
|
FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -392,6 +396,7 @@ class SmartLoadingServiceTest {
|
|||||||
assertThat(item.getValue().getGrossWeightKg()).isEqualByComparingTo("50");
|
assertThat(item.getValue().getGrossWeightKg()).isEqualByComparingTo("50");
|
||||||
verify(splitPreloadService).ensurePendingForPreAllocation(customerId, 10L, 7L, "u");
|
verify(splitPreloadService).ensurePendingForPreAllocation(customerId, 10L, 7L, "u");
|
||||||
verify(loadingModelBridge).bindIncrementalAllocation(1L, customerId, 10L, 7L, "u", "pending-1");
|
verify(loadingModelBridge).bindIncrementalAllocation(1L, customerId, 10L, 7L, "u", "pending-1");
|
||||||
|
verify(fbaPricingService).recalculateUnlockedContainerCustomerFees(eq(99L), eq(customerId), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -866,6 +871,8 @@ class SmartLoadingServiceTest {
|
|||||||
verify(repository, times(2)).remove(eq(1L), anyLong(), eq(0), eq(7L), eq("u"));
|
verify(repository, times(2)).remove(eq(1L), anyLong(), eq(0), eq(7L), eq("u"));
|
||||||
verify(coloadGroups).unlockWholeGroupAfterRemoval(eq(700L), eq(1L),
|
verify(coloadGroups).unlockWholeGroupAfterRemoval(eq(700L), eq(1L),
|
||||||
org.mockito.ArgumentMatchers.startsWith("UNLOCK-"), eq(7L), eq("u"));
|
org.mockito.ArgumentMatchers.startsWith("UNLOCK-"), eq(7L), eq("u"));
|
||||||
|
verify(fbaPricingService).recalculateUnlockedContainerCustomerFees(eq(99L), eq(9L), anyString());
|
||||||
|
verify(fbaPricingService).recalculateUnlockedContainerCustomerFees(eq(99L), eq(19L), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+200
@@ -3,6 +3,8 @@ package com.baiyue.lastleg.inventory.command.service;
|
|||||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
|
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
|
||||||
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandResponse;
|
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandResponse;
|
||||||
|
import com.baiyue.lastleg.inventory.command.dto.ReturnedInventoryRestoreRequest;
|
||||||
|
import com.baiyue.lastleg.inventory.command.dto.ReturnedInventoryRestoreResponse;
|
||||||
import com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper;
|
import com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper;
|
||||||
import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
||||||
import com.baiyue.lastleg.inventory.entity.InventoryLedger;
|
import com.baiyue.lastleg.inventory.entity.InventoryLedger;
|
||||||
@@ -26,8 +28,11 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -245,6 +250,119 @@ public class InventoryCommandService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ReturnedInventoryRestoreResponse restoreReturnedInventory(String customerId, String operatorId,
|
||||||
|
ReturnedInventoryRestoreRequest request) {
|
||||||
|
String exceptionNo = required(request.exceptionNo(), "Return exception number is required");
|
||||||
|
String outboundNo = required(request.outboundNo(), "Return outbound order number is required");
|
||||||
|
String warehouseId = required(request.overseasWarehouseId(), "Return warehouse is required");
|
||||||
|
String idempotencyKey = required(request.idempotencyKey(), "Idempotency key is required");
|
||||||
|
String requestHash = hash(request);
|
||||||
|
InventoryOperation existing = operations.findIdempotency(customerId, idempotencyKey);
|
||||||
|
if (existing != null) return replayReturned(existing, requestHash);
|
||||||
|
if (request.sources() == null || request.sources().isEmpty()) {
|
||||||
|
throw badRequest("Return inventory sources are required");
|
||||||
|
}
|
||||||
|
if (request.receipts() == null || request.receipts().isEmpty()) {
|
||||||
|
throw badRequest("Return inventory receipts are required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ReturnedInventoryRestoreRequest.SourceItem> sources = new LinkedHashMap<>();
|
||||||
|
for (ReturnedInventoryRestoreRequest.SourceItem source : request.sources()) {
|
||||||
|
if (source.outboundPalletItemId() == null || source.inventoryPositionId() == null
|
||||||
|
|| source.maxReturnPackages() <= 0) {
|
||||||
|
throw badRequest("Return inventory source is incomplete");
|
||||||
|
}
|
||||||
|
String key = returnSourceKey(source.outboundPalletItemId(), source.inventoryPositionId());
|
||||||
|
if (sources.putIfAbsent(key, source) != null) throw badRequest("Return inventory source is duplicated");
|
||||||
|
}
|
||||||
|
Map<String, Long> receiptTotals = new LinkedHashMap<>();
|
||||||
|
for (ReturnedInventoryRestoreRequest.Receipt receipt : request.receipts()) {
|
||||||
|
if (receipt.outboundPalletItemId() == null || receipt.inventoryPositionId() == null
|
||||||
|
|| receipt.receivedPackages() <= 0 || receipt.targetPalletId() == null
|
||||||
|
|| receipt.targetLocationId() == null || trim(receipt.targetPalletNo()) == null
|
||||||
|
|| trim(receipt.targetLocationCode()) == null) {
|
||||||
|
throw badRequest("Return inventory receipt is incomplete");
|
||||||
|
}
|
||||||
|
String key = returnSourceKey(receipt.outboundPalletItemId(), receipt.inventoryPositionId());
|
||||||
|
if (!sources.containsKey(key)) throw conflict("Return inventory receipt does not match an approved source");
|
||||||
|
receiptTotals.merge(key, receipt.receivedPackages(), Math::addExact);
|
||||||
|
}
|
||||||
|
|
||||||
|
InventoryOperation operation = new InventoryOperation();
|
||||||
|
operation.setId(IdWorker.getId());
|
||||||
|
operation.setCustomerId(customerId);
|
||||||
|
operation.setIdempotencyKey(idempotencyKey);
|
||||||
|
operation.setOperationType("RETURN_STOCK_IN");
|
||||||
|
operation.setRequestHash(requestHash);
|
||||||
|
operation.setStatus("PROCESSING");
|
||||||
|
operation.setCreatedAt(LocalDateTime.now());
|
||||||
|
operation.setUpdatedAt(operation.getCreatedAt());
|
||||||
|
try {
|
||||||
|
operations.insert(operation);
|
||||||
|
} catch (DuplicateKeyException exception) {
|
||||||
|
InventoryOperation concurrent = operations.findIdempotency(customerId, idempotencyKey);
|
||||||
|
if (concurrent != null) return replayReturned(concurrent, requestHash);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Locked> lockedSources = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, ReturnedInventoryRestoreRequest.SourceItem> entry : sources.entrySet()) {
|
||||||
|
ReturnedInventoryRestoreRequest.SourceItem source = entry.getValue();
|
||||||
|
Locked locked = locked(customerId, source.inventoryPositionId());
|
||||||
|
if (!Objects.equals(locked.balance().getInboundOrderId(), request.inboundOrderId())
|
||||||
|
|| !warehouseId.equals(trim(locked.balance().getOverseasWarehouseId()))) {
|
||||||
|
throw conflict("Return inventory source does not belong to the exception inbound order and warehouse");
|
||||||
|
}
|
||||||
|
String sourceIdentity = returnSourceIdentity(exceptionNo, source);
|
||||||
|
long shipped = commands.shippedPackages(customerId, source.inventoryPositionId(), outboundNo,
|
||||||
|
sku(source.skuCode()));
|
||||||
|
long restored = commands.restoredPackages(customerId, sourceIdentity, sku(source.skuCode()));
|
||||||
|
long allowed = Math.min(source.maxReturnPackages(), shipped) - restored;
|
||||||
|
long requested = receiptTotals.getOrDefault(entry.getKey(), 0L);
|
||||||
|
if (requested > allowed) throw conflict("Return inventory receipt exceeds the remaining shipped packages");
|
||||||
|
lockedSources.put(entry.getKey(), locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> ledgerIds = new ArrayList<>();
|
||||||
|
long beforePackages = 0;
|
||||||
|
long restoredPackages = 0;
|
||||||
|
int sequence = 0;
|
||||||
|
String batchNo = ("RETURN-" + exceptionNo).substring(0, Math.min(128, 7 + exceptionNo.length()));
|
||||||
|
for (ReturnedInventoryRestoreRequest.Receipt receipt : request.receipts()) {
|
||||||
|
String key = returnSourceKey(receipt.outboundPalletItemId(), receipt.inventoryPositionId());
|
||||||
|
ReturnedInventoryRestoreRequest.SourceItem source = sources.get(key);
|
||||||
|
Locked locked = lockedSources.get(key);
|
||||||
|
InventoryBalance balance = locked.balance();
|
||||||
|
InventoryPosition target = returnPosition(customerId, balance, receipt, batchNo);
|
||||||
|
long before = value(target.getAvailablePackages());
|
||||||
|
beforePackages = Math.addExact(beforePackages, before);
|
||||||
|
target.setAvailablePackages(Math.addExact(before, receipt.receivedPackages()));
|
||||||
|
target.setStatus("OCCUPIED");
|
||||||
|
target.setVersion(value(target.getVersion()) + 1);
|
||||||
|
target.setUpdatedAt(LocalDateTime.now());
|
||||||
|
positions.updateById(target);
|
||||||
|
|
||||||
|
balance.setAvailablePackages(Math.addExact(value(balance.getAvailablePackages()), receipt.receivedPackages()));
|
||||||
|
touch(balance);
|
||||||
|
balances.updateById(balance);
|
||||||
|
restoredPackages = Math.addExact(restoredPackages, receipt.receivedPackages());
|
||||||
|
InventoryLedger ledger = returnLedger(customerId, operatorId, operation.getId(), balance, target,
|
||||||
|
source, exceptionNo, outboundNo, returnSourceIdentity(exceptionNo, source), idempotencyKey,
|
||||||
|
sequence++, before, receipt.receivedPackages(), request.evidenceFileIds());
|
||||||
|
ledgers.insert(ledger);
|
||||||
|
ledgerIds.add(ledger.getId());
|
||||||
|
}
|
||||||
|
LocalDateTime completedAt = LocalDateTime.now();
|
||||||
|
ReturnedInventoryRestoreResponse response = new ReturnedInventoryRestoreResponse(operation.getId(), ledgerIds,
|
||||||
|
beforePackages, restoredPackages, Math.addExact(beforePackages, restoredPackages), false, completedAt);
|
||||||
|
operation.setStatus("COMPLETED");
|
||||||
|
operation.setResultJson(write(response));
|
||||||
|
operation.setUpdatedAt(completedAt);
|
||||||
|
operations.updateById(operation);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public InventoryCommandResponse adjust(String customerId, String operatorId,
|
public InventoryCommandResponse adjust(String customerId, String operatorId,
|
||||||
InventoryCommandRequests.Adjustment request) {
|
InventoryCommandRequests.Adjustment request) {
|
||||||
@@ -423,6 +541,42 @@ public class InventoryCommandService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InventoryPosition returnPosition(String customerId, InventoryBalance balance,
|
||||||
|
ReturnedInventoryRestoreRequest.Receipt receipt, String batchNo) {
|
||||||
|
InventoryPosition position = positions.lockPosition(customerId, balance.getId(), receipt.targetPalletId(), batchNo);
|
||||||
|
if (position != null) {
|
||||||
|
if (!required(receipt.targetPalletNo(), "Return pallet number is required").equals(position.getPalletNo())
|
||||||
|
|| !Objects.equals(receipt.targetLocationId(), position.getWarehouseLocationId())
|
||||||
|
|| !required(receipt.targetLocationCode(), "Return location code is required")
|
||||||
|
.equals(position.getWarehouseLocationCode())) {
|
||||||
|
throw conflict("Return target pallet or location changed for the same exception");
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
position = new InventoryPosition();
|
||||||
|
position.setId(IdWorker.getId());
|
||||||
|
position.setCustomerId(customerId);
|
||||||
|
position.setBalanceId(balance.getId());
|
||||||
|
position.setOverseasWarehouseId(balance.getOverseasWarehouseId());
|
||||||
|
position.setDestinationWarehouseCode(balance.getDestinationWarehouseCode());
|
||||||
|
position.setPalletId(receipt.targetPalletId());
|
||||||
|
position.setPalletNo(required(receipt.targetPalletNo(), "Return pallet number is required"));
|
||||||
|
position.setBatchNo(batchNo);
|
||||||
|
position.setWarehouseLocationId(receipt.targetLocationId());
|
||||||
|
position.setWarehouseLocationCode(required(receipt.targetLocationCode(), "Return location code is required"));
|
||||||
|
position.setAvailablePackages(0L); position.setHoldPackages(0L); position.setAllocatedPackages(0L);
|
||||||
|
position.setPickedPackages(0L); position.setReceivingPackages(0L); position.setStatus("OCCUPIED");
|
||||||
|
position.setVersion(0L); position.setCreatedAt(LocalDateTime.now()); position.setUpdatedAt(position.getCreatedAt());
|
||||||
|
try {
|
||||||
|
positions.insert(position);
|
||||||
|
return position;
|
||||||
|
} catch (DuplicateKeyException exception) {
|
||||||
|
InventoryPosition concurrent = positions.lockPosition(customerId, balance.getId(), receipt.targetPalletId(), batchNo);
|
||||||
|
if (concurrent != null) return concurrent;
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Locked locked(String customerId, Long positionId) {
|
private Locked locked(String customerId, Long positionId) {
|
||||||
InventoryPosition position = commands.lockPosition(customerId, positionId);
|
InventoryPosition position = commands.lockPosition(customerId, positionId);
|
||||||
if (position == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inventory position not found");
|
if (position == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inventory position not found");
|
||||||
@@ -483,6 +637,52 @@ public class InventoryCommandService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReturnedInventoryRestoreResponse replayReturned(InventoryOperation operation, String requestHash) {
|
||||||
|
if (!"RETURN_STOCK_IN".equals(operation.getOperationType()) || !requestHash.equals(operation.getRequestHash())) {
|
||||||
|
throw conflict("Idempotency key was already used with a different inventory request");
|
||||||
|
}
|
||||||
|
if (!"COMPLETED".equals(operation.getStatus()) || trim(operation.getResultJson()) == null) {
|
||||||
|
throw conflict("Inventory operation is still processing");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(operation.getResultJson(), ReturnedInventoryRestoreResponse.class).asReplay();
|
||||||
|
} catch (JsonProcessingException exception) {
|
||||||
|
throw new IllegalStateException("Cannot read return inventory idempotency result", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InventoryLedger returnLedger(String customerId, String operatorId, Long operationId,
|
||||||
|
InventoryBalance balance, InventoryPosition position,
|
||||||
|
ReturnedInventoryRestoreRequest.SourceItem source,
|
||||||
|
String exceptionNo, String outboundNo, String sourceIdentity,
|
||||||
|
String idempotencyKey, int sequence, long before, long delta,
|
||||||
|
List<String> evidenceFileIds) {
|
||||||
|
InventoryLedger ledger = new InventoryLedger();
|
||||||
|
ledger.setId(IdWorker.getId()); ledger.setCustomerId(customerId); ledger.setCustomerName(balance.getCustomerName());
|
||||||
|
ledger.setBalanceId(balance.getId()); ledger.setPositionId(position.getId());
|
||||||
|
ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId());
|
||||||
|
ledger.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); ledger.setInboundOrderId(balance.getInboundOrderId());
|
||||||
|
ledger.setPalletId(position.getPalletId()); ledger.setPalletNo(position.getPalletNo()); ledger.setBatchNo(position.getBatchNo());
|
||||||
|
ledger.setWarehouseLocationCode(position.getWarehouseLocationCode()); ledger.setSkuCode(sku(source.skuCode()));
|
||||||
|
ledger.setBusinessType("RETURN_STOCK_IN"); ledger.setBusinessOrderId(balance.getInboundOrderId());
|
||||||
|
ledger.setBusinessOrderNo(exceptionNo); ledger.setInboundCode(balance.getInboundCode());
|
||||||
|
ledger.setInboundOrderNo(balance.getInboundOrderNo()); ledger.setOutboundOrderNo(outboundNo);
|
||||||
|
ledger.setSourceEventId(sourceIdentity); ledger.setIdempotencyKey(idempotencyKey + ":RETURN:" + sequence + ":" + operationId);
|
||||||
|
ledger.setStatusFrom("RETURNED"); ledger.setStatusTo("AVAILABLE"); ledger.setBeforePackages(before);
|
||||||
|
ledger.setDeltaPackages(delta); ledger.setAfterPackages(Math.addExact(before, delta)); ledger.setOperatorId(trim(operatorId));
|
||||||
|
ledger.setRemark("Exception return received; evidenceCount=" + (evidenceFileIds == null ? 0 : evidenceFileIds.size()));
|
||||||
|
ledger.setOccurredAt(LocalDateTime.now()); ledger.setCreatedAt(ledger.getOccurredAt());
|
||||||
|
return ledger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String returnSourceKey(Long outboundPalletItemId, Long inventoryPositionId) {
|
||||||
|
return outboundPalletItemId + ":" + inventoryPositionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String returnSourceIdentity(String exceptionNo, ReturnedInventoryRestoreRequest.SourceItem source) {
|
||||||
|
return "RETURN:" + exceptionNo + ":" + source.outboundPalletItemId() + ":" + source.inventoryPositionId();
|
||||||
|
}
|
||||||
|
|
||||||
private void ledger(String customerId, String operatorId, Long operationId, InventoryBalance balance,
|
private void ledger(String customerId, String operatorId, Long operationId, InventoryBalance balance,
|
||||||
InventoryPosition position, String skuCode, String businessType, String from, String to,
|
InventoryPosition position, String skuCode, String businessType, String from, String to,
|
||||||
long beforePackages, long deltaPackages, long afterPackages,
|
long beforePackages, long deltaPackages, long afterPackages,
|
||||||
|
|||||||
Reference in New Issue
Block a user