贴标申请,仓储转运

This commit is contained in:
2026-09-20 16:55:55 +08:00
parent 2ce2fff8d7
commit 82ff6589cb
8 changed files with 53 additions and 3 deletions
@@ -6364,7 +6364,7 @@ INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id,
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306180,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:discard:list','货物废弃','/overseas-warehouse/cargo-disposal','GET',130,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0); INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306180,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:discard:list','货物废弃','/overseas-warehouse/cargo-disposal','GET',130,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306181,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:create','创建货物废弃申请',NULL,'POST',10,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0); INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306181,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:create','创建货物废弃申请',NULL,'POST',10,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306182,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:cancel','取消货物废弃申请',NULL,'POST',20,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0); INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306182,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:cancel','取消货物废弃申请',NULL,'POST',20,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306190,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:return-order:list','退货预报','/overseas-warehouse/return-relabel','GET',140,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0); INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306190,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:return-order:list','退货换标','/overseas-warehouse/return-relabel','GET',140,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306191,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:create','创建退货预报',NULL,'POST',10,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0); INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306191,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:create','创建退货预报',NULL,'POST',10,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306192,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:relabel','退货转换标申请',NULL,'POST',20,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0); INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5306192,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:relabel','退货转换标申请',NULL,'POST',20,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
@@ -99,7 +99,7 @@ VALUES
(5306180,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:discard:list','货物废弃','/overseas-warehouse/cargo-disposal','GET',130,'ACTIVE',1,0), (5306180,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:discard:list','货物废弃','/overseas-warehouse/cargo-disposal','GET',130,'ACTIVE',1,0),
(5306181,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:create','创建货物废弃申请',NULL,'POST',10,'ACTIVE',1,0), (5306181,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:create','创建货物废弃申请',NULL,'POST',10,'ACTIVE',1,0),
(5306182,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:cancel','取消货物废弃申请',NULL,'POST',20,'ACTIVE',1,0), (5306182,0,'MERCHANT','BUTTON',5306180,'merchant:overseas-warehouse:discard:cancel','取消货物废弃申请',NULL,'POST',20,'ACTIVE',1,0),
(5306190,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:return-order:list','退货预报','/overseas-warehouse/return-relabel','GET',140,'ACTIVE',1,0), (5306190,0,'MERCHANT','MENU',0,'merchant:overseas-warehouse:return-order:list','退货换标','/overseas-warehouse/return-relabel','GET',140,'ACTIVE',1,0),
(5306191,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:create','创建退货预报',NULL,'POST',10,'ACTIVE',1,0), (5306191,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:create','创建退货预报',NULL,'POST',10,'ACTIVE',1,0),
(5306192,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:relabel','退货转换标申请',NULL,'POST',20,'ACTIVE',1,0) (5306192,0,'MERCHANT','BUTTON',5306190,'merchant:overseas-warehouse:return-order:relabel','退货转换标申请',NULL,'POST',20,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type), parent_id=VALUES(parent_id), ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type), parent_id=VALUES(parent_id),
@@ -51,6 +51,7 @@ class OverseasCustomerPermissionSqlContractTest {
"'merchant:cross-border:pallet-shipping:list','美本土散板','/cross-border/pallet-shipping'", "'merchant:cross-border:pallet-shipping:list','美本土散板','/cross-border/pallet-shipping'",
"'merchant:warehouse:inbound:list','海外仓入库订单','/orders/inbound'", "'merchant:warehouse:inbound:list','海外仓入库订单','/orders/inbound'",
"'merchant:overseas-warehouse:return-inventory:list','退货库存','/overseas-warehouse/return-inventory'", "'merchant:overseas-warehouse:return-inventory:list','退货库存','/overseas-warehouse/return-inventory'",
"'merchant:overseas-warehouse:return-order:list','退货换标','/overseas-warehouse/return-relabel'",
"'merchant:overseas-warehouse:relabel:files','管理贴标文件'", "'merchant:overseas-warehouse:relabel:files','管理贴标文件'",
"'merchant:overseas-warehouse:discard:cancel','取消货物废弃申请'"); "'merchant:overseas-warehouse:discard:cancel','取消货物废弃申请'");
} }
@@ -10,6 +10,8 @@ import com.baiyue.lastleg.security.OverseasPermissions;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -28,6 +30,14 @@ public class MerchantReturnOrderController {
@GetMapping("/{returnNo}") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_LIST + "')") @GetMapping("/{returnNo}") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_LIST + "')")
public ApiResponse<ReturnOrderResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user, public ApiResponse<ReturnOrderResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String returnNo) { return ApiResponse.success(service.detail(customer(user), returnNo)); } @PathVariable String returnNo) { return ApiResponse.success(service.detail(customer(user), returnNo)); }
@GetMapping("/{returnNo}/photos/files/{fileId}/download")
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_LIST + "')")
public ResponseEntity<byte[]> downloadPhoto(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String returnNo, @PathVariable long fileId) {
var file = service.downloadPhoto(customer(user), returnNo, fileId);
return ResponseEntity.ok().contentType(MediaType.parseMediaType(file.contentType()))
.header("Cache-Control", "no-store").body(file.content());
}
@GetMapping("/{returnNo}/relabel-candidates") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL + "')") @GetMapping("/{returnNo}/relabel-candidates") @PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL + "')")
public ApiResponse<java.util.List<ReturnOrderResponses.RelabelCandidate>> relabelCandidates( public ApiResponse<java.util.List<ReturnOrderResponses.RelabelCandidate>> relabelCandidates(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String returnNo) { @AuthenticationPrincipal AuthenticatedUser user, @PathVariable String returnNo) {
@@ -1516,6 +1516,17 @@ paths:
security: [{ bearerAuth: [] }] security: [{ bearerAuth: [] }]
parameters: [{ name: returnNo, in: path, required: true, schema: { type: string } }] parameters: [{ name: returnNo, in: path, required: true, schema: { type: string } }]
responses: { '200': { description: 当前客户退货订单详情 }, '404': { description: 当前客户下退货订单不存在 } } responses: { '200': { description: 当前客户退货订单详情 }, '404': { description: 当前客户下退货订单不存在 } }
/api/merchant/v1/last-leg/return-orders/{returnNo}/photos/files/{fileId}/download:
get:
summary: 客户下载退货签收打托照片
description: 权限 merchant:overseas-warehouse:return-order:list;仅允许下载当前客户、当前退货单关联的签收照片,响应为原始文件且禁止缓存。
security: [{ bearerAuth: [] }]
parameters:
- { name: returnNo, in: path, required: true, schema: { type: string } }
- { name: fileId, in: path, required: true, schema: { type: integer, format: int64 } }
responses:
'200': { description: 照片文件, content: { image/*: { schema: { type: string, format: binary } } } }
'404': { description: 当前客户下退货单或关联照片不存在 }
/api/merchant/v1/last-leg/return-orders/{returnNo}/relabel-candidates: /api/merchant/v1/last-leg/return-orders/{returnNo}/relabel-candidates:
get: get:
summary: 查询退货单可转换标候选 summary: 查询退货单可转换标候选
@@ -87,6 +87,7 @@ class MerchantOverseasCustomerApiContractTest {
"create", OverseasPermissions.MERCHANT_RETURN_ORDER_CREATE, "create", OverseasPermissions.MERCHANT_RETURN_ORDER_CREATE,
"page", OverseasPermissions.MERCHANT_RETURN_ORDER_LIST, "page", OverseasPermissions.MERCHANT_RETURN_ORDER_LIST,
"detail", OverseasPermissions.MERCHANT_RETURN_ORDER_LIST, "detail", OverseasPermissions.MERCHANT_RETURN_ORDER_LIST,
"downloadPhoto", OverseasPermissions.MERCHANT_RETURN_ORDER_LIST,
"relabelCandidates", OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL, "relabelCandidates", OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL,
"createRelabel", OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL)); "createRelabel", OverseasPermissions.MERCHANT_RETURN_ORDER_RELABEL));
assertPermissions(MerchantRelabelApplicationController.class, Map.of( assertPermissions(MerchantRelabelApplicationController.class, Map.of(
@@ -32,7 +32,8 @@ class MerchantOverseasOpenApiContractTest {
"/api/merchant/v1/last-leg/local-looseboard-orders:", "/api/merchant/v1/last-leg/local-looseboard-orders:",
"/api/merchant/v1/last-leg/local-looseboard-orders/{orderNo}:", "/api/merchant/v1/last-leg/local-looseboard-orders/{orderNo}:",
"/api/merchant/v1/last-leg/inbound-plans:", "/api/merchant/v1/last-leg/inbound-plans:",
"/api/merchant/v1/last-leg/inbound-plans/{planNo}:"}) { "/api/merchant/v1/last-leg/inbound-plans/{planNo}:",
"/api/merchant/v1/last-leg/return-orders/{returnNo}/photos/files/{fileId}/download:"}) {
assertThat(yaml).contains(path); assertThat(yaml).contains(path);
assertThat(pathBlock(yaml, path)).doesNotContain("name: customerId"); assertThat(pathBlock(yaml, path)).doesNotContain("name: customerId");
} }
@@ -22,6 +22,7 @@ import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper; import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
import com.baiyue.lastleg.relabel.entity.RelabelApplication; import com.baiyue.lastleg.relabel.entity.RelabelApplication;
import com.baiyue.lastleg.relabel.entity.RelabelItem; import com.baiyue.lastleg.relabel.entity.RelabelItem;
import com.baiyue.lastleg.relabel.pda.gateway.RelabelPhotoFileGateway;
import com.baiyue.lastleg.service.FbaWarehouseValidator; import com.baiyue.lastleg.service.FbaWarehouseValidator;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -67,6 +68,7 @@ class ReturnReceiptServiceTest {
@Mock RelabelApplicationMapper relabelApplications; @Mock RelabelApplicationMapper relabelApplications;
@Mock RelabelItemMapper relabelItems; @Mock RelabelItemMapper relabelItems;
@Mock FbaWarehouseValidator fbaWarehouseValidator; @Mock FbaWarehouseValidator fbaWarehouseValidator;
@Mock RelabelPhotoFileGateway photoFiles;
ReturnReceiptService service; ReturnReceiptService service;
PdaWarehouseIdentity identity; PdaWarehouseIdentity identity;
@@ -91,6 +93,30 @@ class ReturnReceiptServiceTest {
identity = new PdaWarehouseIdentity("operator-1", "仓管员", 10L); identity = new PdaWarehouseIdentity("operator-1", "仓管员", 10L);
} }
@Test
void merchantPhotoDownloadUsesAuthenticatedCustomerAndResolvedReturnOrder() {
ReflectionTestUtils.setField(service, "photoFiles", photoFiles);
ReturnOrder order = order("RT-1", "10", ReturnReceiptService.COMPLETED, 2, 2);
var downloaded = new RelabelPhotoFileGateway.DownloadedFile(new byte[] { 1, 2 }, "proof.jpg", "image/jpeg");
when(orders.findNo("customer-1", "RT-1")).thenReturn(order);
when(photoFiles.download("customer-1", 1L, 99L)).thenReturn(downloaded);
assertEquals(downloaded, service.downloadPhoto("customer-1", "RT-1", 99L));
verify(photoFiles).download("customer-1", 1L, 99L);
}
@Test
void merchantPhotoDownloadRejectsUnknownCustomerOrderBeforeFileAccess() {
ReflectionTestUtils.setField(service, "photoFiles", photoFiles);
when(orders.findNo("other-customer", "RT-1")).thenReturn(null);
ResponseStatusException error = assertThrows(ResponseStatusException.class,
() -> service.downloadPhoto("other-customer", "RT-1", 99L));
assertEquals(404, error.getStatusCode().value());
verifyNoInteractions(photoFiles);
}
@Test @Test
void multiSkuPartialRelabelLocksOnlyRequestedQuantities() { void multiSkuPartialRelabelLocksOnlyRequestedQuantities() {
verifyMultiSkuRelabel(30, 15); verifyMultiSkuRelabel(30, 15);