feat(firstleg): expose customs receipt history
This commit is contained in:
+5
-1
@@ -17,10 +17,14 @@ public final class ContainerCustomsDtos {
|
||||
public record FileView(long fileId, String fileName, String fileHash, String contentType,
|
||||
long sizeBytes, int version, LocalDateTime uploadedAt) {}
|
||||
|
||||
public record ReceiptVersionView(long version, FileView file, String operatorName,
|
||||
LocalDateTime createdAt) {}
|
||||
|
||||
public record DocumentView(long documentId, long tenantId, FileView source,
|
||||
List<Long> inboundIds, List<String> inboundCodes,
|
||||
boolean frozen, FileView receipt, String receiptStatus,
|
||||
String receiptRejectReason, FileView finalDocument) {}
|
||||
String receiptRejectReason, FileView finalDocument,
|
||||
List<ReceiptVersionView> receiptHistory) {}
|
||||
|
||||
public record ContainerView(long aggregateId, long loadingPlanId, long containerBookingId,
|
||||
String planNo, String cabinetNo, String containerNo, String status, boolean hasActualCargo,
|
||||
|
||||
+8
@@ -340,6 +340,12 @@ public interface ContainerCustomsMapper {
|
||||
"WHERE r.source_document_id=#{documentId} FOR UPDATE")
|
||||
CompletionRow lockCompletion(@Param("documentId") long documentId);
|
||||
|
||||
@Select("SELECT v.document_version documentVersion,v.file_id fileId,v.file_name fileName,v.file_hash fileHash,"
|
||||
+ "v.content_type contentType,v.size_bytes sizeBytes,v.operator_id operatorId,v.operator_name operatorName,v.created_at createdAt "
|
||||
+ "FROM fl_customs_completion_document_version v WHERE v.completion_document_id=#{completionId} "
|
||||
+ "ORDER BY v.document_version")
|
||||
List<CompletionVersionRow> completionVersions(@Param("completionId") long completionId);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_customs_completion_document
|
||||
(id,source_document_id,file_id,file_name,file_hash,content_type,size_bytes,uploaded_by,uploaded_by_name,
|
||||
@@ -553,6 +559,8 @@ public interface ContainerCustomsMapper {
|
||||
record CompletionRow(Long id,Long sourceDocumentId,Long fileId,String fileName,String fileHash,String contentType,
|
||||
Long sizeBytes,Long uploadedBy,String uploadedByName,LocalDateTime uploadedAt,Integer version,
|
||||
String customerConfirmStatus,String customerRejectReason) {}
|
||||
record CompletionVersionRow(Integer documentVersion,Long fileId,String fileName,String fileHash,String contentType,
|
||||
Long sizeBytes,Long operatorId,String operatorName,LocalDateTime createdAt) {}
|
||||
record FinalRow(Long id,Long sourceDocumentId,Long fileId,String fileName,String fileHash,String contentType,
|
||||
Long sizeBytes,Long uploadedBy,String uploadedByName,LocalDateTime uploadedAt,Integer version) {}
|
||||
record CommandRow(Long id,Long actorId,String action,String idempotencyKey,String requestHash,String status,
|
||||
|
||||
+7
-1
@@ -581,11 +581,13 @@ public class ContainerCustomsService {
|
||||
FinalRow finalDocument = mapper.finalDocument(source.id());
|
||||
ContainerRow container = mapper.containerById(source.customsContainerId());
|
||||
FileView result = completion == null ? null : file(completion);
|
||||
List<ReceiptVersionView> receiptHistory = completion == null ? List.of() : Optional.ofNullable(mapper.completionVersions(completion.id())).orElseGet(List::of).stream()
|
||||
.map(version -> new ReceiptVersionView(version.documentVersion(), file(version), version.operatorName(), version.createdAt())).toList();
|
||||
return new DocumentView(source.id(), source.tenantId(), file(source), scopes.stream().map(ScopeRow::inboundId).toList(),
|
||||
scopes.stream().map(ScopeRow::inboundCode).toList(), source.frozenAt() != null, result,
|
||||
completion == null ? null : completion.customerConfirmStatus(),
|
||||
completion == null ? null : completion.customerRejectReason(),
|
||||
finalDocument == null ? null : file(finalDocument));
|
||||
finalDocument == null ? null : file(finalDocument), receiptHistory);
|
||||
}
|
||||
|
||||
private void completeContainer(ContainerRow container, long operatorId, String operatorName, String remark) {
|
||||
@@ -657,6 +659,10 @@ public class ContainerCustomsService {
|
||||
|
||||
private static FileView file(DocumentRow row) { return new FileView(row.fileId(),row.fileName(),row.fileHash(),row.contentType(),row.sizeBytes(),row.version(),row.uploadedAt()); }
|
||||
private static FileView file(CompletionRow row) { return new FileView(row.fileId(),row.fileName(),row.fileHash(),row.contentType(),row.sizeBytes(),row.version(),row.uploadedAt()); }
|
||||
private static FileView file(ContainerCustomsMapper.CompletionVersionRow row) {
|
||||
return new FileView(row.fileId(), row.fileName(), row.fileHash(), row.contentType(), row.sizeBytes(),
|
||||
row.documentVersion(), row.createdAt());
|
||||
}
|
||||
private static FileView file(FinalRow row) { return new FileView(row.fileId(),row.fileName(),row.fileHash(),row.contentType(),row.sizeBytes(),row.version(),row.uploadedAt()); }
|
||||
private static long size(CustomsFileClient.Metadata value) { return value.sizeBytes() == null ? 0 : value.sizeBytes(); }
|
||||
private static void validateFile(MultipartFile file) { if (file == null || file.isEmpty()) throw bad("请选择文件"); if (file.getSize() > MAX_FILE_BYTES) throw bad("单文件不能超过50MB"); }
|
||||
|
||||
@@ -221,7 +221,8 @@ components:
|
||||
ScopeRequest: {type: object, required: [inboundIds, expectedVersion], additionalProperties: false, properties: {inboundIds: {type: array, minItems: 1, maxItems: 200, items: {$ref: '#/components/schemas/LongId'}}, expectedVersion: {type: integer, minimum: 1}}}
|
||||
FileView: {type: object, required: [fileId, fileName, fileHash, sizeBytes, version, uploadedAt], properties: {fileId: {$ref: '#/components/schemas/LongId'}, fileName: {type: string}, fileHash: {type: string}, contentType: {type: string, nullable: true}, sizeBytes: {type: integer, format: int64}, version: {type: integer}, uploadedAt: {type: string, format: date-time}}}
|
||||
InboundView: {type: object, required: [inboundId, orderId, orderNo, inboundCode, tenantId, customerName, taxMode, covered], properties: {inboundId: {$ref: '#/components/schemas/LongId'}, orderId: {$ref: '#/components/schemas/LongId'}, orderNo: {type: string}, inboundCode: {type: string}, tenantId: {$ref: '#/components/schemas/LongId'}, customerName: {type: string}, taxMode: {type: string, enum: [TAX_REFUND, BUY_DECLARATION]}, covered: {type: boolean}}}
|
||||
DocumentView: {type: object, required: [documentId, tenantId, source, inboundIds, inboundCodes, frozen], properties: {documentId: {$ref: '#/components/schemas/LongId'}, tenantId: {$ref: '#/components/schemas/LongId'}, source: {$ref: '#/components/schemas/FileView'}, inboundIds: {type: array, items: {$ref: '#/components/schemas/LongId'}}, inboundCodes: {type: array, items: {type: string}}, frozen: {type: boolean}, receipt: {allOf: [{$ref: '#/components/schemas/FileView'}], nullable: true, description: 报关行回执文件}, receiptStatus: {type: string, enum: [PENDING, CONFIRMED, REJECTED], nullable: true}, receiptRejectReason: {type: string, nullable: true}, finalDocument: {allOf: [{$ref: '#/components/schemas/FileView'}], nullable: true, description: 真实报关完税文件}}}
|
||||
ReceiptVersionView: {type: object, required: [version, file, createdAt], properties: {version: {type: integer}, file: {$ref: '#/components/schemas/FileView'}, operatorName: {type: string, nullable: true}, createdAt: {type: string, format: date-time}}}
|
||||
DocumentView: {type: object, required: [documentId, tenantId, source, inboundIds, inboundCodes, frozen, receiptHistory], properties: {documentId: {$ref: '#/components/schemas/LongId'}, tenantId: {$ref: '#/components/schemas/LongId'}, source: {$ref: '#/components/schemas/FileView'}, inboundIds: {type: array, items: {$ref: '#/components/schemas/LongId'}}, inboundCodes: {type: array, items: {type: string}}, frozen: {type: boolean}, receipt: {allOf: [{$ref: '#/components/schemas/FileView'}], nullable: true, description: 当前报关行回执文件}, receiptStatus: {type: string, enum: [PENDING, CONFIRMED, REJECTED], nullable: true}, receiptRejectReason: {type: string, nullable: true}, finalDocument: {allOf: [{$ref: '#/components/schemas/FileView'}], nullable: true, description: 真实报关完税文件}, receiptHistory: {type: array, items: {$ref: '#/components/schemas/ReceiptVersionView'}, description: 报关行回执历次上传记录,按版本升序}}}
|
||||
ContainerView: {type: object, required: [aggregateId, loadingPlanId, containerBookingId, status, hasActualCargo, version, taxRefundCount, coveredTaxRefundCount, actualBoxCount, inbounds, documents], properties: {aggregateId: {$ref: '#/components/schemas/LongId'}, loadingPlanId: {$ref: '#/components/schemas/LongId'}, containerBookingId: {$ref: '#/components/schemas/LongId'}, planNo: {type: string}, cabinetNo: {type: string}, containerNo: {type: string, nullable: true}, status: {type: string, enum: [WAITING_CUSTOMER_DOCUMENTS, READY_TO_START, DECLARING, COMPLETED]}, hasActualCargo: {type: boolean, description: 当前完成装柜计划是否仍有实际装柜货物;false时查询成功返回空明细且无需发起报关}, merchantStatus: {type: string, enum: [WAITING_UPLOAD, DOCUMENT_UPLOADED, DECLARING, CUSTOMS_COMPLETED], nullable: true}, version: {type: integer}, taxRefundCount: {type: integer}, coveredTaxRefundCount: {type: integer}, actualBoxCount: {type: integer, format: int64, minimum: 0, description: 当前视角可见的实际装箱数;部分装柜只展示该数量,线下处理后续}, inbounds: {type: array, items: {$ref: '#/components/schemas/InboundView'}}, documents: {type: array, items: {$ref: '#/components/schemas/DocumentView'}}, startedAt: {type: string, format: date-time, nullable: true}, brokerSubmittedAt: {type: string, format: date-time, nullable: true, description: 已将客户确认后的回执发送给报关行执行的时间;不表示报关完成}, completedAt: {type: string, format: date-time, nullable: true}}}
|
||||
DeclarationBatch: {type: object, required: [id, customsContainerId, loadingPlanId, status, sourceVersion, version, results], properties: {id: {$ref: '#/components/schemas/LongId'}, customsContainerId: {$ref: '#/components/schemas/LongId'}, loadingPlanId: {$ref: '#/components/schemas/LongId'}, batchNo: {type: string}, status: {type: string, enum: [READY, SUBMITTED, ACKNOWLEDGED, RESULT_PENDING, CLOSED, CANCELLED]}, sourceVersion: {type: integer}, customsEntryNo: {type: string, nullable: true}, version: {type: integer}, submittedAt: {type: string, format: date-time, nullable: true}, submittedByName: {type: string, nullable: true}, acknowledgedAt: {type: string, format: date-time, nullable: true}, acknowledgedByName: {type: string, nullable: true}, remark: {type: string, nullable: true}, results: {type: array, items: {$ref: '#/components/schemas/ClearanceResult'}}}}
|
||||
ClearanceResult: {type: object, required: [id, declarationBatchId, customsContainerId, resultType, resultStatus, occurredAt, recordedAt], properties: {id: {$ref: '#/components/schemas/LongId'}, declarationBatchId: {$ref: '#/components/schemas/LongId'}, customsContainerId: {$ref: '#/components/schemas/LongId'}, resultType: {type: string, enum: [INSPECTION, SUPPLEMENT_REQUIRED, RELEASED, DETAINED, OTHER]}, resultStatus: {type: string, enum: [OPEN, RESOLVED, CANCELLED]}, customsEntryNo: {type: string, nullable: true}, occurredAt: {type: string, format: date-time}, resolution: {type: string, nullable: true}, evidenceFileId: {$ref: '#/components/schemas/LongId', nullable: true}, recordedBy: {$ref: '#/components/schemas/LongId'}, recordedByName: {type: string, nullable: true}, recordedAt: {type: string, format: date-time}, previousResultId: {$ref: '#/components/schemas/LongId', nullable: true}}}
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ class ContainerCustomsMapperContractTest {
|
||||
@Test void completionQueriesExplicitlyMapEveryRecordComponentIncludingVersion() throws Exception {
|
||||
assertRecordProjection("completion", ContainerCustomsMapper.CompletionRow.class, long.class);
|
||||
assertRecordProjection("lockCompletion", ContainerCustomsMapper.CompletionRow.class, long.class);
|
||||
assertRecordProjectionWithoutVersion("completionVersions", ContainerCustomsMapper.CompletionVersionRow.class, long.class);
|
||||
}
|
||||
|
||||
@Test void declarationAndClearanceQueriesMapExplicitHistoricalFacts() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user