fix(firstleg): map customs document versions explicitly
This commit is contained in:
+24
-11
@@ -8,6 +8,18 @@ import java.util.List;
|
|||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ContainerCustomsMapper {
|
public interface ContainerCustomsMapper {
|
||||||
|
String DOCUMENT_COLUMNS = """
|
||||||
|
d.id id,d.customs_container_id customsContainerId,d.tenant_id tenantId,d.file_id fileId,
|
||||||
|
d.file_name fileName,d.file_hash fileHash,d.content_type contentType,d.size_bytes sizeBytes,
|
||||||
|
d.status status,d.uploaded_by uploadedBy,d.uploaded_at uploadedAt,d.frozen_version frozenVersion,
|
||||||
|
d.frozen_at frozenAt,d.version version
|
||||||
|
""";
|
||||||
|
String COMPLETION_COLUMNS = """
|
||||||
|
r.id id,r.source_document_id sourceDocumentId,r.file_id fileId,r.file_name fileName,
|
||||||
|
r.file_hash fileHash,r.content_type contentType,r.size_bytes sizeBytes,r.uploaded_by uploadedBy,
|
||||||
|
r.uploaded_by_name uploadedByName,r.uploaded_at uploadedAt,r.version version
|
||||||
|
""";
|
||||||
|
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT DISTINCT p.id loadingPlanId,p.plan_no planNo,p.container_booking_id containerBookingId,
|
SELECT DISTINCT p.id loadingPlanId,p.plan_no planNo,p.container_booking_id containerBookingId,
|
||||||
p.plan_status loadingPlanStatus,b.cabinet_no cabinetNo,b.container_no containerNo,
|
p.plan_status loadingPlanStatus,b.cabinet_no cabinetNo,b.container_no containerNo,
|
||||||
@@ -87,21 +99,20 @@ public interface ContainerCustomsMapper {
|
|||||||
@Param("fileId") long fileId,@Param("fileName") String fileName,@Param("fileHash") String fileHash,
|
@Param("fileId") long fileId,@Param("fileName") String fileName,@Param("fileHash") String fileHash,
|
||||||
@Param("contentType") String contentType,@Param("sizeBytes") long sizeBytes,@Param("operatorId") long operatorId);
|
@Param("contentType") String contentType,@Param("sizeBytes") long sizeBytes,@Param("operatorId") long operatorId);
|
||||||
|
|
||||||
@Select("SELECT * FROM fl_customs_customer_document WHERE id=#{id}")
|
@Select("SELECT " + DOCUMENT_COLUMNS + " FROM fl_customs_customer_document d WHERE d.id=#{id}")
|
||||||
DocumentRow document(@Param("id") long id);
|
DocumentRow document(@Param("id") long id);
|
||||||
|
|
||||||
@Select("SELECT * FROM fl_customs_customer_document WHERE id=#{id} FOR UPDATE")
|
@Select("SELECT " + DOCUMENT_COLUMNS + " FROM fl_customs_customer_document d WHERE d.id=#{id} FOR UPDATE")
|
||||||
DocumentRow lockDocument(@Param("id") long id);
|
DocumentRow lockDocument(@Param("id") long id);
|
||||||
|
|
||||||
@Select("""
|
@Select("<script>SELECT " + DOCUMENT_COLUMNS + " FROM fl_customs_customer_document d " +
|
||||||
<script>SELECT * FROM fl_customs_customer_document
|
"WHERE d.customs_container_id=#{containerId} AND d.status='ACTIVE' " +
|
||||||
WHERE customs_container_id=#{containerId} AND status='ACTIVE'
|
"<if test='tenantId != null'>AND d.tenant_id=#{tenantId}</if> " +
|
||||||
<if test='tenantId != null'>AND tenant_id=#{tenantId}</if>
|
"ORDER BY d.created_at,d.id</script>")
|
||||||
ORDER BY created_at,id</script>
|
|
||||||
""")
|
|
||||||
List<DocumentRow> documents(@Param("containerId") long containerId,@Param("tenantId") Long tenantId);
|
List<DocumentRow> documents(@Param("containerId") long containerId,@Param("tenantId") Long tenantId);
|
||||||
|
|
||||||
@Select("SELECT * FROM fl_customs_customer_document WHERE customs_container_id=#{containerId} AND status='ACTIVE' ORDER BY id FOR UPDATE")
|
@Select("SELECT " + DOCUMENT_COLUMNS + " FROM fl_customs_customer_document d " +
|
||||||
|
"WHERE d.customs_container_id=#{containerId} AND d.status='ACTIVE' ORDER BY d.id FOR UPDATE")
|
||||||
List<DocumentRow> lockActiveDocuments(@Param("containerId") long containerId);
|
List<DocumentRow> lockActiveDocuments(@Param("containerId") long containerId);
|
||||||
|
|
||||||
@Update("""
|
@Update("""
|
||||||
@@ -186,10 +197,12 @@ public interface ContainerCustomsMapper {
|
|||||||
""")
|
""")
|
||||||
List<ScopeRow> frozenScopes(@Param("documentId") long documentId);
|
List<ScopeRow> frozenScopes(@Param("documentId") long documentId);
|
||||||
|
|
||||||
@Select("SELECT * FROM fl_customs_completion_document WHERE source_document_id=#{documentId}")
|
@Select("SELECT " + COMPLETION_COLUMNS + " FROM fl_customs_completion_document r " +
|
||||||
|
"WHERE r.source_document_id=#{documentId}")
|
||||||
CompletionRow completion(@Param("documentId") long documentId);
|
CompletionRow completion(@Param("documentId") long documentId);
|
||||||
|
|
||||||
@Select("SELECT * FROM fl_customs_completion_document WHERE source_document_id=#{documentId} FOR UPDATE")
|
@Select("SELECT " + COMPLETION_COLUMNS + " FROM fl_customs_completion_document r " +
|
||||||
|
"WHERE r.source_document_id=#{documentId} FOR UPDATE")
|
||||||
CompletionRow lockCompletion(@Param("documentId") long documentId);
|
CompletionRow lockCompletion(@Param("documentId") long documentId);
|
||||||
|
|
||||||
@Insert("""
|
@Insert("""
|
||||||
|
|||||||
+29
@@ -4,6 +4,10 @@ import com.baiyue.firstleg.customs.container.mapper.ContainerCustomsMapper;
|
|||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.RecordComponent;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class ContainerCustomsMapperContractTest {
|
class ContainerCustomsMapperContractTest {
|
||||||
@@ -27,6 +31,31 @@ class ContainerCustomsMapperContractTest {
|
|||||||
assertThat(value).contains("p.plan_status='COMPLETED'","i.tax_mode='BUY_DECLARATION'","item.deleted=0")
|
assertThat(value).contains("p.plan_status='COMPLETED'","i.tax_mode='BUY_DECLARATION'","item.deleted=0")
|
||||||
.contains("item.id>#{cursor}","item.id<=#{maximumId}");
|
.contains("item.id>#{cursor}","item.id<=#{maximumId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test void documentQueriesExplicitlyMapEveryRecordComponentIncludingVersion() throws Exception {
|
||||||
|
assertRecordProjection("document", ContainerCustomsMapper.DocumentRow.class, long.class);
|
||||||
|
assertRecordProjection("lockDocument", ContainerCustomsMapper.DocumentRow.class, long.class);
|
||||||
|
assertRecordProjection("documents", ContainerCustomsMapper.DocumentRow.class, long.class, Long.class);
|
||||||
|
assertRecordProjection("lockActiveDocuments", ContainerCustomsMapper.DocumentRow.class, long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void completionQueriesExplicitlyMapEveryRecordComponentIncludingVersion() throws Exception {
|
||||||
|
assertRecordProjection("completion", ContainerCustomsMapper.CompletionRow.class, long.class);
|
||||||
|
assertRecordProjection("lockCompletion", ContainerCustomsMapper.CompletionRow.class, long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertRecordProjection(String method,Class<?> recordType,Class<?>... parameterTypes) throws Exception {
|
||||||
|
String value=sql(method,parameterTypes).replaceAll("\\s+"," ").trim();
|
||||||
|
assertThat(value).doesNotContainIgnoringCase("SELECT *");
|
||||||
|
String projection=value.substring(value.indexOf("SELECT ")+7,value.indexOf(" FROM "));
|
||||||
|
List<String> aliases=Arrays.stream(projection.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.map(column->column.substring(column.lastIndexOf(' ')+1))
|
||||||
|
.toList();
|
||||||
|
List<String> components=Arrays.stream(recordType.getRecordComponents()).map(RecordComponent::getName).toList();
|
||||||
|
assertThat(aliases).containsExactlyElementsOf(components).contains("version");
|
||||||
|
}
|
||||||
|
|
||||||
private static String sql(String name,Class<?>... types) throws Exception {
|
private static String sql(String name,Class<?>... types) throws Exception {
|
||||||
return String.join(" ",ContainerCustomsMapper.class.getMethod(name,types).getAnnotation(Select.class).value());
|
return String.join(" ",ContainerCustomsMapper.class.getMethod(name,types).getAnnotation(Select.class).value());
|
||||||
}
|
}
|
||||||
|
|||||||
+29
@@ -13,6 +13,7 @@ import org.springframework.context.ApplicationEventPublisher;
|
|||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
@@ -51,6 +52,34 @@ class ContainerCustomsServiceTest {
|
|||||||
assertThat(view.inbounds()).extracting(value->value.orderId()).containsExactly(21L);
|
assertThat(view.inbounds()).extracting(value->value.orderId()).containsExactly(21L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test void uploadedSourceViewReturnsPersistedNonNullVersion(){
|
||||||
|
var inbound=new ContainerCustomsMapper.InboundRow(9L,"LP9",8L,"COMPLETED","CAB","C",11L,21L,"O","I",7L,"客户","TAX_REFUND");
|
||||||
|
var aggregate=container(ContainerCustomsService.READY,2);
|
||||||
|
var persisted=new ContainerCustomsMapper.DocumentRow(31L,2L,7L,10L,"source.bin","a".repeat(64),
|
||||||
|
"application/octet-stream",1L,"ACTIVE",5L,java.time.LocalDateTime.now(),null,null,1);
|
||||||
|
var file=new MockMultipartFile("file","source.bin","application/octet-stream",new byte[]{1});
|
||||||
|
var requestHash=new AtomicReference<String>();
|
||||||
|
when(mapper.planInbounds(9L)).thenReturn(List.of(inbound));
|
||||||
|
when(mapper.container(9L)).thenReturn(aggregate);
|
||||||
|
when(mapper.lockContainer(9L)).thenReturn(aggregate);
|
||||||
|
when(mapper.missingRefundCount(9L,2L)).thenReturn(0);
|
||||||
|
when(mapper.claimCommand(anyLong(),eq(5L),eq("UPLOAD_SOURCE"),eq("k"),anyString())).thenAnswer(invocation->{
|
||||||
|
requestHash.set(invocation.getArgument(4));
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
when(mapper.command(5L,"UPLOAD_SOURCE","k"))
|
||||||
|
.thenAnswer(invocation->new ContainerCustomsMapper.CommandRow(41L,5L,"UPLOAD_SOURCE","k",
|
||||||
|
requestHash.get(),"SUCCESS",31L,null,null));
|
||||||
|
when(mapper.document(anyLong())).thenReturn(persisted);
|
||||||
|
when(mapper.scopes(31L)).thenReturn(List.of(new ContainerCustomsMapper.ScopeRow(31L,11L,21L,"I")));
|
||||||
|
when(mapper.containerById(2L)).thenReturn(aggregate);
|
||||||
|
|
||||||
|
var view=service.uploadSource(7L,9L,5L,"k",List.of(11L),file);
|
||||||
|
|
||||||
|
assertThat(view.source().version()).isEqualTo(1);
|
||||||
|
verifyNoInteractions(files);
|
||||||
|
}
|
||||||
|
|
||||||
@Test void startFailsClosedWhenAnyRefundInboundHasNoActiveDocumentCoverage(){
|
@Test void startFailsClosedWhenAnyRefundInboundHasNoActiveDocumentCoverage(){
|
||||||
var row=new ContainerCustomsMapper.InboundRow(9L,"LP9",8L,"COMPLETED","CAB","C",11L,21L,"O","I",7L,"客户","TAX_REFUND");
|
var row=new ContainerCustomsMapper.InboundRow(9L,"LP9",8L,"COMPLETED","CAB","C",11L,21L,"O","I",7L,"客户","TAX_REFUND");
|
||||||
var container=new ContainerCustomsMapper.ContainerRow(1L,9L,8L,ContainerCustomsService.READY,null,null,null,null,null,null,null,null,2);
|
var container=new ContainerCustomsMapper.ContainerRow(1L,9L,8L,ContainerCustomsService.READY,null,null,null,null,null,null,null,null,2);
|
||||||
|
|||||||
Reference in New Issue
Block a user