优化
This commit is contained in:
@@ -7,10 +7,13 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
|||||||
public record InboundCandidateQuery(
|
public record InboundCandidateQuery(
|
||||||
@NotEmpty @Size(max=9) List<@NotBlank String> channelGroupCodes,
|
@NotEmpty @Size(max=9) List<@NotBlank String> channelGroupCodes,
|
||||||
@Positive Long warehouseId, @Positive Long customerId, @Size(max=40) String inboundCode,
|
@Positive Long warehouseId, @Positive Long customerId, @Size(max=40) String inboundCode,
|
||||||
LocalDate createdStart, LocalDate createdEnd, CandidateOrderStatus orderStatus,
|
LocalDate createdStart, LocalDate createdEnd,
|
||||||
|
@Size(max=4) List<@NotNull CandidateOrderStatus> orderStatuses,
|
||||||
@Min(1) long page, @Min(1) @Max(200) long size) {
|
@Min(1) long page, @Min(1) @Max(200) long size) {
|
||||||
public InboundCandidateQuery {
|
public InboundCandidateQuery {
|
||||||
channelGroupCodes = channelGroupCodes == null ? List.of() : List.copyOf(channelGroupCodes);
|
channelGroupCodes = channelGroupCodes == null ? List.of() : List.copyOf(channelGroupCodes);
|
||||||
|
orderStatuses = orderStatuses == null ? List.of()
|
||||||
|
: List.copyOf(new java.util.LinkedHashSet<>(orderStatuses));
|
||||||
if (page == 0) page = 1;
|
if (page == 0) page = 1;
|
||||||
if (size == 0) size = 20;
|
if (size == 0) size = 20;
|
||||||
}
|
}
|
||||||
@@ -24,7 +27,8 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
|||||||
Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg,
|
Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg,
|
||||||
BigDecimal averageUnitVolumeCbm, BigDecimal availableVolumeCbm,
|
BigDecimal averageUnitVolumeCbm, BigDecimal availableVolumeCbm,
|
||||||
Long coLoadGroupId, String groupNo, Integer memberCount, Integer groupTotalBoxes,
|
Long coLoadGroupId, String groupNo, Integer memberCount, Integer groupTotalBoxes,
|
||||||
Boolean locked, String blockReason, String orderStatusSnapshot, LocalDateTime expectedArrivalAt,
|
Boolean locked, Boolean canAssign, String blockReason,
|
||||||
|
String orderStatusSnapshot, LocalDateTime expectedArrivalAt,
|
||||||
List<GroupMemberCandidate> groupMembers) {
|
List<GroupMemberCandidate> groupMembers) {
|
||||||
public InboundCandidate { groupMembers = groupMembers == null ? List.of() : List.copyOf(groupMembers); }
|
public InboundCandidate { groupMembers = groupMembers == null ? List.of() : List.copyOf(groupMembers); }
|
||||||
public Long customerId() { return accountId; }
|
public Long customerId() { return accountId; }
|
||||||
@@ -32,7 +36,7 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
|||||||
public record WorkbenchContainerQuery(
|
public record WorkbenchContainerQuery(
|
||||||
@NotEmpty @Size(max=9) List<@NotBlank String> channelGroupCodes,
|
@NotEmpty @Size(max=9) List<@NotBlank String> channelGroupCodes,
|
||||||
@Positive Long warehouseId, @Positive Long customerId, @Size(max=40) String inboundCode,
|
@Positive Long warehouseId, @Positive Long customerId, @Size(max=40) String inboundCode,
|
||||||
LocalDate createdStart, LocalDate createdEnd, CandidateOrderStatus orderStatus,
|
LocalDate createdStart, LocalDate createdEnd,
|
||||||
@Min(1) long page, @Min(1) @Max(200) long size) {
|
@Min(1) long page, @Min(1) @Max(200) long size) {
|
||||||
public WorkbenchContainerQuery {
|
public WorkbenchContainerQuery {
|
||||||
channelGroupCodes = channelGroupCodes == null ? List.of() : List.copyOf(channelGroupCodes);
|
channelGroupCodes = channelGroupCodes == null ? List.of() : List.copyOf(channelGroupCodes);
|
||||||
|
|||||||
+89
-58
@@ -85,36 +85,48 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
CASE WHEN current_group.id IS NULL THEN eligible.inboundId END inboundId,
|
CASE WHEN current_group.id IS NULL THEN eligible.inboundId END inboundId,
|
||||||
MIN(eligible.expectedArrivalAt) sortArrivalAt,MIN(eligible.inboundId) sortInboundId
|
MIN(eligible.expectedArrivalAt) sortArrivalAt,MIN(eligible.inboundId) sortInboundId
|
||||||
FROM (
|
FROM (
|
||||||
SELECT detail.accountId,detail.inboundId,detail.inboundCode,detail.expectedArrivalAt,detail.createdAt,
|
SELECT o.tenant_id accountId,o.id inboundId,o.inbound_code inboundCode,
|
||||||
SUM(detail.availablePackageCount) availablePackageCount
|
o.expected_arrival_at expectedArrivalAt,o.created_at createdAt,
|
||||||
FROM (
|
CASE WHEN o.process_status='WAIT_SPLIT' THEN
|
||||||
SELECT o.tenant_id accountId,o.id inboundId,o.inbound_code inboundCode,
|
COALESCE((SELECT SUM(mi.package_count) FROM fl_inbound_measurement m
|
||||||
o.expected_arrival_at expectedArrivalAt,o.created_at createdAt,i.inbound_detail_id,
|
JOIN fl_inbound_measurement_item mi
|
||||||
SUM(i.package_count)
|
ON mi.tenant_id=m.tenant_id AND mi.measurement_id=m.id
|
||||||
-COALESCE((SELECT SUM(li.package_count) FROM fl_loading_plan_item li
|
AND mi.inbound_detail_id IS NOT NULL AND mi.package_count>0
|
||||||
WHERE li.account_id=o.tenant_id AND li.inbound_id=o.id
|
WHERE m.tenant_id=o.tenant_id AND m.inbound_id=o.id AND m.status='APPROVED'
|
||||||
AND li.inbound_detail_id=i.inbound_detail_id AND li.deleted=0),0)
|
AND m.id=(SELECT MAX(m2.id) FROM fl_inbound_measurement m2
|
||||||
-COALESCE((SELECT SUM(split_item.package_count) FROM fl_inbound_split_item split_item
|
WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id
|
||||||
WHERE o.inbound_role='MASTER' AND split_item.tenant_id=o.tenant_id
|
AND m2.status='APPROVED')),0)
|
||||||
AND split_item.parent_inbound_id=o.id
|
ELSE COALESCE(o.total_package_count,0) END
|
||||||
AND split_item.source_inbound_detail_id=i.inbound_detail_id),0) availablePackageCount
|
-COALESCE((SELECT SUM(li.package_count) FROM fl_loading_plan_item li
|
||||||
FROM fl_inbound_order o
|
WHERE li.account_id=o.tenant_id AND li.inbound_id=o.id AND li.deleted=0),0)
|
||||||
JOIN fl_inbound_measurement m ON m.tenant_id=o.tenant_id AND m.inbound_id=o.id
|
-COALESCE((SELECT SUM(split_item.package_count) FROM fl_inbound_split_item split_item
|
||||||
AND m.status='APPROVED'
|
WHERE o.inbound_role='MASTER' AND split_item.tenant_id=o.tenant_id
|
||||||
AND m.id=(SELECT MAX(m2.id) FROM fl_inbound_measurement m2
|
AND split_item.parent_inbound_id=o.id),0) availablePackageCount
|
||||||
WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED')
|
FROM fl_inbound_order o
|
||||||
JOIN fl_inbound_measurement_item i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id
|
WHERE (#{query.warehouseId} IS NULL OR o.warehouse_id=#{query.warehouseId})
|
||||||
AND i.inbound_detail_id IS NOT NULL AND i.package_count>0
|
AND o.channel_code IN
|
||||||
WHERE (#{query.warehouseId} IS NULL OR o.warehouse_id=#{query.warehouseId})
|
<foreach collection="actualChannels" item="channel" open="(" separator="," close=")">#{channel}</foreach>
|
||||||
AND o.channel_code IN
|
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
||||||
<foreach collection="actualChannels" item="channel" open="(" separator="," close=")">#{channel}</foreach>
|
AND (
|
||||||
AND o.process_status='WAIT_SPLIT'
|
o.process_status IN ('WAIT_MEASURE','MEASURING','MEASURE_PENDING','MEASURE_REJECTED',
|
||||||
AND o.actual_gross_weight_kg IS NOT NULL AND o.actual_net_weight_kg IS NOT NULL
|
'WAIT_PRICING','FEE_CONFIRMING','INBOUNDING','WAIT_SPLIT','LOADING')
|
||||||
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
)
|
||||||
GROUP BY o.tenant_id,o.id,o.inbound_code,o.expected_arrival_at,o.created_at,i.inbound_detail_id
|
<if test="query.orderStatuses != null and !query.orderStatuses.isEmpty()">
|
||||||
HAVING availablePackageCount>0
|
AND (
|
||||||
) detail
|
<foreach collection="query.orderStatuses" item="status" separator=" OR ">
|
||||||
GROUP BY detail.accountId,detail.inboundId,detail.inboundCode,detail.expectedArrivalAt,detail.createdAt
|
<choose>
|
||||||
|
<when test="status.name() == 'WAIT_MEASURE'">o.process_status IN ('WAIT_MEASURE','MEASURING','MEASURE_PENDING','MEASURE_REJECTED','WAIT_PRICING','FEE_CONFIRMING','INBOUNDING')</when>
|
||||||
|
<when test="status.name() == 'WAIT_SPLIT'">o.process_status='WAIT_SPLIT'</when>
|
||||||
|
<when test="status.name() == 'LOADING'">o.process_status='LOADING'</when>
|
||||||
|
<otherwise>1=0</otherwise>
|
||||||
|
</choose>
|
||||||
|
</foreach>
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
<if test="query.customerId != null">AND o.tenant_id=#{query.customerId}</if>
|
||||||
|
<if test="query.inboundCode != null and query.inboundCode.trim() != ''">AND LOWER(o.inbound_code) LIKE CONCAT('%',LOWER(#{query.inboundCode}),'%')</if>
|
||||||
|
<if test="query.createdStart != null">AND o.created_at>=#{query.createdStart}</if>
|
||||||
|
<if test="query.createdEnd != null">AND o.created_at<DATE_ADD(#{query.createdEnd},INTERVAL 1 DAY)</if>
|
||||||
) eligible
|
) eligible
|
||||||
LEFT JOIN fl_coload_group_member current_member
|
LEFT JOIN fl_coload_group_member current_member
|
||||||
ON current_member.tenant_id=eligible.accountId AND current_member.inbound_id=eligible.inboundId
|
ON current_member.tenant_id=eligible.accountId AND current_member.inbound_id=eligible.inboundId
|
||||||
@@ -125,21 +137,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
CASE WHEN current_group.id IS NULL THEN eligible.accountId END,
|
CASE WHEN current_group.id IS NULL THEN eligible.accountId END,
|
||||||
CASE WHEN current_group.id IS NULL THEN eligible.inboundId END
|
CASE WHEN current_group.id IS NULL THEN eligible.inboundId END
|
||||||
HAVING (current_group.id IS NULL OR (
|
HAVING (current_group.id IS NULL OR (
|
||||||
MIN(current_group.group_status)='ACTIVE' AND MAX(current_group.locked_plan_id) IS NULL
|
COUNT(*)=(SELECT COUNT(*) FROM fl_coload_group_member group_member_count
|
||||||
AND COUNT(*)=(SELECT COUNT(*) FROM fl_coload_group_member group_member_count
|
|
||||||
WHERE group_member_count.group_id=current_group.id AND group_member_count.active_flag=1)
|
WHERE group_member_count.group_id=current_group.id AND group_member_count.active_flag=1)
|
||||||
AND SUM(CASE WHEN eligible.availablePackageCount=current_member.full_package_count THEN 1 ELSE 0 END)=COUNT(*)
|
|
||||||
))
|
))
|
||||||
<if test="query.customerId != null">
|
|
||||||
AND SUM(CASE WHEN eligible.accountId=#{query.customerId} THEN 1 ELSE 0 END)>0
|
|
||||||
</if>
|
|
||||||
<if test="query.inboundCode != null and query.inboundCode.trim() != ''">
|
|
||||||
AND SUM(CASE WHEN LOWER(eligible.inboundCode) LIKE CONCAT('%',LOWER(#{query.inboundCode}),'%') THEN 1 ELSE 0 END)>0
|
|
||||||
</if>
|
|
||||||
<if test="query.createdStart != null or query.createdEnd != null">
|
|
||||||
<if test="query.createdStart != null">AND DATE(MIN(eligible.createdAt))>=#{query.createdStart}</if>
|
|
||||||
<if test="query.createdEnd != null">AND DATE(MIN(eligible.createdAt))<=#{query.createdEnd}</if>
|
|
||||||
</if>
|
|
||||||
ORDER BY sortArrivalAt IS NULL,sortArrivalAt,sortInboundId
|
ORDER BY sortArrivalAt IS NULL,sortArrivalAt,sortInboundId
|
||||||
</script>
|
</script>
|
||||||
""")
|
""")
|
||||||
@@ -169,8 +169,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
WHERE member_count.group_id=current_group.id AND member_count.active_flag=1) memberCount,
|
WHERE member_count.group_id=current_group.id AND member_count.active_flag=1) memberCount,
|
||||||
(SELECT SUM(member_boxes.full_package_count) FROM fl_coload_group_member member_boxes
|
(SELECT SUM(member_boxes.full_package_count) FROM fl_coload_group_member member_boxes
|
||||||
WHERE member_boxes.group_id=current_group.id AND member_boxes.active_flag=1) groupTotalBoxes,
|
WHERE member_boxes.group_id=current_group.id AND member_boxes.active_flag=1) groupTotalBoxes,
|
||||||
CASE WHEN current_group.id IS NULL THEN NULL ELSE FALSE END locked,NULL blockReason,
|
CASE WHEN current_group.locked_plan_id IS NOT NULL OR EXISTS (
|
||||||
o.order_status_snapshot orderStatusSnapshot,o.expected_arrival_at expectedArrivalAt
|
SELECT 1 FROM fl_loading_plan_item occupied
|
||||||
|
WHERE occupied.account_id=o.tenant_id AND occupied.inbound_id=o.id AND occupied.deleted=0
|
||||||
|
) THEN TRUE ELSE FALSE END locked,NULL blockReason,
|
||||||
|
'WAIT_SPLIT' orderStatusSnapshot,o.expected_arrival_at expectedArrivalAt
|
||||||
FROM fl_inbound_order o
|
FROM fl_inbound_order o
|
||||||
JOIN fl_inbound_measurement m ON m.tenant_id=o.tenant_id AND m.inbound_id=o.id
|
JOIN fl_inbound_measurement m ON m.tenant_id=o.tenant_id AND m.inbound_id=o.id
|
||||||
AND m.status='APPROVED'
|
AND m.status='APPROVED'
|
||||||
@@ -182,7 +185,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
ON current_member.tenant_id=o.tenant_id AND current_member.inbound_id=o.id AND current_member.active_flag=1
|
ON current_member.tenant_id=o.tenant_id AND current_member.inbound_id=o.id AND current_member.active_flag=1
|
||||||
LEFT JOIN fl_coload_group current_group
|
LEFT JOIN fl_coload_group current_group
|
||||||
ON current_group.id=current_member.group_id AND current_group.group_status='ACTIVE'
|
ON current_group.id=current_member.group_id AND current_group.group_status='ACTIVE'
|
||||||
AND current_group.locked_plan_id IS NULL
|
|
||||||
WHERE o.process_status='WAIT_SPLIT'
|
WHERE o.process_status='WAIT_SPLIT'
|
||||||
AND o.actual_gross_weight_kg IS NOT NULL AND o.actual_net_weight_kg IS NOT NULL
|
AND o.actual_gross_weight_kg IS NOT NULL AND o.actual_net_weight_kg IS NOT NULL
|
||||||
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
||||||
@@ -196,7 +198,45 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
)
|
)
|
||||||
GROUP BY o.id,m.id,i.inbound_detail_id,current_group.id,current_group.group_no
|
GROUP BY o.id,m.id,i.inbound_detail_id,current_group.id,current_group.group_no
|
||||||
HAVING availablePackageCount>0
|
HAVING availablePackageCount>0
|
||||||
ORDER BY o.inbounded_at ASC,o.id ASC,i.inbound_detail_id ASC
|
UNION ALL
|
||||||
|
SELECT o.tenant_id accountId,o.id inboundId,NULL inboundDetailId,
|
||||||
|
o.order_id orderId,o.order_no orderNo,o.inbound_code inboundCode,
|
||||||
|
o.warehouse_id warehouseId,o.channel_code channelCode,o.channel_name channelName,
|
||||||
|
o.destination_warehouse_id destinationWarehouseId,o.destination_code destinationWarehouseCode,
|
||||||
|
o.destination_name destinationWarehouseName,o.customer_name customerName,NULL goodsName,
|
||||||
|
GREATEST(COALESCE(o.total_package_count,0)-COALESCE((SELECT SUM(used.package_count)
|
||||||
|
FROM fl_loading_plan_item used WHERE used.account_id=o.tenant_id
|
||||||
|
AND used.inbound_id=o.id AND used.deleted=0),0),0) availablePackageCount,
|
||||||
|
o.actual_gross_weight_kg/NULLIF(o.total_package_count,0) unitGrossWeightKg,
|
||||||
|
o.actual_net_weight_kg/NULLIF(o.total_package_count,0) unitNetWeightKg,
|
||||||
|
o.actual_volume_cbm/NULLIF(o.total_package_count,0) unitVolumeCbm,
|
||||||
|
current_group.id coLoadGroupId,current_group.group_no groupNo,
|
||||||
|
(SELECT COUNT(*) FROM fl_coload_group_member member_count
|
||||||
|
WHERE member_count.group_id=current_group.id AND member_count.active_flag=1) memberCount,
|
||||||
|
(SELECT SUM(member_boxes.full_package_count) FROM fl_coload_group_member member_boxes
|
||||||
|
WHERE member_boxes.group_id=current_group.id AND member_boxes.active_flag=1) groupTotalBoxes,
|
||||||
|
CASE WHEN current_group.locked_plan_id IS NOT NULL OR EXISTS (
|
||||||
|
SELECT 1 FROM fl_loading_plan_item occupied
|
||||||
|
WHERE occupied.account_id=o.tenant_id AND occupied.inbound_id=o.id AND occupied.deleted=0
|
||||||
|
) THEN TRUE ELSE FALSE END locked,NULL blockReason,
|
||||||
|
CASE WHEN o.process_status='LOADING' THEN 'LOADING' ELSE 'WAIT_MEASURE' END orderStatusSnapshot,
|
||||||
|
o.expected_arrival_at expectedArrivalAt
|
||||||
|
FROM fl_inbound_order o
|
||||||
|
LEFT JOIN fl_coload_group_member current_member
|
||||||
|
ON current_member.tenant_id=o.tenant_id AND current_member.inbound_id=o.id AND current_member.active_flag=1
|
||||||
|
LEFT JOIN fl_coload_group current_group
|
||||||
|
ON current_group.id=current_member.group_id AND current_group.group_status IN ('ACTIVE','LOCKED')
|
||||||
|
WHERE o.process_status IN ('WAIT_MEASURE','MEASURING','MEASURE_PENDING','MEASURE_REJECTED',
|
||||||
|
'WAIT_PRICING','FEE_CONFIRMING','INBOUNDING','LOADING')
|
||||||
|
AND (
|
||||||
|
<foreach collection="units" item="unit" separator=" OR ">
|
||||||
|
<choose>
|
||||||
|
<when test="unit.groupId != null">current_group.id=#{unit.groupId}</when>
|
||||||
|
<otherwise>(current_group.id IS NULL AND o.tenant_id=#{unit.accountId} AND o.id=#{unit.inboundId})</otherwise>
|
||||||
|
</choose>
|
||||||
|
</foreach>
|
||||||
|
)
|
||||||
|
ORDER BY inboundId ASC,inboundDetailId ASC
|
||||||
</script>
|
</script>
|
||||||
""")
|
""")
|
||||||
List<Candidate> globalCandidatesByUnits(@Param("units") List<CandidateUnit> units);
|
List<Candidate> globalCandidatesByUnits(@Param("units") List<CandidateUnit> units);
|
||||||
@@ -216,7 +256,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
AND b.channel_code IN
|
AND b.channel_code IN
|
||||||
<foreach collection="actualChannels" item="channel" open="(" separator="," close=")">#{channel}</foreach>
|
<foreach collection="actualChannels" item="channel" open="(" separator="," close=")">#{channel}</foreach>
|
||||||
<if test="query.warehouseId != null">AND b.warehouse_id=#{query.warehouseId}</if>
|
<if test="query.warehouseId != null">AND b.warehouse_id=#{query.warehouseId}</if>
|
||||||
<if test="query.customerId != null or query.inboundCode != null or query.createdStart != null or query.createdEnd != null or query.orderStatus != null">
|
<if test="query.customerId != null or query.inboundCode != null or query.createdStart != null or query.createdEnd != null">
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1 FROM fl_loading_plan_item filter_item
|
SELECT 1 FROM fl_loading_plan_item filter_item
|
||||||
JOIN fl_inbound_order filter_inbound ON filter_inbound.tenant_id=filter_item.account_id
|
JOIN fl_inbound_order filter_inbound ON filter_inbound.tenant_id=filter_item.account_id
|
||||||
@@ -226,14 +266,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
<if test="query.inboundCode != null and query.inboundCode != ''">AND filter_item.inbound_code LIKE CONCAT('%',#{query.inboundCode},'%')</if>
|
<if test="query.inboundCode != null and query.inboundCode != ''">AND filter_item.inbound_code LIKE CONCAT('%',#{query.inboundCode},'%')</if>
|
||||||
<if test="query.createdStart != null">AND filter_inbound.created_at >= #{query.createdStart}</if>
|
<if test="query.createdStart != null">AND filter_inbound.created_at >= #{query.createdStart}</if>
|
||||||
<if test="query.createdEnd != null">AND filter_inbound.created_at < DATE_ADD(#{query.createdEnd},INTERVAL 1 DAY)</if>
|
<if test="query.createdEnd != null">AND filter_inbound.created_at < DATE_ADD(#{query.createdEnd},INTERVAL 1 DAY)</if>
|
||||||
<if test="orderStatus != null">
|
|
||||||
<choose>
|
|
||||||
<when test="orderStatus == 'PENDING_REVIEW'">AND filter_inbound.order_status_snapshot IN ('SUBMITTED','PENDING_REVIEW')</when>
|
|
||||||
<when test="orderStatus == 'WAIT_MEASURE'">AND filter_inbound.process_status IN ('WAIT_MEASURE','MEASURING','MEASURE_PENDING','MEASURE_REJECTED','WAIT_PRICING','FEE_CONFIRMING','INBOUNDING')</when>
|
|
||||||
<when test="orderStatus == 'WAIT_SPLIT'">AND filter_inbound.process_status='WAIT_SPLIT'</when>
|
|
||||||
<when test="orderStatus == 'LOADING'">AND filter_inbound.process_status='LOADING'</when>
|
|
||||||
</choose>
|
|
||||||
</if>
|
|
||||||
)
|
)
|
||||||
</if>
|
</if>
|
||||||
ORDER BY b.updated_at DESC,b.id DESC
|
ORDER BY b.updated_at DESC,b.id DESC
|
||||||
@@ -241,8 +273,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
|||||||
""")
|
""")
|
||||||
Page<WorkbenchContainer> workbenchContainers(Page<WorkbenchContainer> page,
|
Page<WorkbenchContainer> workbenchContainers(Page<WorkbenchContainer> page,
|
||||||
@Param("query") WorkbenchContainerQuery query,
|
@Param("query") WorkbenchContainerQuery query,
|
||||||
@Param("actualChannels") Set<String> actualChannels,
|
@Param("actualChannels") Set<String> actualChannels);
|
||||||
@Param("orderStatus") String orderStatus);
|
|
||||||
|
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT o.tenant_id accountId,o.id inboundId,i.inbound_detail_id inboundDetailId,
|
SELECT o.tenant_id accountId,o.id inboundId,i.inbound_detail_id inboundDetailId,
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ public class SmartLoadingRepository {
|
|||||||
public PageResult<WorkbenchContainer> workbenchContainers(WorkbenchContainerQuery query,
|
public PageResult<WorkbenchContainer> workbenchContainers(WorkbenchContainerQuery query,
|
||||||
Set<String> actualChannels) {
|
Set<String> actualChannels) {
|
||||||
return PageResult.from(candidates.workbenchContainers(
|
return PageResult.from(candidates.workbenchContainers(
|
||||||
new Page<>(query.page(), query.size()), query, actualChannels,
|
new Page<>(query.page(), query.size()), query, actualChannels));
|
||||||
query.orderStatus() == null ? null : query.orderStatus().name()));
|
|
||||||
}
|
}
|
||||||
public List<ChannelContainerCountRow> channelContainerCounts(long warehouseId) {
|
public List<ChannelContainerCountRow> channelContainerCounts(long warehouseId) {
|
||||||
return candidates.channelContainerCounts(warehouseId);
|
return candidates.channelContainerCounts(warehouseId);
|
||||||
@@ -68,6 +67,9 @@ public class SmartLoadingRepository {
|
|||||||
.eq(InboundOrderEntity::getTenantId, accountId)
|
.eq(InboundOrderEntity::getTenantId, accountId)
|
||||||
.eq(InboundOrderEntity::getId, inboundId));
|
.eq(InboundOrderEntity::getId, inboundId));
|
||||||
}
|
}
|
||||||
|
public InboundOrderEntity inbound(long inboundId) {
|
||||||
|
return inbounds.selectById(inboundId);
|
||||||
|
}
|
||||||
public InboundOrderEntity inboundForUpdate(long accountId, long inboundId) {
|
public InboundOrderEntity inboundForUpdate(long accountId, long inboundId) {
|
||||||
return inbounds.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
|
return inbounds.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
|
||||||
.eq(InboundOrderEntity::getTenantId, accountId)
|
.eq(InboundOrderEntity::getTenantId, accountId)
|
||||||
|
|||||||
@@ -121,12 +121,34 @@ public class SmartLoadingService {
|
|||||||
throw validation("创建开始日期不能晚于结束日期");
|
throw validation("创建开始日期不能晚于结束日期");
|
||||||
}
|
}
|
||||||
Set<String> actualChannels = actualChannels(query.channelGroupCodes());
|
Set<String> actualChannels = actualChannels(query.channelGroupCodes());
|
||||||
if (query.orderStatus() == CandidateOrderStatus.PENDING_REVIEW) {
|
EnumSet<CandidateOrderStatus> selected = query.orderStatuses().isEmpty()
|
||||||
return pendingReviewCandidates(query, actualChannels);
|
? EnumSet.allOf(CandidateOrderStatus.class)
|
||||||
|
: EnumSet.copyOf(query.orderStatuses());
|
||||||
|
boolean includePending = selected.remove(CandidateOrderStatus.PENDING_REVIEW);
|
||||||
|
InboundCandidateQuery firstlegQuery = withStatuses(query, List.copyOf(selected), 1, 1);
|
||||||
|
if (!includePending) return firstlegCandidates(withStatuses(query, List.copyOf(selected),
|
||||||
|
query.page(), query.size()), actualChannels);
|
||||||
|
if (selected.isEmpty()) return pendingReviewCandidates(query, actualChannels, query.page(), query.size());
|
||||||
|
|
||||||
|
PageResult<InboundCandidate> pendingCount = pendingReviewCandidates(query, actualChannels, 1, 1);
|
||||||
|
PageResult<InboundCandidate> firstlegCount = firstlegCandidates(firstlegQuery, actualChannels);
|
||||||
|
long total = pendingCount.total() + firstlegCount.total();
|
||||||
|
long offset = Math.multiplyExact(query.page() - 1, query.size());
|
||||||
|
List<InboundCandidate> records = new ArrayList<>();
|
||||||
|
if (offset < pendingCount.total()) {
|
||||||
|
records.addAll(pendingReviewRange(query, actualChannels, offset, query.size()));
|
||||||
}
|
}
|
||||||
if (query.orderStatus() != null && query.orderStatus() != CandidateOrderStatus.WAIT_SPLIT) {
|
long firstlegOffset = Math.max(0, offset - pendingCount.total());
|
||||||
return new PageResult<>(List.of(), query.page(), query.size(), 0, 0);
|
int remaining = Math.toIntExact(query.size() - records.size());
|
||||||
|
if (remaining > 0 && firstlegOffset < firstlegCount.total()) {
|
||||||
|
records.addAll(firstlegRange(query, actualChannels, List.copyOf(selected), firstlegOffset, remaining));
|
||||||
}
|
}
|
||||||
|
long pages = total == 0 ? 0 : (total + query.size() - 1) / query.size();
|
||||||
|
return new PageResult<>(records, query.page(), query.size(), total, pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PageResult<InboundCandidate> firstlegCandidates(InboundCandidateQuery query,
|
||||||
|
Set<String> actualChannels) {
|
||||||
PageResult<CandidateUnit> unitPage = repository.globalCandidateUnits(query, actualChannels);
|
PageResult<CandidateUnit> unitPage = repository.globalCandidateUnits(query, actualChannels);
|
||||||
if (unitPage.records().isEmpty()) {
|
if (unitPage.records().isEmpty()) {
|
||||||
return new PageResult<>(List.of(), unitPage.page(), unitPage.size(), unitPage.total(), unitPage.pages());
|
return new PageResult<>(List.of(), unitPage.page(), unitPage.size(), unitPage.total(), unitPage.pages());
|
||||||
@@ -156,12 +178,13 @@ public class SmartLoadingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PageResult<InboundCandidate> pendingReviewCandidates(InboundCandidateQuery query,
|
private PageResult<InboundCandidate> pendingReviewCandidates(InboundCandidateQuery query,
|
||||||
Set<String> actualChannels) {
|
Set<String> actualChannels,
|
||||||
|
long page, long size) {
|
||||||
ApiResponse<PageResult<PendingReviewOrderClient.Row>> response;
|
ApiResponse<PageResult<PendingReviewOrderClient.Row>> response;
|
||||||
try {
|
try {
|
||||||
response = pendingReviewOrders.page(internalToken, List.copyOf(actualChannels), query.warehouseId(),
|
response = pendingReviewOrders.page(internalToken, List.copyOf(actualChannels), query.warehouseId(),
|
||||||
query.customerId(), query.inboundCode(), query.createdStart(), query.createdEnd(),
|
query.customerId(), query.inboundCode(), query.createdStart(), query.createdEnd(),
|
||||||
query.page(), query.size());
|
page, size);
|
||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "待审核订单服务暂时不可用");
|
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "待审核订单服务暂时不可用");
|
||||||
}
|
}
|
||||||
@@ -173,11 +196,49 @@ public class SmartLoadingService {
|
|||||||
new InboundCandidate(row.customerId(), null, row.orderId(), row.orderNo(), row.inboundCode(),
|
new InboundCandidate(row.customerId(), null, row.orderId(), row.orderNo(), row.inboundCode(),
|
||||||
row.warehouseId(), row.channelCode(), row.channelName(), null, null, null,
|
row.warehouseId(), row.channelCode(), row.channelName(), null, null, null,
|
||||||
row.customerName(), null, row.totalPackageCount(), null, null, null, null,
|
row.customerName(), null, row.totalPackageCount(), null, null, null, null,
|
||||||
null, null, null, null, false, null,
|
null, null, null, null, false, true, null,
|
||||||
"PENDING_REVIEW", null, List.of())).toList();
|
"PENDING_REVIEW", null, List.of())).toList();
|
||||||
return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages());
|
return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<InboundCandidate> pendingReviewRange(InboundCandidateQuery query, Set<String> channels,
|
||||||
|
long offset, long limit) {
|
||||||
|
return pageRange(offset, limit, (page, size) -> pendingReviewCandidates(query, channels, page, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<InboundCandidate> firstlegRange(InboundCandidateQuery query, Set<String> channels,
|
||||||
|
List<CandidateOrderStatus> statuses,
|
||||||
|
long offset, long limit) {
|
||||||
|
return pageRange(offset, limit, (page, size) -> firstlegCandidates(
|
||||||
|
withStatuses(query, statuses, page, size), channels));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InboundCandidate> pageRange(long offset, long limit,
|
||||||
|
java.util.function.BiFunction<Long, Long, PageResult<InboundCandidate>> source) {
|
||||||
|
if (limit <= 0) return List.of();
|
||||||
|
final long chunk = 200;
|
||||||
|
long page = offset / chunk + 1;
|
||||||
|
int skip = Math.toIntExact(offset % chunk);
|
||||||
|
List<InboundCandidate> result = new ArrayList<>();
|
||||||
|
while (result.size() < limit) {
|
||||||
|
List<InboundCandidate> rows = source.apply(page++, chunk).records();
|
||||||
|
if (rows.isEmpty()) break;
|
||||||
|
int from = Math.min(skip, rows.size());
|
||||||
|
int take = Math.min(Math.toIntExact(limit - result.size()), rows.size() - from);
|
||||||
|
if (take > 0) result.addAll(rows.subList(from, from + take));
|
||||||
|
skip = 0;
|
||||||
|
if (rows.size() < chunk) break;
|
||||||
|
}
|
||||||
|
return List.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InboundCandidateQuery withStatuses(InboundCandidateQuery source,
|
||||||
|
List<CandidateOrderStatus> statuses,
|
||||||
|
long page, long size) {
|
||||||
|
return new InboundCandidateQuery(source.channelGroupCodes(), source.warehouseId(), source.customerId(),
|
||||||
|
source.inboundCode(), source.createdStart(), source.createdEnd(), statuses, page, size);
|
||||||
|
}
|
||||||
|
|
||||||
public PageResult<WorkbenchContainer> containers(WorkbenchContainerQuery query) {
|
public PageResult<WorkbenchContainer> containers(WorkbenchContainerQuery query) {
|
||||||
if (query.createdStart() != null && query.createdEnd() != null
|
if (query.createdStart() != null && query.createdEnd() != null
|
||||||
&& query.createdStart().isAfter(query.createdEnd())) {
|
&& query.createdStart().isAfter(query.createdEnd())) {
|
||||||
@@ -190,11 +251,12 @@ public class SmartLoadingService {
|
|||||||
Candidate first = details.get(0);
|
Candidate first = details.get(0);
|
||||||
int packages = details.stream().map(Candidate::availablePackageCount)
|
int packages = details.stream().map(Candidate::availablePackageCount)
|
||||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||||
BigDecimal volume = details.stream()
|
boolean hasVolume = details.stream().anyMatch(detail -> detail.unitVolumeCbm() != null);
|
||||||
.map(detail -> unit(detail.unitVolumeCbm()).multiply(BigDecimal.valueOf(
|
BigDecimal volume = hasVolume ? details.stream().filter(detail -> detail.unitVolumeCbm() != null)
|
||||||
|
.map(detail -> detail.unitVolumeCbm().multiply(BigDecimal.valueOf(
|
||||||
Math.max(0, detail.availablePackageCount() == null ? 0 : detail.availablePackageCount()))))
|
Math.max(0, detail.availablePackageCount() == null ? 0 : detail.availablePackageCount()))))
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(4, RoundingMode.HALF_UP);
|
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(4, RoundingMode.HALF_UP) : null;
|
||||||
BigDecimal averageVolume = packages == 0 ? BigDecimal.ZERO : volume.divide(
|
BigDecimal averageVolume = packages == 0 || volume == null ? null : volume.divide(
|
||||||
BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
||||||
String goods = details.stream().map(Candidate::goodsName).filter(StringUtils::hasText)
|
String goods = details.stream().map(Candidate::goodsName).filter(StringUtils::hasText)
|
||||||
.map(String::trim).distinct().collect(java.util.stream.Collectors.joining("、"));
|
.map(String::trim).distinct().collect(java.util.stream.Collectors.joining("、"));
|
||||||
@@ -203,7 +265,7 @@ public class SmartLoadingService {
|
|||||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||||
first.customerName(), goods, packages, first.unitGrossWeightKg(), first.unitNetWeightKg(),
|
first.customerName(), goods, packages, first.unitGrossWeightKg(), first.unitNetWeightKg(),
|
||||||
averageVolume, volume, first.coLoadGroupId(), first.groupNo(), first.memberCount(),
|
averageVolume, volume, first.coLoadGroupId(), first.groupNo(), first.memberCount(),
|
||||||
first.groupTotalBoxes(), first.locked(), first.blockReason(), first.orderStatusSnapshot(),
|
first.groupTotalBoxes(), first.locked(), canAssign(first), capabilityBlock(first), first.orderStatusSnapshot(),
|
||||||
first.expectedArrivalAt(), List.of());
|
first.expectedArrivalAt(), List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,11 +279,12 @@ public class SmartLoadingService {
|
|||||||
InboundCandidate first = ordered.get(0);
|
InboundCandidate first = ordered.get(0);
|
||||||
int packages = ordered.stream().map(InboundCandidate::availablePackageCount).mapToInt(Integer::intValue).sum();
|
int packages = ordered.stream().map(InboundCandidate::availablePackageCount).mapToInt(Integer::intValue).sum();
|
||||||
if (packages != metadata.groupTotalBoxes()) return null;
|
if (packages != metadata.groupTotalBoxes()) return null;
|
||||||
BigDecimal volume = ordered.stream().map(InboundCandidate::availableVolumeCbm)
|
boolean hasVolume = ordered.stream().anyMatch(candidate -> candidate.availableVolumeCbm() != null);
|
||||||
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(4, RoundingMode.HALF_UP);
|
BigDecimal volume = hasVolume ? ordered.stream().map(InboundCandidate::availableVolumeCbm)
|
||||||
|
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(4, RoundingMode.HALF_UP) : null;
|
||||||
BigDecimal gross = weightedAverage(ordered, InboundCandidate::unitGrossWeightKg, packages);
|
BigDecimal gross = weightedAverage(ordered, InboundCandidate::unitGrossWeightKg, packages);
|
||||||
BigDecimal net = weightedAverage(ordered, InboundCandidate::unitNetWeightKg, packages);
|
BigDecimal net = weightedAverage(ordered, InboundCandidate::unitNetWeightKg, packages);
|
||||||
BigDecimal averageVolume = packages == 0 ? BigDecimal.ZERO
|
BigDecimal averageVolume = packages == 0 || volume == null ? null
|
||||||
: volume.divide(BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
: volume.divide(BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
||||||
List<GroupMemberCandidate> groupMembers = ordered.stream().map(member -> new GroupMemberCandidate(
|
List<GroupMemberCandidate> groupMembers = ordered.stream().map(member -> new GroupMemberCandidate(
|
||||||
member.accountId(), member.inboundId(), member.orderId(), member.orderNo(), member.inboundCode(),
|
member.accountId(), member.inboundId(), member.orderId(), member.orderNo(), member.inboundCode(),
|
||||||
@@ -235,11 +298,42 @@ public class SmartLoadingService {
|
|||||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||||
customers, goods, packages, gross, net, averageVolume, volume, metadata.coLoadGroupId(), metadata.groupNo(),
|
customers, goods, packages, gross, net, averageVolume, volume, metadata.coLoadGroupId(), metadata.groupNo(),
|
||||||
metadata.memberCount(), metadata.groupTotalBoxes(), false, null, first.orderStatusSnapshot(),
|
metadata.memberCount(), metadata.groupTotalBoxes(),
|
||||||
|
ordered.stream().anyMatch(candidate -> Boolean.TRUE.equals(candidate.locked())),
|
||||||
|
ordered.stream().allMatch(candidate -> Boolean.TRUE.equals(candidate.canAssign())),
|
||||||
|
ordered.stream().map(InboundCandidate::blockReason).filter(StringUtils::hasText).findFirst().orElse(null),
|
||||||
|
groupStatus(ordered),
|
||||||
ordered.stream().map(InboundCandidate::expectedArrivalAt).filter(Objects::nonNull).min(LocalDateTime::compareTo).orElse(null),
|
ordered.stream().map(InboundCandidate::expectedArrivalAt).filter(Objects::nonNull).min(LocalDateTime::compareTo).orElse(null),
|
||||||
groupMembers);
|
groupMembers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean canAssign(Candidate candidate) {
|
||||||
|
return !Boolean.TRUE.equals(candidate.locked()) && !StringUtils.hasText(candidate.blockReason())
|
||||||
|
&& candidate.availablePackageCount() != null && candidate.availablePackageCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String capabilityBlock(Candidate candidate) {
|
||||||
|
if (StringUtils.hasText(candidate.blockReason())) return candidate.blockReason();
|
||||||
|
if (Boolean.TRUE.equals(candidate.locked())) return "该入仓码已被有效柜次占用";
|
||||||
|
if (candidate.availablePackageCount() == null || candidate.availablePackageCount() <= 0) return "没有可加入的剩余箱数";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String groupStatus(List<InboundCandidate> members) {
|
||||||
|
return members.stream().map(InboundCandidate::orderStatusSnapshot).filter(StringUtils::hasText)
|
||||||
|
.min(Comparator.comparingInt(SmartLoadingService::statusOrder)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int statusOrder(String status) {
|
||||||
|
return switch (status) {
|
||||||
|
case "PENDING_REVIEW" -> 0;
|
||||||
|
case "WAIT_MEASURE" -> 1;
|
||||||
|
case "WAIT_SPLIT" -> 2;
|
||||||
|
case "LOADING" -> 3;
|
||||||
|
default -> 4;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static String unitKey(Long groupId, Long accountId, Long inboundId) {
|
private static String unitKey(Long groupId, Long accountId, Long inboundId) {
|
||||||
return groupId == null ? "I|" + accountId + '|' + inboundId : "G|" + groupId;
|
return groupId == null ? "I|" + accountId + '|' + inboundId : "G|" + groupId;
|
||||||
}
|
}
|
||||||
@@ -247,8 +341,8 @@ public class SmartLoadingService {
|
|||||||
private static BigDecimal weightedAverage(List<InboundCandidate> members,
|
private static BigDecimal weightedAverage(List<InboundCandidate> members,
|
||||||
java.util.function.Function<InboundCandidate, BigDecimal> getter,
|
java.util.function.Function<InboundCandidate, BigDecimal> getter,
|
||||||
int packages) {
|
int packages) {
|
||||||
if (packages == 0) return BigDecimal.ZERO;
|
if (packages == 0 || members.stream().map(getter).anyMatch(Objects::isNull)) return null;
|
||||||
return members.stream().map(candidate -> unit(getter.apply(candidate)).multiply(
|
return members.stream().map(candidate -> getter.apply(candidate).multiply(
|
||||||
BigDecimal.valueOf(candidate.availablePackageCount())))
|
BigDecimal.valueOf(candidate.availablePackageCount())))
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||||
.divide(BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
.divide(BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
||||||
@@ -466,6 +560,13 @@ public class SmartLoadingService {
|
|||||||
Plan replay = replay(operation, planId, "ADD_INBOUND", null, requestHash);
|
Plan replay = replay(operation, planId, "ADD_INBOUND", null, requestHash);
|
||||||
if (replay != null) return replay;
|
if (replay != null) return replay;
|
||||||
|
|
||||||
|
InboundOrderEntity projection = repository.inbound(request.inboundId());
|
||||||
|
if (!wholeGroupAssignment && projection != null && Set.of("PREPARING", "WAIT_MEASURE",
|
||||||
|
"MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED", "WAIT_PRICING",
|
||||||
|
"FEE_CONFIRMING", "INBOUNDING").contains(projection.getProcessStatus())) {
|
||||||
|
return assignDeclaredInbound(planId, request, projection, operation, requestHash,
|
||||||
|
operatorId, operatorName);
|
||||||
|
}
|
||||||
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||||
List<Candidate> details = candidates(plan.getContainerBookingId(), booking.getWarehouseId()).stream()
|
List<Candidate> details = candidates(plan.getContainerBookingId(), booking.getWarehouseId()).stream()
|
||||||
@@ -497,6 +598,66 @@ public class SmartLoadingService {
|
|||||||
return view(assigned, booking);
|
return view(assigned, booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Plan assignDeclaredInbound(long planId, AssignInboundRequest request,
|
||||||
|
InboundOrderEntity projection, String operation,
|
||||||
|
String requestHash, long operatorId, String operatorName) {
|
||||||
|
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||||
|
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||||
|
InboundOrderEntity locked = repository.inboundForUpdate(projection.getTenantId(), projection.getId());
|
||||||
|
if (locked == null || !Set.of("PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING",
|
||||||
|
"MEASURE_REJECTED", "WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING")
|
||||||
|
.contains(locked.getProcessStatus())) {
|
||||||
|
throw conflict("入仓码状态已变化,请刷新后重试");
|
||||||
|
}
|
||||||
|
if (!repository.activeItemsByInbound(locked.getTenantId(), locked.getId()).isEmpty()) {
|
||||||
|
throw conflict("该入仓码已被有效柜次占用");
|
||||||
|
}
|
||||||
|
var order = loadInboundSnapshot(locked.getTenantId(), locked.getOrderId());
|
||||||
|
if (!Objects.equals(order.inboundCode(), locked.getInboundCode())
|
||||||
|
|| !Objects.equals(order.warehouseId(), locked.getWarehouseId())
|
||||||
|
|| !Objects.equals(order.channelCode(), locked.getChannelCode())) {
|
||||||
|
throw conflict("订单履约快照已变化,请刷新后重试");
|
||||||
|
}
|
||||||
|
List<DeclaredCargo> declared = loadDeclaredCargo(locked.getTenantId(), locked.getOrderId());
|
||||||
|
List<Candidate> candidates = declared.stream().map(value -> declaredCandidate(
|
||||||
|
order, locked.getId(), value.cargo(), "WAIT_MEASURE")).toList();
|
||||||
|
List<InboundAllocation> allocations = proportionalAllocations(candidates, request.packageCount());
|
||||||
|
List<LoadingPlanItemEntity> current = new ArrayList<>(repository.items(planId));
|
||||||
|
List<LoadingPlanItemEntity> created = new ArrayList<>();
|
||||||
|
int sequence = current.size();
|
||||||
|
for (InboundAllocation allocation : allocations) {
|
||||||
|
if (allocation.packageCount() == 0) continue;
|
||||||
|
Candidate candidate = allocation.candidate();
|
||||||
|
requireSameChannel(booking, candidate);
|
||||||
|
requireSameCustomerScope(booking, candidate);
|
||||||
|
checkRouteAndCapacity(booking, current, candidate, allocation.packageCount(), true);
|
||||||
|
OrderCargoClient.CargoBinding cargo = declared.stream().map(DeclaredCargo::cargo)
|
||||||
|
.filter(value -> Objects.equals(value.cargoId(), candidate.inboundDetailId()))
|
||||||
|
.findFirst().orElseThrow(() -> conflict("订单货物明细已变化,请刷新后重试"));
|
||||||
|
LoadingPlanItemEntity item = snapshot(planId, candidate, cargo, allocation.packageCount(),
|
||||||
|
operatorId, operatorName, ++sequence);
|
||||||
|
current.add(item);
|
||||||
|
created.add(item);
|
||||||
|
}
|
||||||
|
if (created.isEmpty()) throw validation("入仓码没有可加入装柜清单的箱数");
|
||||||
|
splitPreloadService.ensurePendingForPreAllocation(locked.getTenantId(), locked.getId(),
|
||||||
|
operatorId, operatorName);
|
||||||
|
touch(plan, operatorId, operatorName);
|
||||||
|
try {
|
||||||
|
created.forEach(repository::insert);
|
||||||
|
} catch (DuplicateKeyException exception) {
|
||||||
|
throw conflict("该入仓码已被并发加入其他柜次");
|
||||||
|
}
|
||||||
|
history(planId, null, "ADD_INBOUND", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
||||||
|
json(values("version", request.expectedVersion(), "inboundId", request.inboundId())),
|
||||||
|
json(values("requestHash", requestHash, "packageCount", request.packageCount(),
|
||||||
|
"specificationLineCount", created.size(), "provisional", true)), operatorId, operatorName);
|
||||||
|
loadingModelBridge.bindIncrementalAllocation(planId, locked.getTenantId(), locked.getId(),
|
||||||
|
operatorId, operatorName, operation);
|
||||||
|
created.forEach(item -> loadingPickService.sync(planId, item));
|
||||||
|
return view(repository.plan(planId), booking);
|
||||||
|
}
|
||||||
|
|
||||||
List<InboundAllocation> proportionalAllocations(List<Candidate> details, int selected) {
|
List<InboundAllocation> proportionalAllocations(List<Candidate> details, int selected) {
|
||||||
int available = details.stream().map(Candidate::availablePackageCount).filter(Objects::nonNull)
|
int available = details.stream().map(Candidate::availablePackageCount).filter(Objects::nonNull)
|
||||||
.mapToInt(Integer::intValue).sum();
|
.mapToInt(Integer::intValue).sum();
|
||||||
@@ -1075,6 +1236,12 @@ public class SmartLoadingService {
|
|||||||
private Candidate declaredCandidate(PendingReviewOrderClient.Row pending,
|
private Candidate declaredCandidate(PendingReviewOrderClient.Row pending,
|
||||||
com.baiyue.firstleg.dto.OrderApprovedInboundCommand snapshot,
|
com.baiyue.firstleg.dto.OrderApprovedInboundCommand snapshot,
|
||||||
long inboundId, OrderCargoClient.CargoBinding cargo) {
|
long inboundId, OrderCargoClient.CargoBinding cargo) {
|
||||||
|
return declaredCandidate(snapshot, inboundId, cargo, pending.mainStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Candidate declaredCandidate(com.baiyue.firstleg.dto.OrderApprovedInboundCommand snapshot,
|
||||||
|
long inboundId, OrderCargoClient.CargoBinding cargo,
|
||||||
|
String candidateStatus) {
|
||||||
int boxes = cargo.originalPackageCount() == null ? 0 : cargo.originalPackageCount();
|
int boxes = cargo.originalPackageCount() == null ? 0 : cargo.originalPackageCount();
|
||||||
if (boxes <= 0) throw conflict("订单货物箱数不完整,请先修正订单");
|
if (boxes <= 0) throw conflict("订单货物箱数不完整,请先修正订单");
|
||||||
BigDecimal divisor = BigDecimal.valueOf(boxes);
|
BigDecimal divisor = BigDecimal.valueOf(boxes);
|
||||||
@@ -1086,7 +1253,7 @@ public class SmartLoadingService {
|
|||||||
snapshot.channelName(), snapshot.destinationWarehouseId(), snapshot.destinationCode(),
|
snapshot.channelName(), snapshot.destinationWarehouseId(), snapshot.destinationCode(),
|
||||||
snapshot.destinationName(), snapshot.customerName(), cargo.cargoName(), boxes,
|
snapshot.destinationName(), snapshot.customerName(), cargo.cargoName(), boxes,
|
||||||
unitGross, unitNet, unitVolume, null, null, null, null, false, null,
|
unitGross, unitNet, unitVolume, null, null, null, null, false, null,
|
||||||
pending.mainStatus(), snapshot.expectedArrivalAt());
|
candidateStatus, snapshot.expectedArrivalAt());
|
||||||
}
|
}
|
||||||
|
|
||||||
private record DeclaredCargo(OrderCargoClient.CargoBinding cargo) {}
|
private record DeclaredCargo(OrderCargoClient.CargoBinding cargo) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user