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
@@ -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,