This commit is contained in:
wujinliang
2026-08-27 21:48:47 +08:00
parent daf0ee2acd
commit 9d54c57b7c
38 changed files with 432 additions and 37 deletions
@@ -1,5 +1,6 @@
package com.baiyue.firstleg.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -69,6 +70,8 @@ public class ContainerBookingEntity {
@TableField(exist = false) private Long planId;
@TableField(exist = false) private String planNo;
@TableField(exist = false) private String planStatus;
/** Actual completion timestamp projected from the associated loading plan. */
@JsonIgnore @TableField(exist = false) private LocalDateTime completedLoadingAt;
@TableField(exist = false) private Boolean exceptionActive;
@TableField(exist = false) private String destinationClearanceDocumentStatus;
@TableField(exist = false) private String customsDeclarationDocumentStatus;
@@ -42,7 +42,7 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
@Update("UPDATE fl_preload_plan SET status='CONFIRMED',confirmed_by=#{operatorId},"
+ "confirmed_by_name=#{operatorName},confirmed_at=NOW(),updated_by=#{operatorId},"
+ "updated_by_name=#{operatorName},version=version+1 WHERE id=#{preloadPlanId} "
+ "AND tenant_id=#{tenantId} AND inbound_id=#{inboundId} AND status='PENDING' "
+ "AND tenant_id=#{tenantId} AND inbound_id=#{inboundId} AND status IN ('PENDING','CONFIRMING') "
+ "AND version=#{expectedVersion}")
int confirmIncrementalPreload(@Param("preloadPlanId") long preloadPlanId,
@Param("tenantId") long tenantId,
@@ -125,7 +125,9 @@ public class LoadingModelBridgeService {
}
var preload = repository.preload(tenantId, inboundId);
if (preload == null) throw new BizException(ResultCode.NOT_FOUND, "入库单预装计划不存在");
if (!Set.of("PENDING", "CONFIRMED").contains(preload.status())) {
boolean running = "LOADING".equals(plan.status());
if (!Set.of("PENDING", "CONFIRMED").contains(preload.status())
&& !(running && "CONFIRMING".equals(preload.status()))) {
throw conflict("新增入库单预装状态不允许进入当前柜次");
}
PreloadLoadingPlanLinkEntity link = new PreloadLoadingPlanLinkEntity();
@@ -145,7 +147,7 @@ public class LoadingModelBridgeService {
// A running PDA task has already passed plan-level confirmation. Adding an inbound is the
// explicit Admin approval for that new allocation, so its fresh PENDING preload joins the
// same confirmed plan atomically instead of leaving an unreachable pending aggregate.
if ("LOADING".equals(plan.status()) && "PENDING".equals(preload.status())
if (running && Set.of("PENDING", "CONFIRMING").contains(preload.status())
&& repository.confirmIncrementalPreload(preload.id(), tenantId, inboundId,
preload.version(), operatorId, operatorName) != 1) {
throw conflict("新增入库单预装确认并发冲突,请刷新后重试");
@@ -16,6 +16,7 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
@Select("""
<script>
SELECT b.*, p.id AS plan_id, p.plan_no AS plan_no,
CASE WHEN p.plan_status = 'COMPLETED' THEN COALESCE(p.confirmed_at, p.updated_at) END AS completed_loading_at,
CASE WHEN (SELECT eh.action FROM fl_container_exception_history eh
WHERE eh.container_booking_id=b.id ORDER BY eh.occurred_at DESC,eh.id DESC LIMIT 1)='MARK'
THEN TRUE ELSE FALSE END AS exception_active,
@@ -77,6 +78,7 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
@Select("""
SELECT b.*, p.id AS plan_id, p.plan_no AS plan_no,
CASE WHEN p.plan_status = 'COMPLETED' THEN COALESCE(p.confirmed_at, p.updated_at) END AS completed_loading_at,
CASE WHEN (SELECT eh.action FROM fl_container_exception_history eh
WHERE eh.container_booking_id=b.id ORDER BY eh.occurred_at DESC,eh.id DESC LIMIT 1)='MARK'
THEN TRUE ELSE FALSE END AS exception_active,
@@ -13,9 +13,12 @@ import org.springframework.stereotype.Repository;
public class ContainerRepository {
@Resource private ContainerBookingMapper mapper;
public PageResult<ContainerBookingEntity> page(Query query) {
return PageResult.from(mapper.selectBookingPage(new Page<>(query.page(), query.size()), query));
Page<ContainerBookingEntity> result = mapper.selectBookingPage(
new Page<>(query.page(), query.size()), query);
result.getRecords().forEach(this::resolveLoadingTime);
return PageResult.from(result);
}
public ContainerBookingEntity find(long id) { return mapper.selectBooking(id); }
public ContainerBookingEntity find(long id) { return resolveLoadingTime(mapper.selectBooking(id)); }
public ContainerBookingEntity findForUpdate(long id) { return mapper.selectBookingForUpdate(id); }
public ContainerBookingEntity findActiveForUpdate(long id) { return mapper.selectActiveBookingForUpdate(id); }
public boolean containerNoExists(String no, Long excludedId) {
@@ -56,4 +59,12 @@ public class ContainerRepository {
return mapper.syncReleasedTransport(oceanBookingId, billOfLadingNo, vesselName, voyageNo,
estimatedSailingAt, operatorId, operatorName);
}
private ContainerBookingEntity resolveLoadingTime(ContainerBookingEntity value) {
if (value != null && value.getLoadingAt() == null
&& "COMPLETED".equals(value.getPlanStatus())) {
value.setLoadingAt(value.getCompletedLoadingAt());
}
return value;
}
}
@@ -244,7 +244,8 @@ public class FbaPricingRepository {
public int markInboundConfirmed(long tenantId, long inboundId) {
return inbounds.update(null, new LambdaUpdateWrapper<InboundPricingEntity>()
.eq(InboundPricingEntity::getTenantId, tenantId).eq(InboundPricingEntity::getId, inboundId)
.in(InboundPricingEntity::getProcessStatus, List.of("FEE_CONFIRMING", "WAIT_SPLIT"))
.in(InboundPricingEntity::getProcessStatus,
List.of("FEE_CONFIRMING", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED"))
.eq(InboundPricingEntity::getFeeStatus, "CALCULATED")
.set(InboundPricingEntity::getFeeStatus, "CONFIRMED"));
}
@@ -21,6 +21,7 @@ import org.springframework.util.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import java.util.List;
import java.util.Set;
/** 分柜、预装清单及其主状态推进;不校验资料完整度和客户确认。 */
@Service
@@ -73,8 +74,8 @@ public class SplitPreloadService {
/**
* A running PDA task may restore or add an unpicked allocation after the plan-level preload
* confirmation. CONFIRMING remains immutable; CONFIRMED is accepted because the caller owns
* the active PDA task lock and will synchronise the restored loading item immediately.
* confirmation. A running PDA task owns the mutation lock, so CONFIRMING and CONFIRMED
* lifecycles may be reused while the restored loading item is synchronised atomically.
*/
@Transactional
public void ensureForRunningLoadingAllocation(long tenantId, long inboundId, long operatorId,
@@ -96,7 +97,8 @@ public class SplitPreloadService {
}
if (plan != null) {
if (!PreloadStatus.PENDING.equals(plan.getStatus())
&& !(running && PreloadStatus.CONFIRMED.equals(plan.getStatus()))) {
&& !(running && Set.of(PreloadStatus.CONFIRMING, PreloadStatus.CONFIRMED)
.contains(plan.getStatus()))) {
throw new BizException(ResultCode.CONFLICT, "预装计划已进入确认流程,不能修改智能装柜分配");
}
return;