优化
This commit is contained in:
@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name = "order-service", contextId = "firstlegOrderCargoClient",
|
||||
url = "${baiyue.clients.order.url:}", path = "/internal/order/v1/cargoes")
|
||||
@@ -17,8 +18,19 @@ public interface OrderCargoClient {
|
||||
@PathVariable long orderId,
|
||||
@PathVariable long cargoId);
|
||||
|
||||
@GetMapping("/{tenantId}/{orderId}")
|
||||
ApiResponse<List<CargoLabelBinding>> listForCartonLabels(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long tenantId,
|
||||
@PathVariable long orderId);
|
||||
|
||||
record CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm) {
|
||||
}
|
||||
|
||||
record CargoLabelBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer packageCount, BigDecimal lengthCm, BigDecimal widthCm,
|
||||
BigDecimal heightCm) {
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -9,6 +9,8 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementResponses;
|
||||
import com.baiyue.firstleg.dto.CartonMeasurementResult;
|
||||
import com.baiyue.firstleg.service.CartonMeasurementService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.InboundMeasurementService;
|
||||
import com.baiyue.firstleg.service.MeasurementSubmissionService;
|
||||
@@ -33,6 +35,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
public class PlatformInboundMeasurementController {
|
||||
@Resource private InboundMeasurementService service;
|
||||
@Resource private MeasurementSubmissionService submission;
|
||||
@Resource private CartonMeasurementService cartonMeasurements;
|
||||
|
||||
@Operation(summary = "查询入库测量详情", description = "返回原单、实测箱规、审核快照和 OCR 重量结果。")
|
||||
@GetMapping("/measurement")
|
||||
@@ -74,6 +77,24 @@ public class PlatformInboundMeasurementController {
|
||||
return ApiResponse.success(submission.submit(tenantId, inboundId, user.userId(), request));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_CARTON_MEASUREMENT_COUNT_CONFIRM",
|
||||
resourceType = "INBOUND_MEASUREMENT_ITEM", resourceId = "#itemId",
|
||||
description = "确认单箱测量记录代表箱数")
|
||||
@Operation(summary = "确认单箱测量记录代表箱数",
|
||||
description = "只修改人工确认箱数并由服务端重算当前测量批次汇总。")
|
||||
@PutMapping("/measurement/items/{itemId}/confirmed-package-count")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:measure:edit')")
|
||||
public ApiResponse<CartonMeasurementResult> updateConfirmedPackageCount(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@PathVariable long itemId,
|
||||
@Valid @RequestBody InboundMeasurementRequests.ConfirmedPackageCountRequest request) {
|
||||
requirePlatformTenant(user, tenantId);
|
||||
return ApiResponse.success(cartonMeasurements.updateConfirmedPackageCount(
|
||||
tenantId, inboundId, itemId, request.measurementVersion(), request.confirmedPackageCount()));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_WEIGHT_OCR_RECORD", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "登记称重图片 OCR 原值")
|
||||
@Operation(summary = "登记 OCR 重量结果", description = "按图片文件 ID 保存不可覆盖的 OCR 原值。")
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 已完成外部协议转换后的单箱设备测量命令。 */
|
||||
public record CartonMeasurementCommand(
|
||||
String boxCode,
|
||||
BigDecimal lengthCm,
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg,
|
||||
String machineNo,
|
||||
LocalDateTime measuredAt,
|
||||
MultipartFile image
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 单箱当前测量结果;confirmedPackageCount 为平台人工确认的代表箱数。 */
|
||||
public record CartonMeasurementResult(
|
||||
Long measurementId,
|
||||
Integer measurementVersion,
|
||||
Long itemId,
|
||||
String boxCode,
|
||||
Integer confirmedPackageCount,
|
||||
BigDecimal lengthCm,
|
||||
BigDecimal widthCm,
|
||||
BigDecimal heightCm,
|
||||
BigDecimal unitWeightKg,
|
||||
Long imageFileId,
|
||||
String machineNo,
|
||||
LocalDateTime measuredAt,
|
||||
boolean created
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/** 稳定箱号分配、补打和测量解析契约。 */
|
||||
public final class InboundCartonLabels {
|
||||
private InboundCartonLabels() {
|
||||
}
|
||||
|
||||
public record Batch(Long inboundId, Long orderId, String inboundCode, Integer totalCount,
|
||||
List<Label> labels) {
|
||||
}
|
||||
|
||||
public record Label(Long id, Long inboundDetailId, Integer cargoItemNo, String cargoName,
|
||||
Integer detailPackageCount, String boxCode, Integer totalCount,
|
||||
Integer cartonIndex, String boxSpecification, BigDecimal lengthCm,
|
||||
BigDecimal widthCm, BigDecimal heightCm, String topCode,
|
||||
String warehouseText, String cartonText, String routeText) {
|
||||
}
|
||||
|
||||
public record Resolution(Long inboundId, Long orderId, Long inboundDetailId, String boxCode,
|
||||
Integer totalCount, Integer cartonIndex, Integer cargoItemNo,
|
||||
String cargoName, String boxSpecification) {
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,12 @@ public final class InboundMeasurementRequests {
|
||||
) {
|
||||
}
|
||||
|
||||
public record ConfirmedPackageCountRequest(
|
||||
@NotNull @PositiveOrZero Integer measurementVersion,
|
||||
@NotNull @Positive @Max(1000000) Integer confirmedPackageCount
|
||||
) {
|
||||
}
|
||||
|
||||
public record CreateWeightResultRequest(
|
||||
@NotNull @Positive Long fileId,
|
||||
@Size(max = 64) String ocrProvider,
|
||||
|
||||
@@ -63,7 +63,12 @@ public final class InboundMeasurementResponses {
|
||||
BigDecimal outerLengthCm,
|
||||
BigDecimal outerWidthCm,
|
||||
BigDecimal outerHeightCm,
|
||||
String remark
|
||||
String remark,
|
||||
String boxCode,
|
||||
Integer confirmedPackageCount,
|
||||
String machineNo,
|
||||
LocalDateTime deviceMeasuredAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 首次打印前冻结的实际箱号与订单货物明细映射。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_inbound_carton_label")
|
||||
public class InboundCartonLabelEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private Long orderId;
|
||||
private Long inboundDetailId;
|
||||
private Integer cargoItemNo;
|
||||
private String cargoName;
|
||||
private Integer detailPackageCount;
|
||||
private String inboundCode;
|
||||
private String boxCode;
|
||||
private Integer totalCartonCount;
|
||||
private Integer cartonIndex;
|
||||
private String boxSpecification;
|
||||
private BigDecimal lengthCm;
|
||||
private BigDecimal widthCm;
|
||||
private BigDecimal heightCm;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -20,6 +20,7 @@ public class InboundMeasurementItemEntity {
|
||||
private Long measurementId;
|
||||
private Long inboundDetailId;
|
||||
private String externalDetailNo;
|
||||
private String boxCode;
|
||||
private String itemNo;
|
||||
private String boxSpecification;
|
||||
private Integer packageCount;
|
||||
@@ -36,10 +37,13 @@ public class InboundMeasurementItemEntity {
|
||||
private BigDecimal volumeWeightKg;
|
||||
private String calculationRuleVersion;
|
||||
private Long sourceImageFileId;
|
||||
private String machineNo;
|
||||
private LocalDateTime deviceMeasuredAt;
|
||||
private String overLimitWarning;
|
||||
private BigDecimal outerLengthCm;
|
||||
private BigDecimal outerWidthCm;
|
||||
private BigDecimal outerHeightCm;
|
||||
private String remark;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InboundCartonLabelMapper extends BaseMapper<InboundCartonLabelEntity> {
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.mapper.InboundCartonLabelMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundOrderMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class InboundCartonLabelRepository {
|
||||
@Resource private InboundOrderMapper inbounds;
|
||||
@Resource private InboundCartonLabelMapper labels;
|
||||
|
||||
public InboundOrderEntity lockInbound(long tenantId, long inboundId) {
|
||||
return inbounds.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
|
||||
.eq(InboundOrderEntity::getTenantId, tenantId)
|
||||
.eq(InboundOrderEntity::getId, inboundId)
|
||||
.eq(InboundOrderEntity::getDeleted, false)
|
||||
.last("FOR UPDATE"));
|
||||
}
|
||||
|
||||
public List<InboundCartonLabelEntity> findByInbound(long tenantId, long inboundId) {
|
||||
return labels.selectList(new LambdaQueryWrapper<InboundCartonLabelEntity>()
|
||||
.eq(InboundCartonLabelEntity::getTenantId, tenantId)
|
||||
.eq(InboundCartonLabelEntity::getInboundId, inboundId)
|
||||
.orderByAsc(InboundCartonLabelEntity::getCartonIndex));
|
||||
}
|
||||
|
||||
public InboundCartonLabelEntity findByBoxCode(long tenantId, String boxCode) {
|
||||
return labels.selectOne(new LambdaQueryWrapper<InboundCartonLabelEntity>()
|
||||
.eq(InboundCartonLabelEntity::getTenantId, tenantId)
|
||||
.eq(InboundCartonLabelEntity::getBoxCode, boxCode));
|
||||
}
|
||||
|
||||
public void insert(InboundCartonLabelEntity label) {
|
||||
labels.insert(label);
|
||||
}
|
||||
}
|
||||
+31
@@ -70,6 +70,37 @@ public class InboundMeasurementRepository {
|
||||
.orderByAsc(InboundMeasurementItemEntity::getItemNo));
|
||||
}
|
||||
|
||||
public InboundMeasurementItemEntity findItem(long tenantId, long itemId) {
|
||||
return items.selectOne(new LambdaQueryWrapper<InboundMeasurementItemEntity>()
|
||||
.eq(InboundMeasurementItemEntity::getTenantId, tenantId)
|
||||
.eq(InboundMeasurementItemEntity::getId, itemId));
|
||||
}
|
||||
|
||||
public InboundMeasurementItemEntity findItemByBoxCode(long tenantId, String boxCode) {
|
||||
return items.selectOne(new LambdaQueryWrapper<InboundMeasurementItemEntity>()
|
||||
.eq(InboundMeasurementItemEntity::getTenantId, tenantId)
|
||||
.eq(InboundMeasurementItemEntity::getBoxCode, boxCode));
|
||||
}
|
||||
|
||||
public void insertItem(InboundMeasurementItemEntity item) {
|
||||
items.insert(item);
|
||||
}
|
||||
|
||||
public boolean updateItem(InboundMeasurementItemEntity item) {
|
||||
return items.update(item, new LambdaUpdateWrapper<InboundMeasurementItemEntity>()
|
||||
.eq(InboundMeasurementItemEntity::getTenantId, item.getTenantId())
|
||||
.eq(InboundMeasurementItemEntity::getId, item.getId())) == 1;
|
||||
}
|
||||
|
||||
public InboundMeasurementEntity findLatestEditableMeasurement(long tenantId, long inboundId) {
|
||||
return measurements.selectOne(new LambdaQueryWrapper<InboundMeasurementEntity>()
|
||||
.eq(InboundMeasurementEntity::getTenantId, tenantId)
|
||||
.eq(InboundMeasurementEntity::getInboundId, inboundId)
|
||||
.in(InboundMeasurementEntity::getStatus, "DRAFT", "RECEIVED", "REJECTED")
|
||||
.orderByDesc(InboundMeasurementEntity::getCreatedAt)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
public void insertMeasurement(InboundMeasurementEntity measurement) {
|
||||
measurements.insert(measurement);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
/**
|
||||
* 箱号映射查询端口。标签模块负责提供实现,测量模块不解析或推导箱号业务归属。
|
||||
*/
|
||||
public interface CartonLabelBindingResolver {
|
||||
Binding resolveByBoxCode(long tenantId, String boxCode);
|
||||
|
||||
record Binding(long inboundId, long inboundDetailId, String inboundCode,
|
||||
int cartonIndex, int totalCartons, String boxSpecification) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
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.MeasurementImageClient;
|
||||
import com.baiyue.firstleg.dto.CartonMeasurementCommand;
|
||||
import com.baiyue.firstleg.dto.CartonMeasurementResult;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
||||
import feign.FeignException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** 单箱当前测量新增/覆盖与人工确认箱数编排。 */
|
||||
@Service
|
||||
public class CartonMeasurementService {
|
||||
private static final Logger log = LoggerFactory.getLogger(CartonMeasurementService.class);
|
||||
private static final Set<String> DEVICE_WRITABLE_STATUSES = Set.of("WAIT_MEASURE", "MEASURE_REJECTED");
|
||||
private static final BigDecimal CUBIC_CENTIMETERS_PER_CBM = new BigDecimal("1000000");
|
||||
private static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
private static final String CALCULATION_RULE_VERSION = "V1_CBM_CEILING_4_GIRTH_L2WH";
|
||||
private static final long MAX_IMAGE_BYTES = 5L * 1024 * 1024;
|
||||
|
||||
private final InboundMeasurementRepository repository;
|
||||
private final ObjectProvider<CartonLabelBindingResolver> resolverProvider;
|
||||
private final MeasurementImageClient imageClient;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
public CartonMeasurementService(InboundMeasurementRepository repository,
|
||||
ObjectProvider<CartonLabelBindingResolver> resolverProvider,
|
||||
MeasurementImageClient imageClient,
|
||||
TransactionTemplate transactionTemplate) {
|
||||
this.repository = repository;
|
||||
this.resolverProvider = resolverProvider;
|
||||
this.imageClient = imageClient;
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
}
|
||||
|
||||
public CartonMeasurementResult upsert(long tenantId, long operatorId, CartonMeasurementCommand command) {
|
||||
validateCommand(command);
|
||||
String boxCode = command.boxCode().trim();
|
||||
CartonLabelBindingResolver.Binding binding = requireBinding(tenantId, boxCode);
|
||||
InboundOrderEntity inbound = requireDeviceWritableInbound(tenantId, binding.inboundId());
|
||||
MeasurementImageClient.UploadResult uploaded = uploadImage(
|
||||
tenantId, operatorId, inbound.getId(), command.image());
|
||||
UpsertOutcome outcome;
|
||||
try {
|
||||
outcome = transactionTemplate.execute(status -> persist(
|
||||
tenantId, operatorId, binding, inbound, boxCode, command, uploaded.fileId()));
|
||||
} catch (RuntimeException exception) {
|
||||
cleanupImage(tenantId, operatorId, inbound.getId(), uploaded.fileId(), "回传落库失败补偿");
|
||||
throw exception;
|
||||
}
|
||||
if (outcome == null) {
|
||||
cleanupImage(tenantId, operatorId, inbound.getId(), uploaded.fileId(), "事务未返回结果补偿");
|
||||
throw new BizException(ResultCode.INTERNAL_ERROR, "单箱测量保存失败");
|
||||
}
|
||||
if (outcome.oldImageFileId() != null && !outcome.oldImageFileId().equals(uploaded.fileId())) {
|
||||
cleanupImage(tenantId, operatorId, inbound.getId(), outcome.oldImageFileId(), "同箱复测旧图清理");
|
||||
}
|
||||
return outcome.result();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CartonMeasurementResult updateConfirmedPackageCount(
|
||||
long tenantId, long inboundId, long itemId, int expectedMeasurementVersion,
|
||||
int confirmedPackageCount) {
|
||||
if (confirmedPackageCount < 1 || confirmedPackageCount > 1_000_000) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "确认箱数必须是1至1000000的整数");
|
||||
}
|
||||
requireDeviceWritableInbound(tenantId, inboundId);
|
||||
InboundMeasurementItemEntity item = repository.findItem(tenantId, itemId);
|
||||
if (item == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "单箱测量记录不存在");
|
||||
}
|
||||
InboundMeasurementEntity measurement = repository.findMeasurement(
|
||||
tenantId, inboundId, item.getMeasurementId());
|
||||
if (measurement == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "单箱测量记录不属于当前入库单");
|
||||
}
|
||||
requireVersion(expectedMeasurementVersion, measurement.getVersion());
|
||||
item.setPackageCount(confirmedPackageCount);
|
||||
applyCalculatedValues(item);
|
||||
item.setUpdatedAt(LocalDateTime.now());
|
||||
if (!repository.updateItem(item)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
recalculateMeasurement(measurement, repository.listItems(tenantId, measurement.getId()));
|
||||
if (!repository.updateMeasurement(measurement)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
return result(measurement, item, false);
|
||||
}
|
||||
|
||||
private UpsertOutcome persist(long tenantId, long operatorId,
|
||||
CartonLabelBindingResolver.Binding binding,
|
||||
InboundOrderEntity inbound, String boxCode,
|
||||
CartonMeasurementCommand command, long imageFileId) {
|
||||
InboundMeasurementItemEntity item = repository.findItemByBoxCode(tenantId, boxCode);
|
||||
boolean created = item == null;
|
||||
InboundMeasurementEntity measurement;
|
||||
Long oldImageFileId = null;
|
||||
if (created) {
|
||||
measurement = repository.findLatestEditableMeasurement(tenantId, inbound.getId());
|
||||
if (measurement == null) {
|
||||
measurement = newMeasurement(tenantId, operatorId, inbound.getId());
|
||||
repository.insertMeasurement(measurement);
|
||||
}
|
||||
item = new InboundMeasurementItemEntity();
|
||||
item.setId(IdWorker.getId());
|
||||
item.setTenantId(tenantId);
|
||||
item.setMeasurementId(measurement.getId());
|
||||
item.setPackageCount(1);
|
||||
item.setCreatedAt(LocalDateTime.now());
|
||||
} else {
|
||||
measurement = repository.findMeasurement(tenantId, inbound.getId(), item.getMeasurementId());
|
||||
if (measurement == null) {
|
||||
throw new BizException(ResultCode.CONFLICT, "箱号已绑定到其他入库单");
|
||||
}
|
||||
oldImageFileId = item.getSourceImageFileId();
|
||||
}
|
||||
|
||||
item.setInboundDetailId(binding.inboundDetailId());
|
||||
item.setExternalDetailNo(boxCode);
|
||||
item.setBoxCode(boxCode);
|
||||
item.setItemNo("B" + binding.cartonIndex());
|
||||
item.setBoxSpecification(StringUtils.hasText(binding.boxSpecification())
|
||||
? binding.boxSpecification().trim() : boxCode);
|
||||
item.setLengthCm(command.lengthCm());
|
||||
item.setWidthCm(command.widthCm());
|
||||
item.setHeightCm(command.heightCm());
|
||||
item.setUnitWeightKg(command.unitWeightKg());
|
||||
item.setSourceImageFileId(imageFileId);
|
||||
item.setMachineNo(command.machineNo().trim());
|
||||
item.setDeviceMeasuredAt(command.measuredAt());
|
||||
item.setUpdatedAt(LocalDateTime.now());
|
||||
applyCalculatedValues(item);
|
||||
|
||||
if (created) {
|
||||
repository.insertItem(item);
|
||||
} else if (!repository.updateItem(item)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
|
||||
measurement.setStatus(MeasurementBatchService.STATUS_RECEIVED);
|
||||
measurement.setSourceType(MeasurementBatchService.SOURCE_THIRD_PARTY);
|
||||
measurement.setSourceCode("DEVICE");
|
||||
measurement.setDeviceCode(command.machineNo().trim());
|
||||
measurement.setMeasuredAt(command.measuredAt());
|
||||
measurement.setUpdatedAt(LocalDateTime.now());
|
||||
recalculateMeasurement(measurement, repository.listItems(tenantId, measurement.getId()));
|
||||
if (!repository.updateMeasurement(measurement)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
return new UpsertOutcome(result(measurement, item, created), oldImageFileId);
|
||||
}
|
||||
|
||||
private InboundMeasurementEntity newMeasurement(long tenantId, long operatorId, long inboundId) {
|
||||
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
|
||||
measurement.setId(IdWorker.getId());
|
||||
measurement.setTenantId(tenantId);
|
||||
measurement.setInboundId(inboundId);
|
||||
measurement.setSourceType(MeasurementBatchService.SOURCE_THIRD_PARTY);
|
||||
measurement.setSourceCode("DEVICE");
|
||||
measurement.setStatus(MeasurementBatchService.STATUS_RECEIVED);
|
||||
measurement.setCreatedBy(operatorId);
|
||||
measurement.setCreatedAt(LocalDateTime.now());
|
||||
measurement.setUpdatedAt(LocalDateTime.now());
|
||||
measurement.setVersion(0);
|
||||
return measurement;
|
||||
}
|
||||
|
||||
private void applyCalculatedValues(InboundMeasurementItemEntity item) {
|
||||
BigDecimal unitRaw = item.getLengthCm().multiply(item.getWidthCm()).multiply(item.getHeightCm())
|
||||
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
||||
BigDecimal unitVolume = unitRaw.setScale(4, RoundingMode.CEILING);
|
||||
BigDecimal count = BigDecimal.valueOf(item.getPackageCount());
|
||||
BigDecimal totalRaw = unitRaw.multiply(count).setScale(6, RoundingMode.HALF_UP);
|
||||
BigDecimal totalVolume = unitVolume.multiply(count).setScale(4, RoundingMode.CEILING);
|
||||
item.setGirthCm(item.getLengthCm().add(item.getWidthCm().add(item.getHeightCm())
|
||||
.multiply(BigDecimal.valueOf(2))).setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitWeightKg(item.getUnitWeightKg() == null ? null
|
||||
: item.getUnitWeightKg().setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitVolumeCbm(unitVolume);
|
||||
item.setVolumeRawCbm(totalRaw);
|
||||
item.setVolumeCbm(totalVolume);
|
||||
item.setTotalWeightKg(item.getUnitWeightKg() == null ? null
|
||||
: item.getUnitWeightKg().multiply(count).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setCalculationRuleVersion(CALCULATION_RULE_VERSION);
|
||||
}
|
||||
|
||||
private void recalculateMeasurement(InboundMeasurementEntity measurement,
|
||||
List<InboundMeasurementItemEntity> items) {
|
||||
long packages = items.stream().mapToLong(InboundMeasurementItemEntity::getPackageCount).sum();
|
||||
if (packages > Integer.MAX_VALUE) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "测量确认总箱数超过系统上限");
|
||||
}
|
||||
BigDecimal rawVolume = items.stream().map(InboundMeasurementItemEntity::getVolumeRawCbm)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(6, RoundingMode.HALF_UP);
|
||||
BigDecimal totalVolume = items.stream().map(InboundMeasurementItemEntity::getTotalVolumeCbm)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(4, RoundingMode.CEILING);
|
||||
measurement.setTotalPackageCount((int) packages);
|
||||
measurement.setTotalVolumeRawCbm(rawVolume);
|
||||
measurement.setTotalVolumeCbm(totalVolume);
|
||||
measurement.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR)
|
||||
.setScale(3, RoundingMode.CEILING));
|
||||
}
|
||||
|
||||
private CartonLabelBindingResolver.Binding requireBinding(long tenantId, String boxCode) {
|
||||
CartonLabelBindingResolver resolver = resolverProvider.getIfAvailable();
|
||||
if (resolver == null) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "箱号映射服务暂不可用");
|
||||
}
|
||||
CartonLabelBindingResolver.Binding binding = resolver.resolveByBoxCode(tenantId, boxCode);
|
||||
if (binding == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "箱号不存在或不属于当前客户");
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
private InboundOrderEntity requireDeviceWritableInbound(long tenantId, long inboundId) {
|
||||
InboundOrderEntity inbound = repository.findInbound(tenantId, inboundId);
|
||||
if (inbound == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
}
|
||||
if (!DEVICE_WRITABLE_STATUSES.contains(inbound.getProcessStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "测量已提交,设备不能新增或覆盖数据");
|
||||
}
|
||||
return inbound;
|
||||
}
|
||||
|
||||
private MeasurementImageClient.UploadResult uploadImage(long tenantId, long operatorId,
|
||||
long inboundId, MultipartFile image) {
|
||||
try {
|
||||
ApiResponse<MeasurementImageClient.UploadResult> response = imageClient.upload(
|
||||
internalToken, operatorId, tenantId, inboundId, image);
|
||||
if (response == null || !response.successful() || response.data() == null
|
||||
|| response.data().fileId() == null) {
|
||||
throw downstream(response, "测量图片保存失败");
|
||||
}
|
||||
return response.data();
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "文件服务暂不可用,测量数据未保存");
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupImage(long tenantId, long operatorId, long inboundId, long fileId, String action) {
|
||||
try {
|
||||
imageClient.delete(internalToken, operatorId, tenantId, inboundId, fileId);
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("{}失败 tenantId={}, inboundId={}, fileId={}", action, tenantId, inboundId, fileId, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private BizException downstream(ApiResponse<?> response, String fallback) {
|
||||
if (response == null) {
|
||||
return new BizException(ResultCode.SERVICE_UNAVAILABLE, fallback);
|
||||
}
|
||||
String message = StringUtils.hasText(response.message()) ? response.message() : fallback;
|
||||
return new BizException(ResultCode.of(response.code()), message);
|
||||
}
|
||||
|
||||
private void validateCommand(CartonMeasurementCommand command) {
|
||||
if (command == null || !StringUtils.hasText(command.boxCode())
|
||||
|| command.boxCode().trim().length() > 128) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "箱号不能为空且不能超过128个字符");
|
||||
}
|
||||
if (!positive(command.lengthCm()) || !positive(command.widthCm()) || !positive(command.heightCm())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "长、宽、高必须大于0");
|
||||
}
|
||||
if (command.unitWeightKg() != null && !positive(command.unitWeightKg())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "单箱重量必须大于0");
|
||||
}
|
||||
if (!StringUtils.hasText(command.machineNo()) || command.machineNo().trim().length() > 64) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "设备编号不能为空且不能超过64个字符");
|
||||
}
|
||||
if (command.measuredAt() == null) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "设备测量时间不能为空");
|
||||
}
|
||||
if (command.image() == null || command.image().isEmpty()) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "测量图片不能为空");
|
||||
}
|
||||
if (command.image().getSize() > MAX_IMAGE_BYTES) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "测量图片不能超过5MB");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean positive(BigDecimal value) {
|
||||
return value != null && value.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
private void requireVersion(int expected, Integer actual) {
|
||||
if (actual == null || expected != actual) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
}
|
||||
|
||||
private CartonMeasurementResult result(InboundMeasurementEntity measurement,
|
||||
InboundMeasurementItemEntity item, boolean created) {
|
||||
return new CartonMeasurementResult(measurement.getId(), measurement.getVersion(), item.getId(),
|
||||
item.getBoxCode(), item.getPackageCount(), item.getLengthCm(), item.getWidthCm(),
|
||||
item.getHeightCm(), item.getUnitWeightKg(), item.getSourceImageFileId(), item.getMachineNo(),
|
||||
item.getDeviceMeasuredAt(), created);
|
||||
}
|
||||
|
||||
private BizException concurrentModification() {
|
||||
return new BizException(ResultCode.CONFLICT, "测量数据已被其他人修改,请刷新后重试");
|
||||
}
|
||||
|
||||
private record UpsertOutcome(CartonMeasurementResult result, Long oldImageFileId) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
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.OrderCargoClient;
|
||||
import com.baiyue.firstleg.dto.InboundCartonLabels;
|
||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundCartonLabelRepository;
|
||||
import feign.FeignException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** 首次生成后不可变的箱号分配;重打和测量解析只读取该映射。 */
|
||||
@Service
|
||||
public class InboundCartonLabelService {
|
||||
private static final int MAX_LABELS = 2000;
|
||||
private static final int MAX_QR_CODE_POINTS = 34;
|
||||
private static final Set<String> ALLOCATABLE_STATUSES = Set.of("WAIT_MEASURE", "MEASURING");
|
||||
|
||||
@Resource private InboundCartonLabelRepository repository;
|
||||
@Resource private OrderCargoClient orderCargoClient;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
@Transactional
|
||||
public InboundCartonLabels.Batch getOrCreate(long tenantId, long inboundId) {
|
||||
requireIds(tenantId, inboundId);
|
||||
InboundOrderEntity inbound = repository.lockInbound(tenantId, inboundId);
|
||||
if (inbound == null) throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
|
||||
List<InboundCartonLabelEntity> existing = new ArrayList<>(repository.findByInbound(tenantId, inboundId));
|
||||
if (!existing.isEmpty()) return batch(inbound, requireConsistent(existing));
|
||||
if (!ALLOCATABLE_STATUSES.contains(inbound.getProcessStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "当前入库单状态不允许首次生成箱号");
|
||||
}
|
||||
|
||||
List<OrderCargoClient.CargoLabelBinding> cargoes = loadCargoes(inbound);
|
||||
int total = cargoes.stream().mapToInt(this::requirePackageCount).sum();
|
||||
if (total < 1 || total > MAX_LABELS) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "唛头箱数必须为1至2000箱");
|
||||
}
|
||||
requirePrintableInboundCode(inbound.getInboundCode(), total);
|
||||
|
||||
int cartonIndex = 0;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (OrderCargoClient.CargoLabelBinding cargo : cargoes) {
|
||||
int detailCount = requirePackageCount(cargo);
|
||||
for (int offset = 0; offset < detailCount; offset++) {
|
||||
cartonIndex++;
|
||||
InboundCartonLabelEntity label = label(inbound, cargo, total, cartonIndex, now);
|
||||
repository.insert(label);
|
||||
existing.add(label);
|
||||
}
|
||||
}
|
||||
return batch(inbound, List.copyOf(existing));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InboundCartonLabels.Batch list(long tenantId, long inboundId, List<Integer> cartonIndexes) {
|
||||
InboundCartonLabels.Batch batch = getOrCreate(tenantId, inboundId);
|
||||
if (cartonIndexes == null || cartonIndexes.isEmpty()) return batch;
|
||||
Set<Integer> selected = new HashSet<>(cartonIndexes);
|
||||
if (selected.stream().anyMatch(index -> index == null || index < 1 || index > batch.totalCount())) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "补打箱下标超出有效范围");
|
||||
}
|
||||
List<InboundCartonLabels.Label> labels = batch.labels().stream()
|
||||
.filter(label -> selected.contains(label.cartonIndex()))
|
||||
.toList();
|
||||
return new InboundCartonLabels.Batch(batch.inboundId(), batch.orderId(), batch.inboundCode(),
|
||||
batch.totalCount(), labels);
|
||||
}
|
||||
|
||||
public InboundCartonLabels.Resolution resolveByBoxCode(long tenantId, String boxCode) {
|
||||
if (tenantId <= 0 || !StringUtils.hasText(boxCode)) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "客户ID和箱号不能为空");
|
||||
}
|
||||
InboundCartonLabelEntity label = repository.findByBoxCode(tenantId, boxCode.trim());
|
||||
if (label == null) throw new BizException(ResultCode.NOT_FOUND, "箱号不存在");
|
||||
return new InboundCartonLabels.Resolution(label.getInboundId(), label.getOrderId(),
|
||||
label.getInboundDetailId(), label.getBoxCode(), label.getTotalCartonCount(),
|
||||
label.getCartonIndex(), label.getCargoItemNo(), label.getCargoName(),
|
||||
label.getBoxSpecification());
|
||||
}
|
||||
|
||||
private List<OrderCargoClient.CargoLabelBinding> loadCargoes(InboundOrderEntity inbound) {
|
||||
try {
|
||||
ApiResponse<List<OrderCargoClient.CargoLabelBinding>> response = orderCargoClient.listForCartonLabels(
|
||||
internalToken, inbound.getTenantId(), inbound.getOrderId());
|
||||
if (response == null || !response.successful() || response.data() == null) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "订单货物明细查询失败");
|
||||
}
|
||||
if (response.data().isEmpty()) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "订单没有可生成唛头的货物明细");
|
||||
}
|
||||
return response.data().stream()
|
||||
.sorted(java.util.Comparator.comparing(OrderCargoClient.CargoLabelBinding::itemNo)
|
||||
.thenComparing(OrderCargoClient.CargoLabelBinding::cargoId))
|
||||
.toList();
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "订单服务暂不可用,无法生成唛头");
|
||||
}
|
||||
}
|
||||
|
||||
private int requirePackageCount(OrderCargoClient.CargoLabelBinding cargo) {
|
||||
if (cargo.cargoId() == null || cargo.cargoId() <= 0 || cargo.itemNo() == null
|
||||
|| cargo.itemNo() < 1 || cargo.packageCount() == null || cargo.packageCount() < 1) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "订单货物明细缺少有效箱数或序号");
|
||||
}
|
||||
return cargo.packageCount();
|
||||
}
|
||||
|
||||
private void requirePrintableInboundCode(String inboundCode, int total) {
|
||||
if (!StringUtils.hasText(inboundCode) || !inboundCode.matches("[A-Za-z0-9._:-]+")) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "入仓码不能生成可打印二维码");
|
||||
}
|
||||
String longest = inboundCode + "#" + total + "-" + total;
|
||||
if (longest.codePointCount(0, longest.length()) > MAX_QR_CODE_POINTS) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "入仓码和箱号组合超过二维码长度限制");
|
||||
}
|
||||
}
|
||||
|
||||
private InboundCartonLabelEntity label(InboundOrderEntity inbound,
|
||||
OrderCargoClient.CargoLabelBinding cargo,
|
||||
int total, int index, LocalDateTime now) {
|
||||
InboundCartonLabelEntity label = new InboundCartonLabelEntity();
|
||||
label.setId(IdWorker.getId());
|
||||
label.setTenantId(inbound.getTenantId());
|
||||
label.setInboundId(inbound.getId());
|
||||
label.setOrderId(inbound.getOrderId());
|
||||
label.setInboundDetailId(cargo.cargoId());
|
||||
label.setCargoItemNo(cargo.itemNo());
|
||||
label.setCargoName(cargo.cargoName());
|
||||
label.setDetailPackageCount(cargo.packageCount());
|
||||
label.setInboundCode(inbound.getInboundCode());
|
||||
label.setBoxCode(inbound.getInboundCode() + "#" + total + "-" + index);
|
||||
label.setTotalCartonCount(total);
|
||||
label.setCartonIndex(index);
|
||||
label.setLengthCm(cargo.lengthCm());
|
||||
label.setWidthCm(cargo.widthCm());
|
||||
label.setHeightCm(cargo.heightCm());
|
||||
label.setBoxSpecification(specification(cargo));
|
||||
label.setCreatedAt(now);
|
||||
return label;
|
||||
}
|
||||
|
||||
private String specification(OrderCargoClient.CargoLabelBinding cargo) {
|
||||
if (cargo.lengthCm() == null || cargo.widthCm() == null || cargo.heightCm() == null) {
|
||||
return StringUtils.hasText(cargo.cargoName()) ? cargo.cargoName().trim() : "货物明细" + cargo.itemNo();
|
||||
}
|
||||
return decimal(cargo.lengthCm()) + "x" + decimal(cargo.widthCm()) + "x"
|
||||
+ decimal(cargo.heightCm()) + "cm";
|
||||
}
|
||||
|
||||
private String decimal(BigDecimal value) {
|
||||
return value.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private List<InboundCartonLabelEntity> requireConsistent(List<InboundCartonLabelEntity> labels) {
|
||||
int total = labels.get(0).getTotalCartonCount();
|
||||
if (total != labels.size()) throw corrupt();
|
||||
for (int index = 0; index < labels.size(); index++) {
|
||||
InboundCartonLabelEntity label = labels.get(index);
|
||||
if (label.getTotalCartonCount() != total || label.getCartonIndex() != index + 1) throw corrupt();
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private BizException corrupt() {
|
||||
return new BizException(ResultCode.INTERNAL_ERROR, "入库单箱号映射不完整,请联系管理员");
|
||||
}
|
||||
|
||||
private InboundCartonLabels.Batch batch(InboundOrderEntity inbound,
|
||||
List<InboundCartonLabelEntity> entities) {
|
||||
List<InboundCartonLabels.Label> labels = entities.stream().map(entity ->
|
||||
new InboundCartonLabels.Label(entity.getId(), entity.getInboundDetailId(), entity.getCargoItemNo(),
|
||||
entity.getCargoName(), entity.getDetailPackageCount(), entity.getBoxCode(),
|
||||
entity.getTotalCartonCount(), entity.getCartonIndex(), entity.getBoxSpecification(),
|
||||
entity.getLengthCm(), entity.getWidthCm(), entity.getHeightCm(), inbound.getInboundCode(),
|
||||
fallback(inbound.getWarehouseName(), "WAREHOUSE"),
|
||||
"CTNS:" + entity.getTotalCartonCount() + "-" + entity.getCartonIndex(),
|
||||
fallback(inbound.getChannelName(), fallback(inbound.getChannelCode(), "ROUTE"))))
|
||||
.toList();
|
||||
int total = entities.isEmpty() ? 0 : entities.get(0).getTotalCartonCount();
|
||||
return new InboundCartonLabels.Batch(inbound.getId(), inbound.getOrderId(), inbound.getInboundCode(),
|
||||
total, labels);
|
||||
}
|
||||
|
||||
private String fallback(String value, String fallback) {
|
||||
return StringUtils.hasText(value) ? value.trim() : fallback;
|
||||
}
|
||||
|
||||
private void requireIds(long tenantId, long inboundId) {
|
||||
if (tenantId <= 0 || inboundId <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "客户ID和入库单ID必须大于0");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ public class InboundMeasurementService {
|
||||
BigDecimal confirmedWeight = confirmedWeightForSubmission(weights);
|
||||
|
||||
if (!alreadyAdvanced) {
|
||||
requireConfirmedPackageTotal(inbound, items);
|
||||
measurement.setStatus(STATUS_SUBMITTED);
|
||||
if (!repository.updateMeasurement(measurement)) {
|
||||
throw concurrentModification();
|
||||
@@ -287,7 +288,25 @@ public class InboundMeasurementService {
|
||||
item.getUnitWeightKg(), item.getUnitVolumeCbm(), item.getTotalWeightKg(),
|
||||
item.getTotalVolumeCbm(), item.getVolumeWeightKg(), item.getSourceImageFileId(),
|
||||
item.getOverLimitWarning(), item.getOuterLengthCm(), item.getOuterWidthCm(),
|
||||
item.getOuterHeightCm(), item.getRemark());
|
||||
item.getOuterHeightCm(), item.getRemark(), item.getBoxCode(), item.getPackageCount(),
|
||||
item.getMachineNo(), item.getDeviceMeasuredAt(), item.getUpdatedAt());
|
||||
}
|
||||
|
||||
private void requireConfirmedPackageTotal(InboundOrderEntity inbound,
|
||||
List<InboundMeasurementItemEntity> items) {
|
||||
if (inbound.getTotalPackageCount() == null || inbound.getTotalPackageCount() < 1) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "入库单原始总箱数无效,不能提交测量");
|
||||
}
|
||||
long confirmed = items.stream()
|
||||
.map(InboundMeasurementItemEntity::getPackageCount)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.mapToLong(Integer::longValue)
|
||||
.sum();
|
||||
if (confirmed != inbound.getTotalPackageCount()) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"人工确认箱数合计" + confirmed + "箱,与入库单总箱数"
|
||||
+ inbound.getTotalPackageCount() + "箱不一致");
|
||||
}
|
||||
}
|
||||
|
||||
private InboundMeasurementResponses.WeightResult toWeight(InboundWeightResultEntity weight) {
|
||||
|
||||
Reference in New Issue
Block a user