优化
This commit is contained in:
@@ -31,6 +31,15 @@ public interface OrderCargoClient {
|
||||
@PathVariable long tenantId,
|
||||
@PathVariable long orderId);
|
||||
|
||||
@GetMapping("/{tenantId}/{orderId}/measurement-declaration")
|
||||
ApiResponse<MeasurementDeclarationSnapshot> measurementDeclaration(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long tenantId, @PathVariable long orderId);
|
||||
|
||||
record MeasurementDeclarationSnapshot(
|
||||
String referenceId, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm, String defaultActualGoodsName) {}
|
||||
|
||||
record CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@ public class PlatformInboundMeasurementController {
|
||||
resourceId = "#resultId", description = "确认 OCR 重量并累加实际总毛重")
|
||||
@Operation(summary = "确认 OCR 重量",
|
||||
description = "可在同一事务中携带人工毛重修改值并确认;统一换算 KG、扣除托盘皮重后,"
|
||||
+ "按所有已确认净重重算入库单 actualGrossWeightKg。")
|
||||
+ "按所有已确认结果分别重算入库单毛重与扣皮后的货物净重。")
|
||||
@PostMapping("/weight-results/{resultId}/confirm")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:weight:confirm')"
|
||||
+ " and (!#request.hasAdjustment() or hasAuthority('platform:firstleg:inbound:weight:edit'))")
|
||||
|
||||
-36
@@ -6,7 +6,6 @@ import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.ExceptionReleaseRequest;
|
||||
import com.baiyue.firstleg.dto.MeasurementReviewRequest;
|
||||
import com.baiyue.firstleg.dto.MeasurementApprovalResponse;
|
||||
import com.baiyue.firstleg.entity.MeasureReviewRecordEntity;
|
||||
@@ -57,41 +56,6 @@ public class PlatformInboundMeasurementReviewController {
|
||||
request.measurementId(), request.expectedVersion() + 1));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASURE_REJECT", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "测量审核驳回")
|
||||
@Operation(summary = "测量审核驳回",
|
||||
description = "驳回原因必填,只退回测量数据修改;已确认的托盘和库位占用保持不动。")
|
||||
@PostMapping("/{inboundId}/measurements/reject")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:measurement-review')")
|
||||
public ApiResponse<Void> reject(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody MeasurementReviewRequest request) {
|
||||
validateScope(user, tenantId);
|
||||
service.reject(tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASURE_EXCEPTION_RELEASE", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "测量驳回异常放行")
|
||||
@Operation(summary = "测量驳回异常放行",
|
||||
description = "Nacos 开关开启、有异常放行权限且原因非空时,才可从 MEASURE_REJECTED 推进至 WAIT_SPLIT。")
|
||||
@PostMapping("/{inboundId}/measurements/exception-release")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:exception-release')")
|
||||
public ApiResponse<Void> exceptionRelease(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody ExceptionReleaseRequest request) {
|
||||
validateScope(user, tenantId);
|
||||
service.exceptionRelease(
|
||||
tenantId, user.userId(), user.username(), inboundId, idempotencyKey, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询测量审核历史")
|
||||
@GetMapping("/{inboundId}/measurement-review-history")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:measurement-review-history')")
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 当前业务单元所关联柜次的正式运输事实。 */
|
||||
public record CurrentContainerTransport(
|
||||
Long inboundId, Long accountId, Long containerBookingId,
|
||||
String containerNo, String cabinetNo, String vesselName, String voyageNo,
|
||||
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt,
|
||||
LocalDateTime estimatedPortArrivalAt, LocalDateTime actualPortArrivalAt
|
||||
) {}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.DecimalMax;
|
||||
@@ -26,12 +27,13 @@ public final class InboundMeasurementRequests {
|
||||
public record SaveDraftRequest(
|
||||
Long measurementId,
|
||||
@NotNull @PositiveOrZero Integer version,
|
||||
@Size(max = 64) String originalDocumentNo,
|
||||
@DecimalMin("0.000") @Digits(integer = 15, fraction = 3) BigDecimal originalGrossWeightKg,
|
||||
@DecimalMin("0.0000") @Digits(integer = 14, fraction = 4) BigDecimal originalVolumeCbm,
|
||||
@Size(max = 255) String actualGoodsName,
|
||||
@NotEmpty @Size(max = 500) List<@Valid ItemRequest> items
|
||||
) {
|
||||
@JsonAnySetter
|
||||
public void rejectUnknownField(String fieldName, Object ignored) {
|
||||
throw new IllegalArgumentException("测量草稿不接受字段: " + fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
public record ItemRequest(
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class InboundMeasurementResponses {
|
||||
Integer originalPackageCount,
|
||||
Integer actualPackageCount,
|
||||
BigDecimal actualGrossWeightKg,
|
||||
BigDecimal actualNetWeightKg,
|
||||
BigDecimal actualVolumeCbm,
|
||||
BigDecimal volumeWeightKg,
|
||||
String actualGoodsName,
|
||||
|
||||
@@ -28,12 +28,25 @@ public record InboundOrderSummary(
|
||||
LocalDateTime measureCompletedAt, LocalDateTime inboundedAt,
|
||||
String expressCompanyCode, String expressCompanyName, String expressTrackingNo,
|
||||
String containerNo, String cabinetNo,
|
||||
String vesselName, String voyageNo, LocalDateTime sailingAt, LocalDateTime portArrivalAt,
|
||||
String vesselName, String voyageNo,
|
||||
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt,
|
||||
LocalDateTime estimatedPortArrivalAt, LocalDateTime actualPortArrivalAt,
|
||||
LocalDateTime createdAt, LocalDateTime updatedAt
|
||||
) {
|
||||
/** 非统一入仓接口保留既有快照适配;统一订单不得调用此重载。 */
|
||||
public static InboundOrderSummary from(
|
||||
InboundOrderEntity order, InboundTransportSnapshotEntity transport,
|
||||
InboundPickupSnapshotEntity pickup, String locationCodes) {
|
||||
CurrentContainerTransport adapted = transport == null ? null : new CurrentContainerTransport(
|
||||
order.getId(), order.getTenantId(), null, transport.getContainerNo(), transport.getCabinetNo(),
|
||||
transport.getVesselName(), transport.getVoyageNo(), transport.getSailingAt(), null,
|
||||
transport.getPortArrivalAt(), null);
|
||||
return from(order, adapted, pickup, locationCodes);
|
||||
}
|
||||
|
||||
public static InboundOrderSummary from(
|
||||
InboundOrderEntity order, CurrentContainerTransport transport,
|
||||
InboundPickupSnapshotEntity pickup, String locationCodes) {
|
||||
return new InboundOrderSummary(
|
||||
order.getId(), order.getTenantId(), order.getOrderId(), order.getOrderNo(), order.getInboundCode(),
|
||||
order.getProcessStatus(), order.getOrderStatusSnapshot(), order.getMeasureReviewStatus(),
|
||||
@@ -53,12 +66,14 @@ public record InboundOrderSummary(
|
||||
locationCodes,
|
||||
order.getMeasureCompletedAt(), order.getInboundedAt(), order.getExpressCompanyCode(),
|
||||
order.getExpressCompanyName(), order.getExpressTrackingNo(),
|
||||
transport == null ? null : transport.getContainerNo(),
|
||||
transport == null ? null : transport.getCabinetNo(),
|
||||
transport == null ? null : transport.getVesselName(),
|
||||
transport == null ? null : transport.getVoyageNo(),
|
||||
transport == null ? null : transport.getSailingAt(),
|
||||
transport == null ? null : transport.getPortArrivalAt(),
|
||||
transport == null ? null : transport.containerNo(),
|
||||
transport == null ? null : transport.cabinetNo(),
|
||||
transport == null ? null : transport.vesselName(),
|
||||
transport == null ? null : transport.voyageNo(),
|
||||
transport == null ? null : transport.estimatedSailingAt(),
|
||||
transport == null ? null : transport.actualSailingAt(),
|
||||
transport == null ? null : transport.estimatedPortArrivalAt(),
|
||||
transport == null ? null : transport.actualPortArrivalAt(),
|
||||
order.getCreatedAt(), order.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public record MerchantInboundBillingResponse(InboundOrderDetail inbound,
|
||||
|
||||
public record MeasurementSummary(
|
||||
Long measurementId, String sourceType, String measurementStatus,
|
||||
Integer actualPackageCount, BigDecimal actualGrossWeightKg,
|
||||
Integer actualPackageCount, BigDecimal actualGrossWeightKg, BigDecimal actualNetWeightKg,
|
||||
BigDecimal actualVolumeCbm, BigDecimal volumeWeightKg, String actualGoodsName) {
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ public class InboundPricingEntity extends BaseEntity {
|
||||
private Long destinationWarehouseId;
|
||||
private String destinationName;
|
||||
private BigDecimal actualGrossWeightKg;
|
||||
private BigDecimal actualNetWeightKg;
|
||||
private BigDecimal actualVolumeCbm;
|
||||
private BigDecimal chargeableWeightKg;
|
||||
private BigDecimal chargeableVolumeCbm;
|
||||
|
||||
@@ -12,19 +12,14 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_SPLIT', measure_review_status = 'APPROVED',
|
||||
actual_gross_weight_kg = CASE
|
||||
WHEN EXISTS (SELECT 1
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED')
|
||||
THEN (SELECT COALESCE(SUM(weight.net_weight_kg), 0)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED')
|
||||
ELSE (SELECT COALESCE(SUM(item.total_weight_kg), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId})
|
||||
END,
|
||||
actual_gross_weight_kg = (SELECT SUM(weight.gross_weight_kg)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED'),
|
||||
actual_net_weight_kg = (SELECT SUM(weight.net_weight_kg)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED'),
|
||||
actual_volume_cbm = (SELECT COALESCE(SUM(item.total_volume_cbm), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baiyue.firstleg.dto.CurrentContainerTransport;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 统一订单按当前有效装柜明细批量读取柜次船期。 */
|
||||
@Mapper
|
||||
public interface UnifiedInboundTransportMapper {
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT DISTINCT i.inbound_id, i.account_id, p.container_booking_id,
|
||||
b.container_no, b.cabinet_no, b.vessel_name, b.voyage_no,
|
||||
b.estimated_sailing_at, b.actual_sailing_at,
|
||||
b.estimated_port_arrival_at, b.actual_port_arrival_at
|
||||
FROM fl_loading_plan_item i
|
||||
JOIN fl_loading_plan p ON p.id = i.loading_plan_id
|
||||
JOIN fl_container_booking b ON b.id = p.container_booking_id
|
||||
WHERE i.deleted = 0
|
||||
AND p.plan_status IN ('LOADING', 'COMPLETED')
|
||||
AND i.inbound_id IN
|
||||
<foreach collection="inboundIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</script>
|
||||
""")
|
||||
List<CurrentContainerTransport> findCurrentByInboundIds(@Param("inboundIds") List<Long> inboundIds);
|
||||
}
|
||||
+2
-1
@@ -59,7 +59,8 @@ public class InboundMeasurementRepository {
|
||||
return inboundOrders.update(inbound, new LambdaUpdateWrapper<InboundOrderEntity>()
|
||||
.eq(InboundOrderEntity::getTenantId, inbound.getTenantId())
|
||||
.eq(InboundOrderEntity::getId, inbound.getId())
|
||||
.set(InboundOrderEntity::getActualGrossWeightKg, inbound.getActualGrossWeightKg())) == 1;
|
||||
.set(InboundOrderEntity::getActualGrossWeightKg, inbound.getActualGrossWeightKg())
|
||||
.set(InboundOrderEntity::getActualNetWeightKg, inbound.getActualNetWeightKg())) == 1;
|
||||
}
|
||||
|
||||
public InboundMeasurementEntity findMeasurement(long tenantId, long inboundId, long measurementId) {
|
||||
|
||||
@@ -154,29 +154,31 @@ public class InboundOrderRepository {
|
||||
+ "OR (newer.fee_version = f.fee_version AND newer.id > f.id))))",
|
||||
query.feeReviewStatus())
|
||||
.apply(StringUtils.hasText(query.containerNo()),
|
||||
"EXISTS (SELECT 1 FROM fl_inbound_transport_snapshot t "
|
||||
+ "WHERE t.tenant_id = fl_inbound_order.tenant_id AND t.inbound_id = fl_inbound_order.id "
|
||||
+ "AND t.container_no LIKE CONCAT('%', {0}, '%'))", query.containerNo())
|
||||
"EXISTS (SELECT 1 FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id "
|
||||
+ "JOIN fl_container_booking b ON b.id=p.container_booking_id WHERE i.deleted=0 "
|
||||
+ "AND p.plan_status IN ('LOADING','COMPLETED') AND i.account_id=fl_inbound_order.tenant_id "
|
||||
+ "AND i.inbound_id=fl_inbound_order.id AND b.container_no LIKE CONCAT('%', {0}, '%'))", query.containerNo())
|
||||
.apply(StringUtils.hasText(query.cabinetNo()),
|
||||
"EXISTS (SELECT 1 FROM fl_inbound_transport_snapshot t "
|
||||
+ "WHERE t.tenant_id = fl_inbound_order.tenant_id AND t.inbound_id = fl_inbound_order.id "
|
||||
+ "AND t.cabinet_no LIKE CONCAT('%', {0}, '%'))", query.cabinetNo())
|
||||
"EXISTS (SELECT 1 FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id "
|
||||
+ "JOIN fl_container_booking b ON b.id=p.container_booking_id WHERE i.deleted=0 "
|
||||
+ "AND p.plan_status IN ('LOADING','COMPLETED') AND i.account_id=fl_inbound_order.tenant_id "
|
||||
+ "AND i.inbound_id=fl_inbound_order.id AND b.cabinet_no LIKE CONCAT('%', {0}, '%'))", query.cabinetNo())
|
||||
.apply(query.sailingDate() != null,
|
||||
"EXISTS (SELECT 1 FROM fl_inbound_transport_snapshot t "
|
||||
+ "WHERE t.tenant_id = fl_inbound_order.tenant_id AND t.inbound_id = fl_inbound_order.id "
|
||||
+ "AND t.sailing_at >= {0} AND t.sailing_at < {1})",
|
||||
"EXISTS (SELECT 1 FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id "
|
||||
+ "JOIN fl_container_booking b ON b.id=p.container_booking_id WHERE i.deleted=0 "
|
||||
+ "AND p.plan_status IN ('LOADING','COMPLETED') AND i.account_id=fl_inbound_order.tenant_id "
|
||||
+ "AND i.inbound_id=fl_inbound_order.id AND COALESCE(b.actual_sailing_at,b.estimated_sailing_at) "
|
||||
+ ">= {0} AND COALESCE(b.actual_sailing_at,b.estimated_sailing_at) < {1})",
|
||||
query.sailingDate() == null ? null : query.sailingDate().atStartOfDay(),
|
||||
query.sailingDate() == null ? null : query.sailingDate().plusDays(1).atStartOfDay())
|
||||
.apply(Boolean.TRUE.equals(query.loaded()),
|
||||
"EXISTS (SELECT 1 FROM fl_inbound_transport_snapshot t "
|
||||
+ "WHERE t.tenant_id = fl_inbound_order.tenant_id AND t.inbound_id = fl_inbound_order.id "
|
||||
+ "AND COALESCE(TRIM(t.loading_status), '') <> '' "
|
||||
+ "AND t.loading_status NOT IN ('UNLOADED', 'PENDING'))")
|
||||
"EXISTS (SELECT 1 FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id "
|
||||
+ "WHERE i.deleted=0 AND p.plan_status='COMPLETED' "
|
||||
+ "AND i.account_id=fl_inbound_order.tenant_id AND i.inbound_id=fl_inbound_order.id)")
|
||||
.apply(Boolean.FALSE.equals(query.loaded()),
|
||||
"NOT EXISTS (SELECT 1 FROM fl_inbound_transport_snapshot t "
|
||||
+ "WHERE t.tenant_id = fl_inbound_order.tenant_id AND t.inbound_id = fl_inbound_order.id "
|
||||
+ "AND COALESCE(TRIM(t.loading_status), '') <> '' "
|
||||
+ "AND t.loading_status NOT IN ('UNLOADED', 'PENDING'))")
|
||||
"NOT EXISTS (SELECT 1 FROM fl_loading_plan_item i JOIN fl_loading_plan p ON p.id=i.loading_plan_id "
|
||||
+ "WHERE i.deleted=0 AND p.plan_status='COMPLETED' "
|
||||
+ "AND i.account_id=fl_inbound_order.tenant_id AND i.inbound_id=fl_inbound_order.id)")
|
||||
.ge(query.createdFrom() != null, InboundOrderEntity::getCreatedAt, query.createdFrom())
|
||||
.le(query.createdTo() != null, InboundOrderEntity::getCreatedAt, query.createdTo())
|
||||
.orderByDesc(InboundOrderEntity::getCreatedAt).orderByDesc(InboundOrderEntity::getId);
|
||||
|
||||
+19
-13
@@ -8,9 +8,11 @@ import com.baiyue.firstleg.loading.mapper.InboundLoadingPickTaskMapper;
|
||||
import com.baiyue.firstleg.split.entity.PreloadPlanEntity;
|
||||
import com.baiyue.firstleg.split.mapper.PreloadPlanMapper;
|
||||
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.InboundTransportSnapshotEntity;
|
||||
import com.baiyue.firstleg.dto.CurrentContainerTransport;
|
||||
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundTransportSnapshotMapper;
|
||||
import com.baiyue.firstleg.mapper.UnifiedInboundTransportMapper;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -26,7 +28,7 @@ public class UnifiedInboundReadRepository {
|
||||
@Resource private InboundLoadingPickTaskMapper picks;
|
||||
@Resource private LoadingEvidenceMapper evidences;
|
||||
@Resource private InboundStatusHistoryMapper histories;
|
||||
@Resource private InboundTransportSnapshotMapper transports;
|
||||
@Resource private UnifiedInboundTransportMapper transports;
|
||||
|
||||
public PreloadPlanEntity findPreload(long tenantId, long inboundId) {
|
||||
return preloads.selectOne(new LambdaQueryWrapper<PreloadPlanEntity>()
|
||||
@@ -84,19 +86,23 @@ public class UnifiedInboundReadRepository {
|
||||
Function.identity(), (latest, ignored) -> latest));
|
||||
}
|
||||
|
||||
public InboundTransportSnapshotEntity findTransport(long tenantId, long inboundId) {
|
||||
return transports.selectOne(new LambdaQueryWrapper<InboundTransportSnapshotEntity>()
|
||||
.eq(InboundTransportSnapshotEntity::getTenantId, tenantId)
|
||||
.eq(InboundTransportSnapshotEntity::getInboundId, inboundId));
|
||||
public CurrentContainerTransport findTransport(long tenantId, long inboundId) {
|
||||
return findTransports(Map.of(inboundId, tenantId)).get(inboundId);
|
||||
}
|
||||
|
||||
public Map<Long, InboundTransportSnapshotEntity> findTransports(Map<Long, Long> tenantByInbound) {
|
||||
public Map<Long, CurrentContainerTransport> findTransports(Map<Long, Long> tenantByInbound) {
|
||||
if (tenantByInbound.isEmpty()) return Map.of();
|
||||
return transports.selectList(new LambdaQueryWrapper<InboundTransportSnapshotEntity>()
|
||||
.in(InboundTransportSnapshotEntity::getInboundId, tenantByInbound.keySet()))
|
||||
.stream().filter(item -> owns(tenantByInbound, item.getInboundId(), item.getTenantId()))
|
||||
.collect(Collectors.toMap(InboundTransportSnapshotEntity::getInboundId,
|
||||
Function.identity(), (left, right) -> left));
|
||||
Map<Long, List<CurrentContainerTransport>> grouped = transports
|
||||
.findCurrentByInboundIds(List.copyOf(tenantByInbound.keySet())).stream()
|
||||
.filter(item -> owns(tenantByInbound, item.inboundId(), item.accountId()))
|
||||
.collect(Collectors.groupingBy(CurrentContainerTransport::inboundId));
|
||||
grouped.forEach((inboundId, rows) -> {
|
||||
long bookingCount = rows.stream().map(CurrentContainerTransport::containerBookingId).distinct().count();
|
||||
if (bookingCount > 1) throw new BizException(ResultCode.CONFLICT,
|
||||
"同一入库业务单元关联多个有效柜次: inboundId=" + inboundId);
|
||||
});
|
||||
return grouped.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> entry.getValue().get(0)));
|
||||
}
|
||||
|
||||
private static boolean owns(Map<Long, Long> tenantByInbound, Long inboundId, Long tenantId) {
|
||||
|
||||
@@ -150,21 +150,24 @@ public class CartonMeasurementService {
|
||||
.filter(weight -> "CONFIRMED".equals(weight.getRecognitionStatus()))
|
||||
.toList();
|
||||
if (!confirmedWeights.isEmpty()) {
|
||||
if (confirmedWeights.stream().anyMatch(weight -> !positive(weight.getGrossWeightKg())
|
||||
|| !positive(weight.getNetWeightKg()))) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"已确认称重结果缺少有效毛重或净重,请重新确认后再修改箱数");
|
||||
}
|
||||
BigDecimal confirmedGrossWeight = confirmedWeights.stream()
|
||||
.map(InboundWeightResultEntity::getGrossWeightKg)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
BigDecimal confirmedNetWeight = confirmedWeights.stream()
|
||||
.map(InboundWeightResultEntity::getNetWeightKg)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(3, RoundingMode.CEILING);
|
||||
inbound.setActualGrossWeightKg(confirmedNetWeight);
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
inbound.setActualGrossWeightKg(confirmedGrossWeight);
|
||||
inbound.setActualNetWeightKg(confirmedNetWeight);
|
||||
} else {
|
||||
BigDecimal totalWeight = items.stream()
|
||||
.map(InboundMeasurementItemEntity::getTotalWeightKg)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(3, RoundingMode.CEILING);
|
||||
if (positive(totalWeight)) {
|
||||
inbound.setActualGrossWeightKg(totalWeight);
|
||||
}
|
||||
inbound.setActualGrossWeightKg(null);
|
||||
inbound.setActualNetWeightKg(null);
|
||||
}
|
||||
inbound.setActualVolumeCbm(measurement.getTotalVolumeCbm());
|
||||
inbound.setVolumeWeightKg(measurement.getVolumeWeightKg());
|
||||
|
||||
@@ -58,9 +58,9 @@ public class FbaPricingService {
|
||||
ActivePriceBookEstimator.Estimate estimate = priceBookEstimator.estimate(new EstimateRequest(
|
||||
inbound.getWarehouseId(), inbound.getChannelCode(), inbound.getDestinationCode(),
|
||||
destinationType(inbound), inbound.getDestinationWarehouseId(), inbound.getDestinationName(), null,
|
||||
pricingTaxMode(inbound), inbound.getActualGrossWeightKg(), inbound.getActualVolumeCbm()));
|
||||
pricingTaxMode(inbound), inbound.getActualNetWeightKg(), inbound.getActualVolumeCbm()));
|
||||
RateRow rate = estimate.rate();
|
||||
BigDecimal weight = inbound.getActualGrossWeightKg().setScale(3, RoundingMode.HALF_UP);
|
||||
BigDecimal weight = inbound.getActualNetWeightKg().setScale(3, RoundingMode.HALF_UP);
|
||||
BigDecimal volume = inbound.getActualVolumeCbm().setScale(4, RoundingMode.HALF_UP);
|
||||
BigDecimal chargeable = estimate.chargeableQuantity();
|
||||
BigDecimal converted = chargeable;
|
||||
@@ -240,9 +240,9 @@ public class FbaPricingService {
|
||||
throw conflict("当前目的地类型不支持自动计费");
|
||||
}
|
||||
if (!"APPROVED".equals(inbound.getMeasureReviewStatus())) throw conflict("只能消费审核通过的测量快照");
|
||||
if (inbound.getActualGrossWeightKg() == null || inbound.getActualGrossWeightKg().signum() <= 0
|
||||
if (inbound.getActualNetWeightKg() == null || inbound.getActualNetWeightKg().signum() <= 0
|
||||
|| inbound.getActualVolumeCbm() == null || inbound.getActualVolumeCbm().signum() <= 0) {
|
||||
throw conflict("审核通过的测量快照必须包含大于零的实重和体积");
|
||||
throw conflict("审核通过的测量快照必须包含大于零的货物净重和体积");
|
||||
}
|
||||
if (inbound.getCustomerId() == null || inbound.getCustomerId() <= 0) {
|
||||
throw conflict("入库单缺少订单客户归属,不能生成对客费用");
|
||||
|
||||
@@ -390,7 +390,8 @@ public class InboundFeeDetailService {
|
||||
value.setDestinationCode(inbound.getDestinationCode());
|
||||
value.setDestinationName(inbound.getDestinationName());
|
||||
value.setPackageCount(inbound.getTotalPackageCount());
|
||||
value.setActualGrossWeightKg(inbound.getActualGrossWeightKg());
|
||||
// 财务历史字段名保留,但计费事实统一使用扣除托盘皮重后的货物净重。
|
||||
value.setActualGrossWeightKg(inbound.getActualNetWeightKg());
|
||||
value.setActualVolumeCbm(inbound.getActualVolumeCbm());
|
||||
value.setVolumeWeightKg(inbound.getVolumeWeightKg());
|
||||
value.setChargeableWeightKg(inbound.getChargeableWeightKg());
|
||||
|
||||
+118
-41
@@ -68,7 +68,9 @@ public class InboundMeasurementService {
|
||||
? List.of() : repository.listItems(tenantId, measurement.getId());
|
||||
List<InboundWeightResultEntity> weights = repository.listWeights(tenantId, inboundId);
|
||||
MeasureReviewRecordEntity review = repository.findLatestReview(tenantId, inboundId);
|
||||
return toDetail(inbound, measurement, items, weights, review);
|
||||
OrderCargoClient.MeasurementDeclarationSnapshot declaration = needsDeclarationFallback(measurement)
|
||||
? loadDeclaration(inbound) : null;
|
||||
return toDetail(inbound, measurement, items, weights, review, declaration);
|
||||
}
|
||||
|
||||
public List<InboundMeasurementResponses.PalletSnapshot> weightPalletOptions(
|
||||
@@ -87,11 +89,23 @@ public class InboundMeasurementService {
|
||||
InboundMeasurementRequests.SaveDraftRequest request) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
validateInboundDetails(tenantId, inbound, request.items());
|
||||
InboundMeasurementEntity existing = request.measurementId() == null ? null
|
||||
: repository.findMeasurement(tenantId, inboundId, request.measurementId());
|
||||
OrderCargoClient.MeasurementDeclarationSnapshot declaration = loadDeclaration(inbound);
|
||||
String actualGoodsName = StringUtils.hasText(request.actualGoodsName()) ? request.actualGoodsName().trim()
|
||||
: existing != null && StringUtils.hasText(existing.getActualGoodsName())
|
||||
? existing.getActualGoodsName() : defaultGoodsName(declaration);
|
||||
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(),
|
||||
LocalDateTime.now(), null,
|
||||
existing != null && StringUtils.hasText(existing.getOriginalDocumentNo())
|
||||
? existing.getOriginalDocumentNo() : declaration.referenceId(),
|
||||
existing != null && existing.getOriginalGrossWeightKg() != null
|
||||
? existing.getOriginalGrossWeightKg() : declaration.originalGrossWeightKg(),
|
||||
existing != null && existing.getOriginalVolumeCbm() != null
|
||||
? existing.getOriginalVolumeCbm() : declaration.originalVolumeCbm(),
|
||||
actualGoodsName,
|
||||
operatorId, request.items().stream().map(item -> new MeasurementBatchCommand.Item(
|
||||
item.inboundDetailId(), item.itemNo(), item.boxSpecification(), item.packageCount(), item.lengthCm(),
|
||||
item.widthCm(), item.heightCm(), item.unitWeightKg(), item.intelligentImageFileId(),
|
||||
@@ -116,10 +130,7 @@ public class InboundMeasurementService {
|
||||
}
|
||||
List<InboundMeasurementItemEntity> items = repository.listItems(tenantId, measurement.getId());
|
||||
List<InboundWeightResultEntity> weights = repository.listWeights(tenantId, inboundId);
|
||||
BigDecimal confirmedWeight = confirmedWeightForSubmission(weights);
|
||||
int confirmedPalletCount = Math.toIntExact(weights.stream()
|
||||
.filter(weight -> WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus()))
|
||||
.count());
|
||||
ConfirmedWeightTotals confirmedTotals = confirmedWeightTotals(weights);
|
||||
|
||||
if (!alreadyAdvanced) {
|
||||
requireConfirmedPackageTotal(inbound, items);
|
||||
@@ -137,10 +148,9 @@ public class InboundMeasurementService {
|
||||
if (measurement.getTotalPackageCount() != null) {
|
||||
inbound.setTotalPackageCount(measurement.getTotalPackageCount());
|
||||
}
|
||||
if (confirmedWeight != null) {
|
||||
inbound.setActualGrossWeightKg(confirmedWeight);
|
||||
}
|
||||
inbound.setPalletCount(confirmedPalletCount);
|
||||
inbound.setActualGrossWeightKg(confirmedTotals.grossWeightKg());
|
||||
inbound.setActualNetWeightKg(confirmedTotals.netWeightKg());
|
||||
inbound.setPalletCount(confirmedTotals.palletCount());
|
||||
if (measurement.getTotalVolumeCbm() != null) {
|
||||
inbound.setActualVolumeCbm(measurement.getTotalVolumeCbm());
|
||||
}
|
||||
@@ -161,6 +171,7 @@ public class InboundMeasurementService {
|
||||
long tenantId, long inboundId, long operatorId,
|
||||
InboundMeasurementRequests.CreateWeightResultRequest request) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
requireWeightEditableInbound(inbound);
|
||||
if (repository.findWeightByFile(tenantId, inboundId, request.fileId()) != null) {
|
||||
throw new BizException(ResultCode.CONFLICT, "同一称重图片不能重复计重");
|
||||
}
|
||||
@@ -192,6 +203,7 @@ public class InboundMeasurementService {
|
||||
weight.setDeleted(false);
|
||||
weight.setVersion(0);
|
||||
repository.insertWeight(weight);
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
return toWeight(weight);
|
||||
}
|
||||
|
||||
@@ -200,14 +212,12 @@ public class InboundMeasurementService {
|
||||
long tenantId, long inboundId, long resultId, long operatorId, String operatorName,
|
||||
InboundMeasurementRequests.CorrectWeightResultRequest request) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
requireWeightEditableInbound(inbound);
|
||||
InboundWeightResultEntity weight = requireWeight(tenantId, inboundId, resultId);
|
||||
requireVersion(request.version(), weight.getVersion());
|
||||
boolean wasConfirmed = WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus());
|
||||
applyWeightCorrection(weight, request.version(), request.adjustedWeightValue(),
|
||||
request.adjustedWeightUnit(), request.adjustmentReason(), operatorId, operatorName);
|
||||
if (wasConfirmed) {
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
}
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
return toWeight(weight);
|
||||
}
|
||||
|
||||
@@ -246,6 +256,7 @@ public class InboundMeasurementService {
|
||||
long tenantId, long inboundId, long resultId, long operatorId, String operatorName,
|
||||
InboundMeasurementRequests.ConfirmWeightResultRequest request) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
requireWeightConfirmableInbound(inbound, request.hasAdjustment());
|
||||
InboundWeightResultEntity weight = requireWeight(tenantId, inboundId, resultId);
|
||||
requireVersion(request.version(), weight.getVersion());
|
||||
int currentVersion = request.version();
|
||||
@@ -291,19 +302,15 @@ public class InboundMeasurementService {
|
||||
|
||||
@Transactional
|
||||
public void deleteWeightResult(long tenantId, long inboundId, long resultId,
|
||||
int version, long operatorId) {
|
||||
int version, long operatorId) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
if (!WEIGHT_EDITABLE_STATUSES.contains(inbound.getProcessStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "当前入库状态不允许删除称重图片");
|
||||
}
|
||||
requireWeightEditableInbound(inbound);
|
||||
InboundWeightResultEntity weight = requireWeight(tenantId, inboundId, resultId);
|
||||
requireVersion(version, weight.getVersion());
|
||||
if (!repository.deleteWeight(tenantId, inboundId, resultId, version)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
if (WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus())) {
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
}
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
try {
|
||||
ApiResponse<Void> response = imageClient.delete(
|
||||
internalToken, operatorId, tenantId, inboundId, weight.getFileId());
|
||||
@@ -319,15 +326,10 @@ public class InboundMeasurementService {
|
||||
}
|
||||
|
||||
private void recalculateConfirmedWeight(InboundOrderEntity inbound, long operatorId) {
|
||||
List<InboundWeightResultEntity> confirmedWeights = repository
|
||||
.listWeights(inbound.getTenantId(), inbound.getId()).stream()
|
||||
.filter(weight -> WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus()))
|
||||
.toList();
|
||||
BigDecimal total = confirmedWeights.stream()
|
||||
.map(InboundWeightResultEntity::getNetWeightKg)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
inbound.setActualGrossWeightKg(confirmedWeights.isEmpty() ? null : total);
|
||||
ConfirmedWeightTotals totals = confirmedWeightTotals(
|
||||
repository.listWeights(inbound.getTenantId(), inbound.getId()));
|
||||
inbound.setActualGrossWeightKg(totals.grossWeightKg());
|
||||
inbound.setActualNetWeightKg(totals.netWeightKg());
|
||||
if ("WAIT_MEASURE".equals(inbound.getProcessStatus())) {
|
||||
inbound.setProcessStatus("MEASURING");
|
||||
}
|
||||
@@ -337,13 +339,31 @@ public class InboundMeasurementService {
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal confirmedWeightForSubmission(List<InboundWeightResultEntity> weights) {
|
||||
BigDecimal total = weights.stream()
|
||||
private ConfirmedWeightTotals confirmedWeightTotals(List<InboundWeightResultEntity> weights) {
|
||||
List<InboundWeightResultEntity> confirmed = weights.stream()
|
||||
.filter(weight -> WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus()))
|
||||
.toList();
|
||||
if (confirmed.isEmpty()) {
|
||||
return new ConfirmedWeightTotals(null, null, 0);
|
||||
}
|
||||
if (confirmed.stream().anyMatch(weight -> !isPositive(weight.getGrossWeightKg())
|
||||
|| !isPositive(weight.getNetWeightKg()))) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"已确认称重结果缺少有效毛重或净重,请重新确认后再继续");
|
||||
}
|
||||
BigDecimal grossWeightKg = confirmed.stream()
|
||||
.map(InboundWeightResultEntity::getGrossWeightKg)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
BigDecimal netWeightKg = confirmed.stream()
|
||||
.map(InboundWeightResultEntity::getNetWeightKg)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(3, RoundingMode.HALF_UP);
|
||||
return isPositive(total) ? total : null;
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
return new ConfirmedWeightTotals(grossWeightKg, netWeightKg, confirmed.size());
|
||||
}
|
||||
|
||||
private record ConfirmedWeightTotals(
|
||||
BigDecimal grossWeightKg, BigDecimal netWeightKg, int palletCount) {
|
||||
}
|
||||
|
||||
private void requireCompleteAdjustment(BigDecimal value, String unit, String reason) {
|
||||
@@ -353,6 +373,23 @@ public class InboundMeasurementService {
|
||||
}
|
||||
}
|
||||
|
||||
private void requireWeightEditableInbound(InboundOrderEntity inbound) {
|
||||
if (!WEIGHT_EDITABLE_STATUSES.contains(inbound.getProcessStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "当前入库状态不允许修改称重结果");
|
||||
}
|
||||
}
|
||||
|
||||
private void requireWeightConfirmableInbound(InboundOrderEntity inbound, boolean hasAdjustment) {
|
||||
if (WEIGHT_EDITABLE_STATUSES.contains(inbound.getProcessStatus())) {
|
||||
return;
|
||||
}
|
||||
if ("MEASURE_PENDING".equals(inbound.getProcessStatus()) && hasAdjustment) {
|
||||
return;
|
||||
}
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"当前入库状态不允许确认称重结果,待审核状态仅允许人工修正并确认");
|
||||
}
|
||||
|
||||
private void insertRevision(InboundWeightResultEntity weight, int revisionNo, String action,
|
||||
BigDecimal beforeValue, String beforeUnit,
|
||||
BigDecimal afterValue, String afterUnit,
|
||||
@@ -380,7 +417,7 @@ public class InboundMeasurementService {
|
||||
private InboundMeasurementResponses.Detail toDetail(
|
||||
InboundOrderEntity inbound, InboundMeasurementEntity measurement,
|
||||
List<InboundMeasurementItemEntity> items, List<InboundWeightResultEntity> weights,
|
||||
MeasureReviewRecordEntity review) {
|
||||
MeasureReviewRecordEntity review, OrderCargoClient.MeasurementDeclarationSnapshot declaration) {
|
||||
return new InboundMeasurementResponses.Detail(
|
||||
inbound.getId(), inbound.getInboundCode(), inbound.getCompanyName(), inbound.getChannelCode(),
|
||||
inbound.getChannelName(), inbound.getProcessStatus(), inbound.getMeasureReviewStatus(),
|
||||
@@ -391,18 +428,58 @@ public class InboundMeasurementService {
|
||||
measurement == null ? null : measurement.getStatus(),
|
||||
inbound.getVersion(),
|
||||
measurement == null ? null : measurement.getVersion(),
|
||||
measurement == null ? null : measurement.getOriginalDocumentNo(),
|
||||
measurement == null ? null : measurement.getOriginalGrossWeightKg(),
|
||||
measurement == null ? null : measurement.getOriginalVolumeCbm(),
|
||||
measurement != null && StringUtils.hasText(measurement.getOriginalDocumentNo())
|
||||
? measurement.getOriginalDocumentNo() : declaration == null ? null : declaration.referenceId(),
|
||||
measurement != null && measurement.getOriginalGrossWeightKg() != null
|
||||
? measurement.getOriginalGrossWeightKg() : declaration == null ? null : declaration.originalGrossWeightKg(),
|
||||
measurement != null && measurement.getOriginalVolumeCbm() != null
|
||||
? measurement.getOriginalVolumeCbm() : declaration == null ? null : declaration.originalVolumeCbm(),
|
||||
inbound.getTotalPackageCount(),
|
||||
measurement == null ? inbound.getTotalPackageCount() : measurement.getTotalPackageCount(),
|
||||
inbound.getActualGrossWeightKg(),
|
||||
inbound.getActualNetWeightKg(),
|
||||
measurement == null ? inbound.getActualVolumeCbm() : measurement.getTotalVolumeCbm(),
|
||||
measurement == null ? inbound.getVolumeWeightKg() : measurement.getVolumeWeightKg(),
|
||||
measurement == null ? null : measurement.getActualGoodsName(),
|
||||
measurement != null && StringUtils.hasText(measurement.getActualGoodsName())
|
||||
? measurement.getActualGoodsName() : declaration == null ? null : declaration.defaultActualGoodsName(),
|
||||
items.stream().map(this::toItem).toList(), weights.stream().map(this::toWeight).toList());
|
||||
}
|
||||
|
||||
private boolean needsDeclarationFallback(InboundMeasurementEntity measurement) {
|
||||
return measurement == null || !StringUtils.hasText(measurement.getOriginalDocumentNo())
|
||||
|| measurement.getOriginalGrossWeightKg() == null || measurement.getOriginalVolumeCbm() == null
|
||||
|| !StringUtils.hasText(measurement.getActualGoodsName());
|
||||
}
|
||||
|
||||
private OrderCargoClient.MeasurementDeclarationSnapshot loadDeclaration(InboundOrderEntity inbound) {
|
||||
if (inbound.getOrderId() == null || inbound.getOrderId() <= 0) {
|
||||
throw new BizException(ResultCode.CONFLICT, "入库单未关联有效订单,无法读取客户申报数据");
|
||||
}
|
||||
try {
|
||||
ApiResponse<OrderCargoClient.MeasurementDeclarationSnapshot> response = orderCargoClient
|
||||
.measurementDeclaration(internalToken, inbound.getTenantId(), inbound.getOrderId());
|
||||
if (response == null || !response.successful() || response.data() == null) {
|
||||
String message = response == null || !StringUtils.hasText(response.message())
|
||||
? "客户订单申报数据查询失败" : response.message();
|
||||
throw new BizException(response == null ? ResultCode.SERVICE_UNAVAILABLE
|
||||
: ResultCode.of(response.code()), message);
|
||||
}
|
||||
return response.data();
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "订单服务暂不可用,无法读取客户申报数据");
|
||||
}
|
||||
}
|
||||
|
||||
private String defaultGoodsName(OrderCargoClient.MeasurementDeclarationSnapshot declaration) {
|
||||
String value = declaration.defaultActualGoodsName();
|
||||
if (!StringUtils.hasText(value)) return null;
|
||||
if (value.length() > 255) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"客户货物品名汇总超过255字符,请填写精简后的实际品名");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private InboundMeasurementResponses.Item toItem(InboundMeasurementItemEntity item) {
|
||||
return new InboundMeasurementResponses.Item(
|
||||
item.getId(), item.getInboundDetailId(), item.getItemNo(), item.getBoxSpecification(), item.getPackageCount(),
|
||||
|
||||
@@ -28,6 +28,8 @@ public class MeasurementReviewService {
|
||||
@Resource
|
||||
private InboundWorkflowRepository repository;
|
||||
@Resource
|
||||
private com.baiyue.firstleg.repository.InboundMeasurementRepository measurements;
|
||||
@Resource
|
||||
private InboundIdempotencyService idempotency;
|
||||
@Resource
|
||||
private InboundProperties properties;
|
||||
@@ -78,12 +80,30 @@ public class MeasurementReviewService {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"人工确认箱数合计与入库单总箱数不一致,不能审核通过");
|
||||
}
|
||||
BigDecimal weight = order.getActualGrossWeightKg();
|
||||
var weights = measurements.listWeights(tenantId, inboundId);
|
||||
if (weights.isEmpty() || weights.stream().anyMatch(weight ->
|
||||
!"CONFIRMED".equals(weight.getRecognitionStatus())
|
||||
|| weight.getGrossWeightKg() == null
|
||||
|| weight.getGrossWeightKg().signum() <= 0
|
||||
|| weight.getNetWeightKg() == null
|
||||
|| weight.getNetWeightKg().signum() <= 0)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"所有称重结果必须已确认且包含大于零的毛重和货物净重,才能审核通过");
|
||||
}
|
||||
BigDecimal grossWeight = weights.stream()
|
||||
.map(com.baiyue.firstleg.entity.InboundWeightResultEntity::getGrossWeightKg)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal netWeight = weights.stream()
|
||||
.map(com.baiyue.firstleg.entity.InboundWeightResultEntity::getNetWeightKg)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal volume = items.stream().map(com.baiyue.firstleg.entity.InboundMeasurementItemEntity::getTotalVolumeCbm)
|
||||
.filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
if (weight == null || weight.signum() <= 0 || volume.signum() <= 0) {
|
||||
if (order.getActualGrossWeightKg() == null || order.getActualNetWeightKg() == null
|
||||
|| order.getActualGrossWeightKg().compareTo(grossWeight) != 0
|
||||
|| order.getActualNetWeightKg().compareTo(netWeight) != 0
|
||||
|| volume.signum() <= 0) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"审核通过的测量快照必须包含大于零的货物总重量和体积");
|
||||
"审核通过前称重毛重、货物净重汇总和体积必须完整且一致");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ public class MerchantInboundBillingService {
|
||||
var summary = new MerchantInboundBillingResponse.MeasurementSummary(
|
||||
measurement.measurementId(), measurement.sourceType(), measurement.measurementStatus(),
|
||||
measurement.actualPackageCount(), measurement.actualGrossWeightKg(),
|
||||
measurement.actualNetWeightKg(),
|
||||
measurement.actualVolumeCbm(), measurement.volumeWeightKg(), measurement.actualGoodsName());
|
||||
var measurementImages = measurement.items().stream()
|
||||
.filter(item -> item.intelligentImageFileId() != null)
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.baiyue.firstleg.dto.UnifiedInboundDetail;
|
||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||
import com.baiyue.firstleg.dto.UnifiedInboundSummary;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.entity.InboundTransportSnapshotEntity;
|
||||
import com.baiyue.firstleg.dto.CurrentContainerTransport;
|
||||
import com.baiyue.firstleg.loading.entity.LoadingEvidenceEntity;
|
||||
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
||||
import com.baiyue.firstleg.repository.UnifiedInboundReadRepository;
|
||||
@@ -75,7 +75,7 @@ public class UnifiedInboundQueryService {
|
||||
evidence = operations.findEvidence(tenantId, inbound.getId());
|
||||
deliveryAt = deliveryAt(detail.statusHistory());
|
||||
}
|
||||
InboundTransportSnapshotEntity transport = includePhaseThree
|
||||
CurrentContainerTransport transport = includePhaseThree
|
||||
? operations.findTransport(tenantId, inbound.getId()) : null;
|
||||
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt,
|
||||
transport, null, null), timeline);
|
||||
@@ -105,7 +105,7 @@ public class UnifiedInboundQueryService {
|
||||
com.baiyue.firstleg.split.entity.PreloadPlanEntity preload,
|
||||
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick,
|
||||
List<LoadingEvidenceEntity> evidence, LocalDateTime deliveryAt,
|
||||
InboundTransportSnapshotEntity transport, String locationCodes,
|
||||
CurrentContainerTransport transport, String locationCodes,
|
||||
com.baiyue.firstleg.entity.InboundPickupSnapshotEntity pickup) {
|
||||
String mainStatus = mainStatus(inbound.getProcessStatus());
|
||||
List<LoadingEvidenceEntity> activeEvidence = evidence.stream()
|
||||
@@ -124,13 +124,22 @@ public class UnifiedInboundQueryService {
|
||||
? new UnifiedInboundSummary.DeliverySummary(
|
||||
"DELIVERED".equals(mainStatus) ? "COMPLETED" : "PENDING", deliveryAt,
|
||||
inbound.getExpressTrackingNo()) : null;
|
||||
var inboundSummary = detail == null
|
||||
? com.baiyue.firstleg.dto.InboundOrderSummary.from(inbound, transport, pickup, locationCodes) : detail.summary();
|
||||
var baseSummary = detail == null ? null : detail.summary();
|
||||
var inboundSummary = com.baiyue.firstleg.dto.InboundOrderSummary.from(inbound, transport,
|
||||
pickup == null && baseSummary != null && baseSummary.requestedPickupAt() != null
|
||||
? pickupSnapshot(baseSummary.requestedPickupAt()) : pickup,
|
||||
locationCodes == null && baseSummary != null ? baseSummary.locationCodes() : locationCodes);
|
||||
return new UnifiedInboundSummary(inboundSummary, mainStatus,
|
||||
nextAction(mainStatus, inbound.getProcessStatus(), preload == null ? null : preload.getStatus()),
|
||||
preloadSummary, loadingSummary, deliverySummary);
|
||||
}
|
||||
|
||||
private com.baiyue.firstleg.entity.InboundPickupSnapshotEntity pickupSnapshot(LocalDateTime pickupAt) {
|
||||
var snapshot = new com.baiyue.firstleg.entity.InboundPickupSnapshotEntity();
|
||||
snapshot.setPickupAt(pickupAt);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private LocalDateTime deliveryAt(List<InboundOrderDetail.StatusHistorySummary> history) {
|
||||
return history.stream()
|
||||
.filter(item -> "COMPLETE_DELIVERY".equals(item.action()) || "DELIVERED".equals(item.toStatus()))
|
||||
|
||||
Reference in New Issue
Block a user