优化
This commit is contained in:
+2
@@ -38,4 +38,6 @@ public class PlatformSmartLoadingController {
|
||||
public ApiResponse<Plan> warehouseRemark(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody WarehouseRemarkRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.updateWarehouseRemark(planId,itemId,r,u.userId(),u.username()));}
|
||||
@DeleteMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
||||
public ApiResponse<Plan> delete(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody DeleteRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.delete(planId,itemId,r,u.userId(),u.username()));}
|
||||
@DeleteMapping("/plans/{planId}/unpicked-items") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
||||
public ApiResponse<Plan> removeUnpicked(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody RemoveUnpickedRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.removeUnpicked(planId,r,u.userId(),u.username()));}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
@NotNull @Min(0) Integer expectedItemVersion,@NotBlank @Size(max=128) String operationId) {}
|
||||
public record DeleteRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
||||
@NotNull @Min(0) Integer expectedItemVersion,@NotBlank @Size(max=128) String operationId) {}
|
||||
public record RemoveUnpickedRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record WarehouseRemarkRequest(@Size(max=1000) String warehouseRemark,
|
||||
@NotNull @Min(0) Integer expectedPlanVersion, @NotNull @Min(0) Integer expectedItemVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
|
||||
@@ -420,7 +420,6 @@ public class SmartLoadingService {
|
||||
request.inboundDetailId());
|
||||
if (candidate == null) throw conflict("入库货物不存在或尚未完成入库");
|
||||
if (!wholeGroupAssignment) requireStandalone(candidate.accountId(), candidate.inboundId());
|
||||
requireSameChannel(booking, candidate);
|
||||
requireSameCustomerScope(booking, candidate);
|
||||
var task = loadingPickService.mutationSnapshot(planId);
|
||||
if (task.taskId() != null && "LOADING".equals(task.taskStatus())) {
|
||||
@@ -525,7 +524,6 @@ public class SmartLoadingService {
|
||||
for (InboundAllocation allocation : allocations) {
|
||||
if (allocation.packageCount() == 0) continue;
|
||||
Candidate candidate = allocation.candidate();
|
||||
requireSameChannel(booking, candidate);
|
||||
requireSameCustomerScope(booking, candidate);
|
||||
checkRouteAndCapacity(booking, current, candidate, allocation.packageCount(), true);
|
||||
OrderCargoClient.CargoBinding cargo = declared.stream()
|
||||
@@ -581,7 +579,7 @@ public class SmartLoadingService {
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||
List<Candidate> details = frozenDetails == null
|
||||
? candidates(plan.getContainerBookingId(), booking.getWarehouseId()).stream()
|
||||
? manualCandidates(booking).stream()
|
||||
.filter(candidate -> Objects.equals(candidate.inboundId(), request.inboundId())).toList()
|
||||
: List.copyOf(frozenDetails);
|
||||
if (details.isEmpty()) throw conflict("入仓码不存在、已分配完或尚未完成入库");
|
||||
@@ -641,7 +639,6 @@ public class SmartLoadingService {
|
||||
for (InboundAllocation allocation : allocations) {
|
||||
if (allocation.packageCount() == 0) continue;
|
||||
Candidate candidate = allocation.candidate();
|
||||
requireSameChannel(booking, candidate);
|
||||
requireSameCustomerScope(booking, candidate);
|
||||
checkRouteAndCapacity(booking, current, candidate, allocation.packageCount(), true);
|
||||
OrderCargoClient.CargoBinding cargo = declared.stream().map(DeclaredCargo::cargo)
|
||||
@@ -853,7 +850,6 @@ public class SmartLoadingService {
|
||||
Candidate candidate = repository.candidate(planId, booking.getWarehouseId(), item.getInboundId(),
|
||||
item.getInboundDetailId());
|
||||
if (candidate == null || request.packageCount() > candidate.availablePackageCount() + item.getPackageCount()) throw validation("调整箱数超过可用箱数");
|
||||
requireSameChannel(booking, candidate);
|
||||
requireSameCustomerScope(booking, candidate);
|
||||
OrderCargoClient.CargoBinding cargo = loadCargo(candidate);
|
||||
List<LoadingPlanItemEntity> others = new ArrayList<>(repository.items(planId));
|
||||
@@ -945,6 +941,84 @@ public class SmartLoadingService {
|
||||
return view(repository.plan(planId), requireBooking(plan.getContainerBookingId()));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "SMART_LOADING_UNPICKED_REMOVE", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "批量移除智能装柜未拣明细")
|
||||
public Plan removeUnpicked(long planId, RemoveUnpickedRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
String operation = operation(request.operationId());
|
||||
String requestHash = sha256("REMOVE_UNPICKED|" + planId + '|' + request.expectedPlanVersion());
|
||||
Plan replay = replay(operation, planId, "REMOVE_UNPICKED", null, requestHash);
|
||||
if (replay != null) return replay;
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||
List<LoadingPlanItemEntity> items = repository.items(planId);
|
||||
LoadingPickService.MutationSnapshot pickSnapshot = loadingPickService.mutationSnapshot(planId);
|
||||
Map<Long, Integer> picked = pickSnapshot == null ? Map.of() : pickSnapshot.pickedByPlanItem();
|
||||
|
||||
Map<String, GroupSnapshot> groupByMember = new HashMap<>();
|
||||
Map<Long, GroupSnapshot> groups = new LinkedHashMap<>();
|
||||
for (LoadingPlanItemEntity item : items) {
|
||||
String memberKey = item.getAccountId() + "|" + item.getInboundId();
|
||||
if (!groupByMember.containsKey(memberKey)) {
|
||||
groupByMember.put(memberKey, group(item.getAccountId(), item.getInboundId()));
|
||||
}
|
||||
GroupSnapshot group = groupByMember.get(memberKey);
|
||||
if (group != null) groups.putIfAbsent(group.groupId(), group);
|
||||
}
|
||||
Set<Long> groupsWithPickedItems = new HashSet<>();
|
||||
for (LoadingPlanItemEntity item : items) {
|
||||
if (picked.getOrDefault(item.getId(), 0) <= 0) continue;
|
||||
GroupSnapshot group = groupByMember.get(item.getAccountId() + "|" + item.getInboundId());
|
||||
if (group != null) groupsWithPickedItems.add(group.groupId());
|
||||
}
|
||||
|
||||
List<LoadingPlanItemEntity> removable = items.stream().filter(item -> {
|
||||
if (picked.getOrDefault(item.getId(), 0) > 0) return false;
|
||||
GroupSnapshot group = groupByMember.get(item.getAccountId() + "|" + item.getInboundId());
|
||||
return group == null || !groupsWithPickedItems.contains(group.groupId());
|
||||
}).toList();
|
||||
Set<Long> removableIds = removable.stream().map(LoadingPlanItemEntity::getId)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
Set<Long> removableGroupIds = groups.values().stream()
|
||||
.filter(group -> !groupsWithPickedItems.contains(group.groupId()))
|
||||
.filter(group -> items.stream().anyMatch(item -> removableIds.contains(item.getId())
|
||||
&& group.members().stream().anyMatch(member -> member.tenantId() == item.getAccountId()
|
||||
&& member.inboundId() == item.getInboundId())))
|
||||
.map(GroupSnapshot::groupId).collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||
for (Long groupId : removableGroupIds) {
|
||||
requireCompleteGroupAllocation(planId, groups.get(groupId), items);
|
||||
}
|
||||
|
||||
if (!removable.isEmpty()) {
|
||||
touch(plan, operatorId, operatorName);
|
||||
for (LoadingPlanItemEntity item : removable) {
|
||||
loadingPickService.remove(item.getId());
|
||||
if (repository.remove(planId, item.getId(), item.getVersion(), operatorId, operatorName) != 1) {
|
||||
throw conflict("装柜未拣明细已被并发修改");
|
||||
}
|
||||
}
|
||||
Set<String> releasedInbounds = new HashSet<>();
|
||||
for (LoadingPlanItemEntity item : removable) {
|
||||
String key = item.getAccountId() + "|" + item.getInboundId();
|
||||
if (releasedInbounds.add(key)) {
|
||||
loadingModelBridge.removeIfUnallocated(planId, item.getAccountId(), item.getInboundId());
|
||||
}
|
||||
}
|
||||
for (Long groupId : removableGroupIds) {
|
||||
GroupSnapshot group = groups.get(groupId);
|
||||
coloadGroups.unlockWholeGroupAfterRemoval(groupId, planId,
|
||||
"UNLOCK-" + sha256(operation + '|' + groupId).substring(0, 48),
|
||||
operatorId, operatorName);
|
||||
}
|
||||
}
|
||||
history(planId, null, "REMOVE_UNPICKED", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
||||
json(values("version", request.expectedPlanVersion(), "itemCount", items.size())),
|
||||
json(values("requestHash", requestHash, "removedItemCount", removable.size(),
|
||||
"removedGroupCount", removableGroupIds.size())), operatorId, operatorName);
|
||||
return view(repository.plan(planId), booking);
|
||||
}
|
||||
|
||||
private List<Candidate> enrichColoadCandidates(ContainerBookingEntity booking, LoadingPlanEntity plan,
|
||||
List<Candidate> values) {
|
||||
Map<String, GroupSnapshot> snapshots = new HashMap<>();
|
||||
@@ -1013,7 +1087,7 @@ public class SmartLoadingService {
|
||||
if (!"ACTIVE".equals(group.status()) || group.lockedPlanId() != null) {
|
||||
throw conflict("同柜不可拆组已锁定或已消费");
|
||||
}
|
||||
List<Candidate> available = availableCandidates(booking);
|
||||
List<Candidate> available = manualCandidates(booking);
|
||||
Map<Long, List<Candidate>> result = new LinkedHashMap<>();
|
||||
for (Member member : group.members()) {
|
||||
if (!repository.activeItemsByInbound(member.tenantId(), member.inboundId()).isEmpty()) {
|
||||
@@ -1040,6 +1114,11 @@ public class SmartLoadingService {
|
||||
.filter(candidate -> sameChannel(booking, candidate) && sameCustomerScope(booking, candidate)).toList();
|
||||
}
|
||||
|
||||
private List<Candidate> manualCandidates(ContainerBookingEntity booking) {
|
||||
return repository.candidates(booking.getWarehouseId()).stream()
|
||||
.filter(candidate -> sameCustomerScope(booking, candidate)).toList();
|
||||
}
|
||||
|
||||
private boolean groupFits(ContainerBookingEntity booking, List<LoadingPlanItemEntity> planItems,
|
||||
GroupSnapshot group, List<Candidate> available) {
|
||||
if (booking.getCapacityCbm() == null || booking.getMaxWeightKg() == null) return false;
|
||||
@@ -1272,7 +1351,6 @@ public class SmartLoadingService {
|
||||
private record DeclaredCargo(OrderCargoClient.CargoBinding cargo) {}
|
||||
private void checkRouteAndCapacity(ContainerBookingEntity b,List<LoadingPlanItemEntity> lines,Candidate c,
|
||||
int count, boolean requireSameDestination){
|
||||
requireSameChannel(b, c);
|
||||
if (!Objects.equals(b.getWarehouseId(), c.warehouseId())) throw validation("装柜仓库不一致");
|
||||
if (requireSameDestination && StringUtils.hasText(b.getDestinationWarehouseCode())
|
||||
&& !Objects.equals(b.getDestinationWarehouseCode(), c.destinationWarehouseCode())) {
|
||||
@@ -1376,12 +1454,6 @@ public class SmartLoadingService {
|
||||
private static boolean sameCustomerScope(ContainerBookingEntity booking, Candidate candidate) {
|
||||
return booking.getCustomerId() == null || Objects.equals(booking.getCustomerId(), candidate.accountId());
|
||||
}
|
||||
private static void requireSameChannel(ContainerBookingEntity booking, Candidate candidate) {
|
||||
if (!StringUtils.hasText(booking.getChannelCode()) || !StringUtils.hasText(candidate.channelCode())) {
|
||||
throw validation("柜次或入库货物缺少渠道快照,请先完成存量数据回填");
|
||||
}
|
||||
if (!sameChannel(booking, candidate)) throw validation("禁止跨渠道混装");
|
||||
}
|
||||
private static void requireSameCustomerScope(ContainerBookingEntity booking, Candidate candidate) {
|
||||
if (!sameCustomerScope(booking, candidate)) throw forbidden();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user