优化
This commit is contained in:
+4
@@ -10,6 +10,8 @@ public class PlatformSmartLoadingController {
|
||||
@Resource private SmartLoadingService service;
|
||||
@GetMapping("/candidates") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
public ApiResponse<List<Candidate>> candidates(@AuthenticationPrincipal AuthenticatedUser u,@RequestParam @Positive long bookingId,@RequestParam @Positive long warehouseId){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.candidates(bookingId,warehouseId));}
|
||||
@GetMapping("/inbound-candidates") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
public ApiResponse<List<InboundCandidate>> inboundCandidates(@AuthenticationPrincipal AuthenticatedUser u,@RequestParam @Positive long bookingId,@RequestParam @Positive long warehouseId){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.inboundCandidates(bookingId,warehouseId));}
|
||||
@GetMapping("/plans/by-booking/{bookingId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
public ApiResponse<Plan> draftByBooking(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long bookingId){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.draftByBooking(bookingId));}
|
||||
@GetMapping("/plans/{planId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
@@ -18,6 +20,8 @@ public class PlatformSmartLoadingController {
|
||||
public ApiResponse<Plan> create(@AuthenticationPrincipal AuthenticatedUser u,@Valid @RequestBody CreateRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.create(r.bookingId(),r.remark(),u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/assignments") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> assign(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody AssignRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.assign(planId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/inbound-assignments") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> assignInbound(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody AssignInboundRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.assignInbound(planId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/recommend") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> recommend(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody RecommendRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.recommend(planId,r,u.userId(),u.username()));}
|
||||
@PatchMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.util.List;
|
||||
public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
public record InboundCandidate(Long accountId, Long inboundId, Long orderId, String orderNo, String inboundCode,
|
||||
Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId,
|
||||
String destinationWarehouseCode, String destinationWarehouseName, String customerName, String goodsSummary,
|
||||
Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg,
|
||||
BigDecimal averageUnitVolumeCbm, BigDecimal availableVolumeCbm) {}
|
||||
public record Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode,
|
||||
Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, String customerName, String goodsName, Integer availablePackageCount,
|
||||
BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal unitVolumeCbm) {
|
||||
@@ -19,6 +24,9 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
public record CreateRequest(@NotNull @Positive Long bookingId, @Size(max=500) String remark) {}
|
||||
public record AssignRequest(@NotNull @Positive Long inboundId,@NotNull @Positive Long inboundDetailId,
|
||||
@NotNull @Positive Integer packageCount,@NotNull @Min(0) Integer expectedVersion,@NotBlank @Size(max=128) String operationId) {}
|
||||
public record AssignInboundRequest(@NotNull @Positive Long inboundId,
|
||||
@NotNull @Positive Integer packageCount,@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record RecommendRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record AdjustRequest(@NotNull @Positive Integer packageCount,@NotNull @Min(0) Integer expectedPlanVersion,
|
||||
|
||||
@@ -31,6 +31,13 @@ public class SmartLoadingRepository {
|
||||
public Candidate candidate(long warehouseId, long inboundId, long detailId) {
|
||||
return candidates.candidate(warehouseId, inboundId, detailId);
|
||||
}
|
||||
public List<LoadingPlanItemEntity> activeItemsByInbound(long planId, long accountId, long inboundId) {
|
||||
return items.selectList(new LambdaQueryWrapper<LoadingPlanItemEntity>()
|
||||
.eq(LoadingPlanItemEntity::getLoadingPlanId, planId)
|
||||
.eq(LoadingPlanItemEntity::getAccountId, accountId)
|
||||
.eq(LoadingPlanItemEntity::getInboundId, inboundId)
|
||||
.eq(LoadingPlanItemEntity::getDeleted, false));
|
||||
}
|
||||
public LoadingPlanEntity plan(long planId) { return plans.selectById(planId); }
|
||||
public LoadingPlanEntity planByBooking(long bookingId) {
|
||||
return plans.selectOne(new LambdaQueryWrapper<LoadingPlanEntity>()
|
||||
|
||||
@@ -58,6 +58,33 @@ public class SmartLoadingService {
|
||||
.filter(candidate -> sameChannel(booking, candidate) && sameCustomerScope(booking, candidate)).toList();
|
||||
}
|
||||
|
||||
/** Operator-facing candidates are grouped by inbound code; specification rows remain internal snapshots. */
|
||||
public List<InboundCandidate> inboundCandidates(long bookingId, long warehouseId) {
|
||||
Map<Long, List<Candidate>> grouped = candidates(bookingId, warehouseId).stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(Candidate::inboundId, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()));
|
||||
return grouped.values().stream().map(this::inboundCandidate).toList();
|
||||
}
|
||||
|
||||
private InboundCandidate inboundCandidate(List<Candidate> details) {
|
||||
Candidate first = details.get(0);
|
||||
int packages = details.stream().map(Candidate::availablePackageCount)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||
BigDecimal volume = details.stream()
|
||||
.map(detail -> unit(detail.unitVolumeCbm()).multiply(BigDecimal.valueOf(
|
||||
Math.max(0, detail.availablePackageCount() == null ? 0 : detail.availablePackageCount()))))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(4, RoundingMode.HALF_UP);
|
||||
BigDecimal averageVolume = packages == 0 ? BigDecimal.ZERO : volume.divide(
|
||||
BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
||||
String goods = details.stream().map(Candidate::goodsName).filter(StringUtils::hasText)
|
||||
.map(String::trim).distinct().collect(java.util.stream.Collectors.joining("、"));
|
||||
return new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||
first.customerName(), goods, packages, first.unitGrossWeightKg(), first.unitNetWeightKg(),
|
||||
averageVolume, volume);
|
||||
}
|
||||
|
||||
public Plan draftByBooking(long bookingId) {
|
||||
ContainerBookingEntity booking = requireBooking(bookingId);
|
||||
LoadingPlanEntity plan = repository.planByBooking(bookingId);
|
||||
@@ -144,6 +171,84 @@ public class SmartLoadingService {
|
||||
return view(repository.plan(planId), booking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns one inbound code by total carton count. The selected quantity is proportionally
|
||||
* distributed over the remaining measured specifications only for volume estimation and
|
||||
* downstream compatibility; it is not presented as verified physical carton identity.
|
||||
*/
|
||||
@Transactional
|
||||
@BusinessAudit(event = "SMART_LOADING_INBOUND_ASSIGN", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "按入仓码分配智能装柜箱数")
|
||||
public Plan assignInbound(long planId, AssignInboundRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
String operation = operation(request.operationId());
|
||||
String requestHash = sha256("ASSIGN_INBOUND|" + planId + '|' + request.inboundId() + '|'
|
||||
+ request.packageCount() + '|' + request.expectedVersion());
|
||||
Plan replay = replay(operation, planId, "ADD_INBOUND", null, requestHash);
|
||||
if (replay != null) return replay;
|
||||
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||
List<Candidate> details = candidates(plan.getContainerBookingId(), booking.getWarehouseId()).stream()
|
||||
.filter(candidate -> Objects.equals(candidate.inboundId(), request.inboundId())).toList();
|
||||
if (details.isEmpty()) throw conflict("入仓码不存在、已分配完或尚未完成入库");
|
||||
Candidate first = details.get(0);
|
||||
if (!repository.activeItemsByInbound(planId, first.accountId(), request.inboundId()).isEmpty()) {
|
||||
throw conflict("该入仓码已在当前柜次中,请先调整或移除现有分配");
|
||||
}
|
||||
List<InboundAllocation> allocations = proportionalAllocations(details, request.packageCount());
|
||||
int sequence = 0;
|
||||
for (InboundAllocation allocation : allocations) {
|
||||
if (allocation.packageCount() == 0) continue;
|
||||
LoadingPlanEntity current = repository.plan(planId);
|
||||
String childOperation = "INB-" + sha256(operation + '|' + allocation.candidate().inboundDetailId())
|
||||
.substring(0, 48);
|
||||
assign(planId, new AssignRequest(request.inboundId(), allocation.candidate().inboundDetailId(),
|
||||
allocation.packageCount(), current.getVersion(), childOperation), operatorId, operatorName);
|
||||
sequence++;
|
||||
}
|
||||
LoadingPlanEntity assigned = repository.plan(planId);
|
||||
history(planId, null, "ADD_INBOUND", plan.getPlanStatus(), assigned.getPlanStatus(), operation,
|
||||
json(values("version", request.expectedVersion(), "inboundId", request.inboundId())),
|
||||
json(values("requestHash", requestHash, "packageCount", request.packageCount(),
|
||||
"specificationLineCount", sequence, "cartonIdentityStatus", "UNVERIFIED")),
|
||||
operatorId, operatorName);
|
||||
return view(assigned, booking);
|
||||
}
|
||||
|
||||
List<InboundAllocation> proportionalAllocations(List<Candidate> details, int selected) {
|
||||
int available = details.stream().map(Candidate::availablePackageCount).filter(Objects::nonNull)
|
||||
.mapToInt(Integer::intValue).sum();
|
||||
if (selected < 1 || selected > available) throw validation("分配箱数超过入仓码可用箱数");
|
||||
if (selected == available) {
|
||||
return details.stream().map(detail -> new InboundAllocation(detail, detail.availablePackageCount(),
|
||||
BigDecimal.ZERO)).toList();
|
||||
}
|
||||
List<InboundAllocation> result = new ArrayList<>();
|
||||
int allocated = 0;
|
||||
for (Candidate detail : details) {
|
||||
int detailAvailable = Math.max(0, detail.availablePackageCount() == null ? 0 : detail.availablePackageCount());
|
||||
BigDecimal exact = BigDecimal.valueOf(selected).multiply(BigDecimal.valueOf(detailAvailable))
|
||||
.divide(BigDecimal.valueOf(available), 12, RoundingMode.HALF_UP);
|
||||
int base = exact.setScale(0, RoundingMode.DOWN).intValueExact();
|
||||
result.add(new InboundAllocation(detail, base, exact.subtract(BigDecimal.valueOf(base))));
|
||||
allocated += base;
|
||||
}
|
||||
int remaining = selected - allocated;
|
||||
List<Integer> priority = java.util.stream.IntStream.range(0, result.size()).boxed()
|
||||
.sorted(Comparator.<Integer, BigDecimal>comparing(index -> result.get(index).remainder()).reversed()
|
||||
.thenComparingInt(Integer::intValue)).toList();
|
||||
for (int index = 0; index < remaining; index++) {
|
||||
int target = priority.get(index);
|
||||
InboundAllocation current = result.get(target);
|
||||
result.set(target, new InboundAllocation(current.candidate(), current.packageCount() + 1,
|
||||
current.remainder()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
record InboundAllocation(Candidate candidate, int packageCount, BigDecimal remainder) {}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "SMART_LOADING_RECOMMEND", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "执行智能装柜推荐")
|
||||
|
||||
@@ -392,6 +392,17 @@ paths:
|
||||
- {name: bookingId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: warehouseId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
responses: {'200': {description: Candidates filtered by the server to the booking channel}}
|
||||
/api/platform/v1/first-leg/smart-loading/inbound-candidates:
|
||||
get:
|
||||
summary: 按入仓码查询智能装柜候选
|
||||
description: >-
|
||||
需要 platform:firstleg:smart-loading:list 权限。每个入库单/入仓码只返回一行;
|
||||
平均单箱毛重、净重按整票实测重量除以总箱数,平均单箱体积按剩余规格数量加权。
|
||||
这些数据用于数量级计划,不表示具体箱号已核验归属。
|
||||
parameters:
|
||||
- {name: bookingId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: warehouseId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
responses: {'200': {description: 成功,data 为入仓码级候选列表}}
|
||||
/api/platform/v1/first-leg/smart-loading/plans/{planId}:
|
||||
get:
|
||||
summary: 查询智能装柜草稿详情
|
||||
@@ -419,6 +430,20 @@ paths:
|
||||
content: {application/json: {schema: {$ref: '#/components/schemas/SmartLoadingAssignRequest'}}}
|
||||
responses:
|
||||
'200': {description: 成功或业务失败,data 为最新 SmartLoadingPlan}
|
||||
/api/platform/v1/first-leg/smart-loading/plans/{planId}/inbound-assignments:
|
||||
post:
|
||||
summary: 按入仓码分配计划装柜箱数
|
||||
description: >-
|
||||
需要 platform:firstleg:smart-loading:assign 权限。操作员只选择某个入仓码的本次计划箱数;
|
||||
服务端在单一事务内按剩余规格箱数比例生成内部规格快照,用于体积估算及兼容 PDA/拆单。
|
||||
只记录数量计划,具体实装箱号归属为 UNVERIFIED,不得将内部比例分摊宣称为现场箱号事实。
|
||||
parameters:
|
||||
- {name: planId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
requestBody:
|
||||
required: true
|
||||
content: {application/json: {schema: {$ref: '#/components/schemas/SmartLoadingInboundAssignRequest'}}}
|
||||
responses:
|
||||
'200': {description: 成功或业务失败,data 为最新 SmartLoadingPlan}
|
||||
/api/platform/v1/first-leg/smart-loading/plans/{planId}/items/{itemId}:
|
||||
patch:
|
||||
summary: 调整预装计划明细箱数
|
||||
@@ -1594,6 +1619,14 @@ components:
|
||||
packageCount: {type: integer, minimum: 1}
|
||||
expectedVersion: {type: integer, minimum: 0, description: 当前 planVersion}
|
||||
operationId: {type: string, minLength: 1, maxLength: 128}
|
||||
SmartLoadingInboundAssignRequest:
|
||||
type: object
|
||||
required: [inboundId, packageCount, expectedVersion, operationId]
|
||||
properties:
|
||||
inboundId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
packageCount: {type: integer, minimum: 1, description: 该入仓码本次计划装柜总箱数}
|
||||
expectedVersion: {type: integer, minimum: 0, description: 当前 planVersion}
|
||||
operationId: {type: string, minLength: 1, maxLength: 128}
|
||||
SmartLoadingAdjustRequest:
|
||||
type: object
|
||||
required: [packageCount, expectedPlanVersion, expectedItemVersion, operationId]
|
||||
|
||||
+4
@@ -12,6 +12,7 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
authority("draftByBooking","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
|
||||
authority("detail","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
|
||||
authority("candidates","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class,long.class);
|
||||
authority("inboundCandidates","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class,long.class);
|
||||
}
|
||||
@Test void openApiRequiresBookingAndWarehouseForCandidates() throws Exception {
|
||||
String yaml=java.nio.file.Files.readString(java.nio.file.Path.of("src/main/resources/openapi/firstleg-v1.yaml"));
|
||||
@@ -21,6 +22,7 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
}
|
||||
@Test void everyMutationDeclaresDedicatedPermission() throws Exception {
|
||||
authority("assign","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,AssignRequest.class);
|
||||
authority("assignInbound","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,AssignInboundRequest.class);
|
||||
authority("recommend","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,RecommendRequest.class);
|
||||
authority("adjust","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,long.class,AdjustRequest.class);
|
||||
authority("warehouseRemark","platform:firstleg:preload:edit",AuthenticatedUser.class,long.class,long.class,WarehouseRemarkRequest.class);
|
||||
@@ -41,6 +43,8 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
String yaml=java.nio.file.Files.readString(java.nio.file.Path.of("src/main/resources/openapi/firstleg-v1.yaml"));
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/first-leg/smart-loading/plans/{planId}/assignments:")
|
||||
.contains("/api/platform/v1/first-leg/smart-loading/plans/{planId}/inbound-assignments:")
|
||||
.contains("SmartLoadingInboundAssignRequest:")
|
||||
.contains("/api/platform/v1/first-leg/smart-loading/plans/{planId}/items/{itemId}:")
|
||||
.contains("SmartLoadingAssignRequest:")
|
||||
.contains("SmartLoadingAdjustRequest:")
|
||||
|
||||
@@ -104,6 +104,49 @@ class SmartLoadingServiceTest {
|
||||
assertThat(service.draftByBooking(99L).status()).isEqualTo("LOADING");
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupsCandidateSpecificationsByInboundCodeForQuantityOnlyPlanning() {
|
||||
Candidate first = new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "服装", 3,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.2"));
|
||||
Candidate second = new Candidate(9L, 10L, 21L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "鞋类", 7,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.5"));
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.candidates(100L)).thenReturn(List.of(first, second));
|
||||
|
||||
var result = service.inboundCandidates(99L, 100L);
|
||||
|
||||
assertThat(result).singleElement().satisfies(candidate -> {
|
||||
assertThat(candidate.inboundCode()).isEqualTo("I");
|
||||
assertThat(candidate.goodsSummary()).isEqualTo("服装、鞋类");
|
||||
assertThat(candidate.availablePackageCount()).isEqualTo(10);
|
||||
assertThat(candidate.averageUnitVolumeCbm()).isEqualByComparingTo("0.410000");
|
||||
assertThat(candidate.availableVolumeCbm()).isEqualByComparingTo("4.1000");
|
||||
assertThat(candidate.unitGrossWeightKg()).isEqualByComparingTo("2");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void proportionallyDistributesInboundQuantityWithoutLosingCartons() {
|
||||
Candidate first = new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "A", 30,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.2"));
|
||||
Candidate second = new Candidate(9L, 10L, 21L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "B", 40,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.5"));
|
||||
Candidate third = new Candidate(9L, 10L, 22L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "C", 30,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.8"));
|
||||
|
||||
var result = service.proportionalAllocations(List.of(first, second, third), 60);
|
||||
|
||||
assertThat(result).extracting(SmartLoadingService.InboundAllocation::packageCount)
|
||||
.containsExactly(18, 24, 18);
|
||||
assertThat(result).extracting(SmartLoadingService.InboundAllocation::candidate)
|
||||
.extracting(Candidate::inboundDetailId).containsExactly(20L, 21L, 22L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesPickedMinimumAndItemCapabilitiesForRunningTask() {
|
||||
LoadingPlanEntity plan = plan("LOADING", 3);
|
||||
|
||||
Reference in New Issue
Block a user