fix: add ocean booking link candidates query
This commit is contained in:
+5
@@ -27,6 +27,11 @@ public class PlatformOceanBookingController {
|
|||||||
|
|
||||||
@GetMapping @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
@GetMapping @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
||||||
public ApiResponse<PageResult<OceanBookingEntity>> page(@AuthenticationPrincipal AuthenticatedUser user, @Valid Query query) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.page(query)); }
|
public ApiResponse<PageResult<OceanBookingEntity>> page(@AuthenticationPrincipal AuthenticatedUser user, @Valid Query query) { FirstlegScopeGuard.platform(user); return ApiResponse.success(service.page(query)); }
|
||||||
|
@GetMapping("/link-candidates") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
||||||
|
public ApiResponse<PageResult<LinkCandidate>> linkCandidates(
|
||||||
|
@AuthenticationPrincipal AuthenticatedUser user, @Valid LinkCandidateQuery query) {
|
||||||
|
FirstlegScopeGuard.platform(user); return ApiResponse.success(service.linkCandidates(query));
|
||||||
|
}
|
||||||
@GetMapping("/channels") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
@GetMapping("/channels") @PreAuthorize("hasAuthority('platform:firstleg:ocean-booking:list')")
|
||||||
public ApiResponse<List<ChannelOption>> channels(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<List<ChannelOption>> channels(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestParam long originWarehouseId) {
|
@RequestParam long originWarehouseId) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import jakarta.validation.constraints.NotEmpty;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Positive;
|
import jakarta.validation.constraints.Positive;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
|
import jakarta.validation.constraints.DecimalMin;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -26,6 +27,24 @@ public final class OceanBookingDtos {
|
|||||||
public Query { if (page == 0) page = 1; if (size == 0) size = 20; }
|
public Query { if (page == 0) page = 1; if (size == 0) size = 20; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record LinkCandidateQuery(@NotNull @Positive Long warehouseId,
|
||||||
|
@NotBlank @Size(max = 64) String channelCode,
|
||||||
|
@NotBlank @Size(max = 32) String containerType,
|
||||||
|
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
|
||||||
|
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg,
|
||||||
|
@Min(1) Long page,
|
||||||
|
@Min(1) @Max(200) Long size) {
|
||||||
|
public LinkCandidateQuery {
|
||||||
|
if (page == null) page = 1L;
|
||||||
|
if (size == null) size = 20L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record LinkCandidate(Long oceanBookingId, String bookingNo, Long requirementId,
|
||||||
|
String containerType, BigDecimal capacityCbm, BigDecimal maxWeightKg,
|
||||||
|
int requiredQuantity, long allocatedQuantity, long remainingQuantity,
|
||||||
|
String customerName, String warehouseName, String channelName) {}
|
||||||
|
|
||||||
public record PartyRequest(@NotBlank @Size(max = 24) String partyType,
|
public record PartyRequest(@NotBlank @Size(max = 24) String partyType,
|
||||||
@NotBlank @Size(max = 255) String partyName,
|
@NotBlank @Size(max = 255) String partyName,
|
||||||
@Size(max = 1000) String address,
|
@Size(max = 1000) String address,
|
||||||
|
|||||||
+30
@@ -3,6 +3,8 @@ package com.baiyue.firstleg.oceanbooking.mapper;
|
|||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
||||||
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.LinkCandidate;
|
||||||
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.LinkCandidateQuery;
|
||||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Delete;
|
import org.apache.ibatis.annotations.Delete;
|
||||||
@@ -41,6 +43,34 @@ public interface OceanBookingMapper extends BaseMapper<OceanBookingEntity> {
|
|||||||
""")
|
""")
|
||||||
Page<OceanBookingEntity> selectPage(Page<OceanBookingEntity> page, @Param("query") Query query);
|
Page<OceanBookingEntity> selectPage(Page<OceanBookingEntity> page, @Param("query") Query query);
|
||||||
|
|
||||||
|
@Select("""
|
||||||
|
SELECT booking.id ocean_booking_id,booking.booking_no,requirement.id requirement_id,
|
||||||
|
requirement.container_type,requirement.capacity_cbm,requirement.max_weight_kg,
|
||||||
|
requirement.quantity required_quantity,
|
||||||
|
COALESCE(allocated.allocated_quantity,0) allocated_quantity,
|
||||||
|
requirement.quantity-COALESCE(allocated.allocated_quantity,0) remaining_quantity,
|
||||||
|
booking.customer_name,booking.warehouse_name,booking.channel_name
|
||||||
|
FROM fl_ocean_booking booking
|
||||||
|
JOIN fl_ocean_booking_container_requirement requirement
|
||||||
|
ON requirement.ocean_booking_id=booking.id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ocean_booking_id,requirement_id,COUNT(*) allocated_quantity
|
||||||
|
FROM fl_ocean_booking_container_allocation
|
||||||
|
GROUP BY ocean_booking_id,requirement_id
|
||||||
|
) allocated ON allocated.ocean_booking_id=booking.id
|
||||||
|
AND allocated.requirement_id=requirement.id
|
||||||
|
WHERE booking.status='RELEASED'
|
||||||
|
AND booking.warehouse_id=#{query.warehouseId}
|
||||||
|
AND booking.channel_code=#{query.channelCode}
|
||||||
|
AND requirement.container_type=#{query.containerType}
|
||||||
|
AND requirement.capacity_cbm=#{query.capacityCbm}
|
||||||
|
AND requirement.max_weight_kg=#{query.maxWeightKg}
|
||||||
|
AND requirement.quantity>COALESCE(allocated.allocated_quantity,0)
|
||||||
|
ORDER BY booking.updated_at DESC,booking.id DESC,requirement.sort_no ASC,requirement.id ASC
|
||||||
|
""")
|
||||||
|
Page<LinkCandidate> selectLinkCandidates(Page<LinkCandidate> page,
|
||||||
|
@Param("query") LinkCandidateQuery query);
|
||||||
|
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE fl_ocean_booking SET status=#{toStatus},
|
UPDATE fl_ocean_booking SET status=#{toStatus},
|
||||||
updated_by=#{operatorId},updated_by_name=#{operatorName},
|
updated_by=#{operatorId},updated_by_name=#{operatorName},
|
||||||
|
|||||||
+5
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.baiyue.common.core.api.PageResult;
|
import com.baiyue.common.core.api.PageResult;
|
||||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
||||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.RealContainer;
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.RealContainer;
|
||||||
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.LinkCandidate;
|
||||||
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.LinkCandidateQuery;
|
||||||
import com.baiyue.firstleg.oceanbooking.entity.*;
|
import com.baiyue.firstleg.oceanbooking.entity.*;
|
||||||
import com.baiyue.firstleg.oceanbooking.mapper.*;
|
import com.baiyue.firstleg.oceanbooking.mapper.*;
|
||||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||||
@@ -30,6 +32,9 @@ public class OceanBookingRepository {
|
|||||||
public PageResult<OceanBookingEntity> page(Query query) {
|
public PageResult<OceanBookingEntity> page(Query query) {
|
||||||
return PageResult.from(bookings.selectPage(new Page<>(query.page(), query.size()), query));
|
return PageResult.from(bookings.selectPage(new Page<>(query.page(), query.size()), query));
|
||||||
}
|
}
|
||||||
|
public PageResult<LinkCandidate> linkCandidates(LinkCandidateQuery query) {
|
||||||
|
return PageResult.from(bookings.selectLinkCandidates(new Page<>(query.page(), query.size()), query));
|
||||||
|
}
|
||||||
public OceanBookingEntity find(long id) { return bookings.selectById(id); }
|
public OceanBookingEntity find(long id) { return bookings.selectById(id); }
|
||||||
public void insert(OceanBookingEntity value) { bookings.insert(value); }
|
public void insert(OceanBookingEntity value) { bookings.insert(value); }
|
||||||
public int transition(long id, String from, String to, int version, long operatorId, String operatorName) {
|
public int transition(long id, String from, String to, int version, long operatorId, String operatorName) {
|
||||||
|
|||||||
+6
@@ -55,6 +55,12 @@ public class OceanBookingService {
|
|||||||
return repository.page(new Query(query.customerId(), status, trim(query.keyword()), query.page(), query.size()));
|
return repository.page(new Query(query.customerId(), status, trim(query.keyword()), query.page(), query.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PageResult<LinkCandidate> linkCandidates(LinkCandidateQuery query) {
|
||||||
|
return repository.linkCandidates(new LinkCandidateQuery(query.warehouseId(),
|
||||||
|
query.channelCode().trim(), query.containerType().trim(), query.capacityCbm(),
|
||||||
|
query.maxWeightKg(), query.page(), query.size()));
|
||||||
|
}
|
||||||
|
|
||||||
public List<ChannelOption> channels(long originWarehouseId) {
|
public List<ChannelOption> channels(long originWarehouseId) {
|
||||||
if (originWarehouseId <= 0) throw badRequest("请选择有效起运仓");
|
if (originWarehouseId <= 0) throw badRequest("请选择有效起运仓");
|
||||||
Long versionId = priceBooks.activeVersionId();
|
Long versionId = priceBooks.activeVersionId();
|
||||||
|
|||||||
@@ -28,6 +28,25 @@ paths:
|
|||||||
parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ReleaseRequest'}}}}
|
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ReleaseRequest'}}}}
|
||||||
responses: {'200': {description: RELEASED detail}}
|
responses: {'200': {description: RELEASED detail}}
|
||||||
|
/api/platform/v1/ocean-bookings/link-candidates:
|
||||||
|
get:
|
||||||
|
summary: Page released booking requirement candidates for one container
|
||||||
|
description: Requires platform:firstleg:ocean-booking:list. Returns only exact warehouse, channel and fixed container specification matches with remainingQuantity greater than zero. Allocation counts are calculated by one bounded set query; booking detail calls are not used.
|
||||||
|
x-permission: platform:firstleg:ocean-booking:list
|
||||||
|
parameters:
|
||||||
|
- {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||||
|
- {name: channelCode, in: query, required: true, schema: {type: string, maxLength: 64}}
|
||||||
|
- {name: containerType, in: query, required: true, schema: {type: string, maxLength: 32}}
|
||||||
|
- {name: capacityCbm, in: query, required: true, schema: {type: number, exclusiveMinimum: true, minimum: 0}}
|
||||||
|
- {name: maxWeightKg, in: query, required: true, schema: {type: number, exclusiveMinimum: true, minimum: 0}}
|
||||||
|
- {name: page, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, default: 1}}
|
||||||
|
- {name: size, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}}
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Business envelope containing a bounded candidate page
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/LinkCandidateResponse'}
|
||||||
/api/platform/v1/ocean-bookings/channels:
|
/api/platform/v1/ocean-bookings/channels:
|
||||||
get:
|
get:
|
||||||
summary: List ACTIVE channels for an origin warehouse
|
summary: List ACTIVE channels for an origin warehouse
|
||||||
@@ -94,6 +113,38 @@ components:
|
|||||||
channelName: {type: string}
|
channelName: {type: string}
|
||||||
priceModel: {type: string}
|
priceModel: {type: string}
|
||||||
pricingTaxModes: {type: array, items: {type: string}}
|
pricingTaxModes: {type: array, items: {type: string}}
|
||||||
|
LinkCandidate:
|
||||||
|
type: object
|
||||||
|
required: [oceanBookingId, bookingNo, requirementId, containerType, capacityCbm, maxWeightKg, requiredQuantity, allocatedQuantity, remainingQuantity, customerName, warehouseName, channelName]
|
||||||
|
properties:
|
||||||
|
oceanBookingId: {type: string, pattern: '^[0-9]+$'}
|
||||||
|
bookingNo: {type: string}
|
||||||
|
requirementId: {type: string, pattern: '^[0-9]+$'}
|
||||||
|
containerType: {type: string}
|
||||||
|
capacityCbm: {type: number}
|
||||||
|
maxWeightKg: {type: number}
|
||||||
|
requiredQuantity: {type: integer, minimum: 1}
|
||||||
|
allocatedQuantity: {type: integer, format: int64, minimum: 0}
|
||||||
|
remainingQuantity: {type: integer, format: int64, minimum: 1}
|
||||||
|
customerName: {type: string}
|
||||||
|
warehouseName: {type: string}
|
||||||
|
channelName: {type: string}
|
||||||
|
LinkCandidatePage:
|
||||||
|
type: object
|
||||||
|
required: [records, page, size, total, pages]
|
||||||
|
properties:
|
||||||
|
records: {type: array, items: {$ref: '#/components/schemas/LinkCandidate'}}
|
||||||
|
page: {type: integer, format: int64, minimum: 1}
|
||||||
|
size: {type: integer, format: int64, minimum: 1}
|
||||||
|
total: {type: integer, format: int64, minimum: 0}
|
||||||
|
pages: {type: integer, format: int64, minimum: 0}
|
||||||
|
LinkCandidateResponse:
|
||||||
|
type: object
|
||||||
|
required: [code, data]
|
||||||
|
properties:
|
||||||
|
code: {type: string, example: '200'}
|
||||||
|
message: {type: string, nullable: true}
|
||||||
|
data: {$ref: '#/components/schemas/LinkCandidatePage'}
|
||||||
Detail:
|
Detail:
|
||||||
type: object
|
type: object
|
||||||
required: [oceanBooking, parties, containerRequirements, containers, histories]
|
required: [oceanBooking, parties, containerRequirements, containers, histories]
|
||||||
|
|||||||
+17
-1
@@ -20,6 +20,7 @@ class PlatformOceanBookingControllerContractTest {
|
|||||||
.containsExactly("/api/platform/v1/ocean-bookings");
|
.containsExactly("/api/platform/v1/ocean-bookings");
|
||||||
Map<String, String> permissions = Map.ofEntries(
|
Map<String, String> permissions = Map.ofEntries(
|
||||||
Map.entry("page", "platform:firstleg:ocean-booking:list"),
|
Map.entry("page", "platform:firstleg:ocean-booking:list"),
|
||||||
|
Map.entry("linkCandidates", "platform:firstleg:ocean-booking:list"),
|
||||||
Map.entry("channels", "platform:firstleg:ocean-booking:list"),
|
Map.entry("channels", "platform:firstleg:ocean-booking:list"),
|
||||||
Map.entry("detail", "platform:firstleg:ocean-booking:list"),
|
Map.entry("detail", "platform:firstleg:ocean-booking:list"),
|
||||||
Map.entry("create", "platform:firstleg:ocean-booking:create"),
|
Map.entry("create", "platform:firstleg:ocean-booking:create"),
|
||||||
@@ -30,7 +31,8 @@ class PlatformOceanBookingControllerContractTest {
|
|||||||
Method method = method(name);
|
Method method = method(name);
|
||||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||||
.isEqualTo("hasAuthority('" + permission + "')");
|
.isEqualTo("hasAuthority('" + permission + "')");
|
||||||
if (!name.equals("page") && !name.equals("detail") && !name.equals("channels")) {
|
if (!name.equals("page") && !name.equals("detail") && !name.equals("channels")
|
||||||
|
&& !name.equals("linkCandidates")) {
|
||||||
assertThat(method.getAnnotation(BusinessAudit.class)).isNotNull();
|
assertThat(method.getAnnotation(BusinessAudit.class)).isNotNull();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -57,6 +59,20 @@ class PlatformOceanBookingControllerContractTest {
|
|||||||
.doesNotContain("change-request", "change-confirm", "/cancel:", "containers[].containerNo");
|
.doesNotContain("change-request", "change-confirm", "/cancel:", "containers[].containerNo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void linkCandidatesUsesDedicatedGetPathAndStablePageContract() throws Exception {
|
||||||
|
Method method = method("linkCandidates");
|
||||||
|
assertThat(method.getAnnotation(org.springframework.web.bind.annotation.GetMapping.class).value())
|
||||||
|
.containsExactly("/link-candidates");
|
||||||
|
String yaml = Files.readString(Path.of("src/main/resources/openapi/ocean-bookings-v1.yaml"));
|
||||||
|
assertThat(yaml)
|
||||||
|
.contains("/api/platform/v1/ocean-bookings/link-candidates:")
|
||||||
|
.contains("x-permission: platform:firstleg:ocean-booking:list")
|
||||||
|
.contains("warehouseId", "channelCode", "containerType", "capacityCbm", "maxWeightKg")
|
||||||
|
.contains("LinkCandidateResponse", "requiredQuantity", "allocatedQuantity", "remainingQuantity")
|
||||||
|
.contains("booking detail calls are not used");
|
||||||
|
}
|
||||||
|
|
||||||
private static Method method(String name) {
|
private static Method method(String name) {
|
||||||
return Arrays.stream(PlatformOceanBookingController.class.getDeclaredMethods())
|
return Arrays.stream(PlatformOceanBookingController.class.getDeclaredMethods())
|
||||||
.filter(method -> method.getName().equals(name)).findFirst().orElseThrow();
|
.filter(method -> method.getName().equals(name)).findFirst().orElseThrow();
|
||||||
|
|||||||
+20
@@ -1,6 +1,7 @@
|
|||||||
package com.baiyue.firstleg.oceanbooking.repository;
|
package com.baiyue.firstleg.oceanbooking.repository;
|
||||||
|
|
||||||
import com.baiyue.firstleg.oceanbooking.mapper.OceanBookingMapper;
|
import com.baiyue.firstleg.oceanbooking.mapper.OceanBookingMapper;
|
||||||
|
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.LinkCandidateQuery;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
import org.apache.ibatis.annotations.Update;
|
import org.apache.ibatis.annotations.Update;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -36,4 +37,23 @@ class OceanBookingMapperContractTest {
|
|||||||
.contains("status='DRAFT' AND version=#{version}")
|
.contains("status='DRAFT' AND version=#{version}")
|
||||||
.doesNotContain("CONFIRMED", "CHANGE_PENDING", "CANCELLED", "SUBMITTED", "FAILED");
|
.doesNotContain("CONFIRMED", "CHANGE_PENDING", "CANCELLED", "SUBMITTED", "FAILED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void linkCandidatesUsesOneBoundedSetQueryAndExactRemainingSemantics() throws Exception {
|
||||||
|
String sql = String.join("\n", OceanBookingMapper.class.getDeclaredMethod("selectLinkCandidates",
|
||||||
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page.class, LinkCandidateQuery.class)
|
||||||
|
.getAnnotation(Select.class).value());
|
||||||
|
assertThat(sql)
|
||||||
|
.contains("booking.status='RELEASED'")
|
||||||
|
.contains("booking.warehouse_id=#{query.warehouseId}")
|
||||||
|
.contains("booking.channel_code=#{query.channelCode}")
|
||||||
|
.contains("requirement.container_type=#{query.containerType}")
|
||||||
|
.contains("requirement.capacity_cbm=#{query.capacityCbm}")
|
||||||
|
.contains("requirement.max_weight_kg=#{query.maxWeightKg}")
|
||||||
|
.contains("FROM fl_ocean_booking_container_allocation")
|
||||||
|
.contains("GROUP BY ocean_booking_id,requirement_id")
|
||||||
|
.contains("requirement.quantity-COALESCE(allocated.allocated_quantity,0) remaining_quantity")
|
||||||
|
.contains("requirement.quantity>COALESCE(allocated.allocated_quantity,0)")
|
||||||
|
.doesNotContain("fl_container_booking", "SELECT booking.*");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -1,6 +1,7 @@
|
|||||||
package com.baiyue.firstleg.oceanbooking.service;
|
package com.baiyue.firstleg.oceanbooking.service;
|
||||||
|
|
||||||
import com.baiyue.common.core.api.ApiResponse;
|
import com.baiyue.common.core.api.ApiResponse;
|
||||||
|
import com.baiyue.common.core.api.PageResult;
|
||||||
import com.baiyue.common.core.exception.BizException;
|
import com.baiyue.common.core.exception.BizException;
|
||||||
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
|
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
|
||||||
import com.baiyue.firstleg.client.WarehouseSupplierClient;
|
import com.baiyue.firstleg.client.WarehouseSupplierClient;
|
||||||
@@ -170,6 +171,29 @@ class OceanBookingServiceTest {
|
|||||||
verify(repository, times(2)).page(any());
|
verify(repository, times(2)).page(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void linkCandidatesDefaultsPaginationAndDelegatesOneNormalizedSetQuery() {
|
||||||
|
LinkCandidate candidate = new LinkCandidate(101L, "OB-260818-0001", 201L, "40HC",
|
||||||
|
new BigDecimal("76"), new BigDecimal("28000"), 3, 1, 2,
|
||||||
|
"客户A", "深圳中转仓", "美西海运");
|
||||||
|
when(repository.linkCandidates(any())).thenReturn(new PageResult<>(List.of(candidate), 1, 20, 1, 1));
|
||||||
|
|
||||||
|
PageResult<LinkCandidate> result = service.linkCandidates(new LinkCandidateQuery(701L,
|
||||||
|
" SEA-US ", " 40HC ", new BigDecimal("76.0"), new BigDecimal("28000.00"), null, null));
|
||||||
|
|
||||||
|
assertThat(result.records()).containsExactly(candidate);
|
||||||
|
assertThat(result.records().get(0).remainingQuantity()).isEqualTo(2);
|
||||||
|
ArgumentCaptor<LinkCandidateQuery> query = ArgumentCaptor.forClass(LinkCandidateQuery.class);
|
||||||
|
verify(repository).linkCandidates(query.capture());
|
||||||
|
assertThat(query.getValue().channelCode()).isEqualTo("SEA-US");
|
||||||
|
assertThat(query.getValue().containerType()).isEqualTo("40HC");
|
||||||
|
assertThat(query.getValue().page()).isEqualTo(1L);
|
||||||
|
assertThat(query.getValue().size()).isEqualTo(20L);
|
||||||
|
verify(repository, never()).find(anyLong());
|
||||||
|
verify(repository, never()).requirements(anyLong());
|
||||||
|
verify(repository, never()).containers(anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void releaseRejectsInvalidScheduleWithoutTransition() {
|
void releaseRejectsInvalidScheduleWithoutTransition() {
|
||||||
OceanBookingEntity draft = completeBooking("DRAFT", 3);
|
OceanBookingEntity draft = completeBooking("DRAFT", 3);
|
||||||
|
|||||||
Reference in New Issue
Block a user