feat(phase3): connect PDA settlement to real child orders
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package com.baiyue.firstleg.client;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Order-domain child-order contract used by the transactional PDA settlement. */
|
||||
@FeignClient(name = "order-service", contextId = "firstlegOrderFulfillmentSplitClient",
|
||||
url = "${baiyue.clients.order.url:}", path = "/internal/order/v1/fulfillment-splits")
|
||||
public interface OrderFulfillmentSplitClient {
|
||||
@PostMapping
|
||||
ApiResponse<Response> split(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestHeader("X-Operator-Id") long operatorId,
|
||||
@RequestBody CreateRequest request);
|
||||
|
||||
record CreateRequest(Long tenantId, Long parentOrderId, Long sourceOrderId,
|
||||
Long loadingPlanId, Long containerBookingId,
|
||||
Integer sourcePackageCount, Integer loadedPackageCount,
|
||||
Integer remainingPackageCount, Integer expectedParentVersion,
|
||||
String idempotencyKey, String operationId,
|
||||
List<QuantityLine> lines, String operatorName) {}
|
||||
record QuantityLine(Long sourceDetailId, Integer packageCount) {}
|
||||
record Response(Long parentOrderId, String parentOrderNo, Integer parentVersion,
|
||||
Child loadedChild, Child remainderChild, Integer sourcePackageCount,
|
||||
Integer loadedPackageCount, Integer remainingPackageCount,
|
||||
Long loadingPlanId, Long containerBookingId, String idempotencyKey) {}
|
||||
record Child(Long orderId, String orderNo, Integer splitSequence, String splitRole,
|
||||
Integer packageCount, Long sourceOrderId, Long loadingPlanId,
|
||||
Long containerBookingId) {}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ public interface InboundSplitSettlementMapper {
|
||||
@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,
|
||||
SELECT id, tenant_id AS tenantId, order_id AS orderId, order_no AS orderNo,
|
||||
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
|
||||
@@ -44,7 +45,7 @@ public interface InboundSplitSettlementMapper {
|
||||
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},
|
||||
SELECT #{childId},tenant_id,task_id,#{childOrderId},#{childOrderNo},#{childInboundCode},
|
||||
CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'INBOUNDED' END,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,
|
||||
@@ -58,6 +59,7 @@ public interface InboundSplitSettlementMapper {
|
||||
""")
|
||||
int cloneChildWithPurpose(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId,
|
||||
@Param("rootInboundId") long rootInboundId, @Param("childId") long childId,
|
||||
@Param("childOrderId") long childOrderId, @Param("childOrderNo") String childOrderNo,
|
||||
@Param("childInboundCode") String childInboundCode, @Param("packageCount") int packageCount,
|
||||
@Param("splitSequence") int splitSequence, @Param("splitPurpose") String splitPurpose,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
@@ -264,12 +266,14 @@ public interface InboundSplitSettlementMapper {
|
||||
int normalizeChildMetrics(@Param("accountId") long accountId, @Param("childInboundId") long childInboundId,
|
||||
@Param("measurementId") long measurementId);
|
||||
|
||||
record ParentRow(Long id, Long tenantId, String inboundCode, String inboundRole, Long rootInboundId,
|
||||
record ParentRow(Long id, Long tenantId, Long orderId, String orderNo,
|
||||
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);
|
||||
this(id, tenantId, 901L, "ORDER-001", inboundCode, inboundRole, id,
|
||||
processStatus, totalPackageCount, packageCount, version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.client.WarehousePalletLocationClient;
|
||||
import com.baiyue.firstleg.client.OrderFulfillmentSplitClient;
|
||||
import com.baiyue.firstleg.entity.InboundPalletAllocationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
import com.baiyue.firstleg.mapper.InboundSplitSettlementMapper;
|
||||
@@ -41,6 +42,8 @@ public class PdaPickSettlementService {
|
||||
private InboundPalletAllocationRepository allocations;
|
||||
@Resource
|
||||
private WarehousePalletLocationClient warehouse;
|
||||
@Resource
|
||||
private OrderFulfillmentSplitClient orderSplits;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
@@ -154,6 +157,9 @@ public class PdaPickSettlementService {
|
||||
}
|
||||
|
||||
if (root == null || !"MASTER".equals(root.inboundRole())) throw conflict("根主入库单不存在");
|
||||
OrderFulfillmentSplitClient.Response orderSplit = createOrderSplit(parent, root, measurementLines,
|
||||
pickedPackageCount, childPackageCount, loadingPlanId, containerBookingId, operationId,
|
||||
"pda-task-" + taskId, operatorId, operatorName);
|
||||
long loadedChildId = IdWorker.getId();
|
||||
long remainderChildId = childPackageCount > 0 ? IdWorker.getId() : 0L;
|
||||
long batchId = IdWorker.getId();
|
||||
@@ -162,9 +168,11 @@ public class PdaPickSettlementService {
|
||||
int parentVersion = root.version() == null ? 0 : root.version();
|
||||
int sequence = splits.nextSplitSequence(parent.tenantId(), rootInboundId);
|
||||
requireAffected(splits.cloneChildWithPurpose(parent.tenantId(), rootInboundId, rootInboundId, loadedChildId,
|
||||
orderSplit.loadedChild().orderId(), orderSplit.loadedChild().orderNo(),
|
||||
childInboundCode(parent.inboundCode(), loadedChildId), pickedPackageCount, sequence, "LOADED", operatorId, operatorName), "实装子入库单");
|
||||
if (remainderChildId != 0L) {
|
||||
requireAffected(splits.cloneChildWithPurpose(parent.tenantId(), rootInboundId, rootInboundId, remainderChildId,
|
||||
orderSplit.remainderChild().orderId(), orderSplit.remainderChild().orderNo(),
|
||||
childCode, childPackageCount, sequence + 1, "REMAINDER", operatorId, operatorName), "余量子入库单");
|
||||
}
|
||||
requireAffected(splits.insertBatchWithChildren(batchId, parent.tenantId(), rootInboundId, operationId, parentInboundId,
|
||||
@@ -238,6 +246,62 @@ public class PdaPickSettlementService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private OrderFulfillmentSplitClient.Response createOrderSplit(
|
||||
ParentRow source, ParentRow root, List<MeasurementSplitLine> lines,
|
||||
int loadedPackageCount, int remainingPackageCount, long loadingPlanId,
|
||||
long containerBookingId, String operationId, String idempotencyKey,
|
||||
long operatorId, String operatorName) {
|
||||
if (root.orderId() == null || source.orderId() == null) {
|
||||
throw conflict("入仓单缺少真实订单引用");
|
||||
}
|
||||
List<OrderFulfillmentSplitClient.QuantityLine> quantities = lines.stream()
|
||||
.map(line -> new OrderFulfillmentSplitClient.QuantityLine(
|
||||
line.item().inboundDetailId(), line.item().packageCount()))
|
||||
.toList();
|
||||
OrderFulfillmentSplitClient.CreateRequest request = new OrderFulfillmentSplitClient.CreateRequest(
|
||||
source.tenantId(), root.orderId(), source.orderId(), loadingPlanId, containerBookingId,
|
||||
loadedPackageCount + remainingPackageCount, loadedPackageCount, remainingPackageCount,
|
||||
null, idempotencyKey, operationId, quantities, operatorName);
|
||||
ApiResponse<OrderFulfillmentSplitClient.Response> response;
|
||||
try {
|
||||
response = orderSplits.split(internalToken, operatorId, request);
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "订单服务不可用,无法生成真实履约子订单");
|
||||
}
|
||||
if (response == null || !response.successful() || response.data() == null) {
|
||||
ResultCode code = response == null ? ResultCode.SERVICE_UNAVAILABLE : ResultCode.of(response.code());
|
||||
throw new BizException(code, response == null || response.message() == null
|
||||
? "订单服务生成真实履约子订单失败" : response.message());
|
||||
}
|
||||
OrderFulfillmentSplitClient.Response split = response.data();
|
||||
boolean remainderMatches = remainingPackageCount == 0 ? split.remainderChild() == null
|
||||
: validOrderChild(split.remainderChild(), "REMAINDER", source.orderId(), loadingPlanId,
|
||||
containerBookingId, remainingPackageCount);
|
||||
if (!Objects.equals(split.parentOrderId(), root.orderId())
|
||||
|| !Objects.equals(split.sourcePackageCount(), loadedPackageCount + remainingPackageCount)
|
||||
|| !Objects.equals(split.loadedPackageCount(), loadedPackageCount)
|
||||
|| !Objects.equals(split.remainingPackageCount(), remainingPackageCount)
|
||||
|| !Objects.equals(split.loadingPlanId(), loadingPlanId)
|
||||
|| !Objects.equals(split.containerBookingId(), containerBookingId)
|
||||
|| !validOrderChild(split.loadedChild(), "LOADED", source.orderId(), loadingPlanId,
|
||||
containerBookingId, loadedPackageCount)
|
||||
|| !remainderMatches) {
|
||||
throw conflict("订单服务返回的父子订单、柜次或数量不一致");
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
private static boolean validOrderChild(OrderFulfillmentSplitClient.Child child, String role,
|
||||
Long sourceOrderId, long loadingPlanId,
|
||||
long containerBookingId, int packageCount) {
|
||||
return child != null && child.orderId() != null && child.orderId() > 0
|
||||
&& child.orderNo() != null && !child.orderNo().isBlank()
|
||||
&& role.equals(child.splitRole()) && Objects.equals(child.packageCount(), packageCount)
|
||||
&& Objects.equals(child.sourceOrderId(), sourceOrderId)
|
||||
&& Objects.equals(child.loadingPlanId(), loadingPlanId)
|
||||
&& Objects.equals(child.containerBookingId(), containerBookingId);
|
||||
}
|
||||
|
||||
private static Map<Long, Integer> pickedByInbound(List<LoadingPickItemEntity> taskItems) {
|
||||
return taskItems.stream().filter(item -> "PICKED".equals(item.getItemStatus())
|
||||
|| "PARTIAL".equals(item.getItemStatus()))
|
||||
|
||||
Reference in New Issue
Block a user