fix: complete order measurement downstream flow

This commit is contained in:
2026-09-18 14:58:40 +08:00
parent 02d2474141
commit c8a99f9fff
43 changed files with 621 additions and 52 deletions
+1 -1
View File
@@ -36,7 +36,7 @@
| 财务费用来源快照 | `controller/InternalFinanceFeeSourceController.java`;入库费用与柜次费用分别按来源 ID、版本向 Finance 提供不可变快照 |
| 柜次费用明细与跨客户分摊组 | `controller/PlatformContainerFeeDetailController.java`、`service/ContainerFeeDetailService.java`、`dto/ContainerFeeDetailDtos.java`;跨客户公共柜费使用 `allocation-groups` 按柜内真实客户和订单原子分摊,入仓 ID/入仓码同时填写或同时留空;各行金额合计必须等于原始柜费,审核、驳回、调整、作废均整组处理,单行入口拒绝分摊组;契约 `src/main/resources/openapi/container-fees-v1.yaml`;Admin 消费端见 `../../apps/admin-web/src/modules/loading-lists/api/containerFees.ts` |
| 财务整柜成本分摊快照 | `loading/controller/InternalFinanceCostSnapshotController.java`、`loading/service/FinanceCostSnapshotService.java` |
| 分柜、预装、真实柜号后补与正式装柜清单生命周期 | `split/controller/PlatformSplitPreloadController.java`、`controller/PlatformLoadingListController.java`、`service/LoadingListService.java`;契约 `src/main/resources/openapi/loading-lists-v1.yaml`,详情阶段先查 `/loading-plans/{planId}/lifecycle`。创建柜次时原子生成唯一预装计划,加入入仓码明细时同步创建入库预装聚合与关联,不设推送步骤;同一 `planId` 在 `EDITING/LOADING` 返回 `PRELOAD`,`COMPLETED` 返回 `LOADING_LIST`。订舱关联、预装确认和 PDA task 创建不依赖真实集装箱号;可在 `EDITING/LOADING` 首次补录,最终 PDA 完成前必须存在;已有值纠正仅限 `EDITING` 且任务未领取、无物理作业事实 |
| 分柜、预装、真实柜号后补与正式装柜清单生命周期 | `split/controller/PlatformSplitPreloadController.java`、`controller/PlatformLoadingListController.java`、`service/LoadingListService.java`;契约 `src/main/resources/openapi/loading-lists-v1.yaml`,详情阶段先查 `/loading-plans/{planId}/lifecycle`。创建柜次时原子生成唯一预装计划,加入入仓码明细时同步创建入库预装聚合与关联,不设推送步骤;同一 `planId` 在 `EDITING/LOADING` 返回 `PRELOAD`,`COMPLETED` 返回 `LOADING_LIST`。订单协作备注以 Order 为权威,`OrderRemarksSyncService` 负责订单列表与全部未冻结预装明细双向同步,完成装柜后保留冻结快照。订舱关联、预装确认和 PDA task 创建不依赖真实集装箱号;可在 `EDITING/LOADING` 首次补录,最终 PDA 完成前必须存在;已有值纠正仅限 `EDITING` 且任务未领取、无物理作业事实 |
| 同柜不可拆组 | `loading/coload/controller/PlatformColoadGroupController.java`、`service/ColoadGroupService.java`、`persistence/ColoadGroupMapper.java`;契约 `src/main/resources/openapi/coload-groups-v1.yaml`。请求按订单 ID 建组,服务端冻结每票全部 `WAIT_SPLIT` 可用入仓片段与满箱数;仅要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均保持整组原子 |
| 货柜与全局智能排柜 | `controller/PlatformContainerController.java`、`PlatformSmartLoadingController.java`、`service/SmartLoadingService.java`、`repository/SmartLoadingRepository.java`、`client/PendingReviewOrderClient.java`、`loading/`;全局候选合并本服务履约投影与 Order `/internal/order/v1/pending-review-orders` 权威待审核页,工作台柜次从 `/inbound-candidates`、`/containers` 进入;普通箱数、待审核占位、同柜组和全部未拣移除分别走 `/inbound-assignments`、`/pending-review-assignments`、`/group-assignments`、`/unpicked-items`,明细规格拆分留在服务端 |
| 货柜资料、PDA 证据与业务总览 | `controller/PlatformContainerController.java` 的 `/{id}/overview`、`/{id}/documents/{documentType}`,聚合 `mapper/ContainerOverviewMapper.java`;契约 `src/main/resources/openapi/phase3-container-overview-v1.yaml`,四类资料固定为目的港清关、国内出口报关、DO、PU 的当前唯一版本 |
@@ -5,6 +5,8 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.math.BigDecimal;
import java.util.List;
@@ -36,10 +38,21 @@ public interface OrderCargoClient {
@RequestHeader("X-Internal-Service-Token") String token,
@PathVariable long tenantId, @PathVariable long orderId);
@PutMapping("/{tenantId}/{orderId}/remarks")
ApiResponse<RemarksUpdateResponse> updateRemarks(
@RequestHeader("X-Internal-Service-Token") String token,
@PathVariable long tenantId, @PathVariable long orderId,
@RequestBody RemarksUpdateRequest request);
record MeasurementDeclarationSnapshot(
String referenceId, BigDecimal originalGrossWeightKg,
BigDecimal originalVolumeCbm, String defaultActualGoodsName) {}
record RemarksUpdateRequest(String internalRemark, String warehouseRemark,
String customerServiceRemark, long operatorId) {}
record RemarksUpdateResponse(int version, String customerRemark, String internalRemark,
String warehouseRemark, String customerServiceRemark) {}
record CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
@@ -12,7 +12,9 @@ import com.baiyue.firstleg.service.InboundOrderCreationService;
import com.baiyue.firstleg.service.InboundCartonLabelService;
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
import com.baiyue.firstleg.service.OrderRemarksSyncService;
import com.baiyue.firstleg.dto.OrderDeclarationSync;
import com.baiyue.firstleg.dto.OrderRemarksSync;
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
import com.baiyue.firstleg.dto.UnifiedInboundRequest;
@@ -43,6 +45,8 @@ public class InternalInboundOrderController {
private UnifiedInboundQueryService unifiedQueryService;
@Resource
private OrderDeclarationSyncService declarationSync;
@Resource
private OrderRemarksSyncService remarksSync;
@Value("${baiyue.security.internal-service-token}")
private String internalToken;
private byte[] expectedToken;
@@ -127,6 +131,14 @@ public class InternalInboundOrderController {
return ApiResponse.success(null);
}
@PostMapping("/order-remarks-updated")
public ApiResponse<Integer> updateOrderRemarks(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody OrderRemarksSync.Command command) {
requireToken(token);
return ApiResponse.success(remarksSync.sync(command));
}
@PostMapping("/unified-orders/query")
public ApiResponse<PageResult<UnifiedInboundSummary>> unifiedPage(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@@ -0,0 +1,22 @@
package com.baiyue.firstleg.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
public final class OrderRemarksSync {
private OrderRemarksSync() {
}
public record Command(
@Positive long tenantId,
@Positive long orderId,
@Size(max = 1000) String customerRemark,
@Size(max = 1000) String internalRemark,
@Size(max = 1000) String warehouseRemark,
@Size(max = 1000) String customerServiceRemark,
@NotNull @Positive Long operatorId,
@Size(max = 128) String operatorName
) {
}
}
@@ -1,5 +1,5 @@
package com.baiyue.firstleg.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import org.apache.ibatis.annotations.*;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import org.apache.ibatis.annotations.*; import java.util.List;
@Mapper public interface LoadingPlanItemMapper extends BaseMapper<LoadingPlanItemEntity> {
@Update("""
UPDATE fl_loading_plan_item SET deleted=1,removed_by=#{operatorId},removed_by_name=#{operatorName},
@@ -30,4 +30,69 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstl
@Param("customerServiceRemark") String customerServiceRemark,
@Param("operatorId") long operatorId,
@Param("operatorName") String operatorName);
@Select("""
SELECT DISTINCT i.loading_plan_id
FROM fl_loading_plan_item i
JOIN fl_loading_plan p ON p.id=i.loading_plan_id AND p.plan_status IN ('EDITING','LOADING')
WHERE i.account_id=#{tenantId} AND i.order_id=#{orderId} AND i.deleted=0 AND i.frozen_at IS NULL
AND (#{excludedItemId} IS NULL OR i.id&lt;&gt;#{excludedItemId})
AND NOT (i.customer_remark <=> #{customerRemark}
AND i.internal_remark <=> #{internalRemark}
AND i.warehouse_remark <=> #{warehouseRemark}
AND i.customer_service_remark <=> #{customerServiceRemark})
""")
List<Long> findMutablePlanIdsWithDifferentRemarks(@Param("tenantId") long tenantId,
@Param("orderId") long orderId,
@Param("customerRemark") String customerRemark,
@Param("internalRemark") String internalRemark,
@Param("warehouseRemark") String warehouseRemark,
@Param("customerServiceRemark") String customerServiceRemark,
@Param("excludedItemId") Long excludedItemId);
@Update("""
UPDATE fl_loading_plan_item i
JOIN fl_loading_plan p ON p.id=i.loading_plan_id AND p.plan_status IN ('EDITING','LOADING')
SET i.customer_remark=#{customerRemark}, i.internal_remark=#{internalRemark}, i.warehouse_remark=#{warehouseRemark},
i.customer_service_remark=#{customerServiceRemark}, i.updated_by=#{operatorId},
i.updated_by_name=#{operatorName}, i.updated_at=CURRENT_TIMESTAMP(3), i.version=i.version+1
WHERE i.account_id=#{tenantId} AND i.order_id=#{orderId} AND i.deleted=0 AND i.frozen_at IS NULL
AND (#{excludedItemId} IS NULL OR i.id&lt;&gt;#{excludedItemId})
AND NOT (i.customer_remark <=> #{customerRemark}
AND i.internal_remark <=> #{internalRemark}
AND i.warehouse_remark <=> #{warehouseRemark}
AND i.customer_service_remark <=> #{customerServiceRemark})
""")
int syncMutableOrderRemarks(@Param("tenantId") long tenantId, @Param("orderId") long orderId,
@Param("customerRemark") String customerRemark,
@Param("internalRemark") String internalRemark,
@Param("warehouseRemark") String warehouseRemark,
@Param("customerServiceRemark") String customerServiceRemark,
@Param("operatorId") long operatorId,
@Param("operatorName") String operatorName,
@Param("excludedItemId") Long excludedItemId);
@Select("""
SELECT DISTINCT i.loading_plan_id
FROM fl_loading_plan_item i
JOIN fl_loading_plan p ON p.id=i.loading_plan_id AND p.plan_status IN ('EDITING','LOADING')
WHERE i.account_id=#{tenantId} AND i.order_id=#{orderId} AND i.deleted=0 AND i.frozen_at IS NULL
AND NOT (i.customer_remark <=> #{customerRemark})
""")
List<Long> findMutablePlanIdsWithDifferentCustomerRemark(@Param("tenantId") long tenantId,
@Param("orderId") long orderId,
@Param("customerRemark") String customerRemark);
@Update("""
UPDATE fl_loading_plan_item i
JOIN fl_loading_plan p ON p.id=i.loading_plan_id AND p.plan_status IN ('EDITING','LOADING')
SET i.customer_remark=#{customerRemark}, i.updated_by=#{operatorId}, i.updated_by_name=#{operatorName},
i.updated_at=CURRENT_TIMESTAMP(3), i.version=i.version+1
WHERE i.account_id=#{tenantId} AND i.order_id=#{orderId} AND i.deleted=0 AND i.frozen_at IS NULL
AND NOT (i.customer_remark <=> #{customerRemark})
""")
int syncMutableOrderCustomerRemark(@Param("tenantId") long tenantId, @Param("orderId") long orderId,
@Param("customerRemark") String customerRemark,
@Param("operatorId") long operatorId,
@Param("operatorName") String operatorName);
}
@@ -1,5 +1,5 @@
package com.baiyue.firstleg.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstleg.entity.LoadingPlanEntity; import org.apache.ibatis.annotations.*;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstleg.entity.LoadingPlanEntity; import org.apache.ibatis.annotations.*; import java.util.List;
@Mapper public interface LoadingPlanMapper extends BaseMapper<LoadingPlanEntity> {
@Update("""
UPDATE fl_loading_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},
@@ -13,4 +13,16 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstl
WHERE id=#{planId} AND plan_status='EDITING'
""")
int startPicking(@Param("planId") long planId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
@Update("""
<script>
UPDATE fl_loading_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},
updated_at=CURRENT_TIMESTAMP(3),version=version+1
WHERE plan_status IN ('EDITING','LOADING') AND id IN
<foreach collection="planIds" item="planId" open="(" separator="," close=")">#{planId}</foreach>
</script>
""")
int touchForOrderRemarks(@Param("planIds") List<Long> planIds,
@Param("operatorId") long operatorId,
@Param("operatorName") String operatorName);
}
@@ -25,6 +25,7 @@ import java.util.Objects;
public class OrderDeclarationSyncService {
@Resource private OrderDeclarationSyncMapper mapper;
@Resource private ObjectMapper json;
@Resource private OrderRemarksSyncService remarksSync;
@Transactional(readOnly = true)
public ChannelChangeValidationResponse validateChannelChange(ChannelChangeValidationRequest request) {
@@ -86,6 +87,8 @@ public class OrderDeclarationSyncService {
normalizeChannel(event.channelCode()), event.channelName(), event.operatorId()) != 1) {
throw conflict("Firstleg 入库申报快照已发生变化");
}
remarksSync.syncCustomerRemark(event.tenantId(), event.orderId(), event.customerRemark(),
event.operatorId(), "order-declaration");
}
private void recordEvent(UpdatedEvent event, String hash) {
@@ -0,0 +1,57 @@
package com.baiyue.firstleg.service;
import com.baiyue.firstleg.dto.OrderRemarksSync;
import com.baiyue.firstleg.mapper.LoadingPlanItemMapper;
import com.baiyue.firstleg.mapper.LoadingPlanMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class OrderRemarksSyncService {
@Resource private LoadingPlanItemMapper items;
@Resource private LoadingPlanMapper plans;
@Transactional
public int sync(OrderRemarksSync.Command command) {
return sync(command, null, null);
}
@Transactional
public int sync(OrderRemarksSync.Command command, Long alreadyTouchedPlanId) {
return sync(command, alreadyTouchedPlanId, null);
}
@Transactional
public int sync(OrderRemarksSync.Command command, Long alreadyTouchedPlanId, Long excludedItemId) {
List<Long> affectedPlanIds = items.findMutablePlanIdsWithDifferentRemarks(
command.tenantId(), command.orderId(), command.customerRemark(), command.internalRemark(), command.warehouseRemark(),
command.customerServiceRemark(), excludedItemId);
if (affectedPlanIds.isEmpty()) return 0;
int updated = items.syncMutableOrderRemarks(command.tenantId(), command.orderId(),
command.customerRemark(), command.internalRemark(), command.warehouseRemark(), command.customerServiceRemark(),
command.operatorId(), command.operatorName(), excludedItemId);
if (updated > 0) {
List<Long> plansToTouch = affectedPlanIds.stream()
.filter(planId -> !planId.equals(alreadyTouchedPlanId)).toList();
if (!plansToTouch.isEmpty()) {
plans.touchForOrderRemarks(plansToTouch, command.operatorId(), command.operatorName());
}
}
return updated;
}
@Transactional
public int syncCustomerRemark(long tenantId, long orderId, String customerRemark,
long operatorId, String operatorName) {
List<Long> affectedPlanIds = items.findMutablePlanIdsWithDifferentCustomerRemark(
tenantId, orderId, customerRemark);
if (affectedPlanIds.isEmpty()) return 0;
int updated = items.syncMutableOrderCustomerRemark(
tenantId, orderId, customerRemark, operatorId, operatorName);
if (updated > 0) plans.touchForOrderRemarks(affectedPlanIds, operatorId, operatorName);
return updated;
}
}
@@ -6,6 +6,7 @@ import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.firstleg.dto.SmartLoadingDtos.*;
import com.baiyue.firstleg.dto.OrderRemarksSync;
import com.baiyue.firstleg.domain.SmartLoadingChannelGroupCatalog;
import com.baiyue.firstleg.client.OrderCargoClient;
import com.baiyue.firstleg.client.PendingReviewOrderClient;
@@ -56,6 +57,7 @@ public class SmartLoadingService {
@Resource private FbaPricingService fbaPricingService;
@Resource private PriceBookQueryUseCase priceBooks;
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
@Resource private OrderRemarksSyncService orderRemarksSync;
@Resource private ObjectMapper objectMapper;
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
public SmartLoadingService() { }
@@ -1099,6 +1101,9 @@ public class SmartLoadingService {
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
LoadingPlanItemEntity item = repository.item(planId, itemId);
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
if (item.getVersion() != request.expectedItemVersion()) {
throw conflict("装柜计划或明细已被其他人修改,请刷新后重试");
}
if (Objects.equals(item.getInternalRemark(), internalRemark)
&& Objects.equals(item.getWarehouseRemark(), warehouseRemark)
&& Objects.equals(item.getCustomerServiceRemark(), customerServiceRemark)) {
@@ -1109,16 +1114,30 @@ public class SmartLoadingService {
"version", item.getVersion()));
try {
touch(plan, operatorId, operatorName);
ApiResponse<OrderCargoClient.RemarksUpdateResponse> response = orderCargoClient.updateRemarks(
internalToken, item.getAccountId(), item.getOrderId(),
new OrderCargoClient.RemarksUpdateRequest(internalRemark, warehouseRemark,
customerServiceRemark, operatorId));
if (response == null || !response.successful() || response.data() == null) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE,
response == null ? "订单备注服务暂不可用" : response.message());
}
if (repository.updateRemarks(planId, itemId, request.expectedPlanVersion() + 1,
request.expectedItemVersion(), internalRemark, warehouseRemark, customerServiceRemark,
operatorId, operatorName) != 1) {
request.expectedItemVersion(), response.data().internalRemark(), response.data().warehouseRemark(),
response.data().customerServiceRemark(), operatorId, operatorName) != 1) {
throw conflict("装柜计划或明细已被其他人修改,请刷新后重试");
}
orderRemarksSync.sync(new OrderRemarksSync.Command(item.getAccountId(), item.getOrderId(),
response.data().customerRemark(),
response.data().internalRemark(), response.data().warehouseRemark(),
response.data().customerServiceRemark(), operatorId, operatorName), planId, itemId);
} catch (DataAccessException exception) {
if (hasSqlState(exception, "45000")) {
throw conflict("装柜计划或明细状态已变化,备注未保存,请刷新后重试");
}
throw exception;
} catch (FeignException exception) {
throw unavailable();
}
history(planId, itemId, "REMARKS", plan.getPlanStatus(), plan.getPlanStatus(), operation,
before, json(values("internalRemark", internalRemark, "warehouseRemark", warehouseRemark,
@@ -292,6 +292,32 @@ paths:
schema: {$ref: '#/components/schemas/OrderDeclarationUpdatedEvent'}
responses:
'200': {description: 成功、幂等重放或旧版本已忽略;冲突时响应体 code=A0409}
/internal/firstleg/v1/inbounds/order-remarks-updated:
post:
summary: 同步 Order 权威协作备注到未冻结预装明细
description: >-
仅接受内部服务令牌。按客户与订单关联全部 EDITING/LOADING 且未冻结的预装明细,
同步内部、仓库和客服备注并递增受影响计划与明细版本;已完成装柜的冻结快照不修改。
security:
- internalServiceToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenantId, orderId, operatorId]
properties:
tenantId: {type: integer, format: int64, minimum: 1}
orderId: {type: integer, format: int64, minimum: 1}
customerRemark: {type: string, nullable: true, maxLength: 1000}
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}
operatorName: {type: string, nullable: true, maxLength: 128}
responses:
'200': {description: data 返回已同步的预装明细数量}
/internal/firstleg/v1/inbounds/unified-orders/query:
post:
summary: 按统一订单条件分页查询履约摘要(Order 内部)
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.service.InboundOrderCreationService;
import com.baiyue.firstleg.service.InboundCartonLabelService;
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
import com.baiyue.firstleg.service.OrderRemarksSyncService;
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
import com.baiyue.firstleg.dto.OrderDeclarationSync;
import com.baiyue.common.core.api.PageResult;
@@ -36,6 +37,8 @@ class InternalInboundOrderReferenceControllerTest {
private UnifiedInboundQueryService unifiedQueryService;
@MockBean
private OrderDeclarationSyncService declarationSync;
@MockBean
private OrderRemarksSyncService remarksSync;
@Resource
private InternalInboundOrderController controller;
@@ -35,6 +35,7 @@ class OrderDeclarationSyncServiceTest {
}
@MockBean private OrderDeclarationSyncMapper mapper;
@MockBean private OrderRemarksSyncService remarksSync;
@Resource private OrderDeclarationSyncService service;
@Resource private ObjectMapper json;
@@ -0,0 +1,50 @@
package com.baiyue.firstleg.service;
import com.baiyue.firstleg.dto.OrderRemarksSync;
import com.baiyue.firstleg.mapper.LoadingPlanItemMapper;
import com.baiyue.firstleg.mapper.LoadingPlanMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(OrderRemarksSyncService.class)
class OrderRemarksSyncServiceTest {
@MockBean private LoadingPlanItemMapper items;
@MockBean private LoadingPlanMapper plans;
@jakarta.annotation.Resource private OrderRemarksSyncService service;
@Test
void updatesAllMutableOrderItemsAndTouchesAffectedPlans() {
var command = new OrderRemarksSync.Command(10L, 20L, "客户", "内部", "仓库", "客服", 30L, "操作人");
when(items.findMutablePlanIdsWithDifferentRemarks(
10L, 20L, "客户", "内部", "仓库", "客服", null)).thenReturn(List.of(101L, 102L));
when(items.syncMutableOrderRemarks(
10L, 20L, "客户", "内部", "仓库", "客服", 30L, "操作人", null)).thenReturn(3);
assertThat(service.sync(command)).isEqualTo(3);
verify(plans).touchForOrderRemarks(List.of(101L, 102L), 30L, "操作人");
}
@Test
void leavesFrozenOrAlreadyMatchingPlansUntouched() {
var command = new OrderRemarksSync.Command(10L, 20L, null, null, null, null, 30L, null);
when(items.findMutablePlanIdsWithDifferentRemarks(
10L, 20L, null, null, null, null, null)).thenReturn(List.of());
assertThat(service.sync(command)).isZero();
verify(items, never()).syncMutableOrderRemarks(
10L, 20L, null, null, null, null, 30L, null, null);
verify(plans, never()).touchForOrderRemarks(org.mockito.ArgumentMatchers.anyList(),
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.nullable(String.class));
}
}
@@ -102,6 +102,7 @@ class SmartLoadingServiceTest {
@MockBean private PriceBookQueryUseCase priceBooks;
@MockBean private FirstlegDocumentNumberGenerator documentNumbers;
@MockBean private DropshipPalletizationMapper dropshipPallets;
@MockBean private OrderRemarksSyncService orderRemarksSync;
@Resource private SmartLoadingService service;
@TestConfiguration
@@ -119,6 +120,12 @@ class SmartLoadingServiceTest {
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenReturn(
ApiResponse.success(new OrderCargoClient.CargoBinding(20L, 30L, 1, "货物", 5,
new BigDecimal("10"), new BigDecimal("2"))));
when(cargoClient.updateRemarks(anyString(), anyLong(), anyLong(), any()))
.thenAnswer(invocation -> {
OrderCargoClient.RemarksUpdateRequest request = invocation.getArgument(3);
return ApiResponse.success(new OrderCargoClient.RemarksUpdateResponse(2, null,
request.internalRemark(), request.warehouseRemark(), request.customerServiceRemark()));
});
when(fbaPricingService.recalculateUnlockedContainerCustomerFees(anyLong(), anyLong(), anyString()))
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY, 0));
@@ -1955,6 +1962,7 @@ class SmartLoadingServiceTest {
private static LoadingPlanItemEntity item(long id, long account, long detail, int boxes) {
LoadingPlanItemEntity value = new LoadingPlanItemEntity();
value.setId(id); value.setLoadingPlanId(1L); value.setAccountId(account);
value.setOrderId(30L); value.setOrderNo("O30");
value.setInboundId(10L); value.setInboundDetailId(detail); value.setPackageCount(boxes);
value.setVersion(0); value.setDeleted(false);
return value;