fix: add ocean booking link candidates query

This commit is contained in:
2026-08-25 19:44:10 +08:00
parent 7d36759f3f
commit 6e55d94f33
9 changed files with 177 additions and 1 deletions
@@ -27,6 +27,11 @@ public class PlatformOceanBookingController {
@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)); }
@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')")
public ApiResponse<List<ChannelOption>> channels(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long originWarehouseId) {
@@ -12,6 +12,7 @@ import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.DecimalMin;
import java.time.LocalDateTime;
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 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,
@NotBlank @Size(max = 255) String partyName,
@Size(max = 1000) String address,
@@ -3,6 +3,8 @@ package com.baiyue.firstleg.oceanbooking.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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 org.apache.ibatis.annotations.Mapper;
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);
@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 fl_ocean_booking SET status=#{toStatus},
updated_by=#{operatorId},updated_by_name=#{operatorName},
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
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.mapper.*;
import com.baiyue.firstleg.entity.ContainerBookingEntity;
@@ -30,6 +32,9 @@ public class OceanBookingRepository {
public PageResult<OceanBookingEntity> page(Query 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 void insert(OceanBookingEntity value) { bookings.insert(value); }
public int transition(long id, String from, String to, int version, long operatorId, String operatorName) {
@@ -55,6 +55,12 @@ public class OceanBookingService {
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) {
if (originWarehouseId <= 0) throw badRequest("请选择有效起运仓");
Long versionId = priceBooks.activeVersionId();
@@ -28,6 +28,25 @@ paths:
parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ReleaseRequest'}}}}
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:
get:
summary: List ACTIVE channels for an origin warehouse
@@ -94,6 +113,38 @@ components:
channelName: {type: string}
priceModel: {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:
type: object
required: [oceanBooking, parties, containerRequirements, containers, histories]