fix: complete order measurement downstream flow
This commit is contained in:
@@ -36,6 +36,10 @@ public interface FirstlegInboundClient {
|
||||
ApiResponse<Void> updateDeclaration(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody OrderDeclarationDtos.UpdatedEvent event);
|
||||
|
||||
@PostMapping("/order-remarks-updated")
|
||||
ApiResponse<Integer> updateRemarks(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody OrderRemarksSyncCommand command);
|
||||
|
||||
@PostMapping("/order-channel-changes/validate")
|
||||
ApiResponse<OrderChannelChangeValidationResponse> validateOrderChannelChange(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@@ -51,6 +55,9 @@ public interface FirstlegInboundClient {
|
||||
String channelCode) {}
|
||||
record OrderChannelChangeValidationResponse(long orderId, long orderVersion,
|
||||
String channelCode, String mainStatus) {}
|
||||
record OrderRemarksSyncCommand(long tenantId, long orderId, String customerRemark, String internalRemark,
|
||||
String warehouseRemark, String customerServiceRemark,
|
||||
long operatorId, String operatorName) {}
|
||||
|
||||
record OrderApprovedInboundCommand(
|
||||
long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName,
|
||||
|
||||
@@ -16,6 +16,8 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
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.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -32,6 +34,7 @@ import java.util.stream.Collectors;
|
||||
@RequestMapping("/internal/order/v1/cargoes")
|
||||
public class InternalOrderCargoController {
|
||||
@Resource private OrderRepository orders;
|
||||
@Resource private OrderApplicationService orderApplicationService;
|
||||
@Resource private PrintableInboundSnapshotService printableInboundSnapshots;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
private byte[] expectedToken;
|
||||
@@ -146,6 +149,21 @@ public class InternalOrderCargoController {
|
||||
.collect(Collectors.joining("、"))));
|
||||
}
|
||||
|
||||
@PutMapping("/{tenantId}/{orderId}/remarks")
|
||||
public ApiResponse<InternalRemarksUpdateResponse> updateRemarks(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable long tenantId, @PathVariable long orderId,
|
||||
@RequestBody InternalRemarksUpdateRequest request) {
|
||||
requireToken(token);
|
||||
if (tenantId <= 0 || orderId <= 0 || request == null || request.operatorId() <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "客户、订单和操作人必须大于0");
|
||||
}
|
||||
var result = orderApplicationService.updateRemarksFromFirstleg(tenantId, request.operatorId(), orderId,
|
||||
request.internalRemark(), request.warehouseRemark(), request.customerServiceRemark());
|
||||
return ApiResponse.success(new InternalRemarksUpdateResponse(result.version(), result.customerRemark(),
|
||||
result.internalRemark(), result.warehouseRemark(), result.customerServiceRemark()));
|
||||
}
|
||||
|
||||
private BigDecimal sum(List<OrderCargoEntity> cargoes,
|
||||
java.util.function.Function<OrderCargoEntity, BigDecimal> getter) {
|
||||
List<BigDecimal> values = cargoes.stream().map(getter).filter(Objects::nonNull).toList();
|
||||
@@ -205,4 +223,10 @@ public class InternalOrderCargoController {
|
||||
public record MeasurementDeclarationSnapshot(
|
||||
String referenceId, BigDecimal originalGrossWeightKg,
|
||||
BigDecimal originalVolumeCbm, String defaultActualGoodsName) {}
|
||||
|
||||
public record InternalRemarksUpdateRequest(String internalRemark, String warehouseRemark,
|
||||
String customerServiceRemark, long operatorId) {}
|
||||
public record InternalRemarksUpdateResponse(int version, String customerRemark, String internalRemark,
|
||||
String warehouseRemark, String customerServiceRemark) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ public class PlatformUnifiedOrderController {
|
||||
@PathVariable long orderId,
|
||||
@Valid @RequestBody OrderRequests.RemarksUpdateRequest request) {
|
||||
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(orderService.updateRemarks(scopedTenant, user.userId(), orderId, request));
|
||||
return ApiResponse.success(orderService.updateRemarks(
|
||||
scopedTenant, user.userId(), user.username(), orderId, request));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +271,12 @@ public class OrderApplicationService {
|
||||
@Transactional
|
||||
public OrderRequests.RemarksUpdateResponse updateRemarks(long tenantId, long userId, long id,
|
||||
OrderRequests.RemarksUpdateRequest request) {
|
||||
return updateRemarks(tenantId, userId, null, id, request);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OrderRequests.RemarksUpdateResponse updateRemarks(long tenantId, long userId, String operatorName, long id,
|
||||
OrderRequests.RemarksUpdateRequest request) {
|
||||
OrderEntity order = require(tenantId, id);
|
||||
String internalRemark = normalizeRemark(request.internalRemark());
|
||||
String warehouseRemark = normalizeRemark(request.warehouseRemark());
|
||||
@@ -284,10 +290,57 @@ public class OrderApplicationService {
|
||||
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(internalRemark);
|
||||
note.setCreatedBy(userId); note.setUpdatedBy(userId); repository.insertNote(note);
|
||||
}
|
||||
syncRemarksToFirstleg(tenantId, id, order.getCustomerRemark(), internalRemark, warehouseRemark, customerServiceRemark,
|
||||
userId, operatorName);
|
||||
return new OrderRequests.RemarksUpdateResponse(order.getVersion() + 1, order.getCustomerRemark(),
|
||||
internalRemark, warehouseRemark, customerServiceRemark);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OrderRequests.RemarksUpdateResponse updateRemarksFromFirstleg(long tenantId, long userId, long id,
|
||||
String internalValue,
|
||||
String warehouseValue,
|
||||
String customerServiceValue) {
|
||||
requireRemarkLength(internalValue);
|
||||
requireRemarkLength(warehouseValue);
|
||||
requireRemarkLength(customerServiceValue);
|
||||
OrderEntity order = require(tenantId, id);
|
||||
String internalRemark = normalizeRemark(internalValue);
|
||||
String warehouseRemark = normalizeRemark(warehouseValue);
|
||||
String customerServiceRemark = normalizeRemark(customerServiceValue);
|
||||
if (repository.updateRemarks(tenantId, id, order.getVersion(), userId,
|
||||
internalRemark, warehouseRemark, customerServiceRemark) != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "订单备注已被其他人修改,请刷新后重试");
|
||||
}
|
||||
if (!Objects.equals(order.getInternalRemark(), internalRemark) && internalRemark != null) {
|
||||
OrderInternalNoteEntity note = new OrderInternalNoteEntity();
|
||||
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(internalRemark);
|
||||
note.setCreatedBy(userId); note.setUpdatedBy(userId); repository.insertNote(note);
|
||||
}
|
||||
return new OrderRequests.RemarksUpdateResponse(order.getVersion() + 1, order.getCustomerRemark(),
|
||||
internalRemark, warehouseRemark, customerServiceRemark);
|
||||
}
|
||||
|
||||
private void syncRemarksToFirstleg(long tenantId, long orderId, String customerRemark, String internalRemark,
|
||||
String warehouseRemark, String customerServiceRemark,
|
||||
long operatorId, String operatorName) {
|
||||
FirstlegInboundClient client = firstlegProvider == null ? null : firstlegProvider.getIfAvailable();
|
||||
if (client == null) return;
|
||||
ApiResponse<Integer> response = client.updateRemarks(internalToken,
|
||||
new FirstlegInboundClient.OrderRemarksSyncCommand(tenantId, orderId, customerRemark, internalRemark,
|
||||
warehouseRemark, customerServiceRemark, operatorId, operatorName));
|
||||
if (response == null || !response.successful()) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE,
|
||||
response == null ? "头程备注同步暂不可用" : response.message());
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireRemarkLength(String value) {
|
||||
if (value != null && value.length() > 1000) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "每项备注不能超过1000个字符");
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizeRemark(String value) {
|
||||
if (value == null || value.trim().isEmpty()) return null;
|
||||
return value.trim();
|
||||
|
||||
@@ -59,6 +59,34 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/CargoLabelBindingListEnvelope'}
|
||||
'403': {description: 内部服务令牌无效}
|
||||
put:
|
||||
security: []
|
||||
summary: Firstleg 维护订单权威协作备注
|
||||
description: >-
|
||||
仅接受内部服务令牌。预装清单编辑备注时写回 Order 权威订单记录;
|
||||
客户备注保持只读,内部、仓库和客服备注统一按订单版本乐观更新。
|
||||
parameters:
|
||||
- name: X-Internal-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema: {type: string}
|
||||
- {name: tenantId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: orderId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [operatorId]
|
||||
properties:
|
||||
internalRemark: {type: string, nullable: true, maxLength: 1000}
|
||||
warehouseRemark: {type: string, nullable: true, maxLength: 1000}
|
||||
customerServiceRemark: {type: string, nullable: true, maxLength: 1000}
|
||||
operatorId: {type: integer, format: int64, minimum: 1}
|
||||
responses:
|
||||
'200': {description: 返回订单最新版本和四类备注}
|
||||
'403': {description: 内部服务令牌无效}
|
||||
/internal/order/v1/cargoes/{tenantId}/{orderId}/printable-inbound-snapshot:
|
||||
post:
|
||||
security: []
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class InternalOrderCargoControllerTest {
|
||||
@MockBean private OrderRepository orders;
|
||||
@MockBean private OrderApplicationService orderApplicationService;
|
||||
@MockBean private PrintableInboundSnapshotService printableInboundSnapshots;
|
||||
@Resource private InternalOrderCargoController controller;
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.baiyue.order.service;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.order.client.FirstlegInboundClient;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.repository.OrderRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class OrderRemarksSynchronizationTest {
|
||||
@Test
|
||||
void platformUpdateSynchronizesAuthoritativeRemarksToFirstleg() {
|
||||
OrderRepository repository = mock(OrderRepository.class);
|
||||
FirstlegInboundClient firstleg = mock(FirstlegInboundClient.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectProvider<FirstlegInboundClient> provider = mock(ObjectProvider.class);
|
||||
OrderApplicationService service = new OrderApplicationService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
ReflectionTestUtils.setField(service, "firstlegProvider", provider);
|
||||
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
||||
|
||||
OrderEntity order = new OrderEntity();
|
||||
order.setId(20L);
|
||||
order.setTenantId(10L);
|
||||
order.setVersion(7);
|
||||
order.setCustomerRemark("客户备注");
|
||||
when(repository.findOwned(10L, 20L)).thenReturn(order);
|
||||
when(repository.updateRemarks(10L, 20L, 7, 30L, "内部", "仓库", "客服")).thenReturn(1);
|
||||
when(provider.getIfAvailable()).thenReturn(firstleg);
|
||||
when(firstleg.updateRemarks(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(2));
|
||||
|
||||
var response = service.updateRemarks(10L, 30L, "操作人", 20L,
|
||||
new OrderRequests.RemarksUpdateRequest(7, " 内部 ", "仓库", "客服"));
|
||||
|
||||
assertThat(response.version()).isEqualTo(8);
|
||||
ArgumentCaptor<FirstlegInboundClient.OrderRemarksSyncCommand> command =
|
||||
ArgumentCaptor.forClass(FirstlegInboundClient.OrderRemarksSyncCommand.class);
|
||||
verify(firstleg).updateRemarks(org.mockito.ArgumentMatchers.eq("internal-token"), command.capture());
|
||||
assertThat(command.getValue().customerRemark()).isEqualTo("客户备注");
|
||||
assertThat(command.getValue().internalRemark()).isEqualTo("内部");
|
||||
assertThat(command.getValue().warehouseRemark()).isEqualTo("仓库");
|
||||
assertThat(command.getValue().customerServiceRemark()).isEqualTo("客服");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user