This commit is contained in:
2026-08-15 11:51:15 +08:00
parent 46cb95db15
commit 08187363a1
16 changed files with 203 additions and 39 deletions
@@ -27,6 +27,11 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
int allocatedPackages(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
@Param("loadingPlanId") long loadingPlanId);
@Select("SELECT account_id tenantId,inbound_id inboundId,COALESCE(SUM(package_count),0) allocatedPackageCount "
+ "FROM fl_loading_plan_item WHERE loading_plan_id=#{loadingPlanId} AND deleted=0 "
+ "GROUP BY account_id,inbound_id HAVING SUM(package_count)>0")
List<AllocationScope> allocatedScopes(@Param("loadingPlanId") long loadingPlanId);
@Update("UPDATE fl_preload_plan SET 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 version=#{expectedVersion}")
@@ -70,4 +75,5 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
record PreloadSnapshot(Long id, Long tenantId, Long inboundId, String status, Integer version) {}
record LoadingPlanSnapshot(Long id, Long containerBookingId, String containerNo,
String status, Integer version) {}
record AllocationScope(Long tenantId, Long inboundId, Integer allocatedPackageCount) {}
}
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper;
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper.AllocationScope;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Repository;
@@ -24,6 +25,7 @@ public class LoadingModelBridgeRepository {
public int allocatedPackages(long tenantId, long inboundId, long loadingPlanId) {
return mapper.allocatedPackages(tenantId, inboundId, loadingPlanId);
}
public List<AllocationScope> allocatedScopes(long loadingPlanId) { return mapper.allocatedScopes(loadingPlanId); }
public PreloadLoadingPlanLinkEntity byPair(long tenantId, long inboundId, long loadingPlanId) {
return mapper.selectOne(new LambdaQueryWrapper<PreloadLoadingPlanLinkEntity>()
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.BindRequest
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
import com.baiyue.firstleg.loading.bridge.repository.LoadingModelBridgeRepository;
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper.AllocationScope;
import jakarta.annotation.Resource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
@@ -68,6 +69,40 @@ public class LoadingModelBridgeService {
return requireLink(tenantId, inboundId, loadingPlanId);
}
/**
* Creates every preload link required by a pushed loading plan before PDA can start.
* The caller owns the surrounding push transaction, so a missing preload or stale
* version rolls back the push and cannot leave a LOADING task without a link.
*/
@Transactional
public List<Link> bindAllocatedBeforeLoading(long loadingPlanId, long operatorId,
String operatorName, String orchestrationKey) {
var loadingPlan = repository.loadingPlan(loadingPlanId);
if (loadingPlan == null) throw new BizException(ResultCode.NOT_FOUND, "柜次装柜计划不存在");
List<AllocationScope> scopes = repository.allocatedScopes(loadingPlanId);
if (scopes.isEmpty()) throw conflict("装柜计划尚未分配入库单货物,不能开始PDA作业");
if (!"EDITING".equals(loadingPlan.status())) {
if (Set.of("LOADING", "COMPLETED").contains(loadingPlan.status())
&& scopes.stream().allMatch(scope -> repository.byPair(
scope.tenantId(), scope.inboundId(), loadingPlanId) != null)) {
return repository.byLoadingPlan(loadingPlanId);
}
throw conflict("PDA开始前的预装关联不完整,禁止在LOADING后补关联");
}
for (AllocationScope scope : scopes) {
var preload = repository.preload(scope.tenantId(), scope.inboundId());
var currentPlan = repository.loadingPlan(loadingPlanId);
if (preload == null || currentPlan == null) {
throw new BizException(ResultCode.NOT_FOUND, "入库单预装计划不存在");
}
String key = "AUTO:" + hash(orchestrationKey + "|" + scope.tenantId() + "|"
+ scope.inboundId() + "|" + loadingPlanId).substring(0, 60);
bind(scope.tenantId(), scope.inboundId(), loadingPlanId, operatorId, operatorName, key,
new BindRequest(preload.version(), currentPlan.version()));
}
return repository.byLoadingPlan(loadingPlanId);
}
public List<Link> byInbound(long tenantId, long inboundId) { return repository.byInbound(tenantId, inboundId); }
public List<Link> byLoadingPlan(long loadingPlanId) { return repository.byLoadingPlan(loadingPlanId); }
@@ -8,6 +8,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.*;
import com.baiyue.firstleg.client.OrderCargoClient;
import com.baiyue.firstleg.entity.*;
import com.baiyue.firstleg.repository.SmartLoadingRepository;
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -33,6 +34,7 @@ public class SmartLoadingService {
@Resource private SmartLoadingRepository repository;
@Resource private OrderCargoClient orderCargoClient;
@Resource private LoadingPickService loadingPickService;
@Resource private LoadingModelBridgeService loadingModelBridge;
@Resource private ObjectMapper objectMapper;
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
public SmartLoadingService() { }
@@ -233,6 +235,7 @@ public class SmartLoadingService {
throw validation("录入真实柜号后才能推送预装清单并开启PDA作业");
}
if (key.equals(plan.getPushIdempotencyKey())) {
loadingModelBridge.bindAllocatedBeforeLoading(plan.getId(), operatorId, operatorName, key);
loadingPickService.beginIfReady(plan.getContainerBookingId(), operatorId, operatorName);
return view(plan, booking);
}
@@ -246,6 +249,7 @@ public class SmartLoadingService {
} catch (DuplicateKeyException e) { throw conflict("幂等键已用于其他推送"); }
history(planId, null, "PUSH_PRELOAD", "EDITING", "EDITING", key, operatorId, operatorName);
LoadingPlanEntity pushed = repository.plan(planId);
loadingModelBridge.bindAllocatedBeforeLoading(planId, operatorId, operatorName, key);
loadingPickService.beginIfReady(pushed.getContainerBookingId(), operatorId, operatorName);
return view(pushed, requireBooking(pushed.getContainerBookingId()));
}