修复PDA稳定柜次查询契约
This commit is contained in:
+3
-1
@@ -46,9 +46,11 @@ public class PlatformLoadingPdaController {
|
|||||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||||
public ApiResponse<Task> task(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<Task> task(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestParam(required = false) String containerNo,
|
@RequestParam(required = false) String containerNo,
|
||||||
|
@RequestParam(required = false) String cabinetNo,
|
||||||
|
@RequestParam(required = false) Long loadingPlanId,
|
||||||
@RequestParam @Positive long warehouseId) {
|
@RequestParam @Positive long warehouseId) {
|
||||||
FirstlegScopeGuard.platform(user);
|
FirstlegScopeGuard.platform(user);
|
||||||
return ApiResponse.success(service.task(containerNo, warehouseId));
|
return ApiResponse.success(service.task(containerNo, cabinetNo, loadingPlanId, warehouseId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/tasks/pending")
|
@GetMapping("/tasks/pending")
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
|||||||
LoadingPickTaskEntity selectLoadingByContainerAndAccount(@Param("containerNo") String containerNo,
|
LoadingPickTaskEntity selectLoadingByContainerAndAccount(@Param("containerNo") String containerNo,
|
||||||
@Param("warehouseId") long warehouseId);
|
@Param("warehouseId") long warehouseId);
|
||||||
|
|
||||||
|
@Select("SELECT task.* FROM fl_loading_pick_task task JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||||
|
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||||
|
+ "WHERE task.task_status='LOADING' AND booking.cabinet_no=#{cabinetNo} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||||
|
LoadingPickTaskEntity selectLoadingByCabinet(@Param("cabinetNo") String cabinetNo, @Param("warehouseId") long warehouseId);
|
||||||
|
|
||||||
|
@Select("SELECT task.* FROM fl_loading_pick_task task JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||||
|
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||||
|
+ "WHERE task.task_status='LOADING' AND task.loading_plan_id=#{loadingPlanId} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||||
|
LoadingPickTaskEntity selectLoadingByPlan(@Param("loadingPlanId") long loadingPlanId, @Param("warehouseId") long warehouseId);
|
||||||
|
|
||||||
@Select("""
|
@Select("""
|
||||||
<script>
|
<script>
|
||||||
SELECT task.id AS taskId, task.loading_plan_id AS loadingPlanId,
|
SELECT task.id AS taskId, task.loading_plan_id AS loadingPlanId,
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ public class LoadingPickRepository {
|
|||||||
public LoadingPickTaskEntity taskByContainer(String containerNo, long warehouseId) {
|
public LoadingPickTaskEntity taskByContainer(String containerNo, long warehouseId) {
|
||||||
return tasks.selectLoadingByContainerAndAccount(containerNo, warehouseId);
|
return tasks.selectLoadingByContainerAndAccount(containerNo, warehouseId);
|
||||||
}
|
}
|
||||||
|
public LoadingPickTaskEntity taskByCabinet(String cabinetNo, long warehouseId) {
|
||||||
|
return tasks.selectLoadingByCabinet(cabinetNo, warehouseId);
|
||||||
|
}
|
||||||
|
public LoadingPickTaskEntity taskByPlan(long loadingPlanId, long warehouseId) {
|
||||||
|
return tasks.selectLoadingByPlan(loadingPlanId, warehouseId);
|
||||||
|
}
|
||||||
|
|
||||||
public List<PendingTaskRow> pendingTasks(long operatorId, String keyword) {
|
public List<PendingTaskRow> pendingTasks(long operatorId, String keyword) {
|
||||||
return tasks.selectPendingForOperator(operatorId, keyword);
|
return tasks.selectPendingForOperator(operatorId, keyword);
|
||||||
|
|||||||
@@ -125,6 +125,19 @@ public class LoadingPickService {
|
|||||||
return view(task, warehouseId);
|
return view(task, warehouseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task task(String containerNo, String cabinetNo, Long loadingPlanId, long warehouseId) {
|
||||||
|
int selectors = (StringUtils.hasText(containerNo) ? 1 : 0) + (StringUtils.hasText(cabinetNo) ? 1 : 0)
|
||||||
|
+ (loadingPlanId != null ? 1 : 0);
|
||||||
|
if (selectors != 1) throw new BizException(ResultCode.BAD_REQUEST,
|
||||||
|
"containerNo、cabinetNo、loadingPlanId必须且只能提供一个");
|
||||||
|
LoadingPickTaskEntity task = StringUtils.hasText(cabinetNo)
|
||||||
|
? repository.taskByCabinet(cabinetNo.trim(), warehouseId)
|
||||||
|
: loadingPlanId != null ? repository.taskByPlan(loadingPlanId, warehouseId)
|
||||||
|
: repository.taskByContainer(containerNo.trim(), warehouseId);
|
||||||
|
if (task == null) throw new BizException(ResultCode.NOT_FOUND, "未找到装柜中的PDA拣货任务");
|
||||||
|
return view(task, warehouseId);
|
||||||
|
}
|
||||||
|
|
||||||
public Task task(long taskId, long warehouseId) {
|
public Task task(long taskId, long warehouseId) {
|
||||||
return view(requiredTask(taskId), warehouseId);
|
return view(requiredTask(taskId), warehouseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,6 +252,13 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200': {description: 成功、草稿不存在或计划已进入预装流程}
|
'200': {description: 成功、草稿不存在或计划已进入预装流程}
|
||||||
'403': {description: 无智能装柜查询权限}
|
'403': {description: 无智能装柜查询权限}
|
||||||
|
/api/platform/v1/first-leg/smart-loading/candidates:
|
||||||
|
get:
|
||||||
|
summary: Query same-channel smart-loading candidates
|
||||||
|
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: Candidates filtered by the server to the booking channel}}
|
||||||
/api/platform/v1/first-leg/smart-loading/plans/{planId}:
|
/api/platform/v1/first-leg/smart-loading/plans/{planId}:
|
||||||
get:
|
get:
|
||||||
summary: 查询智能装柜草稿详情
|
summary: 查询智能装柜草稿详情
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
class PlatformLoadingPdaControllerContractTest {
|
class PlatformLoadingPdaControllerContractTest {
|
||||||
@Test
|
@Test
|
||||||
void exposesAssignmentWorkAndReviewWithDedicatedAuthorities() throws Exception {
|
void exposesAssignmentWorkAndReviewWithDedicatedAuthorities() throws Exception {
|
||||||
authority("task", "platform:firstleg:pda:view", AuthenticatedUser.class, String.class, long.class);
|
authority("task", "platform:firstleg:pda:view", AuthenticatedUser.class, String.class, String.class, Long.class, long.class);
|
||||||
authority("pendingTasks", "platform:firstleg:pda:view", AuthenticatedUser.class, String.class);
|
authority("pendingTasks", "platform:firstleg:pda:view", AuthenticatedUser.class, String.class);
|
||||||
authority("inventorySnapshot", "platform:firstleg:pda:view", AuthenticatedUser.class, long.class, long.class);
|
authority("inventorySnapshot", "platform:firstleg:pda:view", AuthenticatedUser.class, long.class, long.class);
|
||||||
authority("claim", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, ClaimRequest.class);
|
authority("claim", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, ClaimRequest.class);
|
||||||
|
|||||||
+7
@@ -11,6 +11,13 @@ class PlatformSmartLoadingControllerContractTest {
|
|||||||
@Test void draftQueriesUseListPermission() throws Exception {
|
@Test void draftQueriesUseListPermission() throws Exception {
|
||||||
authority("draftByBooking","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
|
authority("draftByBooking","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
|
||||||
authority("detail","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);
|
||||||
|
}
|
||||||
|
@Test void openApiRequiresBookingAndWarehouseForCandidates() throws Exception {
|
||||||
|
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/candidates:")
|
||||||
|
.contains("name: bookingId, in: query, required: true")
|
||||||
|
.contains("name: warehouseId, in: query, required: true");
|
||||||
}
|
}
|
||||||
@Test void everyMutationDeclaresDedicatedPermission() throws Exception {
|
@Test void everyMutationDeclaresDedicatedPermission() throws Exception {
|
||||||
authority("assign","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,AssignRequest.class);
|
authority("assign","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,AssignRequest.class);
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.baiyue.firstleg.repository;
|
||||||
|
|
||||||
|
import com.baiyue.firstleg.mapper.LoadingPickTaskMapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class LoadingPickStableLookupContractTest {
|
||||||
|
@Test void stableLookupsUseCabinetOrPlanAndNotMutableContainerIdentity() throws Exception {
|
||||||
|
String cabinet = LoadingPickTaskMapper.class.getMethod("selectLoadingByCabinet", String.class, long.class)
|
||||||
|
.getAnnotation(Select.class).value()[0];
|
||||||
|
String plan = LoadingPickTaskMapper.class.getMethod("selectLoadingByPlan", long.class, long.class)
|
||||||
|
.getAnnotation(Select.class).value()[0];
|
||||||
|
String legacy = LoadingPickTaskMapper.class.getMethod("selectLoadingByContainerAndAccount", String.class, long.class)
|
||||||
|
.getAnnotation(Select.class).value()[0];
|
||||||
|
assertThat(cabinet).contains("booking.cabinet_no=#{cabinetNo}").doesNotContain("task.container_no");
|
||||||
|
assertThat(plan).contains("task.loading_plan_id=#{loadingPlanId}").doesNotContain("task.container_no");
|
||||||
|
assertThat(legacy).contains("booking.container_no = #{containerNo}").doesNotContain("task.container_no =");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@SpringJUnitConfig(LoadingPickService.class)
|
@SpringJUnitConfig(LoadingPickService.class)
|
||||||
class LoadingPickServiceTest {
|
class LoadingPickServiceTest {
|
||||||
@@ -52,6 +53,49 @@ class LoadingPickServiceTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private LoadingPickService service;
|
private LoadingPickService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stableCabinetAndPlanQueriesReturnSameTaskWithCurrentContainerNumber() {
|
||||||
|
LoadingPickTaskEntity task = task();
|
||||||
|
task.setContainerBookingId(40L); task.setCabinetNo("CAB-001");
|
||||||
|
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
|
||||||
|
booking.setId(40L); booking.setContainerNo("MSCU-NEW");
|
||||||
|
when(repository.taskByCabinet("CAB-001", 101L)).thenReturn(task);
|
||||||
|
when(repository.taskByPlan(10L, 101L)).thenReturn(task);
|
||||||
|
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
|
||||||
|
when(repository.items(20L, 101L)).thenReturn(List.of());
|
||||||
|
when(repository.cartonLabels(20L)).thenReturn(List.of());
|
||||||
|
|
||||||
|
var byCabinet = service.task(null, "CAB-001", null, 101L);
|
||||||
|
var byPlan = service.task(null, null, 10L, 101L);
|
||||||
|
|
||||||
|
assertThat(byCabinet.id()).isEqualTo(20L);
|
||||||
|
assertThat(byPlan.id()).isEqualTo(20L);
|
||||||
|
assertThat(byCabinet.currentContainerNo()).isEqualTo("MSCU-NEW");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsMissingOrConflictingCompatibilitySelectors() {
|
||||||
|
assertThatThrownBy(() -> service.task(null, null, null, 101L))
|
||||||
|
.isInstanceOf(BizException.class).hasMessageContaining("必须且只能提供一个");
|
||||||
|
assertThatThrownBy(() -> service.task("MSCU", "CAB", null, 101L))
|
||||||
|
.isInstanceOf(BizException.class).hasMessageContaining("必须且只能提供一个");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void legacyContainerLookupReturnsCurrentTransportDisplayWithoutChangingIdentity() {
|
||||||
|
LoadingPickTaskEntity task = task(); task.setContainerBookingId(40L); task.setCabinetNo("CAB-001");
|
||||||
|
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
|
||||||
|
booking.setId(40L); booking.setContainerNo("MSCU-CURRENT");
|
||||||
|
when(repository.taskByContainer("MSCU-CURRENT", 101L)).thenReturn(task);
|
||||||
|
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
|
||||||
|
when(repository.items(20L, 101L)).thenReturn(List.of());
|
||||||
|
when(repository.cartonLabels(20L)).thenReturn(List.of());
|
||||||
|
var result = service.task("MSCU-CURRENT", null, null, 101L);
|
||||||
|
assertThat(result.id()).isEqualTo(20L);
|
||||||
|
assertThat(result.loadingPlanId()).isEqualTo(10L);
|
||||||
|
assertThat(result.currentContainerNo()).isEqualTo("MSCU-CURRENT");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void keepsPlanLoadingWhenItemsRemain() {
|
void keepsPlanLoadingWhenItemsRemain() {
|
||||||
LoadingPickTaskEntity task = task();
|
LoadingPickTaskEntity task = task();
|
||||||
|
|||||||
Reference in New Issue
Block a user