fix(overseas): repair pallet transitions and transfer queries
This commit is contained in:
+16
-4
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.lastleg.palletops.gateway;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.lastleg.config.LastlegRestClientFactory;
|
||||
import com.baiyue.lastleg.config.LastlegRestClientProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
@@ -111,18 +113,28 @@ public class HttpPalletPhysicalGateway implements PalletPhysicalGateway {
|
||||
.onStatus(HttpStatusCode::isError, (request, result) -> {
|
||||
throw new IllegalStateException("Warehouse rejected pallet transition: HTTP " + result.getStatusCode());
|
||||
}).body(TransitionEnvelope.class);
|
||||
if (response == null || response.data() == null) throw new IllegalStateException("Warehouse returned no transition");
|
||||
requireTransitionSuccess(response);
|
||||
if (response.data() == null) throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "Warehouse returned no transition");
|
||||
return response.data();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compensate(long warehouseId, Transition transition) {
|
||||
client.post().uri("/internal/v1/pda-pallet-transitions/{id}/{key}/compensate",
|
||||
TransitionEnvelope response = client.post().uri("/internal/v1/pda-pallet-transitions/{id}/{key}/compensate",
|
||||
transition.palletId(), transition.operationKey())
|
||||
.header("X-Internal-Service-Token", internalToken).body(Map.of("warehouseId", warehouseId))
|
||||
.retrieve().onStatus(HttpStatusCode::isError, (request, result) -> {
|
||||
throw new IllegalStateException("Warehouse rejected pallet compensation: HTTP " + result.getStatusCode());
|
||||
}).toBodilessEntity();
|
||||
}).body(TransitionEnvelope.class);
|
||||
requireTransitionSuccess(response);
|
||||
}
|
||||
|
||||
private void requireTransitionSuccess(TransitionEnvelope response) {
|
||||
if (response != null && "200".equals(response.code())) return;
|
||||
if (response != null && "A0409".equals(response.code())) {
|
||||
throw new BizException(ResultCode.CONFLICT, response.message());
|
||||
}
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "Warehouse pallet transition failed");
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -130,5 +142,5 @@ public class HttpPalletPhysicalGateway implements PalletPhysicalGateway {
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record LocationEnvelope(LocationReference data) { }
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record TransitionEnvelope(Transition data) { }
|
||||
private record TransitionEnvelope(String code, String message, Transition data) { }
|
||||
}
|
||||
|
||||
+4
-2
@@ -433,8 +433,10 @@ public class WarehouseTransferService {
|
||||
value.getFbaWarehouseCode(), value.getDestinationType(), value.getDestinationWarehouseCode(),
|
||||
value.getDestinationWarehouseName(), value.getPackageCount(), value.getDeliveryMethod(),
|
||||
Boolean.TRUE.equals(value.getNeedRelabel()), value.getRelabelNo(),
|
||||
relabelApplicationsByNo.get(value.getRelabelNo()) == null ? null : relabelApplicationsByNo.get(value.getRelabelNo()).getStatus(),
|
||||
relabelApplicationsByNo.get(value.getRelabelNo()) == null ? null : relabelApplicationsByNo.get(value.getRelabelNo()).getFulfillmentStatus(),
|
||||
value.getRelabelNo() == null || relabelApplicationsByNo.get(value.getRelabelNo()) == null
|
||||
? null : relabelApplicationsByNo.get(value.getRelabelNo()).getStatus(),
|
||||
value.getRelabelNo() == null || relabelApplicationsByNo.get(value.getRelabelNo()) == null
|
||||
? null : relabelApplicationsByNo.get(value.getRelabelNo()).getFulfillmentStatus(),
|
||||
value.getStatus(),
|
||||
value.getEstimatedArrivalAt(), value.getCreatedAt())).toList();
|
||||
return new PageResult<>(records, result.getCurrent(), result.getSize(), result.getTotal(), result.getPages());
|
||||
|
||||
+26
@@ -26,6 +26,32 @@ class HttpPalletPhysicalGatewayTest {
|
||||
ReflectionTestUtils.setField(gateway, "internalToken", "test-token");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyPreservesBusinessConflictInsteadOfReportingMissingTransition() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/pda-pallet-transitions/30"))
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"A0409","message":"Pallet version changed","data":null}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
var pallet = new PalletPhysicalGateway.PalletReference(30L, "PAL-1", 88L, 40L, "IDLE", "XLX1", 2);
|
||||
assertThatThrownBy(() -> gateway.apply(88L, pallet, "OCCUPIED", 40L, "key", "swap"))
|
||||
.isInstanceOf(com.baiyue.common.core.exception.BizException.class)
|
||||
.hasMessageContaining("Pallet version changed");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void compensationBusinessFailureIsNotReportedAsSuccess() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/pda-pallet-transitions/30/key/compensate"))
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"A0409","message":"Pallet changed after operation","data":null}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
var transition = new PalletPhysicalGateway.Transition("key", 30L, "PAL-1", 88L,
|
||||
"IDLE", "OCCUPIED", 40L, 40L, 2, 3, "APPLIED", false);
|
||||
assertThatThrownBy(() -> gateway.compensate(88L, transition))
|
||||
.isInstanceOf(com.baiyue.common.core.exception.BizException.class);
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveMapsBusinessNotFoundEnvelopeToStableNotFound() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/MISSING"))
|
||||
|
||||
+17
@@ -127,6 +127,23 @@ class WarehouseTransferServiceTest {
|
||||
assertEquals("READY_FOR_DISPATCH", result.records().get(0).relabelFulfillmentStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void transferPageWithoutRelabelReturnsEmptyRelabelStatuses() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_PDA_RECEIVING);
|
||||
transfer.setRelabelNo(null);
|
||||
Page<WarehouseTransferOrder> resultPage = new Page<>(1, 20, 1);
|
||||
resultPage.setRecords(List.of(transfer));
|
||||
when(transfers.selectPage(any(), any())).thenReturn(resultPage);
|
||||
|
||||
var result = service.page("TENANT-A", new WarehouseTransferRequests.Query(
|
||||
1, 20, null, null, null, null, null, null, null, null));
|
||||
|
||||
assertEquals(1, result.records().size());
|
||||
assertNull(result.records().get(0).relabelApplicationStatus());
|
||||
assertNull(result.records().get(0).relabelFulfillmentStatus());
|
||||
verify(relabelApplications, never()).selectList(any());
|
||||
}
|
||||
|
||||
@Mock WarehouseTransferOrderMapper transfers;
|
||||
@Mock WarehouseTransferItemMapper transferItems;
|
||||
@Mock WarehouseTransferCartonMapper transferCartons;
|
||||
|
||||
@@ -41,7 +41,7 @@ test('relabel transfer and discard cover create replay conflict list detail and
|
||||
assert.match(source, /query\(`\$\{RELABEL\}\/inventory-candidates`, \{/)
|
||||
for (const token of [
|
||||
'客户创建贴标申请', '客户贴标申请幂等重放', '贴标同幂等键异载荷返回A0409',
|
||||
'客户查询自身贴标申请列表', '客户查询自身贴标申请详情', 'PENDING_PROCESSING',
|
||||
'客户查询自身贴标申请列表', '客户查询自身贴标申请详情', 'PENDING_REVIEW',
|
||||
'客户创建仓储转运申请', '客户仓储转运申请幂等重放', '转运同幂等键异载荷返回A0409',
|
||||
'客户查询自身仓储转运列表', '客户查询自身仓储转运详情',
|
||||
'客户创建货物废弃申请', '客户货物废弃申请幂等重放',
|
||||
|
||||
@@ -129,7 +129,7 @@ async function verifyRelabel(context, token, order) {
|
||||
const created = await merchant(context, token, 'POST', '客户创建贴标申请', RELABEL, body)
|
||||
const relabelNo = id(created.data?.application?.relabelNo)
|
||||
context.assert(MODULE, '客户贴标申请进入合法待处理状态', created.response,
|
||||
Boolean(relabelNo) && created.data?.application?.status === 'PENDING_PROCESSING'
|
||||
Boolean(relabelNo) && created.data?.application?.status === 'PENDING_REVIEW'
|
||||
&& created.data?.application?.customerOrderNo === order.row.orderNo,
|
||||
`relabelNo=${relabelNo || 'missing'}, status=${created.data?.application?.status || 'missing'}; ${trace(created.response)}`, OPTIONS)
|
||||
if (!relabelNo) return false
|
||||
@@ -145,9 +145,9 @@ async function verifyRelabel(context, token, order) {
|
||||
const detail = await merchant(context, token, 'GET', '客户查询自身贴标申请详情',
|
||||
`${RELABEL}/${encodeURIComponent(relabelNo)}`)
|
||||
context.assert(MODULE, '贴标详情保留明细与创建历史', detail.response,
|
||||
id(detail.data?.application?.relabelNo) === relabelNo && detail.data?.application?.status === 'PENDING_PROCESSING'
|
||||
id(detail.data?.application?.relabelNo) === relabelNo && detail.data?.application?.status === 'PENDING_REVIEW'
|
||||
&& Array.isArray(detail.data?.items) && detail.data.items.length === 1
|
||||
&& Array.isArray(detail.data?.history) && detail.data.history.some((item) => item?.toStatus === 'PENDING_PROCESSING'),
|
||||
&& Array.isArray(detail.data?.history) && detail.data.history.some((item) => item?.toStatus === 'PENDING_REVIEW'),
|
||||
`items=${detail.data?.items?.length ?? 'missing'}, histories=${detail.data?.history?.length ?? 'missing'}; ${trace(detail.response)}`, OPTIONS)
|
||||
context.createdResources.phase4OverseasClientServices.relabelNo = relabelNo
|
||||
return true
|
||||
@@ -299,7 +299,7 @@ export async function run(context) {
|
||||
const transferDone = await verifyTransfer(context, token, orders)
|
||||
const discardDone = await verifyDiscard(context, token, orders)
|
||||
context.createdResources.phase4OverseasClientServices.finalStatuses = {
|
||||
relabel: relabelDone ? 'PENDING_PROCESSING' : null,
|
||||
relabel: relabelDone ? 'PENDING_REVIEW' : null,
|
||||
transfer: transferDone ? 'APPROVED' : null,
|
||||
discard: discardDone ? 'PENDING_REVIEW' : null,
|
||||
}
|
||||
|
||||
@@ -94,8 +94,9 @@ test('PDA operations reuse received plan pallets and the public location without
|
||||
/auxiliaryPallets: generatedContainerPallets\.filter/)
|
||||
assert.match(source, /resolvePublicLocation/)
|
||||
assert.match(source, /resolveReusableAuxiliaryPallet/)
|
||||
assert.match(source, /locationType === 'VIRTUAL'/)
|
||||
assert.match(source, /P3托盘已在公共库位不重复移库/)
|
||||
assert.match(source, /\['STORAGE', 'NORMAL'\]\.includes\(item\?\.locationType\)/)
|
||||
assert.match(source, /status === 'IDLE' && item\?\.locationCode !== sourceLocationCode/)
|
||||
assert.doesNotMatch(source, /P3托盘已在公共库位不重复移库/)
|
||||
assert.doesNotMatch(source, /warehouse-admin[^'`]*\/locations`,\s*\{/)
|
||||
assert.doesNotMatch(source, /warehouse-admin[^'`]*\/pallets`,\s*\{/)
|
||||
assert.doesNotMatch(source, /createLocation|createEmptyPallet/)
|
||||
|
||||
@@ -98,11 +98,11 @@ async function resolvePallet(context, token, palletCode, name = '扫码解析托
|
||||
'/api/pda/v1/last-leg/pallet-operations/pallets/resolve', { code: palletCode })
|
||||
}
|
||||
|
||||
async function resolvePublicLocation(context, fixture) {
|
||||
async function resolvePublicLocation(context, fixture, sourceLocationCode) {
|
||||
const response = await context.requestOverseas('GET',
|
||||
`/api/platform/v1/warehouse-admin/${encodeURIComponent(fixture.overseasWarehouseId)}/locations?page=1&size=200`)
|
||||
const location = records(response?.body?.data).find((item) => item?.locationType === 'VIRTUAL'
|
||||
&& item?.status === 'ACTIVE')
|
||||
const location = records(response?.body?.data).find((item) => ['STORAGE', 'NORMAL'].includes(item?.locationType)
|
||||
&& item?.status === 'IDLE' && item?.locationCode !== sourceLocationCode)
|
||||
context.addStep(MODULE, '复用当前海外仓公共库位', response,
|
||||
success(response) && longId(id(location?.id)) && nonBlank(location?.locationCode),
|
||||
`locationId=${id(location?.id) || 'missing'}, locationCode=${location?.locationCode || 'missing'}; ${trace(response)}`, OPTIONS)
|
||||
@@ -196,22 +196,14 @@ async function verifyStocktake(context, token, fixture) {
|
||||
}
|
||||
|
||||
async function verifyInventoryMove(context, token, fixture) {
|
||||
const target = await resolvePublicLocation(context, fixture)
|
||||
if (!target) return false
|
||||
let pallet = (await resolvePallet(context, token, fixture.pallet.palletCode, 'P3移库前解析整托')).data
|
||||
if (!pallet || !nonBlank(pallet.locationCode)) {
|
||||
blocker(context, 'P3整托移库', '本轮完成托盘无法解析当前位置')
|
||||
return false
|
||||
}
|
||||
const originalLocationCode = pallet.locationCode
|
||||
if (originalLocationCode === target.locationCode) {
|
||||
const availablePackages = (pallet.inventory || []).reduce((sum, item) =>
|
||||
sum + Number(item?.availablePackages || 0), 0)
|
||||
context.assert(MODULE, 'P3托盘已在公共库位不重复移库', null,
|
||||
availablePackages > 0,
|
||||
`palletCode=${pallet.palletCode}, locationCode=${target.locationCode}, availablePackages=${availablePackages}`, OPTIONS)
|
||||
return availablePackages > 0
|
||||
}
|
||||
const target = await resolvePublicLocation(context, fixture, originalLocationCode)
|
||||
if (!target) return false
|
||||
const path = '/api/pda/v1/last-leg/inventory-moves'
|
||||
const body = { sourcePalletCode: pallet.palletCode, targetLocationCode: target.locationCode,
|
||||
expectedPalletVersion: Number(pallet.palletVersion), idempotencyKey: key(context, 'MOVE-FORWARD'),
|
||||
@@ -220,6 +212,7 @@ async function verifyInventoryMove(context, token, fixture) {
|
||||
context.assert(MODULE, 'P3整托移库数量和目标位置正确', moved.response,
|
||||
moved.data?.status === 'COMPLETED' && moved.data?.targetLocationCode === target.locationCode
|
||||
&& Number(moved.data?.packageCount) > 0, trace(moved.response), OPTIONS)
|
||||
if (!success(moved.response)) return false
|
||||
const replay = await request(context, token, 'POST', 'P3整托移库同键重放', path, body)
|
||||
context.assert(MODULE, 'P3移库重放标记且结果稳定', replay.response,
|
||||
replay.data?.replayed === true && replay.data?.moveNo === moved.data?.moveNo, trace(replay.response), OPTIONS)
|
||||
@@ -272,6 +265,7 @@ async function verifyPalletOperations(context, token, fixture) {
|
||||
target: { palletCode: auxiliary.palletCode, expectedVersion: Number(auxiliary.palletVersion) },
|
||||
reason: `${context.marker}-swap` }
|
||||
const swapped = await replayOperation(context, token, 'P4整托换托', `${base}/swap`, swapBody, 'PALLET_SWAP')
|
||||
if (!success(swapped.response)) return false
|
||||
await expectConflict(context, token, 'POST', 'P4换托同键异载荷A0409', `${base}/swap`,
|
||||
{ ...swapBody, reason: `${swapBody.reason}-DIFFERENT` })
|
||||
let occupiedSwap = operationPallet(swapped.data, auxiliary.palletCode)
|
||||
@@ -301,6 +295,7 @@ async function verifyPalletOperations(context, token, fixture) {
|
||||
reason: `${context.marker}-disassemble` }
|
||||
const disassembled = await replayOperation(context, token, 'P4拆托一箱到辅助托盘',
|
||||
`${base}/disassemble`, disassembleBody, 'PALLET_DISASSEMBLE')
|
||||
if (!success(disassembled.response)) return false
|
||||
const splitOccupied = operationPallet(disassembled.data, auxiliary.palletCode)
|
||||
|| (await resolvePallet(context, token, auxiliary.palletCode, 'P4拆托后解析辅助托盘')).data
|
||||
const mainAfterSplit = operationPallet(disassembled.data, main.palletCode)
|
||||
@@ -434,12 +429,16 @@ async function verifyRelabel(context, token, merchantToken, fixture) {
|
||||
const relabelNo = id(created?.application?.relabelNo)
|
||||
context.addStep(MODULE, 'P5基于本轮完成库存创建真实换标申请', createdResponse,
|
||||
success(createdResponse) && nonBlank(relabelNo)
|
||||
&& created?.application?.status === 'PENDING_PROCESSING',
|
||||
&& created?.application?.status === 'PENDING_REVIEW',
|
||||
`relabelNo=${relabelNo || 'missing'}, sku=${inventory.skuCode}; ${trace(createdResponse)}`, OPTIONS)
|
||||
if (!relabelNo) {
|
||||
blocker(context, 'P5真实换标申请', '平台正式换标创建接口未返回申请单号', createdResponse)
|
||||
return false
|
||||
}
|
||||
const reviewed = await request(context, context.overseasToken, 'PUT', 'P5尾程订单审核直接贴标申请',
|
||||
query(`/api/platform/v1/last-leg/orders/${encodeURIComponent(id(created.application.lastlegOrderId))}/relabel-review`, { customerId }),
|
||||
{ status: 'PENDING_PROCESSING', remark: context.marker })
|
||||
if (!success(reviewed.response) || reviewed.data?.application?.status !== 'PENDING_PROCESSING') return false
|
||||
const funded = await poll(async () => context.requestOverseas('GET', `${relabelBase}/${encodeURIComponent(relabelNo)}`),
|
||||
(response) => success(response) && response.body?.data?.application?.walletStatus === 'FROZEN')
|
||||
context.addStep(MODULE, 'P5换标费用冻结后开放PDA作业', funded.result,
|
||||
|
||||
@@ -244,6 +244,15 @@ async function relabelFlow(context, overseasToken, warehouseId, suffix, terminal
|
||||
`relabelNo=${relabelNo}, pollRequests=${frozen.attempts}, walletStatus=${frozen.result?.body?.data?.application?.walletStatus || 'missing'}`)
|
||||
if (frozen.result?.body?.data?.application?.walletStatus !== 'FROZEN') return null
|
||||
|
||||
const reviewStatus = terminalStatus === 'COMPLETED' ? 'PENDING_PROCESSING' : 'CANCELLED'
|
||||
const review = await context.requestOverseas('PUT', query(
|
||||
`/api/platform/v1/last-leg/orders/${encodeURIComponent(id(created.body.data.application.lastlegOrderId))}/relabel-review`,
|
||||
{ customerId: id(context.context.merchantQualificationFixture.tenantId) }),
|
||||
{ status: reviewStatus, remark: context.marker })
|
||||
assert(context, `${suffix}尾程订单审核直接贴标申请`, review,
|
||||
success(review) && review.body.data?.application?.status === reviewStatus)
|
||||
if (!success(review)) return null
|
||||
|
||||
if (terminalStatus === 'COMPLETED') {
|
||||
const processing = await context.requestOverseas('PUT', `${detailPath}/status`, { status: 'PROCESSING', remark: context.marker })
|
||||
assert(context, '换标单冻结后才允许进入处理中', processing,
|
||||
@@ -257,7 +266,7 @@ async function relabelFlow(context, overseasToken, warehouseId, suffix, terminal
|
||||
assert(context, '换标客户确认后进入待出库', readyForOutbound,
|
||||
success(readyForOutbound) && readyForOutbound.body.data?.application?.status === 'READY_FOR_OUTBOUND')
|
||||
}
|
||||
const terminal = await context.requestOverseas('PUT', `${detailPath}/status`, { status: terminalStatus, remark: context.marker })
|
||||
const terminal = terminalStatus === 'CANCELLED' ? review : await context.requestOverseas('PUT', `${detailPath}/status`, { status: terminalStatus, remark: context.marker })
|
||||
assert(context, `${suffix}换标单进入${terminalStatus}`, terminal,
|
||||
success(terminal) && terminal.body.data?.application?.status === terminalStatus)
|
||||
const walletTerminal = terminalStatus === 'COMPLETED' ? 'SETTLED' : 'RELEASED'
|
||||
|
||||
+4
-3
@@ -18,6 +18,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
@@ -102,15 +103,15 @@ public class WarehousePalletTransitionService {
|
||||
WarehouseLocationEntity target;
|
||||
if (sourceLocationId <= targetLocationId) {
|
||||
source = requireLocation(warehouseId, sourceLocationId);
|
||||
target = sourceLocationId == targetLocationId ? source : requireLocation(warehouseId, targetLocationId);
|
||||
target = Objects.equals(sourceLocationId, targetLocationId) ? source : requireLocation(warehouseId, targetLocationId);
|
||||
} else {
|
||||
target = requireLocation(warehouseId, targetLocationId);
|
||||
source = requireLocation(warehouseId, sourceLocationId);
|
||||
}
|
||||
requireLocationMatchesPallet(source, pallet.getStatus());
|
||||
if (sourceLocationId != targetLocationId) requireAvailableTarget(target, targetStatus);
|
||||
if (!Objects.equals(sourceLocationId, targetLocationId)) requireAvailableTarget(target, targetStatus);
|
||||
|
||||
if (sourceLocationId == targetLocationId) {
|
||||
if (Objects.equals(sourceLocationId, targetLocationId)) {
|
||||
transitionSameLocation(warehouseId, source, pallet.getStatus(), targetStatus);
|
||||
} else {
|
||||
releaseSourceLocation(warehouseId, source, pallet.getStatus());
|
||||
|
||||
+16
@@ -48,6 +48,22 @@ class WarehousePalletTransitionServiceTest {
|
||||
assertEquals(4, captor.getValue().getBeforeVersion());
|
||||
}
|
||||
|
||||
@Test void releasesOccupiedPalletAtSameLargeLocationId() {
|
||||
long locationId = 2104611550538616833L;
|
||||
when(mapper.lockPublicPallet(88L, 10L)).thenReturn(pallet("P-LARGE", "OCCUPIED", 4, locationId));
|
||||
when(mapper.lockPublicLocation(88L, locationId)).thenReturn(location(locationId, "STORAGE", "OCCUPIED"));
|
||||
when(mapper.transitionLocation(88L, locationId, "OCCUPIED", "IDLE")).thenReturn(1);
|
||||
when(mapper.transition(88L, 10L, "IDLE", locationId, 4)).thenReturn(1);
|
||||
|
||||
var response = service.apply(10L, new PalletTransitionRequests.Apply(
|
||||
88L, 4, "IDLE", locationId, "large-location-release", "swap"));
|
||||
|
||||
assertEquals("IDLE", response.afterStatus());
|
||||
assertEquals(locationId, response.afterLocationId());
|
||||
verify(mapper, times(1)).lockPublicLocation(88L, locationId);
|
||||
verify(mapper).transitionLocation(88L, locationId, "OCCUPIED", "IDLE");
|
||||
}
|
||||
|
||||
@Test void exactReplayDoesNotMutatePhysicalPalletAgain() {
|
||||
WarehousePalletOperationEntity existing = operation();
|
||||
when(mapper.findOperation("operation-1")).thenReturn(existing);
|
||||
|
||||
Reference in New Issue
Block a user