feat: add editable order collaboration remarks
This commit is contained in:
+4
-4
@@ -54,7 +54,7 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
authority("removeGroup","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,RemoveGroupRequest.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);
|
||||
authority("remarks","platform:firstleg:preload:edit",AuthenticatedUser.class,long.class,long.class,RemarksRequest.class);
|
||||
authority("delete","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,DeleteRequest.class);
|
||||
authority("removeUnpicked","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,RemoveUnpickedRequest.class);
|
||||
}
|
||||
@@ -77,13 +77,13 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
.contains("SmartLoadingAdjustRequest:")
|
||||
.contains("SmartLoadingDeleteRequest:")
|
||||
.contains("SmartLoadingRemoveUnpickedRequest:")
|
||||
.contains("SmartLoadingWarehouseRemarkRequest:")
|
||||
.contains("/items/{itemId}/warehouse-remark:")
|
||||
.contains("SmartLoadingRemarksRequest:")
|
||||
.contains("/items/{itemId}/remarks:")
|
||||
.contains("需要 platform:firstleg:preload:edit 权限")
|
||||
.contains("warehouseRemark: {type: string, nullable: true, maxLength: 1000")
|
||||
.contains("成功响应中的 version 是递增后的 planVersion")
|
||||
.contains("items[].version 是递增后的 itemVersion")
|
||||
.contains("数量调整或动态增量不会覆盖已有 warehouseRemark")
|
||||
.contains("数量调整或动态增量不会覆盖已有协作备注")
|
||||
.contains("柜内总容积和总重量分别允许达到柜型额定值的150%")
|
||||
.contains("人工分配不限制订单国内仓及渠道与柜次一致")
|
||||
.contains("智能推荐允许跨国内仓,但仍只选择柜次渠道")
|
||||
|
||||
+9
-7
@@ -122,8 +122,8 @@ class SmartLoadingSqlContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("仓库备注使用计划先行与明细单表CAS并允许已拣明细仅改备注")
|
||||
void shouldUpdateWarehouseRemarkWithoutMultiTableTriggerConflict() throws Exception {
|
||||
@DisplayName("可编辑备注使用计划先行与明细单表CAS并允许已拣明细仅改备注")
|
||||
void shouldUpdateEditableRemarksWithoutMultiTableTriggerConflict() throws Exception {
|
||||
String mapper = Files.readString(Path.of(
|
||||
"src/main/java/com/baiyue/firstleg/mapper/LoadingPlanItemMapper.java"), StandardCharsets.UTF_8);
|
||||
assertThat(mapper)
|
||||
@@ -133,8 +133,8 @@ class SmartLoadingSqlContractTest {
|
||||
.contains("i.frozen_at IS NULL")
|
||||
.doesNotContain("UPDATE fl_loading_plan_item i JOIN fl_loading_plan p")
|
||||
.doesNotContain("p.version=p.version+1");
|
||||
assertWarehouseRemarkTriggerContract(migration());
|
||||
assertWarehouseRemarkTriggerContract(unifiedMigration());
|
||||
assertEditableRemarksTriggerContract(migration());
|
||||
assertEditableRemarksTriggerContract(unifiedMigration());
|
||||
}
|
||||
|
||||
private static void assertContainerLifecycleSchema(String sql) {
|
||||
@@ -151,11 +151,13 @@ class SmartLoadingSqlContractTest {
|
||||
.contains("column_name='merged_into_container_booking_id'");
|
||||
}
|
||||
|
||||
private static void assertWarehouseRemarkTriggerContract(String sql) {
|
||||
private static void assertEditableRemarksTriggerContract(String sql) {
|
||||
assertThat(sql)
|
||||
.contains("IF NOT (NEW.warehouse_remark <=> OLD.warehouse_remark) THEN")
|
||||
.contains("IF NOT (NEW.internal_remark <=> OLD.internal_remark)")
|
||||
.contains("OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)")
|
||||
.contains("OR NOT (NEW.customer_service_remark <=> OLD.customer_service_remark) THEN")
|
||||
.contains("IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted")
|
||||
.contains("已有PDA拣货记录的装柜明细修改仓库备注时不得变更其他业务字段")
|
||||
.contains("已有PDA拣货记录的装柜明细修改备注时不得变更其他业务字段")
|
||||
.contains("ELSEIF NOT EXISTS (")
|
||||
.contains("IF OLD.frozen_at IS NOT NULL THEN")
|
||||
.contains("装柜完成后的明细永久冻结");
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainer;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainerQuery;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.Plan;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.RecommendRequest;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.WarehouseRemarkRequest;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.RemarksRequest;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
|
||||
@@ -1267,7 +1267,7 @@ class SmartLoadingServiceTest {
|
||||
when(cargoClient.detail(anyString(), eq(9L), eq(30L), eq(20L))).thenReturn(ApiResponse.success(
|
||||
new OrderCargoClient.CargoBinding(20L, 30L, 1, "中文品名", 10,
|
||||
new BigDecimal("20"), new BigDecimal("2"), "SKU-1", "English name",
|
||||
4, 100, new BigDecimal("10"), "货物备注", "客户备注", "最新内部备注")));
|
||||
4, 100, new BigDecimal("10"), "货物备注", "客户备注", "最新内部备注", "仓库备注", "客服备注")));
|
||||
doAnswer(invocation -> {
|
||||
Object value = invocation.getArgument(0);
|
||||
if (value instanceof LoadingPlanItemEntity item) item.setId(88L);
|
||||
@@ -1288,6 +1288,8 @@ class SmartLoadingServiceTest {
|
||||
assertThat(saved.getNetWeightKg()).isEqualByComparingTo("7.5");
|
||||
assertThat(saved.getCustomerRemark()).isEqualTo("客户备注");
|
||||
assertThat(saved.getInternalRemark()).isEqualTo("最新内部备注");
|
||||
assertThat(saved.getWarehouseRemark()).isEqualTo("仓库备注");
|
||||
assertThat(saved.getCustomerServiceRemark()).isEqualTo("客服备注");
|
||||
assertThat(saved.getInternalRemark()).isNotEqualTo("货物备注");
|
||||
|
||||
ArgumentCaptor<LoadingPlanHistoryEntity> historyCaptor = ArgumentCaptor.forClass(LoadingPlanHistoryEntity.class);
|
||||
@@ -1451,20 +1453,20 @@ class SmartLoadingServiceTest {
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(line));
|
||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||
when(repository.updateWarehouseRemark(1L, 8L, 3, 0, null, 7L, "u")).thenReturn(1);
|
||||
when(repository.updateRemarks(1L, 8L, 3, 0, null, null, null, 7L, "u")).thenReturn(1);
|
||||
|
||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest(" ", 2, 0, "warehouse-clear"), 7L, "u");
|
||||
Plan result = service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, " ", null, 2, 0, "warehouse-clear"), 7L, "u");
|
||||
|
||||
assertThat(result.status()).isEqualTo("LOADING");
|
||||
verify(loadingPickService).lockMutable(1L);
|
||||
var order = inOrder(loadingPickService, repository);
|
||||
order.verify(loadingPickService).lockMutable(1L);
|
||||
order.verify(repository).touch(1L, 2, 7L, "u");
|
||||
order.verify(repository).updateWarehouseRemark(1L, 8L, 3, 0, null, 7L, "u");
|
||||
order.verify(repository).updateRemarks(1L, 8L, 3, 0, null, null, null, 7L, "u");
|
||||
ArgumentCaptor<LoadingPlanHistoryEntity> history = ArgumentCaptor.forClass(LoadingPlanHistoryEntity.class);
|
||||
verify(repository).insert(history.capture());
|
||||
assertThat(history.getValue().getActionType()).isEqualTo("WAREHOUSE_REMARK");
|
||||
assertThat(history.getValue().getActionType()).isEqualTo("REMARKS");
|
||||
assertThat(history.getValue().getBeforeSnapshot()).contains("旧仓库备注");
|
||||
}
|
||||
|
||||
@@ -1477,14 +1479,14 @@ class SmartLoadingServiceTest {
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(line));
|
||||
when(repository.touch(1L, 1, 7L, "u")).thenReturn(1);
|
||||
when(repository.updateWarehouseRemark(1L, 8L, 2, 0, "待核对外箱", 7L, "u")).thenReturn(1);
|
||||
when(repository.updateRemarks(1L, 8L, 2, 0, null, "待核对外箱", null, 7L, "u")).thenReturn(1);
|
||||
|
||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest("待核对外箱", 1, 0, "warehouse-editing"), 7L, "u");
|
||||
Plan result = service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, "待核对外箱", null, 1, 0, "warehouse-editing"), 7L, "u");
|
||||
|
||||
assertThat(result.status()).isEqualTo("EDITING");
|
||||
assertThat(result.version()).isEqualTo(2);
|
||||
verify(repository).updateWarehouseRemark(1L, 8L, 2, 0, "待核对外箱", 7L, "u");
|
||||
verify(repository).updateRemarks(1L, 8L, 2, 0, null, "待核对外箱", null, 7L, "u");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1497,13 +1499,13 @@ class SmartLoadingServiceTest {
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(line));
|
||||
|
||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest(" 待核对外箱 ", 2, 0, "warehouse-noop"), 7L, "u");
|
||||
Plan result = service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, " 待核对外箱 ", null, 2, 0, "warehouse-noop"), 7L, "u");
|
||||
|
||||
assertThat(result.version()).isEqualTo(2);
|
||||
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), anyLong(), anyString());
|
||||
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), any(), any(), anyLong(), anyString());
|
||||
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||
}
|
||||
|
||||
@@ -1514,10 +1516,10 @@ class SmartLoadingServiceTest {
|
||||
when(repository.plan(1L)).thenReturn(plan);
|
||||
when(repository.item(1L, 8L)).thenReturn(line);
|
||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||
when(repository.updateWarehouseRemark(1L, 8L, 3, 0, "新备注", 7L, "u")).thenReturn(0);
|
||||
when(repository.updateRemarks(1L, 8L, 3, 0, null, "新备注", null, 7L, "u")).thenReturn(0);
|
||||
|
||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest("新备注", 2, 0, "warehouse-conflict"), 7L, "u"), BizException.class);
|
||||
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, "新备注", null, 2, 0, "warehouse-conflict"), 7L, "u"), BizException.class);
|
||||
|
||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||
verify(repository).touch(1L, 2, 7L, "u");
|
||||
@@ -1531,15 +1533,15 @@ class SmartLoadingServiceTest {
|
||||
when(repository.plan(1L)).thenReturn(plan);
|
||||
when(repository.item(1L, 8L)).thenReturn(line);
|
||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||
when(repository.updateWarehouseRemark(1L, 8L, 3, 0, "新备注", 7L, "u"))
|
||||
when(repository.updateRemarks(1L, 8L, 3, 0, null, "新备注", null, 7L, "u"))
|
||||
.thenThrow(new DataIntegrityViolationException("trigger",
|
||||
new SQLException("状态冲突", "45000")));
|
||||
|
||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest("新备注", 2, 0, "warehouse-trigger"), 7L, "u"), BizException.class);
|
||||
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, "新备注", null, 2, 0, "warehouse-trigger"), 7L, "u"), BizException.class);
|
||||
|
||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||
assertThat(error.getMessage()).contains("仓库备注未保存").contains("刷新后重试");
|
||||
assertThat(error.getMessage()).contains("备注未保存").contains("刷新后重试");
|
||||
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||
}
|
||||
|
||||
@@ -1547,46 +1549,46 @@ class SmartLoadingServiceTest {
|
||||
void stalePlanVersionRejectsWarehouseRemarkBeforeItemWrite() {
|
||||
when(repository.plan(1L)).thenReturn(plan("LOADING", 3));
|
||||
|
||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest("新备注", 2, 0, "warehouse-stale-plan"), 7L, "u"), BizException.class);
|
||||
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, "新备注", null, 2, 0, "warehouse-stale-plan"), 7L, "u"), BizException.class);
|
||||
|
||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), anyLong(), anyString());
|
||||
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), any(), any(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completedPlanRejectsWarehouseRemarkBeforeAnyWrite() {
|
||||
when(repository.plan(1L)).thenReturn(plan("COMPLETED", 4));
|
||||
|
||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest("新备注", 4, 0, "warehouse-completed"), 7L, "u"), BizException.class);
|
||||
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, "新备注", null, 4, 0, "warehouse-completed"), 7L, "u"), BizException.class);
|
||||
|
||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||
verify(loadingPickService, never()).lockMutable(anyLong());
|
||||
verify(repository, never()).item(anyLong(), anyLong());
|
||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), anyLong(), anyString());
|
||||
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), any(), any(), anyLong(), anyString());
|
||||
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exactWarehouseRemarkReplayIsReadOnly() {
|
||||
String requestHash = sha256("WAREHOUSE_REMARK|1|8|备注|2|0");
|
||||
String requestHash = sha256("REMARKS|1|8||备注||2|0");
|
||||
when(repository.operation("warehouse-replay"))
|
||||
.thenReturn(history(1L, 8L, "WAREHOUSE_REMARK", requestHash));
|
||||
.thenReturn(history(1L, 8L, "REMARKS", requestHash));
|
||||
when(repository.plan(1L)).thenReturn(plan("LOADING", 3));
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(item(8, 9, 20, 1)));
|
||||
|
||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
||||
new WarehouseRemarkRequest("备注", 2, 0, "warehouse-replay"), 7L, "u");
|
||||
Plan result = service.updateRemarks(1L, 8L,
|
||||
new RemarksRequest(null, "备注", null, 2, 0, "warehouse-replay"), 7L, "u");
|
||||
|
||||
assertThat(result.version()).isEqualTo(3);
|
||||
verify(loadingPickService, never()).lockMutable(anyLong());
|
||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), anyLong(), anyString());
|
||||
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||
any(), any(), any(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user