This commit is contained in:
2026-08-13 14:37:51 +08:00
parent bc62650ca9
commit 65cd7e0a39
63 changed files with 380 additions and 226 deletions
@@ -29,7 +29,7 @@ public class MerchantInboundBillingController {
@GetMapping("/{orderId}/inbound-billing")
@PreAuthorize("hasAuthority('merchant:order:view')")
@Operation(summary = "查询订单入库结果和最新费用",
description = "客户只读查看已达到 INBOUNDED + APPROVED + CONFIRMED 的二期费用,不需要确认费用。")
description = "客户只读查看已达到 WAIT_SPLIT + APPROVED + CONFIRMED 的二期费用,不需要确认费用。")
public ApiResponse<MerchantInboundBillingResponse> find(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long orderId) {
return ApiResponse.success(service.find(FirstlegScopeGuard.merchant(user), orderId));
@@ -51,7 +51,7 @@ public class PlatformInboundMeasurementReviewController {
@Valid @RequestBody MeasurementReviewRequest request) {
validateScope(user, tenantId);
service.approve(tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
// 统一订单主线要求测量审核后直接进入 INBOUNDED,不把计费、库位或其他资料
// 统一订单主线要求测量审核后直接进入 WAIT_SPLIT,不把计费、库位或其他资料
// 作为本版本的强制门禁。保留原 FeeSnapshotResponse 泛型以兼容既有客户端。
return ApiResponse.success(null);
}
@@ -10,8 +10,8 @@ public enum InboundListTab {
WAIT_REVIEW(List.of("MEASURE_PENDING")),
/** 待发送费用:等待计费、费用确认或正在完成入库。 */
WAIT_FEE_SEND(List.of("WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING")),
/** 已入库:费用已发送且入库完成。 */
INBOUNDED(List.of("INBOUNDED"));
/** 待分柜:入库和费用发布完成,等待形成有效分柜方案。 */
WAIT_SPLIT(List.of("WAIT_SPLIT"));
private final List<String> processStatuses;
@@ -18,8 +18,6 @@ public final class InboundProcessStatus {
public static final String FEE_CONFIRMING = "FEE_CONFIRMING";
/** 费用已确认,正在执行托盘、库位占用和入库收口。 */
public static final String INBOUNDING = "INBOUNDING";
/** 历史已入库终态;存量数据仍允许进入分柜。 */
public static final String INBOUNDED = "INBOUNDED";
/** 入库和费用发布完成,等待分柜。 */
public static final String WAIT_SPLIT = "WAIT_SPLIT";
/** 已进入装柜作业,PDA 可执行拣货。 */
@@ -33,7 +31,7 @@ public final class InboundProcessStatus {
public static final Set<String> VALUES = Set.of(
WAIT_MEASURE, MEASURING, MEASURE_PENDING, MEASURE_REJECTED,
WAIT_PRICING, FEE_CONFIRMING, INBOUNDING, INBOUNDED, WAIT_SPLIT,
WAIT_PRICING, FEE_CONFIRMING, INBOUNDING, WAIT_SPLIT,
LOADING, LOADED, DELIVERED, VOIDED
);
@@ -14,7 +14,7 @@ public record UnifiedInboundQuery(
Long tenantId,
@Size(max = 64) String keyword,
@Size(max = 64) String orderNo,
@Schema(allowableValues = {"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT",
@Schema(allowableValues = {"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_SPLIT",
"LOADING", "LOADED", "DELIVERED"})
@Size(max = 32) String mainStatus,
Long customerId,
@@ -62,7 +62,7 @@ public interface LoadingInboundMapper {
AND loaded.process_status<>'LOADED' AND loaded.deleted=0
SET root.process_status='LOADED', root.updated_by=#{operatorId}, root.version=root.version+1
WHERE root.tenant_id=#{tenantId} AND root.id=#{rootInboundId} AND root.deleted=0
AND root.inbound_role='MASTER' AND root.process_status IN ('INBOUNDED','LOADING')
AND root.inbound_role='MASTER' AND root.process_status IN ('WAIT_SPLIT','LOADING')
AND remainder.id IS NULL AND loaded.id IS NULL
""")
int aggregateRootLoaded(@Param("tenantId") long tenantId,
@@ -46,7 +46,7 @@ public interface InboundSplitSettlementMapper {
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,#{childOrderId},#{childOrderNo},#{childInboundCode},
CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'INBOUNDED' END,order_status_snapshot,
CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'WAIT_SPLIT' 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,
channel_code,channel_name,carrier_id,carrier_name,tax_mode,pricing_tax_mode,container_mode,customs_inspection,
@@ -151,7 +151,7 @@ public interface InboundSplitSettlementMapper {
SET package_count = GREATEST(COALESCE(package_count, total_package_count) - #{packageCount}, 0),
split_status = 'PARTIALLY_SPLIT', updated_by = #{operatorId}, version = version + 1
WHERE id = #{parentInboundId} AND tenant_id = #{accountId} AND deleted = 0
AND inbound_role = 'MASTER' AND process_status = 'INBOUNDED' AND version = #{expectedVersion}
AND inbound_role = 'MASTER' AND process_status = 'WAIT_SPLIT' AND version = #{expectedVersion}
""")
int markParentPartiallySplit(@Param("accountId") long accountId, @Param("parentInboundId") long parentInboundId,
@Param("packageCount") int packageCount, @Param("operatorId") long operatorId,
@@ -160,7 +160,7 @@ public interface InboundSplitSettlementMapper {
@Update("UPDATE fl_inbound_order SET package_count=#{loadedPackageCount},process_status='LOADING',"
+ "split_status='FULLY_SPLIT',updated_by=#{operatorId},version=version+1 "
+ "WHERE id=#{sourceInboundId} AND tenant_id=#{accountId} AND deleted=0 "
+ "AND process_status IN ('INBOUNDED','LOADING') AND split_status='NOT_SPLIT' "
+ "AND process_status IN ('WAIT_SPLIT','LOADING') AND split_status='NOT_SPLIT' "
+ "AND COALESCE(package_count,total_package_count)>=#{loadedPackageCount} AND version=#{expectedVersion}")
int markSourceLoaded(@Param("accountId") long accountId, @Param("sourceInboundId") long sourceInboundId,
@Param("loadedPackageCount") int loadedPackageCount,
@@ -23,7 +23,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
AND m.id=(SELECT MAX(m2.id) FROM fl_inbound_measurement m2
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 IN ('WAIT_SPLIT','INBOUNDED')
WHERE o.warehouse_id=#{warehouseId} AND o.process_status='WAIT_SPLIT'
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
@@ -61,7 +61,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba
AND m.id=(SELECT MAX(m2.id) FROM fl_inbound_measurement m2
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 IN ('WAIT_SPLIT','INBOUNDED')
WHERE o.warehouse_id=#{warehouseId} AND o.id=#{inboundId} AND o.process_status='WAIT_SPLIT'
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
@@ -209,7 +209,7 @@ public class FbaPricingRepository {
.eq(InboundPricingEntity::getProcessStatus, processStatus)
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED"))
.set(InboundPricingEntity::getFeeStatus, "CALCULATED");
if (!List.of("WAIT_SPLIT", "INBOUNDED").contains(processStatus)) {
if (!"WAIT_SPLIT".equals(processStatus)) {
update.set(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING");
}
if ("KG".equals(chargeMode)) update.set(InboundPricingEntity::getChargeableWeightKg, chargeable);
@@ -229,7 +229,7 @@ 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", "INBOUNDED"))
.in(InboundPricingEntity::getProcessStatus, List.of("FEE_CONFIRMING", "WAIT_SPLIT"))
.eq(InboundPricingEntity::getFeeStatus, "CALCULATED")
.set(InboundPricingEntity::getFeeStatus, "CONFIRMED"));
}
@@ -58,7 +58,7 @@ public class AutomaticInboundCompletionService {
}
private static boolean completed(InboundOrderEntity order) {
return ("WAIT_SPLIT".equals(order.getProcessStatus()) || "INBOUNDED".equals(order.getProcessStatus()))
return "WAIT_SPLIT".equals(order.getProcessStatus())
&& "SENT".equals(order.getFeeStatus());
}
@@ -209,7 +209,7 @@ public class FbaPricingService {
private InboundPricingEntity requireEligibleInbound(long tenantId, long inboundId) {
InboundPricingEntity inbound = requireApprovedInbound(tenantId, inboundId);
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING", "WAIT_SPLIT", "INBOUNDED").contains(inbound.getProcessStatus())
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING", "WAIT_SPLIT").contains(inbound.getProcessStatus())
|| !List.of("UNPRICED", "CALCULATED").contains(inbound.getFeeStatus())) {
throw conflict("当前入库单状态不允许费用试算");
}
@@ -235,7 +235,7 @@ public class FbaPricingService {
}
private static boolean isCompletedInbound(String processStatus) {
return "WAIT_SPLIT".equals(processStatus) || "INBOUNDED".equals(processStatus);
return "WAIT_SPLIT".equals(processStatus);
}
private static PricingDestinationType destinationType(InboundPricingEntity inbound) {
@@ -114,7 +114,7 @@ public class MerchantInboundBillingService {
}
private static boolean isPublished(InboundOrderEntity inbound) {
return (InboundProcessStatus.INBOUNDED.equals(inbound.getProcessStatus())
return (InboundProcessStatus.WAIT_SPLIT.equals(inbound.getProcessStatus())
|| "WAIT_SPLIT".equals(inbound.getProcessStatus()))
&& "APPROVED".equals(inbound.getMeasureReviewStatus())
&& "CONFIRMED".equals(inbound.getFeeStatus());
@@ -98,7 +98,7 @@ public class PdaPickSettlementService {
ParentRow parent = rootInboundId == parentInboundId ? root : splits.lockParent(parentInboundId);
if (parent == null || parent.tenantId() == null || parent.tenantId() <= 0
|| !("MASTER".equals(parent.inboundRole()) || "CHILD".equals(parent.inboundRole()))
|| !("INBOUNDED".equals(parent.processStatus()) || "LOADING".equals(parent.processStatus()))
|| !("WAIT_SPLIT".equals(parent.processStatus()) || "LOADING".equals(parent.processStatus()))
|| ("MASTER".equals(parent.inboundRole()) && "FULLY_SPLIT".equals(parent.splitStatus()))) {
throw conflict("待拆分主入库单不在可拆分状态");
}
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
@Service
public class UnifiedInboundQueryService {
private static final Set<String> MAIN_STATUSES = Set.of(
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
@Resource private InboundOrderRepository inbounds;
@@ -140,7 +140,8 @@ public class UnifiedInboundQueryService {
return switch (status) {
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING" -> "WAIT_MEASURE";
case "INBOUNDED", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED" -> status;
case "WAIT_SPLIT" -> "WAIT_SPLIT";
case "LOADING", "LOADED", "DELIVERED" -> status;
default -> null;
};
}
@@ -150,7 +151,7 @@ public class UnifiedInboundQueryService {
return switch (mainStatus) {
case "PENDING_REVIEW" -> "APPROVE_ORDER";
case "WAIT_MEASURE" -> "MEASURE_PENDING".equals(processStatus) ? "CONFIRM_MEASUREMENT" : null;
case "INBOUNDED", "WAIT_SPLIT" -> !("INBOUNDED".equals(processStatus) || "WAIT_SPLIT".equals(processStatus)) ? null
case "WAIT_SPLIT" -> !"WAIT_SPLIT".equals(processStatus) ? null
: "CONFIRMED".equals(preloadStatus) ? null
: preloadStatus == null ? "SMART_SPLIT" : "CONFIRM_PRELOAD";
case "LOADING" -> "COMPLETE_LOADING";
@@ -169,7 +170,6 @@ public class UnifiedInboundQueryService {
return switch (mainStatus) {
case "WAIT_MEASURE" -> List.of("WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING");
case "INBOUNDED" -> List.of("INBOUNDED");
case "WAIT_SPLIT" -> List.of("WAIT_SPLIT");
case "LOADING", "LOADED", "DELIVERED" -> List.of(mainStatus);
default -> List.of("__NO_MATCH__");
@@ -13,7 +13,7 @@ public interface SplitInboundMapper {
@Update("UPDATE fl_inbound_order SET process_status='LOADING', updated_by=#{operatorId}, "
+ "version=version+1 WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0 "
+ "AND process_status IN ('WAIT_SPLIT','INBOUNDED') AND version=#{expectedVersion}")
+ "AND process_status='WAIT_SPLIT' AND version=#{expectedVersion}")
int beginLoading(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
@@ -177,7 +177,7 @@ public class SplitPreloadService {
}
private static boolean isWaitingSplit(String status) {
return "WAIT_SPLIT".equals(status) || "INBOUNDED".equals(status);
return "WAIT_SPLIT".equals(status);
}
private void addHistory(long tenantId, long inboundId, String from, String to, String action,