fix(firstleg): hide void photo download paths
This commit is contained in:
@@ -100,8 +100,10 @@ public class ContainerOverviewService {
|
|||||||
private PdaSummary pda(ContainerOverviewMapper.PickTaskRow task) {
|
private PdaSummary pda(ContainerOverviewMapper.PickTaskRow task) {
|
||||||
List<Photo> photos = mapper.photos(task.loadingPlanId()).stream().map(value -> new Photo(value.evidenceId(),
|
List<Photo> photos = mapper.photos(task.loadingPlanId()).stream().map(value -> new Photo(value.evidenceId(),
|
||||||
value.tenantId(), value.inboundId(), value.evidenceType(), value.fileId(), value.fileName(),
|
value.tenantId(), value.inboundId(), value.evidenceType(), value.fileId(), value.fileName(),
|
||||||
value.status(), "/api/platform/v1/containers/" + task.containerBookingId() + "/pda/photos/"
|
value.status(), "ACTIVE".equals(value.status())
|
||||||
+ value.fileId() + "/download", value.uploadedAt())).toList();
|
? "/api/platform/v1/containers/" + task.containerBookingId() + "/pda/photos/"
|
||||||
|
+ value.fileId() + "/download"
|
||||||
|
: null, value.uploadedAt())).toList();
|
||||||
var counts = new LinkedHashMap<String, Integer>();
|
var counts = new LinkedHashMap<String, Integer>();
|
||||||
photos.stream().filter(photo -> "ACTIVE".equals(photo.status()))
|
photos.stream().filter(photo -> "ACTIVE".equals(photo.status()))
|
||||||
.forEach(photo -> counts.merge(photo.evidenceType(), 1, Integer::sum));
|
.forEach(photo -> counts.merge(photo.evidenceType(), 1, Integer::sum));
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ components:
|
|||||||
Container: {type: object, properties: {id: {type: integer, format: int64}, bookingNo: {type: string}, cabinetNo: {type: string}, containerNo: {type: string}, status: {type: string}, warehouseId: {type: integer, format: int64}, warehouseName: {type: string}, voyageNo: {type: string}, vesselName: {type: string}, version: {type: integer, minimum: 0}}}
|
Container: {type: object, properties: {id: {type: integer, format: int64}, bookingNo: {type: string}, cabinetNo: {type: string}, containerNo: {type: string}, status: {type: string}, warehouseId: {type: integer, format: int64}, warehouseName: {type: string}, voyageNo: {type: string}, vesselName: {type: string}, version: {type: integer, minimum: 0}}}
|
||||||
PdaSummary: {type: object, properties: {taskId: {type: integer, format: int64}, loadingPlanId: {type: integer, format: int64}, containerBookingId: {type: integer, format: int64}, taskStatus: {type: string, description: '持久化任务态:LOADING/COMPLETED'}, workflowStatus: {type: string, enum: [IN_PROGRESS, AWAITING_REVIEW, COMPLETED], description: 'PDA作业展示态'}, taskVersion: {type: integer}, itemCount: {type: integer}, completedItemCount: {type: integer}, unresolvedItemCount: {type: integer}, photoCounts: {type: array, items: {$ref: '#/components/schemas/PhotoCount'}}, photos: {type: array, items: {$ref: '#/components/schemas/Photo'}}, exceptionReasons: {type: array, items: {type: string}}}}
|
PdaSummary: {type: object, properties: {taskId: {type: integer, format: int64}, loadingPlanId: {type: integer, format: int64}, containerBookingId: {type: integer, format: int64}, taskStatus: {type: string, description: '持久化任务态:LOADING/COMPLETED'}, workflowStatus: {type: string, enum: [IN_PROGRESS, AWAITING_REVIEW, COMPLETED], description: 'PDA作业展示态'}, taskVersion: {type: integer}, itemCount: {type: integer}, completedItemCount: {type: integer}, unresolvedItemCount: {type: integer}, photoCounts: {type: array, items: {$ref: '#/components/schemas/PhotoCount'}}, photos: {type: array, items: {$ref: '#/components/schemas/Photo'}}, exceptionReasons: {type: array, items: {type: string}}}}
|
||||||
PhotoCount: {type: object, properties: {evidenceType: {type: string}, count: {type: integer}}}
|
PhotoCount: {type: object, properties: {evidenceType: {type: string}, count: {type: integer}}}
|
||||||
Photo: {type: object, properties: {evidenceId: {type: integer, format: int64}, tenantId: {type: integer, format: int64}, inboundId: {type: integer, format: int64}, evidenceType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, status: {type: string}, downloadPath: {type: string}, uploadedAt: {type: string, format: date-time}}}
|
Photo: {type: object, properties: {evidenceId: {type: integer, format: int64}, tenantId: {type: integer, format: int64}, inboundId: {type: integer, format: int64}, evidenceType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, status: {type: string, enum: [ACTIVE, VOID]}, downloadPath: {type: string, nullable: true, description: '仅 ACTIVE 照片提供鉴权下载路径;VOID 历史照片为空'}, uploadedAt: {type: string, format: date-time}}}
|
||||||
Document: {type: object, properties: {id: {type: integer, format: int64}, documentType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, fileHash: {type: string}, uploadedBy: {type: integer, format: int64}, uploadedByName: {type: string}, uploadedAt: {type: string, format: date-time}, version: {type: integer}, completed: {type: boolean}}}
|
Document: {type: object, properties: {id: {type: integer, format: int64}, documentType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, fileHash: {type: string}, uploadedBy: {type: integer, format: int64}, uploadedByName: {type: string}, uploadedAt: {type: string, format: date-time}, version: {type: integer}, completed: {type: boolean}}}
|
||||||
ExceptionState: {type: object, properties: {active: {type: boolean}, reason: {type: string}, markedBy: {type: integer, format: int64}, markedByName: {type: string}, markedAt: {type: string, format: date-time}}}
|
ExceptionState: {type: object, properties: {active: {type: boolean}, reason: {type: string}, markedBy: {type: integer, format: int64}, markedByName: {type: string}, markedAt: {type: string, format: date-time}}}
|
||||||
ExceptionHistory: {type: object, properties: {id: {type: integer, format: int64}, action: {type: string, enum: [MARK, CANCEL]}, reason: {type: string}, operatorId: {type: integer, format: int64}, operatorName: {type: string}, occurredAt: {type: string, format: date-time}}}
|
ExceptionHistory: {type: object, properties: {id: {type: integer, format: int64}, action: {type: string, enum: [MARK, CANCEL]}, reason: {type: string}, operatorId: {type: integer, format: int64}, operatorName: {type: string}, occurredAt: {type: string, format: date-time}}}
|
||||||
|
|||||||
+3
-1
@@ -42,7 +42,9 @@ class ContainerControllerContractTest {
|
|||||||
"/documents/{documentType}/{fileId}/download:", "ExceptionRequest",
|
"/documents/{documentType}/{fileId}/download:", "ExceptionRequest",
|
||||||
"enum: [DELIVERY_ORDER, PICKUP_ORDER]", "Idempotency-Key", "photoCounts:",
|
"enum: [DELIVERY_ORDER, PICKUP_ORDER]", "Idempotency-Key", "photoCounts:",
|
||||||
"/pda/photos/{fileId}/download:", "downloadPath:", "workflowStatus:",
|
"/pda/photos/{fileId}/download:", "downloadPath:", "workflowStatus:",
|
||||||
"AWAITING_REVIEW", "version: {type: integer, minimum: 0}")
|
"AWAITING_REVIEW", "version: {type: integer, minimum: 0}",
|
||||||
|
"downloadPath: {type: string, nullable: true",
|
||||||
|
"仅 ACTIVE 照片提供鉴权下载路径;VOID 历史照片为空")
|
||||||
.doesNotContain("?containerNo", "containerNo in: query");
|
.doesNotContain("?containerNo", "containerNo in: query");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-2
@@ -42,8 +42,11 @@ class ContainerOverviewServiceTest {
|
|||||||
when(mapper.itemCount(61L)).thenReturn(2);
|
when(mapper.itemCount(61L)).thenReturn(2);
|
||||||
when(mapper.completedItemCount(61L)).thenReturn(2);
|
when(mapper.completedItemCount(61L)).thenReturn(2);
|
||||||
when(mapper.unresolvedItemCount(61L)).thenReturn(0);
|
when(mapper.unresolvedItemCount(61L)).thenReturn(0);
|
||||||
when(mapper.photos(52L)).thenReturn(java.util.List.of(new ContainerOverviewMapper.PhotoRow(
|
when(mapper.photos(52L)).thenReturn(java.util.List.of(
|
||||||
71L, 81L, 91L, "LOADING_DOOR_OPEN", 101L, "door.jpg", "ACTIVE", LocalDateTime.now())));
|
new ContainerOverviewMapper.PhotoRow(
|
||||||
|
71L, 81L, 91L, "LOADING_DOOR_OPEN", 101L, "door.jpg", "ACTIVE", LocalDateTime.now()),
|
||||||
|
new ContainerOverviewMapper.PhotoRow(
|
||||||
|
72L, 81L, 91L, "LOADING_DOOR_OPEN", 102L, "void.jpg", "VOID", LocalDateTime.now())));
|
||||||
when(mapper.documents(41L)).thenReturn(java.util.List.of());
|
when(mapper.documents(41L)).thenReturn(java.util.List.of());
|
||||||
when(mapper.exceptions(41L)).thenReturn(java.util.List.of());
|
when(mapper.exceptions(41L)).thenReturn(java.util.List.of());
|
||||||
when(mapper.transport(41L)).thenReturn(java.util.List.of());
|
when(mapper.transport(41L)).thenReturn(java.util.List.of());
|
||||||
@@ -56,6 +59,13 @@ class ContainerOverviewServiceTest {
|
|||||||
assertThat(overview.pda().workflowStatus()).isEqualTo("AWAITING_REVIEW");
|
assertThat(overview.pda().workflowStatus()).isEqualTo("AWAITING_REVIEW");
|
||||||
assertThat(overview.pda().photos().get(0).downloadPath())
|
assertThat(overview.pda().photos().get(0).downloadPath())
|
||||||
.isEqualTo("/api/platform/v1/containers/41/pda/photos/101/download");
|
.isEqualTo("/api/platform/v1/containers/41/pda/photos/101/download");
|
||||||
|
assertThat(overview.pda().photos()).hasSize(2);
|
||||||
|
assertThat(overview.pda().photos().get(1).status()).isEqualTo("VOID");
|
||||||
|
assertThat(overview.pda().photos().get(1).downloadPath()).isNull();
|
||||||
|
assertThat(overview.pda().photoCounts()).singleElement().satisfies(count -> {
|
||||||
|
assertThat(count.evidenceType()).isEqualTo("LOADING_DOOR_OPEN");
|
||||||
|
assertThat(count.count()).isEqualTo(1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test void downloadsOnlyActivePhotoOwnedByContainerLoadingPlan() {
|
@Test void downloadsOnlyActivePhotoOwnedByContainerLoadingPlan() {
|
||||||
@@ -87,6 +97,19 @@ class ContainerOverviewServiceTest {
|
|||||||
verifyNoInteractions(evidenceFiles);
|
verifyNoInteractions(evidenceFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test void rejectsVoidedPhotoBeforeCallingFileService() {
|
||||||
|
ContainerBookingEntity booking = new ContainerBookingEntity(); booking.setId(41L);
|
||||||
|
when(containers.find(41L)).thenReturn(booking);
|
||||||
|
when(mapper.pickTask(41L)).thenReturn(new ContainerOverviewMapper.PickTaskRow(
|
||||||
|
61L, 52L, 41L, "LOADING", 7));
|
||||||
|
when(mapper.photos(52L)).thenReturn(java.util.List.of(new ContainerOverviewMapper.PhotoRow(
|
||||||
|
71L, 81L, 91L, "LOADING_DOOR_OPEN", 101L, "void.jpg", "VOID", LocalDateTime.now())));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service.downloadPhoto(41L, 101L))
|
||||||
|
.isInstanceOf(BizException.class).hasMessageContaining("不属于该货柜");
|
||||||
|
verifyNoInteractions(evidenceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
@Test void markLocksContainerAndPersistsIdempotentImmutableHistory() {
|
@Test void markLocksContainerAndPersistsIdempotentImmutableHistory() {
|
||||||
ContainerBookingEntity booking = new ContainerBookingEntity(); booking.setId(41L); booking.setVersion(3);
|
ContainerBookingEntity booking = new ContainerBookingEntity(); booking.setId(41L); booking.setVersion(3);
|
||||||
when(containers.findForUpdate(41L)).thenReturn(booking);
|
when(containers.findForUpdate(41L)).thenReturn(booking);
|
||||||
|
|||||||
Reference in New Issue
Block a user