fix(iam): 收敛仓库角色权限并纳入统一迁移
This commit is contained in:
+32
-13
@@ -3,7 +3,6 @@ package com.baiyue.firstleg.loading.controller;
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.CompleteDeliveryRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.CompleteLoadingRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.UpsertEvidenceRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.VoidEvidenceRequest;
|
||||
@@ -21,7 +20,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/** 平台端装柜证据、装柜完成和派送完成操作。 */
|
||||
/** 平台端装柜证据和国内装柜完成操作。 */
|
||||
@Tag(name = "订单装柜与派送")
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/inbounds")
|
||||
@@ -39,6 +38,37 @@ public class PlatformLoadingDeliveryController {
|
||||
return ApiResponse.success(service.upsertEvidence(tenantId, inboundId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
/** PDA现场拍照专用入口。与后台人工上传使用不同权限,避免拣货员获得后台证据维护入口。 */
|
||||
@Operation(summary = "PDA追加装柜证据")
|
||||
@PostMapping("/{inboundId}/loading/evidence/pda")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<LoadingActionResponse> pdaEvidence(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId, @PathVariable long inboundId,
|
||||
@RequestHeader("Idempotency-Key") String key, @Valid @RequestBody UpsertEvidenceRequest request) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.upsertEvidence(tenantId, inboundId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "PDA查询装柜证据")
|
||||
@GetMapping("/{inboundId}/loading/evidence/pda")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<EvidenceListResponse> pdaEvidence(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId, @PathVariable long inboundId) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.evidence(tenantId, inboundId));
|
||||
}
|
||||
|
||||
@Operation(summary = "PDA作废错误装柜证据")
|
||||
@PostMapping("/{inboundId}/loading/evidence/{evidenceId}/void/pda")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<LoadingActionResponse> pdaVoidEvidence(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId, @PathVariable long inboundId,
|
||||
@PathVariable long evidenceId, @RequestHeader("Idempotency-Key") String key,
|
||||
@Valid @RequestBody VoidEvidenceRequest request) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.voidEvidence(tenantId, inboundId, evidenceId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询装柜证据")
|
||||
@GetMapping("/{inboundId}/loading/evidence")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:loading:view')")
|
||||
@@ -71,17 +101,6 @@ public class PlatformLoadingDeliveryController {
|
||||
return ApiResponse.success(service.completeLoading(tenantId, inboundId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "确认派送完成")
|
||||
@PostMapping("/{inboundId}/delivery/complete")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:delivery:complete')")
|
||||
@BusinessAudit(event = "INBOUND_COMPLETE_DELIVERY", resourceType = "INBOUND", resourceId = "#inboundId", description = "确认派送完成")
|
||||
public ApiResponse<LoadingActionResponse> completeDelivery(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId, @PathVariable long inboundId,
|
||||
@RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CompleteDeliveryRequest request) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.completeDelivery(tenantId, inboundId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "PDA开始拣货")
|
||||
@PostMapping("/{inboundId}/loading/picking/start")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:picking:edit')")
|
||||
|
||||
@@ -25,10 +25,6 @@ public final class LoadingRequests {
|
||||
@NotNull @PositiveOrZero Integer expectedVersion,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record CompleteDeliveryRequest(
|
||||
@NotNull @PositiveOrZero Integer expectedVersion,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record PickingRequest(@NotNull @PositiveOrZero Integer expectedVersion,
|
||||
@PositiveOrZero Integer expectedCount,
|
||||
@PositiveOrZero Integer pickedCount,
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.baiyue.firstleg.loading.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/** SQL-backed hard gate for every loading plan linked to an inbound. */
|
||||
@Mapper
|
||||
public interface LoadingCompletionGateMapper {
|
||||
@Select("""
|
||||
SELECT COUNT(*) linkCount,
|
||||
COALESCE(SUM(CASE WHEN EXISTS (
|
||||
SELECT 1 FROM fl_ocean_booking_container_allocation a
|
||||
JOIN fl_ocean_booking b ON b.id=a.ocean_booking_id
|
||||
WHERE a.container_booking_id=p.container_booking_id AND b.status='RELEASED') THEN 1 ELSE 0 END),0) oceanReady,
|
||||
COALESCE(SUM(CASE WHEN EXISTS (
|
||||
SELECT 1 FROM fl_truck_appointment t
|
||||
WHERE t.container_booking_id=p.container_booking_id AND t.loading_plan_id=p.id
|
||||
AND t.status='COMPLETED') THEN 1 ELSE 0 END),0) truckReady,
|
||||
COALESCE(SUM(CASE WHEN EXISTS (
|
||||
SELECT 1 FROM fl_customs_case c
|
||||
JOIN fl_customs_receipt r ON r.customs_case_id=c.id
|
||||
WHERE c.tenant_id=l.tenant_id AND c.inbound_id=l.inbound_id
|
||||
AND c.container_booking_id=p.container_booking_id
|
||||
AND c.status='CLEARANCE_COMPLETED' AND r.receipt_type='RELEASED' AND r.file_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM fl_customs_receipt newer
|
||||
WHERE newer.submission_batch_id=r.submission_batch_id
|
||||
AND newer.receipt_type=r.receipt_type AND newer.receipt_version>r.receipt_version))
|
||||
THEN 1 ELSE 0 END),0) customsReady
|
||||
FROM fl_preload_loading_plan_link l
|
||||
JOIN fl_loading_plan p ON p.id=l.loading_plan_id
|
||||
WHERE l.tenant_id=#{tenantId} AND l.inbound_id=#{inboundId}
|
||||
""")
|
||||
GateSnapshot snapshot(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
record GateSnapshot(Integer linkCount, Integer oceanReady, Integer truckReady, Integer customsReady) {
|
||||
public boolean allBookingReady() { return count(linkCount) > 0 && count(linkCount) == count(oceanReady); }
|
||||
public boolean allTruckReady() { return count(linkCount) > 0 && count(linkCount) == count(truckReady); }
|
||||
public boolean allCustomsReady() { return count(linkCount) > 0 && count(linkCount) == count(customsReady); }
|
||||
private static int count(Integer value) { return value == null ? 0 : value; }
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,5 @@ public interface LoadingInboundMapper {
|
||||
int completeLoading(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
@Update("UPDATE fl_inbound_order SET process_status='DELIVERED', updated_by=#{operatorId}, version=version+1 "
|
||||
+ "WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0 AND process_status='LOADED' AND version=#{expectedVersion}")
|
||||
int completeDelivery(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
record Snapshot(Long id, Long tenantId, String processStatus, Integer version) {}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,6 @@ public class LoadingRepository {
|
||||
return inbounds.completeLoading(tenantId, inboundId, operatorId, expectedVersion);
|
||||
}
|
||||
|
||||
public int completeDelivery(long tenantId, long inboundId, long operatorId, int expectedVersion) {
|
||||
return inbounds.completeDelivery(tenantId, inboundId, operatorId, expectedVersion);
|
||||
}
|
||||
|
||||
public List<LoadingEvidenceEntity> listEvidence(long tenantId, long inboundId) {
|
||||
return evidences.selectList(new LambdaQueryWrapper<LoadingEvidenceEntity>()
|
||||
.eq(LoadingEvidenceEntity::getTenantId, tenantId)
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.firstleg.loading.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingCompletionGateMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** Centralized, database-backed LOADED hard gate. */
|
||||
@Service
|
||||
public class LoadingCompletionGateService {
|
||||
@Resource private LoadingCompletionGateMapper mapper;
|
||||
|
||||
public void requireBookingTransportAndCustoms(long tenantId, long inboundId) {
|
||||
LoadingCompletionGateMapper.GateSnapshot gate = mapper.snapshot(tenantId, inboundId);
|
||||
if (gate == null || gate.linkCount() == null || gate.linkCount() == 0) {
|
||||
throw blocked("未找到有效柜次关联");
|
||||
}
|
||||
if (!gate.allBookingReady()) throw blocked("订舱尚未完成(必须为 RELEASED)");
|
||||
if (!gate.allTruckReady()) throw blocked("拖车尚未完成(必须为 COMPLETED)");
|
||||
if (!gate.allCustomsReady()) throw blocked("缺少有效报关 RELEASED 放行回执");
|
||||
}
|
||||
|
||||
private static BizException blocked(String message) {
|
||||
return new BizException(ResultCode.CONFLICT, "LOADED门禁未通过:" + message);
|
||||
}
|
||||
}
|
||||
+15
-22
@@ -35,17 +35,17 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** 装柜证据、拣货复核、装柜完成和派送完成。 */
|
||||
/** 装柜证据、拣货复核和国内装柜完成。 */
|
||||
@Service
|
||||
public class LoadingDeliveryService {
|
||||
private static final String UPSERT_EVIDENCE = "UPSERT_LOADING_EVIDENCE";
|
||||
private static final String VOID_EVIDENCE = "VOID_LOADING_EVIDENCE";
|
||||
private static final String COMPLETE_LOADING = "COMPLETE_LOADING";
|
||||
private static final String COMPLETE_DELIVERY = "COMPLETE_DELIVERY";
|
||||
|
||||
@Resource private LoadingRepository repository;
|
||||
@Resource private LoadingPickRepository loadingPicks;
|
||||
@Resource private LoadingModelBridgeService loadingModelBridge;
|
||||
@Resource private LoadingCompletionGateService completionGate;
|
||||
@Resource private InboundStatusHistoryMapper histories;
|
||||
@Resource private LoadingEvidenceFileClient files;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@@ -57,7 +57,7 @@ public class LoadingDeliveryService {
|
||||
String hash = hash(request);
|
||||
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, UPSERT_EVIDENCE, key, hash);
|
||||
if (replay != null) return replay;
|
||||
requireLoadingStage(tenantId, inboundId);
|
||||
requireMutableLoadingStage(tenantId, inboundId);
|
||||
if (!LoadingEvidenceType.VALUES.contains(request.evidenceType())) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "不支持的装柜证据类型");
|
||||
}
|
||||
@@ -105,6 +105,7 @@ public class LoadingDeliveryService {
|
||||
}
|
||||
if (!"LOADING".equals(inbound.processStatus())) throw new BizException(ResultCode.CONFLICT, "当前订单不在装柜中");
|
||||
loadingModelBridge.requireCompleted(tenantId, inboundId);
|
||||
completionGate.requireBookingTransportAndCustoms(tenantId, inboundId);
|
||||
requireCompletedPicking(tenantId, inboundId);
|
||||
requireCompletionEvidence(tenantId, inboundId);
|
||||
if (repository.completeLoading(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) throw concurrent();
|
||||
@@ -113,24 +114,6 @@ public class LoadingDeliveryService {
|
||||
new LoadingActionResponse("LOADED", evidenceResponse(tenantId, inboundId)));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LoadingActionResponse completeDelivery(long tenantId, long inboundId, long operatorId,
|
||||
String operatorName, String key, CompleteDeliveryRequest request) {
|
||||
String hash = hash(request);
|
||||
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, COMPLETE_DELIVERY, key, hash);
|
||||
if (replay != null) return replay;
|
||||
Snapshot inbound = requireInbound(tenantId, inboundId);
|
||||
if ("DELIVERED".equals(inbound.processStatus())) {
|
||||
return complete(tenantId, inboundId, operatorId, COMPLETE_DELIVERY, key,
|
||||
new LoadingActionResponse("DELIVERED", evidenceResponse(tenantId, inboundId)));
|
||||
}
|
||||
if (!"LOADED".equals(inbound.processStatus())) throw new BizException(ResultCode.CONFLICT, "当前订单不在装柜完成");
|
||||
if (repository.completeDelivery(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) throw concurrent();
|
||||
addHistory(tenantId, inboundId, "LOADED", "DELIVERED", COMPLETE_DELIVERY, request.remark(), operatorId, operatorName);
|
||||
return complete(tenantId, inboundId, operatorId, COMPLETE_DELIVERY, key,
|
||||
new LoadingActionResponse("DELIVERED", evidenceResponse(tenantId, inboundId)));
|
||||
}
|
||||
|
||||
public EvidenceListResponse evidence(long tenantId, long inboundId) {
|
||||
requireLoadingStage(tenantId, inboundId);
|
||||
return evidenceResponse(tenantId, inboundId);
|
||||
@@ -142,7 +125,7 @@ public class LoadingDeliveryService {
|
||||
String hash = hash(request);
|
||||
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, VOID_EVIDENCE, key, hash);
|
||||
if (replay != null) return replay;
|
||||
requireLoadingStage(tenantId, inboundId);
|
||||
requireMutableLoadingStage(tenantId, inboundId);
|
||||
LoadingEvidenceEntity target = repository.listEvidence(tenantId, inboundId).stream()
|
||||
.filter(item -> Long.valueOf(evidenceId).equals(item.getId())).findFirst().orElse(null);
|
||||
if (target == null) throw new BizException(ResultCode.NOT_FOUND, "装柜证据不存在");
|
||||
@@ -298,6 +281,16 @@ public class LoadingDeliveryService {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Evidence is append-only while loading. Once LOADED is reached this API is read-only;
|
||||
* corrections are handled by the offline/manual process defined for phase three. */
|
||||
private Snapshot requireMutableLoadingStage(long tenantId, long inboundId) {
|
||||
Snapshot value = requireInbound(tenantId, inboundId);
|
||||
if (!"LOADING".equals(value.processStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "装柜完成后不允许在线修改证据");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private EvidenceListResponse evidenceResponse(long tenantId, long inboundId) {
|
||||
List<EvidenceResponse> values = repository.listEvidence(tenantId, inboundId).stream().map(e ->
|
||||
new EvidenceResponse(e.getId(), e.getInboundId(), e.getTenantId(), e.getEvidenceType(), e.getFileId(),
|
||||
|
||||
+87
-77
@@ -11,8 +11,20 @@ import java.util.List;
|
||||
/** PDA部分拣货结算的客户归属锁定查询。 */
|
||||
@Mapper
|
||||
public interface InboundSplitSettlementMapper {
|
||||
@Select("SELECT id,tenant_id tenantId,inbound_role inboundRole,root_inbound_id rootInboundId,"
|
||||
+ "split_purpose splitPurpose,split_status splitStatus FROM fl_inbound_order WHERE id=#{inboundId} AND deleted=0")
|
||||
Lineage lineage(@Param("inboundId") long inboundId);
|
||||
@Select("SELECT COALESCE(MAX(split_sequence),0)+1 FROM fl_inbound_order WHERE tenant_id=#{accountId} AND parent_inbound_id=#{parentInboundId} AND deleted=0 FOR UPDATE")
|
||||
int nextSplitSequence(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId);
|
||||
|
||||
@Select("SELECT id FROM fl_inbound_split_batch WHERE tenant_id=#{accountId} AND operation_id=#{operationId} LIMIT 1 FOR UPDATE")
|
||||
Long appliedBatch(@Param("accountId") long accountId, @Param("operationId") String operationId);
|
||||
|
||||
@Select("SELECT container_booking_id FROM fl_loading_plan WHERE id=#{loadingPlanId}")
|
||||
Long containerBookingId(@Param("loadingPlanId") long loadingPlanId);
|
||||
@Select("""
|
||||
SELECT id, tenant_id AS tenantId, inbound_code AS inboundCode, inbound_role AS inboundRole,
|
||||
root_inbound_id AS rootInboundId,
|
||||
process_status AS processStatus, total_package_count AS totalPackageCount,
|
||||
package_count AS packageCount, version
|
||||
FROM fl_inbound_order
|
||||
@@ -21,68 +33,52 @@ public interface InboundSplitSettlementMapper {
|
||||
""")
|
||||
ParentRow lockParent(@Param("inboundId") long inboundId);
|
||||
|
||||
@Select("""
|
||||
SELECT id
|
||||
FROM fl_inbound_order
|
||||
WHERE tenant_id = #{accountId} AND parent_inbound_id = #{parentInboundId}
|
||||
AND inbound_role = 'CHILD' AND deleted = 0
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
@Insert("""
|
||||
INSERT INTO fl_inbound_order (id,tenant_id,task_id,order_id,order_no,inbound_code,process_status,
|
||||
order_status_snapshot,measure_review_status,fee_status,fee_review_status,notification_status,
|
||||
expected_arrival_at,customer_id,customer_name,company_id,company_name,warehouse_id,warehouse_name,
|
||||
delivery_warehouse_id,delivery_warehouse_name,business_type,channel_code,channel_name,carrier_id,carrier_name,
|
||||
tax_mode,pricing_tax_mode,container_mode,customs_inspection,destination_type,destination_warehouse_id,
|
||||
destination_code,destination_name,hold_flag,current_hold_reason,total_package_count,package_count,
|
||||
actual_gross_weight_kg,chargeable_weight_kg,actual_volume_cbm,chargeable_volume_cbm,pallet_count,volume_weight_kg,
|
||||
actual_net_weight_kg,measure_completed_at,inbounded_at,express_company_code,express_company_name,
|
||||
express_tracking_no,public_remark,created_by,created_by_name,created_at,updated_by,updated_at,deleted,version,
|
||||
inbound_role,parent_inbound_id,root_inbound_id,split_sequence,split_status,split_purpose)
|
||||
SELECT #{childId},tenant_id,task_id,order_id,order_no,#{childInboundCode},'INBOUNDED',order_status_snapshot,
|
||||
'APPROVED',fee_status,fee_review_status,notification_status,expected_arrival_at,customer_id,customer_name,
|
||||
company_id,company_name,warehouse_id,warehouse_name,delivery_warehouse_id,delivery_warehouse_name,business_type,
|
||||
channel_code,channel_name,carrier_id,carrier_name,tax_mode,pricing_tax_mode,container_mode,customs_inspection,
|
||||
destination_type,destination_warehouse_id,destination_code,destination_name,hold_flag,current_hold_reason,
|
||||
#{packageCount},#{packageCount},actual_gross_weight_kg,chargeable_weight_kg,actual_volume_cbm,chargeable_volume_cbm,
|
||||
pallet_count,volume_weight_kg,actual_net_weight_kg,measure_completed_at,inbounded_at,express_company_code,
|
||||
express_company_name,express_tracking_no,public_remark,#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3),
|
||||
#{operatorId},CURRENT_TIMESTAMP(3),0,0,'CHILD',#{parentInboundId},#{rootInboundId},#{splitSequence},'NOT_SPLIT',#{splitPurpose}
|
||||
FROM fl_inbound_order WHERE id=#{parentInboundId} AND tenant_id=#{accountId} AND deleted=0
|
||||
""")
|
||||
Long childId(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId);
|
||||
int cloneChildWithPurpose(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId,
|
||||
@Param("rootInboundId") long rootInboundId, @Param("childId") long childId,
|
||||
@Param("childInboundCode") String childInboundCode, @Param("packageCount") int packageCount,
|
||||
@Param("splitSequence") int splitSequence, @Param("splitPurpose") String splitPurpose,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_inbound_order (
|
||||
id, tenant_id, task_id, order_id, order_no, inbound_code, process_status,
|
||||
order_status_snapshot, measure_review_status, fee_status, fee_review_status, notification_status,
|
||||
expected_arrival_at, customer_id, customer_name, company_id, company_name,
|
||||
warehouse_id, warehouse_name, delivery_warehouse_id, delivery_warehouse_name,
|
||||
business_type, channel_code, channel_name, carrier_id, carrier_name, tax_mode,
|
||||
pricing_tax_mode, container_mode, customs_inspection, destination_type,
|
||||
destination_warehouse_id, destination_code, destination_name, hold_flag,
|
||||
current_hold_reason, total_package_count, actual_gross_weight_kg,
|
||||
chargeable_weight_kg, actual_volume_cbm, chargeable_volume_cbm, pallet_count,
|
||||
volume_weight_kg, actual_net_weight_kg, measure_completed_at, inbounded_at,
|
||||
express_company_code, express_company_name, express_tracking_no, public_remark,
|
||||
created_by, created_by_name, created_at, updated_by, updated_at, deleted, version,
|
||||
inbound_role, parent_inbound_id, root_inbound_id, split_sequence, split_status, package_count
|
||||
)
|
||||
SELECT #{childId}, tenant_id, task_id, order_id, order_no, #{childInboundCode}, 'INBOUNDED',
|
||||
order_status_snapshot, 'APPROVED', fee_status, fee_review_status, notification_status,
|
||||
expected_arrival_at, customer_id, customer_name, company_id, company_name,
|
||||
warehouse_id, warehouse_name, delivery_warehouse_id, delivery_warehouse_name,
|
||||
business_type, channel_code, channel_name, carrier_id, carrier_name, tax_mode,
|
||||
pricing_tax_mode, container_mode, customs_inspection, destination_type,
|
||||
destination_warehouse_id, destination_code, destination_name, hold_flag,
|
||||
current_hold_reason, #{packageCount}, actual_gross_weight_kg,
|
||||
chargeable_weight_kg, actual_volume_cbm, chargeable_volume_cbm, pallet_count,
|
||||
volume_weight_kg, actual_net_weight_kg, measure_completed_at, inbounded_at,
|
||||
express_company_code, express_company_name, express_tracking_no, public_remark,
|
||||
#{operatorId}, #{operatorName}, CURRENT_TIMESTAMP(3), #{operatorId}, CURRENT_TIMESTAMP(3), 0, 0,
|
||||
'CHILD', id, id, 1, 'NOT_SPLIT', #{packageCount}
|
||||
FROM fl_inbound_order
|
||||
WHERE id = #{parentInboundId} AND tenant_id = #{accountId} AND deleted = 0
|
||||
INSERT INTO fl_inbound_split_batch (id,tenant_id,parent_inbound_id,source_inbound_id,operation_id,idempotency_key,
|
||||
expected_parent_version,package_count,loaded_child_inbound_id,remainder_child_inbound_id,
|
||||
loaded_package_count,remaining_package_count,loading_plan_id,container_booking_id,status,remark,
|
||||
created_by,created_by_name,created_at,version)
|
||||
VALUES (#{batchId},#{accountId},#{parentInboundId},#{sourceInboundId},#{operationId},#{idempotencyKey},#{expectedParentVersion},
|
||||
#{packageCount},#{loadedChildId},#{remainderChildId},#{loadedPackageCount},#{remainingPackageCount},
|
||||
#{loadingPlanId},#{containerBookingId},'PREPARED',#{remark},#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3),0)
|
||||
""")
|
||||
int cloneChild(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId,
|
||||
@Param("childId") long childId, @Param("childInboundCode") String childInboundCode,
|
||||
@Param("packageCount") int packageCount, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_inbound_split_batch
|
||||
(id, tenant_id, parent_inbound_id, operation_id, idempotency_key, expected_parent_version,
|
||||
package_count, status, remark, created_by, created_by_name, created_at, version)
|
||||
VALUES
|
||||
(#{batchId}, #{accountId}, #{parentInboundId}, #{operationId}, #{idempotencyKey},
|
||||
#{expectedParentVersion}, #{packageCount}, 'PREPARED', #{remark}, #{operatorId},
|
||||
#{operatorName}, CURRENT_TIMESTAMP(3), 0)
|
||||
""")
|
||||
int insertBatch(@Param("batchId") long batchId, @Param("accountId") long accountId,
|
||||
@Param("parentInboundId") long parentInboundId, @Param("operationId") String operationId,
|
||||
@Param("idempotencyKey") String idempotencyKey,
|
||||
@Param("expectedParentVersion") int expectedParentVersion,
|
||||
@Param("packageCount") int packageCount, @Param("remark") String remark,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
int insertBatchWithChildren(@Param("batchId") long batchId, @Param("accountId") long accountId,
|
||||
@Param("parentInboundId") long parentInboundId, @Param("operationId") String operationId,
|
||||
@Param("sourceInboundId") long sourceInboundId,
|
||||
@Param("idempotencyKey") String idempotencyKey, @Param("expectedParentVersion") int expectedParentVersion,
|
||||
@Param("packageCount") int packageCount, @Param("loadedChildId") Long loadedChildId,
|
||||
@Param("remainderChildId") Long remainderChildId, @Param("loadedPackageCount") int loadedPackageCount,
|
||||
@Param("remainingPackageCount") int remainingPackageCount, @Param("loadingPlanId") Long loadingPlanId,
|
||||
@Param("containerBookingId") Long containerBookingId, @Param("remark") String remark,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_inbound_split_item
|
||||
@@ -158,6 +154,17 @@ public interface InboundSplitSettlementMapper {
|
||||
@Param("packageCount") int packageCount, @Param("operatorId") long operatorId,
|
||||
@Param("expectedVersion") int expectedVersion);
|
||||
|
||||
@Update("UPDATE fl_inbound_order SET package_count=0,split_status='FULLY_SPLIT',updated_by=#{operatorId},version=version+1 "
|
||||
+ "WHERE id=#{parentInboundId} AND tenant_id=#{accountId} AND inbound_role='MASTER' AND deleted=0 AND version=#{expectedVersion}")
|
||||
int markRootSplit(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId,
|
||||
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
@Update("UPDATE fl_inbound_order SET split_status='FULLY_SPLIT',updated_by=#{operatorId},version=version+1 "
|
||||
+ "WHERE id=#{sourceInboundId} AND tenant_id=#{accountId} AND inbound_role='CHILD' "
|
||||
+ "AND split_purpose='REMAINDER' AND split_status='NOT_SPLIT' AND deleted=0 AND version=#{expectedVersion}")
|
||||
int markRemainderConsumed(@Param("accountId") long accountId, @Param("sourceInboundId") long sourceInboundId,
|
||||
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_split_batch
|
||||
SET status = 'APPLIED', applied_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||
@@ -240,32 +247,35 @@ public interface InboundSplitSettlementMapper {
|
||||
@Param("inboundId") long inboundId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_measurement
|
||||
SET total_package_count = #{packageCount}, updated_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||
WHERE id = #{measurementId} AND tenant_id = #{accountId} AND inbound_id = #{inboundId}
|
||||
AND status = 'APPROVED'
|
||||
UPDATE fl_inbound_order child
|
||||
JOIN fl_inbound_measurement measurement ON measurement.tenant_id=child.tenant_id
|
||||
AND measurement.inbound_id=child.id AND measurement.id=#{measurementId}
|
||||
SET child.actual_gross_weight_kg=(SELECT COALESCE(SUM(item.total_weight_kg),0)
|
||||
FROM fl_inbound_measurement_item item WHERE item.measurement_id=measurement.id),
|
||||
child.actual_volume_cbm=(SELECT COALESCE(SUM(item.total_volume_cbm),0)
|
||||
FROM fl_inbound_measurement_item item WHERE item.measurement_id=measurement.id),
|
||||
child.chargeable_weight_kg=NULL,child.chargeable_volume_cbm=NULL,
|
||||
child.volume_weight_kg=(SELECT COALESCE(SUM(item.volume_weight_kg),0)
|
||||
FROM fl_inbound_measurement_item item WHERE item.measurement_id=measurement.id),
|
||||
child.actual_net_weight_kg=NULL,child.pallet_count=0,child.version=child.version+1
|
||||
WHERE child.id=#{childInboundId} AND child.tenant_id=#{accountId} AND child.inbound_role='CHILD'
|
||||
""")
|
||||
int updateSourceMeasurementPackageCount(@Param("accountId") long accountId,
|
||||
@Param("inboundId") long inboundId,
|
||||
@Param("measurementId") long measurementId,
|
||||
@Param("packageCount") int packageCount);
|
||||
int normalizeChildMetrics(@Param("accountId") long accountId, @Param("childInboundId") long childInboundId,
|
||||
@Param("measurementId") long measurementId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_measurement_item
|
||||
SET package_count = #{packageCount}, updated_at = CURRENT_TIMESTAMP(3)
|
||||
WHERE id = #{measurementItemId} AND measurement_id = #{measurementId} AND tenant_id = #{accountId}
|
||||
""")
|
||||
int updateSourceMeasurementItemPackageCount(@Param("accountId") long accountId,
|
||||
@Param("measurementId") long measurementId,
|
||||
@Param("measurementItemId") long measurementItemId,
|
||||
@Param("packageCount") int packageCount);
|
||||
|
||||
record ParentRow(Long id, Long tenantId, String inboundCode, String inboundRole,
|
||||
record ParentRow(Long id, Long tenantId, String inboundCode, String inboundRole, Long rootInboundId,
|
||||
String processStatus, Integer totalPackageCount, Integer packageCount,
|
||||
Integer version) {
|
||||
public ParentRow(Long id, Long tenantId, String inboundCode, String inboundRole,
|
||||
String processStatus, Integer totalPackageCount, Integer packageCount, Integer version) {
|
||||
this(id, tenantId, inboundCode, inboundRole, id, processStatus, totalPackageCount, packageCount, version);
|
||||
}
|
||||
}
|
||||
|
||||
record MeasurementItemRow(Long measurementItemId, Long measurementId, Long inboundDetailId,
|
||||
Integer packageCount) {
|
||||
}
|
||||
|
||||
record Lineage(Long id, Long tenantId, String inboundRole, Long rootInboundId,
|
||||
String splitPurpose, String splitStatus) {}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
|
||||
WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED')
|
||||
JOIN fl_inbound_measurement_item i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id
|
||||
WHERE o.warehouse_id=#{warehouseId} AND o.process_status='INBOUNDED'
|
||||
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
||||
AND i.inbound_detail_id IS NOT NULL
|
||||
AND i.package_count>COALESCE((SELECT SUM(li.package_count) FROM fl_loading_plan_item li
|
||||
WHERE li.account_id=o.tenant_id AND li.inbound_id=o.id
|
||||
@@ -61,6 +62,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
|
||||
WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED')
|
||||
JOIN fl_inbound_measurement_item i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id
|
||||
WHERE o.warehouse_id=#{warehouseId} AND o.id=#{inboundId} AND o.process_status='INBOUNDED'
|
||||
AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT'))
|
||||
AND i.inbound_detail_id=#{detailId}
|
||||
ORDER BY m.id DESC LIMIT 1 FOR UPDATE
|
||||
""")
|
||||
|
||||
-13
@@ -4,7 +4,6 @@ import com.baiyue.firstleg.outbound.dispatch.dto.OutboundDispatchDtos.TimelineIt
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -63,18 +62,6 @@ public interface OutboundDispatchReadMapper {
|
||||
""")
|
||||
Evidence evidence(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Update("UPDATE fl_inbound_order SET process_status='WAITING_FOR_LOADING',updated_by=#{operatorId},"
|
||||
+ "version=version+1,updated_at=CURRENT_TIMESTAMP(3) WHERE tenant_id=#{tenantId} AND id=#{inboundId} "
|
||||
+ "AND deleted=0 AND process_status='LOADED'")
|
||||
int confirmInbound(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("operatorId") long operatorId);
|
||||
|
||||
@Update("UPDATE fl_inbound_order SET process_status='LOADED',updated_by=#{operatorId},"
|
||||
+ "version=version+1,updated_at=CURRENT_TIMESTAMP(3) WHERE tenant_id=#{tenantId} AND id=#{inboundId} "
|
||||
+ "AND deleted=0 AND process_status='WAITING_FOR_LOADING'")
|
||||
int reverseInbound(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("operatorId") long operatorId);
|
||||
|
||||
@Select("""
|
||||
SELECT module,action,fromStatus,toStatus,operatorId,operatorName,remark,occurredAt FROM (
|
||||
SELECT 'INBOUND' module,h.action action,h.from_status fromStatus,h.to_status toStatus,
|
||||
|
||||
-6
@@ -45,12 +45,6 @@ public class OutboundDispatchRepository {
|
||||
public int reverse(long id, int version, long operatorId, String operatorName, String remark) {
|
||||
return dispatches.reverse(id, version, operatorId, operatorName, remark);
|
||||
}
|
||||
public int confirmInbound(long tenantId, long inboundId, long operatorId) {
|
||||
return reads.confirmInbound(tenantId, inboundId, operatorId);
|
||||
}
|
||||
public int reverseInbound(long tenantId, long inboundId, long operatorId) {
|
||||
return reads.reverseInbound(tenantId, inboundId, operatorId);
|
||||
}
|
||||
public void history(OutboundDispatchHistoryEntity value) { histories.insert(value); }
|
||||
public boolean claim(long operatorId, String operation, String key, String hash) {
|
||||
return operations.claim(IdWorker.getId(), operatorId, operation, key, hash) == 1;
|
||||
|
||||
+2
-4
@@ -68,8 +68,7 @@ public class OutboundDispatchService {
|
||||
if (OutboundDispatchStatus.WAITING_FOR_LOADING.name().equals(state.getStatus())) {
|
||||
throw conflict("当前出仓单已确认派车");
|
||||
}
|
||||
if (repository.confirm(state.getId(), state.getVersion(), operatorId, operatorName, request.remark()) != 1
|
||||
|| repository.confirmInbound(binding.tenantId(), binding.inboundId(), operatorId) != 1) {
|
||||
if (repository.confirm(state.getId(), state.getVersion(), operatorId, operatorName, request.remark()) != 1) {
|
||||
throw concurrent();
|
||||
}
|
||||
history(state, "CONFIRM_DISPATCH", OutboundDispatchStatus.READY_FOR_DISPATCH.name(),
|
||||
@@ -92,8 +91,7 @@ public class OutboundDispatchService {
|
||||
throw conflict("当前出仓单不在等待装船状态");
|
||||
}
|
||||
if (!Objects.equals(state.getVersion(), request.expectedVersion())) throw concurrent();
|
||||
if (repository.reverse(state.getId(), state.getVersion(), operatorId, operatorName, request.remark()) != 1
|
||||
|| repository.reverseInbound(binding.tenantId(), binding.inboundId(), operatorId) != 1) {
|
||||
if (repository.reverse(state.getId(), state.getVersion(), operatorId, operatorName, request.remark()) != 1) {
|
||||
throw concurrent();
|
||||
}
|
||||
history(state, "REVERSE_DISPATCH", OutboundDispatchStatus.WAITING_FOR_LOADING.name(),
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** PDA 任务结算:未装柜箱数生成二级子入库单,并转移其库位资源归属。 */
|
||||
/** PDA 任务结算:按柜次生成实装/余量履约分片,并结算测量与库位资源。 */
|
||||
@Service
|
||||
public class PdaPickSettlementService {
|
||||
private static final long PLATFORM_WAREHOUSE_RESOURCE_TENANT_ID = 0L;
|
||||
@@ -44,10 +44,7 @@ public class PdaPickSettlementService {
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
/**
|
||||
* 同一父单最多生成一张子单。子单承接该父单所有未装柜数量,而不是只承接 PARTIAL 行的差额。
|
||||
* 纯跳单才恢复智能装柜候选;同单存在已拣或部分拣货时,跳单行随未装柜数量一并归入子单。
|
||||
*/
|
||||
/** 每个柜次生成一张 LOADED 分片;存在余量时另生成可继续分柜的 REMAINDER 分片。 */
|
||||
@Transactional
|
||||
public void settle(long taskId, long operatorId, String operatorName) {
|
||||
if (!picks.allTaskItemsResolved(taskId)) {
|
||||
@@ -84,15 +81,31 @@ public class PdaPickSettlementService {
|
||||
private boolean settleInbound(long parentInboundId, int pickedPackageCount,
|
||||
List<LoadingPickItemEntity> taskItems, long taskId,
|
||||
long operatorId, String operatorName) {
|
||||
ParentRow parent = splits.lockParent(parentInboundId);
|
||||
InboundSplitSettlementMapper.Lineage lineage = splits.lineage(parentInboundId);
|
||||
if (lineage == null) throw conflict("待拆分入库单不存在");
|
||||
if ("CHILD".equals(lineage.inboundRole())
|
||||
&& !("REMAINDER".equals(lineage.splitPurpose()) && "NOT_SPLIT".equals(lineage.splitStatus()))) {
|
||||
throw conflict("仅未消费的余量子单可以继续分柜");
|
||||
}
|
||||
if (!"MASTER".equals(lineage.inboundRole()) && lineage.rootInboundId() == null) {
|
||||
throw conflict("余量子单缺少根主单");
|
||||
}
|
||||
long rootInboundId = "MASTER".equals(lineage.inboundRole()) ? parentInboundId : lineage.rootInboundId();
|
||||
ParentRow root = splits.lockParent(rootInboundId);
|
||||
ParentRow parent = rootInboundId == parentInboundId ? root : splits.lockParent(parentInboundId);
|
||||
if (parent == null || parent.tenantId() == null || parent.tenantId() <= 0
|
||||
|| !"MASTER".equals(parent.inboundRole()) || !"INBOUNDED".equals(parent.processStatus())) {
|
||||
|| !("MASTER".equals(parent.inboundRole()) || "CHILD".equals(parent.inboundRole()))
|
||||
|| !"INBOUNDED".equals(parent.processStatus())) {
|
||||
throw conflict("待拆分主入库单不在可拆分状态");
|
||||
}
|
||||
Long existingChild = splits.childId(parent.tenantId(), parentInboundId);
|
||||
if (existingChild != null) {
|
||||
return false;
|
||||
}
|
||||
String operationId = "PDA-SPLIT-" + taskId + '-' + parentInboundId;
|
||||
Long appliedBatchId = splits.appliedBatch(parent.tenantId(), operationId);
|
||||
if (appliedBatchId != null && appliedBatchId > 0) return false;
|
||||
var task = picks.task(taskId);
|
||||
if (task == null || task.getLoadingPlanId() == null) throw conflict("PDA任务缺少真实装柜计划");
|
||||
long loadingPlanId = task.getLoadingPlanId();
|
||||
Long containerBookingId = splits.containerBookingId(loadingPlanId);
|
||||
if (containerBookingId == null) throw conflict("PDA任务装柜计划缺少真实柜次");
|
||||
int parentAvailable = count(parent.packageCount(), "主入库单可用箱数");
|
||||
if (pickedPackageCount <= 0 || pickedPackageCount > parentAvailable) {
|
||||
throw conflict("本次已拣箱数与主入库单可用箱数不一致");
|
||||
@@ -112,14 +125,8 @@ public class PdaPickSettlementService {
|
||||
throw conflict("父入库单库位剩余箱数与未装柜箱数不一致");
|
||||
}
|
||||
long warehouseId = warehouseId(confirmedAllocations);
|
||||
if (childPackageCount == 0) {
|
||||
if (!transferItems.isEmpty() || releasedReservationIds.isEmpty()) {
|
||||
throw conflict("全量拣货后库位资源结算不完整");
|
||||
}
|
||||
allocations.markReleased(parent.tenantId(), parentInboundId, releasedReservationIds);
|
||||
settleWarehouseResources(parentInboundId, 0L, "", taskId, operatorId, operatorName,
|
||||
warehouseId, parentAvailable, 0, releasedReservationIds, List.of());
|
||||
return false;
|
||||
if (childPackageCount == 0 && (!transferItems.isEmpty() || releasedReservationIds.isEmpty())) {
|
||||
throw conflict("全量拣货后库位资源结算不完整");
|
||||
}
|
||||
|
||||
List<MeasurementItemRow> measurements = splits.lockLatestApprovedItemsForInbound(
|
||||
@@ -142,56 +149,91 @@ public class PdaPickSettlementService {
|
||||
throw conflict("主入库单测量明细剩余箱数与未装柜箱数不一致");
|
||||
}
|
||||
|
||||
if (transferItems.isEmpty()) {
|
||||
if (childPackageCount > 0 && transferItems.isEmpty()) {
|
||||
throw conflict("父入库单可转移库位剩余箱数与未装柜箱数不一致");
|
||||
}
|
||||
|
||||
long childId = IdWorker.getId();
|
||||
if (root == null || !"MASTER".equals(root.inboundRole())) throw conflict("根主入库单不存在");
|
||||
long loadedChildId = IdWorker.getId();
|
||||
long remainderChildId = childPackageCount > 0 ? IdWorker.getId() : 0L;
|
||||
long batchId = IdWorker.getId();
|
||||
String operationId = "PDA-SPLIT-" + taskId + '-' + parentInboundId;
|
||||
String idempotencyKey = "pda-task-" + taskId;
|
||||
String childCode = childInboundCode(parent.inboundCode(), childId);
|
||||
int parentVersion = parent.version() == null ? 0 : parent.version();
|
||||
requireAffected(splits.cloneChild(parent.tenantId(), parentInboundId, childId, childCode, childPackageCount,
|
||||
operatorId, operatorName), "子入库单");
|
||||
requireAffected(splits.insertBatch(batchId, parent.tenantId(), parentInboundId, operationId,
|
||||
idempotencyKey, parentVersion, childPackageCount, "PDA拣货后未装柜箱数", operatorId, operatorName), "拆分批次");
|
||||
long childMeasurementId = IdWorker.getId();
|
||||
String childCode = childInboundCode(parent.inboundCode(), remainderChildId == 0 ? loadedChildId : remainderChildId);
|
||||
int parentVersion = root.version() == null ? 0 : root.version();
|
||||
int sequence = splits.nextSplitSequence(parent.tenantId(), rootInboundId);
|
||||
requireAffected(splits.cloneChildWithPurpose(parent.tenantId(), rootInboundId, rootInboundId, loadedChildId,
|
||||
childInboundCode(parent.inboundCode(), loadedChildId), pickedPackageCount, sequence, "LOADED", operatorId, operatorName), "实装子入库单");
|
||||
if (remainderChildId != 0L) {
|
||||
requireAffected(splits.cloneChildWithPurpose(parent.tenantId(), rootInboundId, rootInboundId, remainderChildId,
|
||||
childCode, childPackageCount, sequence + 1, "REMAINDER", operatorId, operatorName), "余量子入库单");
|
||||
}
|
||||
requireAffected(splits.insertBatchWithChildren(batchId, parent.tenantId(), rootInboundId, operationId, parentInboundId,
|
||||
idempotencyKey, parentVersion, parentAvailable, loadedChildId, remainderChildId == 0L ? null : remainderChildId,
|
||||
pickedPackageCount, childPackageCount, loadingPlanId, containerBookingId,
|
||||
"PDA拣货后生成实装/余量子单", operatorId, operatorName), "拆分批次");
|
||||
long loadedMeasurementId = IdWorker.getId();
|
||||
requireAffected(splits.cloneMeasurement(parent.tenantId(), parentInboundId, sourceMeasurementId,
|
||||
childId, childMeasurementId, batchId, childPackageCount, operatorId), "子入库测量批次");
|
||||
loadedChildId, loadedMeasurementId, batchId, pickedPackageCount, operatorId), "实装子入库测量批次");
|
||||
long childMeasurementId = childPackageCount > 0 ? IdWorker.getId() : 0L;
|
||||
if (childPackageCount > 0) {
|
||||
requireAffected(splits.cloneMeasurement(parent.tenantId(), parentInboundId, sourceMeasurementId,
|
||||
remainderChildId, childMeasurementId, batchId, childPackageCount, operatorId), "余量子入库测量批次");
|
||||
}
|
||||
|
||||
int lineNo = 1;
|
||||
for (MeasurementSplitLine line : measurementLines) {
|
||||
if (line.pickedPackageCount() <= 0) continue;
|
||||
long splitItemId = IdWorker.getId();
|
||||
requireAffected(splits.insertSplitItem(splitItemId, parent.tenantId(), batchId, rootInboundId,
|
||||
loadedChildId, lineNo, line.item().inboundDetailId(), sourceMeasurementId,
|
||||
line.item().measurementItemId(), line.pickedPackageCount()), "实装拆分明细");
|
||||
requireAffected(splits.cloneMeasurementItem(parent.tenantId(), sourceMeasurementId,
|
||||
line.item().measurementItemId(), loadedMeasurementId, splitItemId, IdWorker.getId(), lineNo,
|
||||
line.pickedPackageCount()), "实装子入库测量明细");
|
||||
lineNo++;
|
||||
}
|
||||
for (MeasurementSplitLine line : measurementLines) {
|
||||
if (line.remainingPackageCount() <= 0) {
|
||||
continue;
|
||||
}
|
||||
long splitItemId = IdWorker.getId();
|
||||
requireAffected(splits.insertSplitItem(splitItemId, parent.tenantId(), batchId, parentInboundId,
|
||||
childId, lineNo, line.item().inboundDetailId(), sourceMeasurementId,
|
||||
requireAffected(splits.insertSplitItem(splitItemId, parent.tenantId(), batchId, rootInboundId,
|
||||
remainderChildId == 0L ? loadedChildId : remainderChildId, lineNo, line.item().inboundDetailId(), sourceMeasurementId,
|
||||
line.item().measurementItemId(), line.remainingPackageCount()), "拆分明细");
|
||||
requireAffected(splits.cloneMeasurementItem(parent.tenantId(), sourceMeasurementId,
|
||||
line.item().measurementItemId(), childMeasurementId, splitItemId, IdWorker.getId(), lineNo,
|
||||
line.remainingPackageCount()), "子入库测量明细");
|
||||
lineNo++;
|
||||
}
|
||||
requireAffected(splits.updateSourceMeasurementPackageCount(parent.tenantId(), parentInboundId,
|
||||
sourceMeasurementId, pickedPackageCount), "主入库测量批次");
|
||||
for (MeasurementSplitLine line : measurementLines) {
|
||||
requireAffected(splits.updateSourceMeasurementItemPackageCount(parent.tenantId(), sourceMeasurementId,
|
||||
line.item().measurementItemId(), line.pickedPackageCount()), "主入库测量明细");
|
||||
requireAffected(splits.normalizeChildMetrics(parent.tenantId(), loadedChildId, loadedMeasurementId),
|
||||
"实装子单重量体积归一化");
|
||||
if (remainderChildId != 0L) {
|
||||
requireAffected(splits.normalizeChildMetrics(parent.tenantId(), remainderChildId, childMeasurementId),
|
||||
"余量子单重量体积归一化");
|
||||
}
|
||||
requireAffected(splits.markRootSplit(parent.tenantId(), rootInboundId, operatorId, parentVersion), "主入库单拆分状态");
|
||||
if ("CHILD".equals(parent.inboundRole())) {
|
||||
requireAffected(splits.markRemainderConsumed(parent.tenantId(), parentInboundId, operatorId,
|
||||
parent.version() == null ? 0 : parent.version()), "余量子单消费状态");
|
||||
}
|
||||
requireAffected(splits.markParentPartiallySplit(parent.tenantId(), parentInboundId, childPackageCount,
|
||||
operatorId, parentVersion), "主入库单拆分状态");
|
||||
requireAffected(splits.applyBatch(parent.tenantId(), batchId), "拆分批次生效");
|
||||
requireAffected(splits.insertHistory(IdWorker.getId(), parent.tenantId(), batchId,
|
||||
operationId + "-APPLY", "{\"childInboundId\":" + childId + ",\"packageCount\":"
|
||||
+ childPackageCount + "}", operatorId, operatorName, "PDA拣货自动生成子入库单"), "拆分审计");
|
||||
operationId + "-APPLY", "{\"sourceInboundId\":" + parentInboundId
|
||||
+ ",\"loadedChildInboundId\":" + loadedChildId
|
||||
+ ",\"remainderChildInboundId\":" + (remainderChildId == 0L ? "null" : remainderChildId)
|
||||
+ ",\"loadedPackageCount\":" + pickedPackageCount
|
||||
+ ",\"remainingPackageCount\":" + childPackageCount
|
||||
+ ",\"loadingPlanId\":" + loadingPlanId
|
||||
+ ",\"containerBookingId\":" + containerBookingId + "}", operatorId, operatorName,
|
||||
"PDA拣货自动生成实装/余量子入库单"), "拆分审计");
|
||||
|
||||
allocations.copyRemainingToChild(parent.tenantId(), childId, confirmedAllocations);
|
||||
if (remainderChildId != 0L) {
|
||||
allocations.copyRemainingToChild(parent.tenantId(), remainderChildId, confirmedAllocations);
|
||||
}
|
||||
allocations.markTransferred(parent.tenantId(), parentInboundId, confirmedAllocations,
|
||||
releasedReservationIds);
|
||||
allocations.markReleased(parent.tenantId(), parentInboundId, releasedReservationIds);
|
||||
settleWarehouseResources(parentInboundId, childId, childCode, taskId, operatorId, operatorName,
|
||||
settleWarehouseResources(parentInboundId, remainderChildId, childCode, taskId, operatorId, operatorName,
|
||||
warehouseId, parentAvailable, childPackageCount, releasedReservationIds, transferItems);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user