暂存提交
This commit is contained in:
+1
-1
@@ -51,7 +51,7 @@ public class PlatformInboundMeasurementReviewController {
|
||||
@Valid @RequestBody MeasurementReviewRequest request) {
|
||||
validateScope(user, tenantId);
|
||||
service.approve(tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
|
||||
// 统一订单主线要求测量审核后直接进入 WAIT_SPLIT,不把计费、库位或其他资料
|
||||
// 统一订单主线要求测量审核后直接进入 INBOUNDED,不把计费、库位或其他资料
|
||||
// 作为本版本的强制门禁。保留原 FeeSnapshotResponse 泛型以兼容既有客户端。
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ public final class InboundProcessStatus {
|
||||
public static final String INBOUNDING = "INBOUNDING";
|
||||
/** 已入库完成,费用已发送给客户。 */
|
||||
public static final String INBOUNDED = "INBOUNDED";
|
||||
/** 等待智能装柜或预装确认。 */
|
||||
public static final String WAIT_SPLIT = "WAIT_SPLIT";
|
||||
/** 已进入装柜作业,PDA 可执行拣货。 */
|
||||
public static final String LOADING = "LOADING";
|
||||
/** 装柜完成,等待派送完成。 */
|
||||
@@ -34,7 +32,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, LOADING, LOADED, DELIVERED, VOIDED
|
||||
LOADING, LOADED, DELIVERED, VOIDED
|
||||
);
|
||||
|
||||
private InboundProcessStatus() {
|
||||
|
||||
@@ -34,6 +34,7 @@ public final class InboundMeasurementRequests {
|
||||
}
|
||||
|
||||
public record ItemRequest(
|
||||
@NotNull @Positive Long inboundDetailId,
|
||||
@NotBlank @Size(max = 32) String itemNo,
|
||||
@NotBlank @Size(max = 64) String boxSpecification,
|
||||
@NotNull @Positive @Max(1000000) Integer packageCount,
|
||||
|
||||
@@ -46,6 +46,7 @@ public final class InboundMeasurementResponses {
|
||||
|
||||
public record Item(
|
||||
Long id,
|
||||
Long inboundDetailId,
|
||||
String itemNo,
|
||||
String boxSpecification,
|
||||
Integer packageCount,
|
||||
|
||||
@@ -28,6 +28,7 @@ public record MeasurementBatchCommand(
|
||||
List<Item> items
|
||||
) {
|
||||
public record Item(
|
||||
Long inboundDetailId,
|
||||
String itemNo,
|
||||
String boxSpecification,
|
||||
int packageCount,
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.apache.ibatis.annotations.Update;
|
||||
public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_SPLIT', measure_review_status = 'APPROVED',
|
||||
SET process_status = 'INBOUNDED', measure_review_status = 'APPROVED',
|
||||
inbounded_at = COALESCE(inbounded_at, CURRENT_TIMESTAMP(3)),
|
||||
updated_by = #{operatorId}, version = version + 1
|
||||
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||
|
||||
@@ -37,9 +37,9 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_loading_pick_item
|
||||
SET picked_package_count = picked_package_count + #{count},
|
||||
item_status = CASE WHEN picked_package_count + #{count} = planned_package_count
|
||||
SET item_status = CASE WHEN picked_package_count + #{count} = planned_package_count
|
||||
THEN 'PICKED' ELSE 'PICKING' END,
|
||||
picked_package_count = picked_package_count + #{count},
|
||||
version = version + 1
|
||||
WHERE id = #{id} AND version = #{version}
|
||||
AND EXISTS (SELECT 1 FROM fl_loading_plan_item plan_item
|
||||
|
||||
@@ -209,7 +209,7 @@ public class FbaPricingRepository {
|
||||
.eq(InboundPricingEntity::getProcessStatus, processStatus)
|
||||
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED"))
|
||||
.set(InboundPricingEntity::getFeeStatus, "CALCULATED");
|
||||
if (!"WAIT_SPLIT".equals(processStatus)) {
|
||||
if (!"INBOUNDED".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"))
|
||||
.in(InboundPricingEntity::getProcessStatus, List.of("FEE_CONFIRMING", "INBOUNDED"))
|
||||
.eq(InboundPricingEntity::getFeeStatus, "CALCULATED")
|
||||
.set(InboundPricingEntity::getFeeStatus, "CONFIRMED"));
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ public class FbaPricingService {
|
||||
throw conflict("入库单状态已变化,费用试算未保存");
|
||||
}
|
||||
complete(token, fee.getId(), now);
|
||||
String nextStatus = "WAIT_SPLIT".equals(inbound.getProcessStatus())
|
||||
? "WAIT_SPLIT" : "FEE_CONFIRMING";
|
||||
String nextStatus = "INBOUNDED".equals(inbound.getProcessStatus())
|
||||
? "INBOUNDED" : "FEE_CONFIRMING";
|
||||
return response(inbound, fee, rate.getUnitPrice(), nextStatus);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,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").contains(inbound.getProcessStatus())
|
||||
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDED").contains(inbound.getProcessStatus())
|
||||
|| !List.of("UNPRICED", "CALCULATED").contains(inbound.getFeeStatus())) {
|
||||
throw conflict("当前入库单状态不允许费用试算");
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package com.baiyue.firstleg.service;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementResponses;
|
||||
import com.baiyue.firstleg.dto.MeasurementBatchCommand;
|
||||
@@ -17,6 +19,8 @@ import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import feign.FeignException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -44,6 +48,10 @@ public class InboundMeasurementService {
|
||||
private InboundMeasurementRepository repository;
|
||||
@Resource
|
||||
private MeasurementBatchService batchService;
|
||||
@Resource
|
||||
private OrderCargoClient orderCargoClient;
|
||||
@Value("${baiyue.security.internal-service-token:}")
|
||||
private String internalToken;
|
||||
|
||||
public InboundMeasurementResponses.Detail detail(long tenantId, long inboundId) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
@@ -60,13 +68,14 @@ public class InboundMeasurementService {
|
||||
long tenantId, long inboundId, long operatorId,
|
||||
InboundMeasurementRequests.SaveDraftRequest request) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
validateInboundDetails(tenantId, inbound, request.items());
|
||||
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||
tenantId, inboundId, request.measurementId(), request.version(),
|
||||
MeasurementBatchService.SOURCE_MANUAL, "PLATFORM", null, null, null,
|
||||
LocalDateTime.now(), null, request.originalDocumentNo(),
|
||||
request.originalGrossWeightKg(), request.originalVolumeCbm(), request.actualGoodsName(),
|
||||
operatorId, request.items().stream().map(item -> new MeasurementBatchCommand.Item(
|
||||
item.itemNo(), item.boxSpecification(), item.packageCount(), item.lengthCm(),
|
||||
item.inboundDetailId(), item.itemNo(), item.boxSpecification(), item.packageCount(), item.lengthCm(),
|
||||
item.widthCm(), item.heightCm(), item.unitWeightKg(), item.intelligentImageFileId(),
|
||||
item.overLimitWarning(), item.outerLengthCm(), item.outerWidthCm(), item.outerHeightCm(),
|
||||
item.remark())).toList());
|
||||
@@ -286,7 +295,7 @@ public class InboundMeasurementService {
|
||||
|
||||
private InboundMeasurementResponses.Item toItem(InboundMeasurementItemEntity item) {
|
||||
return new InboundMeasurementResponses.Item(
|
||||
item.getId(), item.getItemNo(), item.getBoxSpecification(), item.getPackageCount(),
|
||||
item.getId(), item.getInboundDetailId(), item.getItemNo(), item.getBoxSpecification(), item.getPackageCount(),
|
||||
item.getLengthCm(), item.getWidthCm(), item.getHeightCm(), item.getGirthCm(),
|
||||
item.getUnitWeightKg(), item.getUnitVolumeCbm(), item.getTotalWeightKg(),
|
||||
item.getTotalVolumeCbm(), item.getVolumeWeightKg(), item.getSourceImageFileId(),
|
||||
@@ -311,6 +320,27 @@ public class InboundMeasurementService {
|
||||
return inbound;
|
||||
}
|
||||
|
||||
private void validateInboundDetails(long tenantId, InboundOrderEntity inbound,
|
||||
List<InboundMeasurementRequests.ItemRequest> items) {
|
||||
if (inbound.getOrderId() == null || inbound.getOrderId() <= 0) {
|
||||
throw new BizException(ResultCode.CONFLICT, "入库单未关联有效订单,不能保存测量明细");
|
||||
}
|
||||
for (InboundMeasurementRequests.ItemRequest item : items) {
|
||||
try {
|
||||
ApiResponse<OrderCargoClient.CargoBinding> response = orderCargoClient.detail(
|
||||
internalToken, tenantId, inbound.getOrderId(), item.inboundDetailId());
|
||||
if (response == null || !response.successful() || response.data() == null) {
|
||||
String message = response == null || !StringUtils.hasText(response.message())
|
||||
? "订单货物明细校验失败" : response.message();
|
||||
throw new BizException(ResultCode.of(response == null
|
||||
? ResultCode.SERVICE_UNAVAILABLE.code() : response.code()), message);
|
||||
}
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "订单服务暂不可用,无法校验入库明细");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InboundMeasurementEntity requireMeasurement(long tenantId, long inboundId, long measurementId) {
|
||||
InboundMeasurementEntity measurement = repository.findMeasurement(tenantId, inboundId, measurementId);
|
||||
if (measurement == null) {
|
||||
|
||||
@@ -140,6 +140,7 @@ public class MeasurementBatchService {
|
||||
BigDecimal totalVolume = unitVolume.multiply(packageCount).setScale(4, RoundingMode.CEILING);
|
||||
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setInboundDetailId(source.inboundDetailId());
|
||||
item.setItemNo(source.itemNo());
|
||||
item.setBoxSpecification(source.boxSpecification());
|
||||
item.setPackageCount(source.packageCount());
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.util.StringUtils;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 测量审核应用服务。客服确认测量数据后将入库单推进到产品主状态 WAIT_SPLIT;
|
||||
* 测量审核应用服务。客服确认测量数据后将入库单推进到产品主状态 INBOUNDED;
|
||||
* 计费、库位和其他资料不作为本版本的强制门禁。
|
||||
*/
|
||||
@Service
|
||||
@@ -80,7 +80,7 @@ public class MeasurementReviewService {
|
||||
repository.insertReview(reviewRecord(tenantId, inboundId, request.measurementId(), result,
|
||||
request.reason(), false, null, false, operatorId, operatorName));
|
||||
repository.insertHistory(history(tenantId, inboundId, "MEASURE_PENDING",
|
||||
approved ? "WAIT_SPLIT" : "MEASURE_REJECTED",
|
||||
approved ? "INBOUNDED" : "MEASURE_REJECTED",
|
||||
approved ? "MEASURE_APPROVE" : "MEASURE_REJECT", request.reason(), operatorId, operatorName));
|
||||
idempotency.complete(context, inboundId);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class SmartLoadingService {
|
||||
public Plan detail(long planId) {
|
||||
LoadingPlanEntity plan = repository.plan(planId);
|
||||
if (plan == null) throw new BizException(ResultCode.NOT_FOUND, "装柜计划不存在");
|
||||
return draftView(plan, requireBooking(plan.getContainerBookingId()));
|
||||
return view(plan, requireBooking(plan.getContainerBookingId()));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
@@ -24,11 +24,11 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Read-only adapter from internal inbound states to the seven product main statuses. */
|
||||
/** Read-only adapter from internal inbound states to the product main statuses. */
|
||||
@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", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
|
||||
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
|
||||
|
||||
@Resource private InboundOrderRepository inbounds;
|
||||
@@ -120,8 +120,8 @@ public class UnifiedInboundQueryService {
|
||||
if (status == null) return null;
|
||||
return switch (status) {
|
||||
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED" -> "WAIT_MEASURE";
|
||||
case "WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "WAIT_SPLIT" -> "WAIT_SPLIT";
|
||||
case "INBOUNDED" -> "INBOUNDED";
|
||||
// WAIT_SPLIT is retained only to normalize records created before status unification.
|
||||
case "WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "WAIT_SPLIT", "INBOUNDED" -> "INBOUNDED";
|
||||
case "LOADING", "LOADED", "DELIVERED" -> status;
|
||||
default -> null;
|
||||
};
|
||||
@@ -132,7 +132,7 @@ public class UnifiedInboundQueryService {
|
||||
return switch (mainStatus) {
|
||||
case "PENDING_REVIEW" -> "APPROVE_ORDER";
|
||||
case "WAIT_MEASURE" -> "MEASURE_PENDING".equals(processStatus) ? "CONFIRM_MEASUREMENT" : null;
|
||||
case "WAIT_SPLIT" -> !"WAIT_SPLIT".equals(processStatus) ? null
|
||||
case "INBOUNDED" -> !List.of("INBOUNDED", "WAIT_SPLIT").contains(processStatus) ? null
|
||||
: "CONFIRMED".equals(preloadStatus) ? null
|
||||
: preloadStatus == null ? "SMART_SPLIT" : "CONFIRM_PRELOAD";
|
||||
case "LOADING" -> "COMPLETE_LOADING";
|
||||
@@ -146,8 +146,8 @@ public class UnifiedInboundQueryService {
|
||||
if (!StringUtils.hasText(mainStatus)) return List.of();
|
||||
return switch (mainStatus) {
|
||||
case "WAIT_MEASURE" -> List.of("WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED");
|
||||
case "WAIT_SPLIT" -> List.of("WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "WAIT_SPLIT");
|
||||
case "INBOUNDED", "LOADING", "LOADED", "DELIVERED" -> List.of(mainStatus);
|
||||
case "INBOUNDED" -> List.of("WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "WAIT_SPLIT", "INBOUNDED");
|
||||
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='WAIT_SPLIT' AND version=#{expectedVersion}")
|
||||
+ "AND process_status='INBOUNDED' AND version=#{expectedVersion}")
|
||||
int beginLoading(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("operatorId") long operatorId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class SplitPreloadService {
|
||||
SplitActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, SMART_SPLIT, key);
|
||||
if (replay != null) return replay;
|
||||
InboundSnapshot inbound = requireInbound(tenantId, inboundId);
|
||||
if (!"WAIT_SPLIT".equals(inbound.processStatus())) {
|
||||
if (!"INBOUNDED".equals(inbound.processStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "仅待分柜订单可以生成预装清单");
|
||||
}
|
||||
PreloadPlanEntity plan = repository.findPlan(tenantId, inboundId);
|
||||
@@ -67,7 +67,7 @@ public class SplitPreloadService {
|
||||
}
|
||||
PreloadPlanResponse result = response(repository.findPlan(tenantId, inboundId));
|
||||
return complete(tenantId, inboundId, operatorId, SMART_SPLIT, key,
|
||||
new SplitActionResponse("WAIT_SPLIT", result));
|
||||
new SplitActionResponse("INBOUNDED", result));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -77,11 +77,11 @@ public class SplitPreloadService {
|
||||
SplitActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, START_CONFIRMING, key);
|
||||
if (replay != null) return replay;
|
||||
InboundSnapshot inbound = requireInbound(tenantId, inboundId);
|
||||
requireStatus(inbound, "WAIT_SPLIT", "仅待分柜订单可以提交预装清单确认");
|
||||
requireStatus(inbound, "INBOUNDED", "仅待分柜订单可以提交预装清单确认");
|
||||
PreloadPlanEntity plan = requirePlan(tenantId, inboundId);
|
||||
if (PreloadStatus.CONFIRMING.equals(plan.getStatus())) {
|
||||
return complete(tenantId, inboundId, operatorId, START_CONFIRMING, key,
|
||||
new SplitActionResponse("WAIT_SPLIT", response(plan)));
|
||||
new SplitActionResponse("INBOUNDED", response(plan)));
|
||||
}
|
||||
if (!PreloadStatus.PENDING.equals(plan.getStatus())
|
||||
|| repository.changePlanStatus(tenantId, inboundId, PreloadStatus.PENDING,
|
||||
@@ -89,7 +89,7 @@ public class SplitPreloadService {
|
||||
throw concurrent();
|
||||
}
|
||||
return complete(tenantId, inboundId, operatorId, START_CONFIRMING, key,
|
||||
new SplitActionResponse("WAIT_SPLIT", response(repository.findPlan(tenantId, inboundId))));
|
||||
new SplitActionResponse("INBOUNDED", response(repository.findPlan(tenantId, inboundId))));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -103,7 +103,7 @@ public class SplitPreloadService {
|
||||
return complete(tenantId, inboundId, operatorId, CONFIRM_PRELOAD, key,
|
||||
new SplitActionResponse("LOADING", response(repository.findPlan(tenantId, inboundId))));
|
||||
}
|
||||
requireStatus(inbound, "WAIT_SPLIT", "仅待分柜订单可以确认预装清单");
|
||||
requireStatus(inbound, "INBOUNDED", "仅待分柜订单可以确认预装清单");
|
||||
PreloadPlanEntity plan = requirePlan(tenantId, inboundId);
|
||||
if (PreloadStatus.CONFIRMED.equals(plan.getStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "预装清单已确认但订单状态尚未推进,请刷新重试");
|
||||
@@ -115,7 +115,7 @@ public class SplitPreloadService {
|
||||
if (repository.beginLoading(tenantId, inboundId, operatorId, inbound.version()) != 1) {
|
||||
throw concurrent();
|
||||
}
|
||||
addHistory(tenantId, inboundId, "WAIT_SPLIT", "LOADING", CONFIRM_PRELOAD,
|
||||
addHistory(tenantId, inboundId, "INBOUNDED", "LOADING", CONFIRM_PRELOAD,
|
||||
operatorId, operatorName);
|
||||
return complete(tenantId, inboundId, operatorId, CONFIRM_PRELOAD, key,
|
||||
new SplitActionResponse("LOADING", response(repository.findPlan(tenantId, inboundId))));
|
||||
|
||||
Reference in New Issue
Block a user