This commit is contained in:
2026-08-07 18:03:56 +08:00
parent 645c483649
commit 1a76602872
15 changed files with 273 additions and 6 deletions
@@ -10,6 +10,7 @@ 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.dto.CartonMeasurementCommand;
import com.baiyue.firstleg.service.CartonMeasurementService;
import com.baiyue.firstleg.service.FirstlegScopeGuard;
import com.baiyue.firstleg.service.InboundMeasurementService;
@@ -23,10 +24,13 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
/** 平台后台入库测量录入、提交和 OCR 重量结果维护接口。 */
@Tag(name = "平台入库测量", description = "维护测量草稿、箱规明细和可追溯 OCR 重量结果。")
@@ -95,6 +99,26 @@ public class PlatformInboundMeasurementController {
tenantId, inboundId, itemId, request.measurementVersion(), request.confirmedPackageCount()));
}
@BusinessAudit(event = "INBOUND_CARTON_SCAN", resourceType = "INBOUND",
resourceId = "#inboundId", description = "平台模拟设备单箱扫描")
@Operation(summary = "平台模拟单箱设备扫描",
description = "联调入口;data 中传箱号和设备测量值,image 传当前箱图片,复用正式单箱覆盖规则。")
@PostMapping(value = "/measurement/carton-scans", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('platform:firstleg:inbound:measure:edit')")
public ApiResponse<CartonMeasurementResult> simulateCartonScan(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@PathVariable long inboundId,
@Valid @RequestPart("data") InboundMeasurementRequests.CartonScanRequest request,
@RequestPart("image") MultipartFile image) {
requirePlatformTenant(user, tenantId);
CartonMeasurementCommand command = new CartonMeasurementCommand(
request.boxCode(), request.length(), request.width(), request.height(), request.weight(),
request.machineNo(), request.measuredAt(), image);
return ApiResponse.success(cartonMeasurements.upsertForInbound(
tenantId, user.userId(), inboundId, command));
}
@BusinessAudit(event = "INBOUND_WEIGHT_OCR_RECORD", resourceType = "INBOUND",
resourceId = "#inboundId", description = "登记称重图片 OCR 原值")
@Operation(summary = "登记 OCR 重量结果", description = "按图片文件 ID 保存不可覆盖的 OCR 原值。")
@@ -13,6 +13,7 @@ import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -62,14 +63,25 @@ public final class InboundMeasurementRequests {
) {
}
/** 平台联调使用;最终设备协议在外部适配层转换为相同领域命令。 */
public record CartonScanRequest(
@NotBlank @Size(max = 128) String boxCode,
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal length,
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal width,
@NotNull @DecimalMin("0.001") @Digits(integer = 9, fraction = 3) BigDecimal height,
@DecimalMin("0.001") @Digits(integer = 15, fraction = 3) BigDecimal weight,
@NotBlank @Size(max = 64) String machineNo,
@NotNull LocalDateTime measuredAt
) {
}
public record CreateWeightResultRequest(
@NotNull @Positive Long fileId,
@Size(max = 64) String ocrProvider,
@Size(max = 1000) String ocrRawText,
@DecimalMin("0.000") @Digits(integer = 15, fraction = 3) BigDecimal ocrWeightValue,
@Size(max = 8) String ocrWeightUnit,
@DecimalMin("0.00000") @DecimalMax("1.00000")
@Digits(integer = 1, fraction = 5) BigDecimal confidence
@DecimalMin("0.00000") @DecimalMax("1.00000") BigDecimal confidence
) {
}
@@ -1,6 +1,7 @@
package com.baiyue.firstleg.repository;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.mapper.InboundCartonLabelMapper;
@@ -36,6 +37,15 @@ public class InboundCartonLabelRepository {
.eq(InboundCartonLabelEntity::getBoxCode, boxCode));
}
public boolean initializeOriginalPackageCount(long tenantId, long inboundId, int packageCount) {
return inbounds.update(null, new LambdaUpdateWrapper<InboundOrderEntity>()
.eq(InboundOrderEntity::getTenantId, tenantId)
.eq(InboundOrderEntity::getId, inboundId)
.isNull(InboundOrderEntity::getTotalPackageCount)
.set(InboundOrderEntity::getTotalPackageCount, packageCount)
.setSql("version = version + 1")) == 1;
}
public void insert(InboundCartonLabelEntity label) {
labels.insert(label);
}
@@ -59,9 +59,25 @@ public class CartonMeasurementService {
}
public CartonMeasurementResult upsert(long tenantId, long operatorId, CartonMeasurementCommand command) {
return upsert(tenantId, operatorId, null, command);
}
public CartonMeasurementResult upsertForInbound(long tenantId, long operatorId, long inboundId,
CartonMeasurementCommand command) {
if (inboundId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "入库单ID必须大于0");
}
return upsert(tenantId, operatorId, inboundId, command);
}
private CartonMeasurementResult upsert(long tenantId, long operatorId, Long expectedInboundId,
CartonMeasurementCommand command) {
validateCommand(command);
String boxCode = command.boxCode().trim();
CartonLabelBindingResolver.Binding binding = requireBinding(tenantId, boxCode);
if (expectedInboundId != null && binding.inboundId() != expectedInboundId) {
throw new BizException(ResultCode.NOT_FOUND, "箱号不属于当前入库单");
}
InboundOrderEntity inbound = requireDeviceWritableInbound(tenantId, binding.inboundId());
MeasurementImageClient.UploadResult uploaded = uploadImage(
tenantId, operatorId, inbound.getId(), command.image());
@@ -41,7 +41,11 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
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 (!existing.isEmpty()) {
List<InboundCartonLabelEntity> consistent = requireConsistent(existing);
freezeOriginalPackageCount(inbound, consistent.get(0).getTotalCartonCount());
return batch(inbound, consistent);
}
if (!ALLOCATABLE_STATUSES.contains(inbound.getProcessStatus())) {
throw new BizException(ResultCode.CONFLICT, "当前入库单状态不允许首次生成箱号");
}
@@ -51,6 +55,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
if (total < 1 || total > MAX_LABELS) {
throw new BizException(ResultCode.VALIDATION_FAILED, "唛头箱数必须为1至2000箱");
}
freezeOriginalPackageCount(inbound, total);
requirePrintableInboundCode(inbound.getInboundCode(), total);
int cartonIndex = 0;
@@ -146,6 +151,20 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
}
}
private void freezeOriginalPackageCount(InboundOrderEntity inbound, int total) {
Integer original = inbound.getTotalPackageCount();
if (original != null && original != total) {
throw new BizException(ResultCode.CONFLICT, "订单总箱数与已冻结箱号数量不一致");
}
if (original == null) {
if (!repository.initializeOriginalPackageCount(inbound.getTenantId(), inbound.getId(), total)) {
throw new BizException(ResultCode.CONFLICT, "订单总箱数已变化,请刷新后重试");
}
inbound.setTotalPackageCount(total);
inbound.setVersion(inbound.getVersion() == null ? 1 : inbound.getVersion() + 1);
}
}
private InboundCartonLabelEntity label(InboundOrderEntity inbound,
OrderCargoClient.CargoLabelBinding cargo,
int total, int index, LocalDateTime now) {
@@ -138,7 +138,7 @@ public class InboundMeasurementService {
weight.setOcrRawText(request.ocrRawText());
weight.setOcrWeightValue(request.ocrWeightValue());
weight.setOcrWeightUnit(unit);
weight.setConfidence(request.confidence());
weight.setConfidence(normalizeConfidence(request.confidence()));
weight.setRecognitionStatus(isPositive(request.ocrWeightValue()) && isSupportedUnit(unit)
? WEIGHT_PENDING_CONFIRM : WEIGHT_NEEDS_MANUAL);
weight.setCreatedBy(operatorId);
@@ -356,6 +356,17 @@ public class InboundMeasurementService {
return unit.trim().toUpperCase(Locale.ROOT);
}
private BigDecimal normalizeConfidence(BigDecimal confidence) {
if (confidence == null) {
return null;
}
if (confidence.compareTo(BigDecimal.ZERO) < 0
|| confidence.compareTo(BigDecimal.ONE) > 0) {
throw new BizException(ResultCode.VALIDATION_FAILED, "OCR 置信度必须在 0 到 1 之间");
}
return confidence.setScale(5, RoundingMode.HALF_UP);
}
private boolean isSupportedUnit(String unit) {
if (!StringUtils.hasText(unit)) {
return false;