优化
This commit is contained in:
+14
-6
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.PalletRequests;
|
||||
@@ -10,6 +11,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -21,7 +24,6 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.List;
|
||||
|
||||
/** 内部托盘主数据只读接口。 */
|
||||
@Validated
|
||||
@@ -41,7 +43,8 @@ public class InternalPalletController {
|
||||
}
|
||||
|
||||
@GetMapping("/{palletId}/snapshot")
|
||||
@Operation(summary = "查询托盘只读快照", description = "固定按平台公共作用域校验仓库归属,不返回动态状态或可用性")
|
||||
@Operation(summary = "查询当前可用于称重的托盘快照",
|
||||
description = "固定按平台公共作用域校验启用中的国内仓归属;托盘必须当前未删除")
|
||||
public ApiResponse<PalletRequests.Snapshot> snapshot(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable @Min(1) long palletId, @RequestParam @Min(1) long warehouseId) {
|
||||
@@ -50,12 +53,17 @@ public class InternalPalletController {
|
||||
}
|
||||
|
||||
@GetMapping("/snapshots")
|
||||
@Operation(summary = "查询仓库托盘快照列表", description = "返回该仓全部未删除托盘主数据,不按动态状态或占用过滤")
|
||||
public ApiResponse<List<PalletRequests.Snapshot>> snapshots(
|
||||
@Operation(summary = "分页查询当前可用于称重的托盘候选",
|
||||
description = "固定按平台公共作用域、启用中的国内仓和当前未删除资格过滤;托盘可复用,不按预占或占用过滤")
|
||||
public ApiResponse<PageResult<PalletRequests.Snapshot>> snapshots(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@RequestParam @Min(1) long warehouseId) {
|
||||
@RequestParam @Min(1) long warehouseId,
|
||||
@RequestParam(defaultValue = "1") @Min(1) long page,
|
||||
@RequestParam(defaultValue = "20") @Min(1) @Max(100) long size,
|
||||
@RequestParam(required = false) @Size(max = 50) String keyword) {
|
||||
verify(token);
|
||||
return ApiResponse.success(service.snapshots(0L, warehouseId));
|
||||
return ApiResponse.success(service.snapshotCandidates(
|
||||
0L, warehouseId, new PalletRequests.SnapshotPageQuery(page, size, keyword)));
|
||||
}
|
||||
|
||||
private void verify(String token) {
|
||||
|
||||
@@ -84,4 +84,21 @@ public final class PalletRequests {
|
||||
@Schema(description = "皮重,单位kg") BigDecimal tareWeightKg
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "InternalPalletCandidatePageQuery", description = "称重托盘候选分页查询")
|
||||
public record SnapshotPageQuery(
|
||||
@Min(1) @Schema(description = "页码", defaultValue = "1") long page,
|
||||
@Min(1) @Max(100) @Schema(description = "每页条数", defaultValue = "20", maximum = "100") long size,
|
||||
@Size(max = 50) @Schema(description = "关键词;匹配托盘编码或类型") String keyword
|
||||
) {
|
||||
public SnapshotPageQuery {
|
||||
if (page == 0) {
|
||||
page = 1;
|
||||
}
|
||||
if (size == 0) {
|
||||
size = 20;
|
||||
}
|
||||
keyword = keyword == null ? null : keyword.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,18 @@ public class PalletRepository {
|
||||
return mapper.selectIncludingDeleted(tenantId, warehouseId, id);
|
||||
}
|
||||
|
||||
public List<PalletEntity> findAllByWarehouse(long tenantId, long warehouseId) {
|
||||
return mapper.selectList(new LambdaQueryWrapper<PalletEntity>()
|
||||
public PageResult<PalletEntity> candidatePage(long tenantId, long warehouseId,
|
||||
PalletRequests.SnapshotPageQuery query) {
|
||||
LambdaQueryWrapper<PalletEntity> wrapper = new LambdaQueryWrapper<PalletEntity>()
|
||||
.eq(PalletEntity::getTenantId, tenantId)
|
||||
.eq(PalletEntity::getWarehouseId, warehouseId)
|
||||
.and(StringUtils.hasText(query.keyword()), nested -> nested
|
||||
.like(PalletEntity::getPalletCode, query.keyword())
|
||||
.or()
|
||||
.like(PalletEntity::getPalletType, query.keyword()))
|
||||
.orderByAsc(PalletEntity::getPalletCode)
|
||||
.orderByAsc(PalletEntity::getId));
|
||||
.orderByAsc(PalletEntity::getId);
|
||||
return PageResult.from(mapper.selectPage(new Page<>(query.page(), query.size()), wrapper));
|
||||
}
|
||||
|
||||
public boolean existsCode(long tenantId, long warehouseId, String code, Long excludedId) {
|
||||
|
||||
@@ -125,16 +125,19 @@ public class PalletApplicationService {
|
||||
}
|
||||
|
||||
public PalletRequests.Snapshot snapshot(long tenantId, long warehouseId, long palletId) {
|
||||
requireWarehouse(tenantId, warehouseId, true);
|
||||
PalletEntity entity = owned(tenantId, warehouseId, palletId);
|
||||
return new PalletRequests.Snapshot(entity.getId(), entity.getPalletCode(), entity.getPalletType(),
|
||||
entity.getCapacityCbm(), entity.getWeightKg());
|
||||
}
|
||||
|
||||
public List<PalletRequests.Snapshot> snapshots(long tenantId, long warehouseId) {
|
||||
requireWarehouse(tenantId, warehouseId, false);
|
||||
return repository.findAllByWarehouse(tenantId, warehouseId).stream()
|
||||
public PageResult<PalletRequests.Snapshot> snapshotCandidates(
|
||||
long tenantId, long warehouseId, PalletRequests.SnapshotPageQuery query) {
|
||||
requireWarehouse(tenantId, warehouseId, true);
|
||||
PageResult<PalletEntity> source = repository.candidatePage(tenantId, warehouseId, query);
|
||||
return new PageResult<>(source.records().stream()
|
||||
.map(entity -> new PalletRequests.Snapshot(entity.getId(), entity.getPalletCode(), entity.getPalletType(),
|
||||
entity.getCapacityCbm(), entity.getWeightKg()))
|
||||
.toList();
|
||||
.toList(), source.page(), source.size(), source.total(), source.pages());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ openapi: 3.0.3
|
||||
info:
|
||||
title: Warehouse Pallet API
|
||||
version: 1.0.0
|
||||
description: 托盘是可复用标识和称重主数据,不承载 AVAILABLE、RESERVED 或 OCCUPIED 动态状态。
|
||||
description: 托盘是可复用标识和称重主数据;称重候选按当前启用国内仓、同仓归属和未删除资格过滤,不按预占或占用过滤。
|
||||
paths:
|
||||
/internal/warehouse/v1/pallets/{palletId}/snapshot:
|
||||
get:
|
||||
summary: 查询单条托盘主数据快照
|
||||
description: 固定使用平台公共作用域 tenant_id=0,并按 warehouseId 校验归属;不返回状态或可用性。
|
||||
summary: 查询单条当前称重可用托盘快照
|
||||
description: 固定使用平台公共作用域 tenant_id=0,并校验启用国内仓、warehouseId 归属和当前未删除资格。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- { in: path, name: palletId, required: true, schema: { type: integer, format: int64, minimum: 1 } }
|
||||
@@ -18,15 +18,18 @@ paths:
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/PalletSnapshotResponse' } } }
|
||||
/internal/warehouse/v1/pallets/snapshots:
|
||||
get:
|
||||
summary: 查询仓库全部托盘主数据快照
|
||||
description: 固定使用平台公共作用域 tenant_id=0,返回该仓全部未删除主数据,不按状态或占用过滤。
|
||||
summary: 分页查询仓库当前称重托盘候选
|
||||
description: 固定使用平台公共作用域 tenant_id=0,按启用国内仓、同仓归属和当前未删除资格过滤;托盘可复用,不按预占或占用过滤。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- { in: query, name: warehouseId, required: true, schema: { type: integer, format: int64, minimum: 1 } }
|
||||
- { in: query, name: page, required: false, schema: { type: integer, format: int64, minimum: 1, default: 1 } }
|
||||
- { in: query, name: size, required: false, schema: { type: integer, format: int64, minimum: 1, maximum: 100, default: 20 } }
|
||||
- { in: query, name: keyword, required: false, description: 匹配托盘编码或类型, schema: { type: string, maxLength: 50 } }
|
||||
responses:
|
||||
'200':
|
||||
description: 业务响应
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/PalletSnapshotListResponse' } } }
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/PalletSnapshotPageResponse' } } }
|
||||
components:
|
||||
parameters:
|
||||
InternalToken:
|
||||
@@ -61,12 +64,21 @@ components:
|
||||
required: [data]
|
||||
properties:
|
||||
data: { $ref: '#/components/schemas/PalletSnapshot' }
|
||||
PalletSnapshotListResponse:
|
||||
PalletSnapshotPageResponse:
|
||||
allOf:
|
||||
- { $ref: '#/components/schemas/ApiEnvelope' }
|
||||
- type: object
|
||||
required: [data]
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/PalletSnapshot' }
|
||||
type: object
|
||||
required: [records, page, size, total, pages]
|
||||
properties:
|
||||
records:
|
||||
type: array
|
||||
maxItems: 100
|
||||
items: { $ref: '#/components/schemas/PalletSnapshot' }
|
||||
page: { type: integer, format: int64, minimum: 1 }
|
||||
size: { type: integer, format: int64, minimum: 1, maximum: 100 }
|
||||
total: { type: integer, format: int64, minimum: 0 }
|
||||
pages: { type: integer, format: int64, minimum: 0 }
|
||||
|
||||
+9
-5
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.warehouse.dto.PalletRequests;
|
||||
import com.baiyue.warehouse.service.PalletApplicationService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -39,17 +40,20 @@ class InternalPalletControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void listsAllWarehouseSnapshotsFromPlatformScope() {
|
||||
void pagesWarehouseCandidatesFromPlatformScope() {
|
||||
List<PalletRequests.Snapshot> expected = List.of(snapshot(30L, "TP-001"), snapshot(31L, "TP-002"));
|
||||
when(service.snapshots(0L, 20L)).thenReturn(expected);
|
||||
PalletRequests.SnapshotPageQuery query = new PalletRequests.SnapshotPageQuery(2, 20, "TP");
|
||||
PageResult<PalletRequests.Snapshot> page = new PageResult<>(expected, 2, 20, 1000, 50);
|
||||
when(service.snapshotCandidates(0L, 20L, query)).thenReturn(page);
|
||||
|
||||
assertThat(controller.snapshots(TOKEN, 20L).data()).isEqualTo(expected);
|
||||
verify(service).snapshots(0L, 20L);
|
||||
assertThat(controller.snapshots(TOKEN, 20L, 2, 20, "TP").data()).isEqualTo(page);
|
||||
verify(service).snapshotCandidates(0L, 20L, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidInternalToken() {
|
||||
assertThatThrownBy(() -> controller.snapshots("invalid", 20L)).isInstanceOf(BizException.class);
|
||||
assertThatThrownBy(() -> controller.snapshots("invalid", 20L, 1, 20, null))
|
||||
.isInstanceOf(BizException.class);
|
||||
verifyNoInteractions(service);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ class PalletOpenApiContractTest {
|
||||
assertThat(yaml)
|
||||
.contains("/internal/warehouse/v1/pallets/{palletId}/snapshot:")
|
||||
.contains("/internal/warehouse/v1/pallets/snapshots:")
|
||||
.contains("name: keyword")
|
||||
.contains("maximum: 100")
|
||||
.contains("maxItems: 100")
|
||||
.contains("PalletSnapshotPageResponse:")
|
||||
.contains("required: [palletId, palletCode, palletType, capacityCbm, tareWeightKg]")
|
||||
.contains("additionalProperties: false")
|
||||
.doesNotContain("tenantId:")
|
||||
|
||||
@@ -64,6 +64,26 @@ class PalletRepositoryTest {
|
||||
.containsExactlyInAnyOrder(10001L, 20L, 30L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidatePageIsWarehouseScopedKeywordFilteredAndBounded() {
|
||||
when(mapper.selectPage(any(Page.class), any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
repository.candidatePage(0L, 20L,
|
||||
new PalletRequests.SnapshotPageQuery(3, 100, " TP-9 "));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Page<PalletEntity>> page = ArgumentCaptor.forClass(Page.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<PalletEntity>> wrapper =
|
||||
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(mapper).selectPage(page.capture(), wrapper.capture());
|
||||
wrapper.getValue().getSqlSegment();
|
||||
assertThat(page.getValue().getCurrent()).isEqualTo(3);
|
||||
assertThat(page.getValue().getSize()).isEqualTo(100);
|
||||
assertThat(wrapper.getValue().getParamNameValuePairs().values())
|
||||
.contains(0L, 20L, "%TP-9%");
|
||||
}
|
||||
|
||||
private Collection<Object> queryValues(Long tenantId, Long warehouseId) {
|
||||
when(mapper.selectPage(any(Page.class), any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
repository.page(tenantId,
|
||||
|
||||
+25
-3
@@ -18,6 +18,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@SpringJUnitConfig(PalletApplicationService.class)
|
||||
class PalletApplicationServiceTest {
|
||||
@@ -64,21 +65,42 @@ class PalletApplicationServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsReusablePalletSnapshotsWithoutResourceStatus() {
|
||||
void returnsControlledReusablePalletCandidatePage() {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setWarehouseType("DOMESTIC");
|
||||
warehouse.setStatus("ACTIVE");
|
||||
when(warehouseRepository.findOwned(0L, 20L)).thenReturn(warehouse);
|
||||
PalletEntity entity = pallet();
|
||||
entity.setPalletCode("TP-001");
|
||||
entity.setPalletType("STANDARD");
|
||||
entity.setCapacityCbm(new BigDecimal("1.200000"));
|
||||
entity.setWeightKg(new BigDecimal("8.000"));
|
||||
when(repository.findAllByWarehouse(0L, 20L)).thenReturn(java.util.List.of(entity));
|
||||
PalletRequests.SnapshotPageQuery query = new PalletRequests.SnapshotPageQuery(1, 20, "TP-0");
|
||||
when(repository.candidatePage(0L, 20L, query)).thenReturn(
|
||||
new PageResult<>(List.of(entity), 1, 20, 1000, 50));
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(service.snapshots(0L, 20L)).singleElement().satisfies(snapshot -> {
|
||||
PageResult<PalletRequests.Snapshot> result = service.snapshotCandidates(0L, 20L, query);
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(result.records()).singleElement().satisfies(snapshot -> {
|
||||
org.assertj.core.api.Assertions.assertThat(snapshot.palletId()).isEqualTo(30L);
|
||||
org.assertj.core.api.Assertions.assertThat(snapshot.tareWeightKg()).isEqualByComparingTo("8.000");
|
||||
});
|
||||
org.assertj.core.api.Assertions.assertThat(result.total()).isEqualTo(1000);
|
||||
org.assertj.core.api.Assertions.assertThat(result.records()).hasSize(1);
|
||||
verify(repository).candidatePage(0L, 20L, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCandidatesFromInactiveWarehouse() {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setWarehouseType("DOMESTIC");
|
||||
warehouse.setStatus("INACTIVE");
|
||||
when(warehouseRepository.findOwned(0L, 20L)).thenReturn(warehouse);
|
||||
|
||||
assertThatThrownBy(() -> service.snapshotCandidates(
|
||||
0L, 20L, new PalletRequests.SnapshotPageQuery(1, 20, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("启用中");
|
||||
}
|
||||
|
||||
private static PalletEntity pallet() {
|
||||
|
||||
Reference in New Issue
Block a user