fix(firstleg): replay completed customs uploads
This commit is contained in:
+4
-4
@@ -173,6 +173,10 @@ public class ContainerCustomsService {
|
||||
int expectedVersion, boolean finalConfirmation, MultipartFile file) {
|
||||
validateFile(file);
|
||||
DocumentRow readable=requiredDocument(documentId);
|
||||
String action = "UPLOAD_COMPLETION_" + documentId;
|
||||
String requestHash = hash(action, expectedVersion, finalConfirmation, fileHash(file));
|
||||
CommandRow replay = claim(operatorId, action, key, requestHash);
|
||||
if (replay != null) return documentView(requiredDocument(documentId), false);
|
||||
ContainerRow container = mapper.lockContainerById(readable.customsContainerId());
|
||||
DocumentRow source = requiredDocumentLocked(documentId, null);
|
||||
if (container == null || !DECLARING.equals(container.status()) || source.frozenAt() == null) {
|
||||
@@ -183,10 +187,6 @@ public class ContainerCustomsService {
|
||||
boolean lastNewCompletion=completion==null&&missingBefore==1;
|
||||
if(lastNewCompletion&&!finalConfirmation)throw conflict("这是最后一份完成文件,请二次确认后上传");
|
||||
if(finalConfirmation&&!lastNewCompletion)throw conflict("只有最后一份尚未上传的完成文件才能确认整柜完成");
|
||||
String action = "UPLOAD_COMPLETION_" + documentId;
|
||||
String requestHash = hash(action, expectedVersion, finalConfirmation, fileHash(file));
|
||||
CommandRow replay = claim(operatorId, action, key, requestHash);
|
||||
if (replay != null) return documentView(requiredDocument(documentId), false);
|
||||
CustomsFileClient.Metadata metadata = upload(source.tenantId(), RESULT_FILE_TYPE, Long.toString(container.loadingPlanId()), operatorId, file);
|
||||
long completionId;
|
||||
int version;
|
||||
|
||||
+61
@@ -106,6 +106,7 @@ class ContainerCustomsServiceTest {
|
||||
|
||||
@Test void lastCompletionRequiresConfirmationBeforeCallingFile(){
|
||||
var source=document(1L);var container=container(ContainerCustomsService.DECLARING,3);
|
||||
when(mapper.claimCommand(anyLong(),eq(5L),eq("UPLOAD_COMPLETION_1"),eq("k"),anyString())).thenReturn(1);
|
||||
when(mapper.document(1L)).thenReturn(source);when(mapper.lockContainerById(2L)).thenReturn(container);
|
||||
when(mapper.lockDocument(1L)).thenReturn(source);when(mapper.lockCompletion(1L)).thenReturn(null);
|
||||
when(mapper.missingCompletionCount(2L)).thenReturn(1);
|
||||
@@ -117,6 +118,7 @@ class ContainerCustomsServiceTest {
|
||||
|
||||
@Test void completedContainerLocksCompletionReplacement(){
|
||||
var source=document(1L);when(mapper.document(1L)).thenReturn(source);
|
||||
when(mapper.claimCommand(anyLong(),eq(5L),eq("UPLOAD_COMPLETION_1"),eq("k"),anyString())).thenReturn(1);
|
||||
when(mapper.lockContainerById(2L)).thenReturn(container(ContainerCustomsService.COMPLETED,4));
|
||||
when(mapper.lockDocument(1L)).thenReturn(source);
|
||||
var file=new MockMultipartFile("file","result.bin","application/octet-stream",new byte[]{1});
|
||||
@@ -125,6 +127,65 @@ class ContainerCustomsServiceTest {
|
||||
verifyNoInteractions(files);
|
||||
}
|
||||
|
||||
@Test void finalCompletionReplaySucceedsAfterContainerCompletedAndRejectsChangedPayload(){
|
||||
var source=document(1L);
|
||||
var declaring=container(ContainerCustomsService.DECLARING,3);
|
||||
var completed=container(ContainerCustomsService.COMPLETED,4);
|
||||
var containerState=new AtomicReference<>(declaring);
|
||||
var completionState=new AtomicReference<ContainerCustomsMapper.CompletionRow>();
|
||||
var commandState=new AtomicReference<ContainerCustomsMapper.CommandRow>();
|
||||
var file=new MockMultipartFile("file","result.pdf","application/pdf",new byte[]{1});
|
||||
var changedFile=new MockMultipartFile("file","result.pdf","application/pdf",new byte[]{2});
|
||||
var metadata=new CustomsFileClient.Metadata(99L,7L,"LOADING_LIST_CUSTOMS_RESULT","9","result.pdf",
|
||||
"b".repeat(64),"application/pdf",1L);
|
||||
when(mapper.document(1L)).thenReturn(source);
|
||||
when(mapper.claimCommand(anyLong(),eq(5L),eq("UPLOAD_COMPLETION_1"),eq("completion-key"),anyString()))
|
||||
.thenAnswer(invocation->{
|
||||
if(commandState.get()!=null)return 0;
|
||||
commandState.set(new ContainerCustomsMapper.CommandRow(invocation.getArgument(0),5L,
|
||||
"UPLOAD_COMPLETION_1","completion-key",invocation.getArgument(4),"PROCESSING",null,null,null));
|
||||
return 1;
|
||||
});
|
||||
when(mapper.command(5L,"UPLOAD_COMPLETION_1","completion-key")).thenAnswer(invocation->commandState.get());
|
||||
when(mapper.lockContainerById(2L)).thenAnswer(invocation->containerState.get());
|
||||
when(mapper.lockDocument(1L)).thenReturn(source);
|
||||
when(mapper.lockCompletion(1L)).thenAnswer(invocation->completionState.get());
|
||||
when(mapper.missingCompletionCount(2L)).thenReturn(1);
|
||||
when(files.upload(any(),eq(5L),eq(7L),eq("LOADING_LIST_CUSTOMS_RESULT"),eq("9"),same(file)))
|
||||
.thenReturn(com.baiyue.common.core.api.ApiResponse.success(metadata));
|
||||
when(mapper.insertCompletion(anyLong(),eq(1L),eq(99L),eq("result.pdf"),eq("b".repeat(64)),
|
||||
eq("application/pdf"),eq(1L),eq(5L),eq("admin"))).thenAnswer(invocation->{
|
||||
completionState.set(new ContainerCustomsMapper.CompletionRow(invocation.getArgument(0),1L,99L,
|
||||
"result.pdf","b".repeat(64),"application/pdf",1L,5L,"admin",java.time.LocalDateTime.now(),1));
|
||||
return 1;
|
||||
});
|
||||
when(mapper.complete(2L,3,5L,"admin")).thenAnswer(invocation->{containerState.set(completed);return 1;});
|
||||
when(mapper.completeCommand(eq(5L),eq("UPLOAD_COMPLETION_1"),eq("completion-key"),anyLong()))
|
||||
.thenAnswer(invocation->{
|
||||
var current=commandState.get();
|
||||
commandState.set(new ContainerCustomsMapper.CommandRow(current.id(),current.actorId(),current.action(),
|
||||
current.idempotencyKey(),current.requestHash(),"SUCCESS",invocation.getArgument(3),null,null));
|
||||
return 1;
|
||||
});
|
||||
when(mapper.frozenScopes(1L)).thenReturn(List.of(new ContainerCustomsMapper.ScopeRow(1L,11L,21L,"I")));
|
||||
when(mapper.completion(1L)).thenAnswer(invocation->completionState.get());
|
||||
when(mapper.containerById(2L)).thenAnswer(invocation->containerState.get());
|
||||
|
||||
var first=service.uploadCompletion(1L,5L,"admin","completion-key",0,true,file);
|
||||
var replay=service.uploadCompletion(1L,5L,"admin","completion-key",0,true,file);
|
||||
|
||||
assertThat(first.completion().fileId()).isEqualTo(99L);
|
||||
assertThat(replay.completion().fileId()).isEqualTo(99L);
|
||||
assertThatThrownBy(()->service.uploadCompletion(1L,5L,"admin","completion-key",0,true,changedFile))
|
||||
.isInstanceOfSatisfying(BizException.class,
|
||||
error->assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT));
|
||||
verify(files,times(1)).upload(any(),eq(5L),eq(7L),eq("LOADING_LIST_CUSTOMS_RESULT"),eq("9"),same(file));
|
||||
verify(mapper,times(1)).lockContainerById(2L);
|
||||
verify(mapper,times(1)).insertCompletion(anyLong(),eq(1L),eq(99L),anyString(),anyString(),anyString(),
|
||||
eq(1L),eq(5L),eq("admin"));
|
||||
verify(mapper,times(1)).complete(2L,3,5L,"admin");
|
||||
}
|
||||
|
||||
@Test void merchantCannotDownloadCompletionBeforeWholeContainerCompletes(){
|
||||
var source=document(1L);when(mapper.document(1L)).thenReturn(source);
|
||||
when(mapper.containerById(2L)).thenReturn(container(ContainerCustomsService.DECLARING,3));
|
||||
|
||||
Reference in New Issue
Block a user