fix(pda): allow receiving photo lookup by container

This commit is contained in:
2026-09-20 18:12:48 +08:00
parent 82ff6589cb
commit 0fef9a8133
5 changed files with 95 additions and 2 deletions
@@ -95,6 +95,19 @@ public class ReceivingContainerController {
.contentLength(download.content().length).body(download.content());
}
@GetMapping("/{containerNo}/unload-photos/files/{fileId}/download")
public ResponseEntity<byte[]> downloadContainerPhoto(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String containerNo,
@PathVariable String fileId) {
PdaWarehouseIdentity identity = identity(user);
var download = service.downloadContainerPhoto(identity.overseasWarehouseId(), containerNo, fileId);
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.inline().filename(download.fileName(), StandardCharsets.UTF_8).build().toString())
.contentType(MediaType.parseMediaType(download.contentType()))
.contentLength(download.content().length).body(download.content());
}
@GetMapping("/current/domestic-loading-photos/{evidenceType}/content")
public ResponseEntity<byte[]> downloadDomesticLoadingPhoto(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String evidenceType) {
@@ -107,6 +120,19 @@ public class ReceivingContainerController {
.contentLength(download.content().length).body(download.content());
}
@GetMapping("/{containerNo}/domestic-loading-photos/{evidenceType}/content")
public ResponseEntity<byte[]> downloadContainerDomesticLoadingPhoto(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String containerNo,
@PathVariable String evidenceType) {
PdaWarehouseIdentity identity = identity(user);
var download = service.downloadContainerDomesticLoadingPhoto(identity.overseasWarehouseId(), containerNo, evidenceType);
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.inline().filename(download.fileName(), StandardCharsets.UTF_8).build().toString())
.contentType(MediaType.parseMediaType(download.contentType()))
.contentLength(download.content().length).body(download.content());
}
@PostMapping("/current/sign")
public ApiResponse<ReceivingContainerResponses.Session> sign(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.Sign request) {
@@ -22,6 +22,10 @@ public interface ReceivingContainerSessionMapper extends BaseMapper<ReceivingCon
"WHERE active_container_key=#{activeContainerKey} LIMIT 1")
ReceivingContainerSession findActiveByContainer(@Param("activeContainerKey") String activeContainerKey);
@Select("SELECT * FROM ll_receiving_container_session " +
"WHERE container_no=#{containerNo} ORDER BY created_at DESC LIMIT 1")
ReceivingContainerSession findLatestByContainerNo(@Param("containerNo") String containerNo);
@Select("SELECT * FROM ll_receiving_container_session WHERE active_container_key IS NOT NULL")
List<ReceivingContainerSession> findActiveContainers();
@@ -70,8 +70,12 @@ public class ReceivingContainerService {
private static final String PLATFORM_SCOPE = "0";
private static final String PHOTO_DOWNLOAD_PATH =
"/api/pda/v1/last-leg/receiving-containers/current/unload-photos/files/{fileId}/download";
private static final String CONTAINER_PHOTO_DOWNLOAD_PATH =
"/api/pda/v1/last-leg/receiving-containers/{containerNo}/unload-photos/files/{fileId}/download";
private static final String DOMESTIC_PHOTO_DOWNLOAD_PATH =
"/api/pda/v1/last-leg/receiving-containers/current/domestic-loading-photos/{type}/content";
private static final String CONTAINER_DOMESTIC_PHOTO_DOWNLOAD_PATH =
"/api/pda/v1/last-leg/receiving-containers/{containerNo}/domestic-loading-photos/{type}/content";
private static final String PALLET_MERGE_HINT =
"该入库计划可选择合托:合托时扫描同目的仓已使用的托盘码,独立收货时扫描新托盘码;不强制合托";
private static final int MAX_PACKAGES_PER_PALLET = 66;
@@ -275,6 +279,39 @@ public class ReceivingContainerService {
return domesticEvidenceFiles.download(bookingId, evidence.getSourceFileId());
}
public UnloadPhotoFileGateway.DownloadedFile downloadContainerPhoto(
long overseasWarehouseId, String containerNo, String fileId) {
ReceivingContainerSession session = requireContainerPhotoSession(overseasWarehouseId, containerNo);
long parsedFileId = positiveFileId(fileId);
if (signedPhotos(session).stream().noneMatch(photo -> fileId.equals(photo.fileId()))) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "该柜没有对应的卸柜照片");
}
return unloadPhotos.download(PLATFORM_SCOPE, session.getId(), parsedFileId);
}
public DomesticLoadingEvidenceGateway.DownloadedFile downloadContainerDomesticLoadingPhoto(
long overseasWarehouseId, String containerNo, String evidenceType) {
ReceivingContainerSession session = requireContainerPhotoSession(overseasWarehouseId, containerNo);
String type = requiredEvidenceType(evidenceType);
var evidence = domesticEvidence.find(session.getSourceContainerBookingId(), type);
if (evidence == null) throw notFound("当前柜没有该国内装柜照片");
long bookingId;
try { bookingId = Long.parseLong(session.getSourceContainerBookingId()); }
catch (NumberFormatException exception) { throw new ResponseStatusException(
HttpStatus.SERVICE_UNAVAILABLE, "当前柜国内照片来源无效", exception); }
return domesticEvidenceFiles.download(bookingId, evidence.getSourceFileId());
}
private ReceivingContainerSession requireContainerPhotoSession(long overseasWarehouseId, String containerNo) {
String value = trim(containerNo);
if (value == null) throw notFound("柜号不能为空");
ReceivingContainerSession session = sessions.findLatestByContainerNo(value);
if (session == null || !Objects.equals(session.getOverseasWarehouseId(), overseasWarehouseId)) {
throw notFound("当前仓没有该柜的照片");
}
return session;
}
public void requireCurrentWarehouse(String operatorKey, long overseasWarehouseId) {
requireSessionWarehouse(sessions.findActiveByOperator(activeOperatorKey(operatorKey)), overseasWarehouseId);
}
@@ -1050,7 +1087,8 @@ public class ReceivingContainerService {
if (bookingId == null) return List.of();
return domesticEvidence.listByContainer(bookingId).stream()
.map(value -> new ReceivingContainerResponses.DomesticLoadingPhoto(value.getEvidenceType(),
evidenceLabel(value.getEvidenceType()), DOMESTIC_PHOTO_DOWNLOAD_PATH.replace("{type}", value.getEvidenceType())))
evidenceLabel(value.getEvidenceType()), CONTAINER_DOMESTIC_PHOTO_DOWNLOAD_PATH
.replace("{containerNo}", session.getContainerNo()).replace("{type}", value.getEvidenceType())))
.toList();
}
@@ -1384,7 +1422,8 @@ public class ReceivingContainerService {
private List<ReceivingContainerResponses.SignedPhoto> signPhotos(ReceivingContainerSession session) {
return signedPhotos(session).stream()
.map(photo -> new ReceivingContainerResponses.SignedPhoto(photo.role(), photo.fileId(),
PHOTO_DOWNLOAD_PATH.replace("{fileId}", photo.fileId())))
CONTAINER_PHOTO_DOWNLOAD_PATH.replace("{containerNo}", session.getContainerNo())
.replace("{fileId}", photo.fileId())))
.toList();
}
@@ -84,6 +84,17 @@ paths:
application/octet-stream: { schema: { type: string, format: binary } }
'403': { description: fileId 未绑定到当前操作员的当前柜会话三类签收照片 }
'404': { description: 当前柜会话或照片不存在 }
/api/pda/v1/last-leg/receiving-containers/{containerNo}/unload-photos/files/{fileId}/download:
get:
summary: 按柜号下载卸柜签收照片
description: 权限 lastleg:pda:operate;按当前 PDA 绑定仓和柜号读取历史或当前柜的签收照片,不要求存在未完成接柜会话。
security: [{ bearerAuth: [] }]
parameters:
- { name: containerNo, in: path, required: true, schema: { type: string } }
- { name: fileId, in: path, required: true, schema: { type: string, pattern: '^[1-9][0-9]{0,18}$' } }
responses:
'200': { description: 签收照片字节流, content: { image/jpeg: { schema: { type: string, format: binary } }, image/png: { schema: { type: string, format: binary } } } }
'404': { description: 当前仓没有该柜或照片不存在 }
/api/pda/v1/last-leg/receiving-containers/current/domestic-loading-photos/{evidenceType}/content:
get:
summary: 查看当前柜国内装柜照片
@@ -100,6 +111,17 @@ paths:
application/octet-stream: { schema: { type: string, format: binary } }
'404': { description: 当前柜或指定类型照片不存在 }
'503': { description: 国内照片服务暂不可用 }
/api/pda/v1/last-leg/receiving-containers/{containerNo}/domestic-loading-photos/{evidenceType}/content:
get:
summary: 按柜号查看国内装柜照片
description: 权限 lastleg:pda:operate;按当前 PDA 绑定仓和柜号读取历史或当前柜的国内装柜照片,不要求存在未完成接柜会话。
security: [{ bearerAuth: [] }]
parameters:
- { name: containerNo, in: path, required: true, schema: { type: string } }
- { name: evidenceType, in: path, required: true, schema: { type: string, enum: [PHOTO_STEP_1, PHOTO_STEP_2, PHOTO_STEP_3, PHOTO_STEP_4, SEAL] } }
responses:
'200': { description: 国内装柜照片字节流, content: { image/jpeg: { schema: { type: string, format: binary } }, image/png: { schema: { type: string, format: binary } } } }
'404': { description: 当前仓没有该柜或指定类型照片不存在 }
/api/pda/v1/last-leg/receiving-containers/current/sign:
post:
summary: 签收当前柜
@@ -140,6 +140,8 @@ class ReceivingContainerContractTest {
assertTrue(yaml.contains(prefix + "/current/receipts/pallet:"));
assertTrue(yaml.contains(prefix + "/current/complete:"));
assertTrue(yaml.contains(prefix + "/current/unload-photos/files/{fileId}/download:"));
assertTrue(yaml.contains(prefix + "/{containerNo}/unload-photos/files/{fileId}/download:"));
assertTrue(yaml.contains(prefix + "/{containerNo}/domestic-loading-photos/{evidenceType}/content:"));
assertFalse(yaml.contains("/api/pda/v1/last-leg/receiving-containers/{planNo}"));
String receivingPaths = yaml.substring(yaml.indexOf(prefix), yaml.indexOf("/api/platform/v1/last-leg/orders:"));
List<String> successResponses = receivingPaths.lines().map(String::trim)