优化
This commit is contained in:
+4
-2
@@ -151,9 +151,11 @@ public class PlatformInboundMeasurementController {
|
||||
|
||||
@BusinessAudit(event = "INBOUND_WEIGHT_OCR_CONFIRM", resourceType = "INBOUND_WEIGHT_RESULT",
|
||||
resourceId = "#resultId", description = "确认 OCR 重量并累加实际总毛重")
|
||||
@Operation(summary = "确认 OCR 重量", description = "统一换算为 KG 并重算所有已确认图片的总重量。")
|
||||
@Operation(summary = "确认 OCR 重量",
|
||||
description = "可在同一事务中携带人工修改值并确认;统一换算为 KG 后重算所有已确认图片的总重量。")
|
||||
@PostMapping("/weight-results/{resultId}/confirm")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:weight:confirm')")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:weight:confirm')"
|
||||
+ " and (!#request.hasAdjustment() or hasAuthority('platform:firstleg:inbound:weight:edit'))")
|
||||
public ApiResponse<InboundMeasurementResponses.WeightResult> confirmWeightResult(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
|
||||
@@ -94,7 +94,13 @@ public final class InboundMeasurementRequests {
|
||||
}
|
||||
|
||||
public record ConfirmWeightResultRequest(
|
||||
@NotNull @PositiveOrZero Integer version
|
||||
@NotNull @PositiveOrZero Integer version,
|
||||
@DecimalMin("0.001") @Digits(integer = 15, fraction = 3) BigDecimal adjustedWeightValue,
|
||||
@Size(max = 8) String adjustedWeightUnit,
|
||||
@Size(max = 500) String adjustmentReason
|
||||
) {
|
||||
public boolean hasAdjustment() {
|
||||
return adjustedWeightValue != null || adjustedWeightUnit != null || adjustmentReason != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,13 +156,22 @@ public class InboundMeasurementService {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
InboundWeightResultEntity weight = requireWeight(tenantId, inboundId, resultId);
|
||||
requireVersion(request.version(), weight.getVersion());
|
||||
String adjustedUnit = normalizeUnit(request.adjustedWeightUnit());
|
||||
applyWeightCorrection(weight, request.version(), request.adjustedWeightValue(),
|
||||
request.adjustedWeightUnit(), request.adjustmentReason(), operatorId, operatorName);
|
||||
return toWeight(weight);
|
||||
}
|
||||
|
||||
private int applyWeightCorrection(
|
||||
InboundWeightResultEntity weight, int expectedVersion, BigDecimal adjustedWeightValue,
|
||||
String adjustedWeightUnit, String adjustmentReason, long operatorId, String operatorName) {
|
||||
requireCompleteAdjustment(adjustedWeightValue, adjustedWeightUnit, adjustmentReason);
|
||||
String adjustedUnit = normalizeUnit(adjustedWeightUnit);
|
||||
BigDecimal beforeValue = effectiveValue(weight);
|
||||
String beforeUnit = effectiveUnit(weight);
|
||||
|
||||
weight.setAdjustedWeightValue(request.adjustedWeightValue());
|
||||
weight.setAdjustedWeightValue(adjustedWeightValue);
|
||||
weight.setAdjustedWeightUnit(adjustedUnit);
|
||||
weight.setAdjustmentReason(request.adjustmentReason());
|
||||
weight.setAdjustmentReason(adjustmentReason.trim());
|
||||
weight.setAdjustedBy(operatorId);
|
||||
weight.setAdjustedAt(LocalDateTime.now());
|
||||
weight.setFinalWeightKg(null);
|
||||
@@ -173,10 +182,12 @@ public class InboundMeasurementService {
|
||||
if (!repository.updateWeight(weight)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
insertRevision(weight, request.version() + 1, "CORRECT", beforeValue, beforeUnit,
|
||||
request.adjustedWeightValue(), adjustedUnit, null, request.adjustmentReason(),
|
||||
int correctedVersion = expectedVersion + 1;
|
||||
weight.setVersion(correctedVersion);
|
||||
insertRevision(weight, correctedVersion, "CORRECT", beforeValue, beforeUnit,
|
||||
adjustedWeightValue, adjustedUnit, null, adjustmentReason.trim(),
|
||||
operatorId, operatorName);
|
||||
return toWeight(weight);
|
||||
return correctedVersion;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -186,7 +197,12 @@ public class InboundMeasurementService {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
InboundWeightResultEntity weight = requireWeight(tenantId, inboundId, resultId);
|
||||
requireVersion(request.version(), weight.getVersion());
|
||||
if (WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus())) {
|
||||
int currentVersion = request.version();
|
||||
if (request.hasAdjustment()) {
|
||||
currentVersion = applyWeightCorrection(weight, currentVersion,
|
||||
request.adjustedWeightValue(), request.adjustedWeightUnit(),
|
||||
request.adjustmentReason(), operatorId, operatorName);
|
||||
} else if (WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus())) {
|
||||
return toWeight(weight);
|
||||
}
|
||||
BigDecimal value = effectiveValue(weight);
|
||||
@@ -204,7 +220,9 @@ public class InboundMeasurementService {
|
||||
if (!repository.updateWeight(weight)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
insertRevision(weight, request.version() + 1, "CONFIRM", value, unit, value, unit,
|
||||
int confirmedVersion = currentVersion + 1;
|
||||
weight.setVersion(confirmedVersion);
|
||||
insertRevision(weight, confirmedVersion, "CONFIRM", value, unit, value, unit,
|
||||
finalKg, null, operatorId, operatorName);
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
return toWeight(weight);
|
||||
@@ -235,6 +253,13 @@ public class InboundMeasurementService {
|
||||
return isPositive(total) ? total : null;
|
||||
}
|
||||
|
||||
private void requireCompleteAdjustment(BigDecimal value, String unit, String reason) {
|
||||
if (!isPositive(value) || !StringUtils.hasText(unit) || !StringUtils.hasText(reason)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"人工修改重量时必须同时填写有效重量、单位和修改原因");
|
||||
}
|
||||
}
|
||||
|
||||
private void insertRevision(InboundWeightResultEntity weight, int revisionNo, String action,
|
||||
BigDecimal beforeValue, String beforeUnit,
|
||||
BigDecimal afterValue, String afterUnit, BigDecimal finalWeightKg,
|
||||
|
||||
@@ -224,6 +224,25 @@ paths:
|
||||
responses:
|
||||
'200': {description: 成功或业务失败,按响应体 code 判断}
|
||||
'403': {description: 无测量编辑权限}
|
||||
/api/platform/v1/first-leg/inbounds/{inboundId}/weight-results/{resultId}/confirm:
|
||||
post:
|
||||
summary: 确认 OCR 重量
|
||||
description: >-
|
||||
需要 platform:firstleg:inbound:weight:confirm 权限;携带人工修改字段时还需要
|
||||
platform:firstleg:inbound:weight:edit 权限。修改与确认在同一事务内完成,保留 OCR 原值,
|
||||
分别写入 CORRECT 和 CONFIRM 修订记录,并重算所有已确认图片的实际总毛重。
|
||||
parameters:
|
||||
- {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: resultId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ConfirmWeightResultRequest'}
|
||||
responses:
|
||||
'200': {description: 成功或业务失败,按响应体 code 判断}
|
||||
'403': {description: 无确认权限,或修改重量时无编辑权限}
|
||||
/api/platform/v1/inbounds/{inboundId}/measurements/approve:
|
||||
post:
|
||||
summary: 审核通过测量
|
||||
@@ -339,6 +358,15 @@ components:
|
||||
in: query
|
||||
schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}
|
||||
schemas:
|
||||
ConfirmWeightResultRequest:
|
||||
type: object
|
||||
required: [version]
|
||||
description: 人工修改字段必须同时提供;全部省略时直接确认现有 OCR 或已修正重量。
|
||||
properties:
|
||||
version: {type: integer, minimum: 0}
|
||||
adjustedWeightValue: {type: number, format: double, minimum: 0.001, nullable: true}
|
||||
adjustedWeightUnit: {type: string, enum: [KG, LB], nullable: true}
|
||||
adjustmentReason: {type: string, minLength: 1, maxLength: 500, nullable: true}
|
||||
OrderApprovedInboundCommand:
|
||||
type: object
|
||||
required: [tenantId, orderId, inboundCode, customerName, warehouseId]
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ class PlatformInboundMeasurementControllerContractTest {
|
||||
assertAuthority("correctWeightResult", "hasAuthority('platform:firstleg:inbound:weight:edit')",
|
||||
AuthenticatedUser.class, long.class, long.class, long.class,
|
||||
InboundMeasurementRequests.CorrectWeightResultRequest.class);
|
||||
assertAuthority("confirmWeightResult", "hasAuthority('platform:firstleg:inbound:weight:confirm')",
|
||||
assertAuthority("confirmWeightResult", "hasAuthority('platform:firstleg:inbound:weight:confirm')"
|
||||
+ " and (!#request.hasAdjustment() or hasAuthority('platform:firstleg:inbound:weight:edit'))",
|
||||
AuthenticatedUser.class, long.class, long.class, long.class,
|
||||
InboundMeasurementRequests.ConfirmWeightResultRequest.class);
|
||||
}
|
||||
|
||||
+53
-1
@@ -22,10 +22,12 @@ import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.tuple;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -113,7 +115,7 @@ class InboundMeasurementServiceTest {
|
||||
|
||||
InboundMeasurementResponses.WeightResult response = service.confirmWeightResult(
|
||||
1L, 10L, 20L, 9L, "操作员",
|
||||
new InboundMeasurementRequests.ConfirmWeightResultRequest(0));
|
||||
new InboundMeasurementRequests.ConfirmWeightResultRequest(0, null, null, null));
|
||||
|
||||
assertThat(response.finalWeightKg()).isEqualByComparingTo("4.536");
|
||||
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("6.536");
|
||||
@@ -121,6 +123,56 @@ class InboundMeasurementServiceTest {
|
||||
verify(repository).insertRevision(any(InboundWeightRevisionEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmingAdjustedWeightWritesCorrectionAndConfirmationInOneTransaction() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
InboundWeightResultEntity weight = weight(20L, "285.500", "KG", "PENDING_CONFIRM", 0);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
|
||||
when(repository.updateWeight(weight)).thenReturn(true);
|
||||
when(repository.listWeights(1L, 10L)).thenReturn(List.of(weight));
|
||||
when(repository.updateInbound(inbound)).thenReturn(true);
|
||||
|
||||
InboundMeasurementResponses.WeightResult response = service.confirmWeightResult(
|
||||
1L, 10L, 20L, 9L, "操作员",
|
||||
new InboundMeasurementRequests.ConfirmWeightResultRequest(
|
||||
0, new BigDecimal("286.125"), "kg", "OCR 原值 285.500 KG,人工确认 286.125 KG"));
|
||||
|
||||
assertThat(response.adjustedWeightValue()).isEqualByComparingTo("286.125");
|
||||
assertThat(response.finalWeightKg()).isEqualByComparingTo("286.125");
|
||||
assertThat(response.recognitionStatus()).isEqualTo("CONFIRMED");
|
||||
assertThat(response.version()).isEqualTo(2);
|
||||
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("286.125");
|
||||
verify(repository, times(2)).updateWeight(weight);
|
||||
ArgumentCaptor<InboundWeightRevisionEntity> revisions =
|
||||
ArgumentCaptor.forClass(InboundWeightRevisionEntity.class);
|
||||
verify(repository, times(2)).insertRevision(revisions.capture());
|
||||
assertThat(revisions.getAllValues()).extracting(
|
||||
InboundWeightRevisionEntity::getRevisionNo,
|
||||
InboundWeightRevisionEntity::getAction)
|
||||
.containsExactly(tuple(1, "CORRECT"), tuple(2, "CONFIRM"));
|
||||
assertThat(revisions.getAllValues().get(0).getReason())
|
||||
.isEqualTo("OCR 原值 285.500 KG,人工确认 286.125 KG");
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmingRejectsPartialAdjustmentBeforePersisting() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
InboundWeightResultEntity weight = weight(20L, "285.500", "KG", "PENDING_CONFIRM", 0);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
|
||||
|
||||
assertThatThrownBy(() -> service.confirmWeightResult(
|
||||
1L, 10L, 20L, 9L, "操作员",
|
||||
new InboundMeasurementRequests.ConfirmWeightResultRequest(
|
||||
0, new BigDecimal("286.125"), null, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("有效重量、单位和修改原因");
|
||||
|
||||
verify(repository, never()).updateWeight(any());
|
||||
verify(repository, never()).insertRevision(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void submitAdvancesOnlyToMeasurePendingAfterAllWeightsConfirmed() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
|
||||
Reference in New Issue
Block a user