fix(customs): keep declaration pending while exceptions remain

This commit is contained in:
2026-09-30 11:48:14 +08:00
parent 6731be1a6b
commit c7f880300c
4 changed files with 49 additions and 4 deletions
@@ -428,8 +428,11 @@ public class ContainerCustomsService {
if (mapper.insertClearanceResult(resultId, batch.id(), batch.customsContainerId(), type, resultStatus,
batch.customsEntryNo(), request.occurredAt(), trim(request.resolution()), request.evidenceFileId(),
operatorId, operatorName, null) != 1) throw conflict("海关结果保存失败");
if ("OPEN".equals(resultStatus)) mapper.markDeclarationResultPending(batch.id());
else mapper.closeDeclarationBatch(batch.id());
if ("OPEN".equals(resultStatus) || mapper.openClearanceResultCount(batch.id()) > 0) {
mapper.markDeclarationResultPending(batch.id());
} else {
mapper.closeDeclarationBatch(batch.id());
}
ContainerRow container = mapper.lockContainerById(batch.customsContainerId());
if (container != null) history(container, "RECORD_CUSTOMS_CLEARANCE_RESULT", container.status(), container.status(), type, operatorId, operatorName);
completeCommand(operatorId, action, key, resultId);
@@ -4,11 +4,14 @@ import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.customs.container.dto.ContainerCustomsDtos.ActionRequest;
import com.baiyue.firstleg.customs.container.dto.ContainerCustomsDtos.ScopeRequest;
import com.baiyue.firstleg.customs.container.dto.ContainerCustomsDtos.ClearanceResultRequest;
import com.baiyue.firstleg.customs.container.mapper.ContainerCustomsMapper;
import com.baiyue.firstleg.customs.container.service.ContainerCustomsService;
import com.baiyue.firstleg.customs.client.CustomsFileClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.*;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.web.MockMultipartFile;
@@ -438,6 +441,44 @@ class ContainerCustomsServiceTest {
verify(events,times(1)).publishEvent(new ContainerCustomsService.CustomsCompletedEvent(8L));
}
@ParameterizedTest
@CsvSource({
"INSPECTION,true,1,true",
"RELEASED,false,2,true",
"OTHER,true,0,false",
"RELEASED,false,0,false",
"DETAINED,false,1,true"
})
void newlyResolvedResultDoesNotCloseBatchWithOtherOpenExceptions(
String type, boolean resolved, int openResults, boolean remainsPending) {
var at = java.time.LocalDateTime.of(2026,9,30,11,0);
var batch = new ContainerCustomsMapper.DeclarationBatchRow(
41L,2L,9L,"CD-LP9-01","RESULT_PENDING",3,"CUSTOMS-001","admin",at,
"admin",at,"",4,at,at);
String status = "RELEASED".equals(type) || resolved ? "RESOLVED" : "OPEN";
when(mapper.claimCommand(anyLong(),eq(5L),anyString(),eq("result-key"),anyString())).thenReturn(1);
when(mapper.lockDeclarationBatch(41L)).thenReturn(batch);
when(mapper.insertClearanceResult(anyLong(),eq(41L),eq(2L),eq(type),eq(status),
eq("CUSTOMS-001"),eq(at),eq("人工处理说明"),isNull(),eq(5L),eq("admin"),isNull())).thenReturn(1);
when(mapper.openClearanceResultCount(41L)).thenReturn(openResults);
when(mapper.completeCommand(eq(5L),anyString(),eq("result-key"),anyLong())).thenReturn(1);
when(mapper.clearanceResult(anyLong())).thenAnswer(call -> new ContainerCustomsMapper.ClearanceResultRow(
call.getArgument(0),41L,2L,type,status,"CUSTOMS-001",at,"人工处理说明",null,5L,"admin",at,null));
var result = service.addClearanceResult(41L,5L,"admin","result-key",
new ClearanceResultRequest(4,type,at,"人工处理说明",null,resolved));
assertThat(result.resultStatus()).isEqualTo(status);
if (remainsPending) {
verify(mapper).markDeclarationResultPending(41L);
verify(mapper,never()).closeDeclarationBatch(anyLong());
} else {
verify(mapper).closeDeclarationBatch(41L);
verify(mapper,never()).markDeclarationResultPending(anyLong());
}
verifyNoInteractions(events,files);
}
private static ContainerCustomsMapper.DocumentRow document(long id){return new ContainerCustomsMapper.DocumentRow(id,2L,7L,10L,"source.bin","a".repeat(64),"application/octet-stream",1L,"ACTIVE",5L,java.time.LocalDateTime.now(),1,java.time.LocalDateTime.now(),1);}
private static ContainerCustomsMapper.BrokerSubmissionRow brokerSubmission(){return new ContainerCustomsMapper.BrokerSubmissionRow(41L,2L,9L,"BR-LP9-01","SENT",3,"admin",java.time.LocalDateTime.now(),1);}
private static ContainerCustomsMapper.ContainerRow container(String status,int version){return new ContainerCustomsMapper.ContainerRow(2L,9L,8L,status,null,null,null,null,null,null,null,null,version);}
@@ -53,7 +53,7 @@ test('钱包闭环覆盖充值、授信、采购冻结结算和换标结算释
test('托盘汇总和拆柜成本验证总数、分摊及跨区金额守恒', () => {
for (const token of [
'/api/platform/v1/pallets/summary',
'/api/platform/v1/warehouse-admin/pallets/summary',
'/api/platform/v1/overseas-warehouse-resources/pallets/summary',
'/pallets/summary?page=1&size=20',
'OVERSEAS_CONTAINER',
'DESTUFF_PALLET',
@@ -61,6 +61,7 @@ test('托盘汇总和拆柜成本验证总数、分摊及跨区金额守恒', ()
'sourceFeeId',
'pollRequests=',
]) assert.ok(source.includes(token), token)
assert.doesNotMatch(source, /\/warehouse-admin\/pallets\/summary/)
assert.match(source, /Number\(d\.totalCount\) === Number\(d\.idleCount\) \+ Number\(d\.occupiedCount\)/)
assert.match(source, /allocated === money\(fact\.expectedPalletCost\)/)
assert.match(source, /delivered\.reduce[\s\S]*fact\.expectedPalletCost/)
@@ -304,7 +304,7 @@ async function verifyPalletSummaries(context, warehouseId) {
success(domestic) && summaryValid(d, ['totalCount', 'idleCount', 'occupiedCount'])
&& Number(d.totalCount) === Number(d.idleCount) + Number(d.occupiedCount),
`total=${d?.totalCount}, idle=${d?.idleCount}, occupied=${d?.occupiedCount}`)
const overseas = await context.request('GET', '/api/platform/v1/warehouse-admin/pallets/summary?page=1&size=20')
const overseas = await context.request('GET', '/api/platform/v1/overseas-warehouse-resources/pallets/summary?page=1&size=20')
const o = overseas?.body?.data
assert(context, '海外托盘汇总含闲置、占用和报废数量', overseas,
success(overseas) && summaryValid(o, ['totalCount', 'idleCount', 'occupiedCount', 'scrappedCount'])