fix: complete order measurement downstream flow
This commit is contained in:
@@ -444,6 +444,8 @@ CREATE TABLE ll_dispatch_item (
|
|||||||
contact_name varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '私人仓联系人快照',
|
contact_name varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '私人仓联系人快照',
|
||||||
contact_phone varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '私人仓联系电话快照',
|
contact_phone varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '私人仓联系电话快照',
|
||||||
package_count int NOT NULL DEFAULT '0' COMMENT '箱数',
|
package_count int NOT NULL DEFAULT '0' COMMENT '箱数',
|
||||||
|
gross_weight_kg decimal(18,3) NOT NULL DEFAULT '0.000' COMMENT '毛重(千克)',
|
||||||
|
volume_weight_kg decimal(18,3) NOT NULL DEFAULT '0.000' COMMENT '材积重(千克)',
|
||||||
volume_cbm decimal(18,4) NOT NULL DEFAULT '0.0000' COMMENT '体积(立方米)',
|
volume_cbm decimal(18,4) NOT NULL DEFAULT '0.0000' COMMENT '体积(立方米)',
|
||||||
source_stage varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据来源阶段',
|
source_stage varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据来源阶段',
|
||||||
carton_marks varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '换标箱唛映射快照',
|
carton_marks varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '换标箱唛映射快照',
|
||||||
@@ -1674,6 +1676,9 @@ CREATE TABLE ll_outbound_order (
|
|||||||
status varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
|
status varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
|
||||||
package_count int NOT NULL DEFAULT '0' COMMENT '箱数',
|
package_count int NOT NULL DEFAULT '0' COMMENT '箱数',
|
||||||
pallet_count int NOT NULL DEFAULT '0' COMMENT '托盘数',
|
pallet_count int NOT NULL DEFAULT '0' COMMENT '托盘数',
|
||||||
|
gross_weight_kg decimal(18,3) NOT NULL DEFAULT '0.000' COMMENT '毛重(千克)',
|
||||||
|
volume_weight_kg decimal(18,3) NOT NULL DEFAULT '0.000' COMMENT '材积重(千克)',
|
||||||
|
volume_cbm decimal(18,4) NOT NULL DEFAULT '0.0000' COMMENT '体积(立方米)',
|
||||||
idempotency_key varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务幂等键',
|
idempotency_key varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务幂等键',
|
||||||
created_by varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人ID',
|
created_by varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人ID',
|
||||||
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||||
@@ -1699,6 +1704,9 @@ CREATE TABLE ll_outbound_pallet_item (
|
|||||||
destination_warehouse_code varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的仓编码快照',
|
destination_warehouse_code varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的仓编码快照',
|
||||||
destination_warehouse_name varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的仓名称快照',
|
destination_warehouse_name varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的仓名称快照',
|
||||||
package_count int NOT NULL DEFAULT '0' COMMENT '箱数',
|
package_count int NOT NULL DEFAULT '0' COMMENT '箱数',
|
||||||
|
gross_weight_kg decimal(18,3) NOT NULL DEFAULT '0.000' COMMENT '毛重(千克)',
|
||||||
|
volume_weight_kg decimal(18,3) NOT NULL DEFAULT '0.000' COMMENT '材积重(千克)',
|
||||||
|
volume_cbm decimal(18,4) NOT NULL DEFAULT '0.0000' COMMENT '体积(立方米)',
|
||||||
picked_package_count int NOT NULL DEFAULT '0' COMMENT '已拣箱数',
|
picked_package_count int NOT NULL DEFAULT '0' COMMENT '已拣箱数',
|
||||||
status varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态',
|
status varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态',
|
||||||
sku_code varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '一件代发SKU',
|
sku_code varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '一件代发SKU',
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@
|
|||||||
| 财务费用来源快照 | `controller/InternalFinanceFeeSourceController.java`;入库费用与柜次费用分别按来源 ID、版本向 Finance 提供不可变快照 |
|
| 财务费用来源快照 | `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` |
|
| 柜次费用明细与跨客户分摊组 | `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` |
|
| 财务整柜成本分摊快照 | `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` 可用入仓片段与满箱数;仅要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均保持整组原子 |
|
| 同柜不可拆组 | `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`,明细规格拆分留在服务端 |
|
| 货柜与全局智能排柜 | `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 的当前唯一版本 |
|
| 货柜资料、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.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
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.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -36,10 +38,21 @@ public interface OrderCargoClient {
|
|||||||
@RequestHeader("X-Internal-Service-Token") String token,
|
@RequestHeader("X-Internal-Service-Token") String token,
|
||||||
@PathVariable long tenantId, @PathVariable long orderId);
|
@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(
|
record MeasurementDeclarationSnapshot(
|
||||||
String referenceId, BigDecimal originalGrossWeightKg,
|
String referenceId, BigDecimal originalGrossWeightKg,
|
||||||
BigDecimal originalVolumeCbm, String defaultActualGoodsName) {}
|
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,
|
record CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||||
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
||||||
|
|||||||
+12
@@ -12,7 +12,9 @@ import com.baiyue.firstleg.service.InboundOrderCreationService;
|
|||||||
import com.baiyue.firstleg.service.InboundCartonLabelService;
|
import com.baiyue.firstleg.service.InboundCartonLabelService;
|
||||||
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
||||||
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
|
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
|
||||||
|
import com.baiyue.firstleg.service.OrderRemarksSyncService;
|
||||||
import com.baiyue.firstleg.dto.OrderDeclarationSync;
|
import com.baiyue.firstleg.dto.OrderDeclarationSync;
|
||||||
|
import com.baiyue.firstleg.dto.OrderRemarksSync;
|
||||||
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
|
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
|
||||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||||
import com.baiyue.firstleg.dto.UnifiedInboundRequest;
|
import com.baiyue.firstleg.dto.UnifiedInboundRequest;
|
||||||
@@ -43,6 +45,8 @@ public class InternalInboundOrderController {
|
|||||||
private UnifiedInboundQueryService unifiedQueryService;
|
private UnifiedInboundQueryService unifiedQueryService;
|
||||||
@Resource
|
@Resource
|
||||||
private OrderDeclarationSyncService declarationSync;
|
private OrderDeclarationSyncService declarationSync;
|
||||||
|
@Resource
|
||||||
|
private OrderRemarksSyncService remarksSync;
|
||||||
@Value("${baiyue.security.internal-service-token}")
|
@Value("${baiyue.security.internal-service-token}")
|
||||||
private String internalToken;
|
private String internalToken;
|
||||||
private byte[] expectedToken;
|
private byte[] expectedToken;
|
||||||
@@ -127,6 +131,14 @@ public class InternalInboundOrderController {
|
|||||||
return ApiResponse.success(null);
|
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")
|
@PostMapping("/unified-orders/query")
|
||||||
public ApiResponse<PageResult<UnifiedInboundSummary>> unifiedPage(
|
public ApiResponse<PageResult<UnifiedInboundSummary>> unifiedPage(
|
||||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
@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;
|
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> {
|
@Mapper public interface LoadingPlanItemMapper extends BaseMapper<LoadingPlanItemEntity> {
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE fl_loading_plan_item SET deleted=1,removed_by=#{operatorId},removed_by_name=#{operatorName},
|
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("customerServiceRemark") String customerServiceRemark,
|
||||||
@Param("operatorId") long operatorId,
|
@Param("operatorId") long operatorId,
|
||||||
@Param("operatorName") String operatorName);
|
@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<>#{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<>#{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;
|
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> {
|
@Mapper public interface LoadingPlanMapper extends BaseMapper<LoadingPlanEntity> {
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE fl_loading_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},
|
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'
|
WHERE id=#{planId} AND plan_status='EDITING'
|
||||||
""")
|
""")
|
||||||
int startPicking(@Param("planId") long planId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
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 {
|
public class OrderDeclarationSyncService {
|
||||||
@Resource private OrderDeclarationSyncMapper mapper;
|
@Resource private OrderDeclarationSyncMapper mapper;
|
||||||
@Resource private ObjectMapper json;
|
@Resource private ObjectMapper json;
|
||||||
|
@Resource private OrderRemarksSyncService remarksSync;
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public ChannelChangeValidationResponse validateChannelChange(ChannelChangeValidationRequest request) {
|
public ChannelChangeValidationResponse validateChannelChange(ChannelChangeValidationRequest request) {
|
||||||
@@ -86,6 +87,8 @@ public class OrderDeclarationSyncService {
|
|||||||
normalizeChannel(event.channelCode()), event.channelName(), event.operatorId()) != 1) {
|
normalizeChannel(event.channelCode()), event.channelName(), event.operatorId()) != 1) {
|
||||||
throw conflict("Firstleg 入库申报快照已发生变化");
|
throw conflict("Firstleg 入库申报快照已发生变化");
|
||||||
}
|
}
|
||||||
|
remarksSync.syncCustomerRemark(event.tenantId(), event.orderId(), event.customerRemark(),
|
||||||
|
event.operatorId(), "order-declaration");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recordEvent(UpdatedEvent event, String hash) {
|
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.core.exception.BizException;
|
||||||
import com.baiyue.common.audit.BusinessAudit;
|
import com.baiyue.common.audit.BusinessAudit;
|
||||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.*;
|
import com.baiyue.firstleg.dto.SmartLoadingDtos.*;
|
||||||
|
import com.baiyue.firstleg.dto.OrderRemarksSync;
|
||||||
import com.baiyue.firstleg.domain.SmartLoadingChannelGroupCatalog;
|
import com.baiyue.firstleg.domain.SmartLoadingChannelGroupCatalog;
|
||||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||||
import com.baiyue.firstleg.client.PendingReviewOrderClient;
|
import com.baiyue.firstleg.client.PendingReviewOrderClient;
|
||||||
@@ -56,6 +57,7 @@ public class SmartLoadingService {
|
|||||||
@Resource private FbaPricingService fbaPricingService;
|
@Resource private FbaPricingService fbaPricingService;
|
||||||
@Resource private PriceBookQueryUseCase priceBooks;
|
@Resource private PriceBookQueryUseCase priceBooks;
|
||||||
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
||||||
|
@Resource private OrderRemarksSyncService orderRemarksSync;
|
||||||
@Resource private ObjectMapper objectMapper;
|
@Resource private ObjectMapper objectMapper;
|
||||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||||
public SmartLoadingService() { }
|
public SmartLoadingService() { }
|
||||||
@@ -1099,6 +1101,9 @@ public class SmartLoadingService {
|
|||||||
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
||||||
LoadingPlanItemEntity item = repository.item(planId, itemId);
|
LoadingPlanItemEntity item = repository.item(planId, itemId);
|
||||||
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
|
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
|
||||||
|
if (item.getVersion() != request.expectedItemVersion()) {
|
||||||
|
throw conflict("装柜计划或明细已被其他人修改,请刷新后重试");
|
||||||
|
}
|
||||||
if (Objects.equals(item.getInternalRemark(), internalRemark)
|
if (Objects.equals(item.getInternalRemark(), internalRemark)
|
||||||
&& Objects.equals(item.getWarehouseRemark(), warehouseRemark)
|
&& Objects.equals(item.getWarehouseRemark(), warehouseRemark)
|
||||||
&& Objects.equals(item.getCustomerServiceRemark(), customerServiceRemark)) {
|
&& Objects.equals(item.getCustomerServiceRemark(), customerServiceRemark)) {
|
||||||
@@ -1109,16 +1114,30 @@ public class SmartLoadingService {
|
|||||||
"version", item.getVersion()));
|
"version", item.getVersion()));
|
||||||
try {
|
try {
|
||||||
touch(plan, operatorId, operatorName);
|
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,
|
if (repository.updateRemarks(planId, itemId, request.expectedPlanVersion() + 1,
|
||||||
request.expectedItemVersion(), internalRemark, warehouseRemark, customerServiceRemark,
|
request.expectedItemVersion(), response.data().internalRemark(), response.data().warehouseRemark(),
|
||||||
operatorId, operatorName) != 1) {
|
response.data().customerServiceRemark(), operatorId, operatorName) != 1) {
|
||||||
throw conflict("装柜计划或明细已被其他人修改,请刷新后重试");
|
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) {
|
} catch (DataAccessException exception) {
|
||||||
if (hasSqlState(exception, "45000")) {
|
if (hasSqlState(exception, "45000")) {
|
||||||
throw conflict("装柜计划或明细状态已变化,备注未保存,请刷新后重试");
|
throw conflict("装柜计划或明细状态已变化,备注未保存,请刷新后重试");
|
||||||
}
|
}
|
||||||
throw exception;
|
throw exception;
|
||||||
|
} catch (FeignException exception) {
|
||||||
|
throw unavailable();
|
||||||
}
|
}
|
||||||
history(planId, itemId, "REMARKS", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
history(planId, itemId, "REMARKS", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
||||||
before, json(values("internalRemark", internalRemark, "warehouseRemark", warehouseRemark,
|
before, json(values("internalRemark", internalRemark, "warehouseRemark", warehouseRemark,
|
||||||
|
|||||||
@@ -292,6 +292,32 @@ paths:
|
|||||||
schema: {$ref: '#/components/schemas/OrderDeclarationUpdatedEvent'}
|
schema: {$ref: '#/components/schemas/OrderDeclarationUpdatedEvent'}
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功、幂等重放或旧版本已忽略;冲突时响应体 code=A0409}
|
'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:
|
/internal/firstleg/v1/inbounds/unified-orders/query:
|
||||||
post:
|
post:
|
||||||
summary: 按统一订单条件分页查询履约摘要(Order 内部)
|
summary: 按统一订单条件分页查询履约摘要(Order 内部)
|
||||||
|
|||||||
+3
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.service.InboundOrderCreationService;
|
|||||||
import com.baiyue.firstleg.service.InboundCartonLabelService;
|
import com.baiyue.firstleg.service.InboundCartonLabelService;
|
||||||
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
||||||
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
|
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
|
||||||
|
import com.baiyue.firstleg.service.OrderRemarksSyncService;
|
||||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||||
import com.baiyue.firstleg.dto.OrderDeclarationSync;
|
import com.baiyue.firstleg.dto.OrderDeclarationSync;
|
||||||
import com.baiyue.common.core.api.PageResult;
|
import com.baiyue.common.core.api.PageResult;
|
||||||
@@ -36,6 +37,8 @@ class InternalInboundOrderReferenceControllerTest {
|
|||||||
private UnifiedInboundQueryService unifiedQueryService;
|
private UnifiedInboundQueryService unifiedQueryService;
|
||||||
@MockBean
|
@MockBean
|
||||||
private OrderDeclarationSyncService declarationSync;
|
private OrderDeclarationSyncService declarationSync;
|
||||||
|
@MockBean
|
||||||
|
private OrderRemarksSyncService remarksSync;
|
||||||
@Resource
|
@Resource
|
||||||
private InternalInboundOrderController controller;
|
private InternalInboundOrderController controller;
|
||||||
|
|
||||||
|
|||||||
+1
@@ -35,6 +35,7 @@ class OrderDeclarationSyncServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MockBean private OrderDeclarationSyncMapper mapper;
|
@MockBean private OrderDeclarationSyncMapper mapper;
|
||||||
|
@MockBean private OrderRemarksSyncService remarksSync;
|
||||||
@Resource private OrderDeclarationSyncService service;
|
@Resource private OrderDeclarationSyncService service;
|
||||||
@Resource private ObjectMapper json;
|
@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 PriceBookQueryUseCase priceBooks;
|
||||||
@MockBean private FirstlegDocumentNumberGenerator documentNumbers;
|
@MockBean private FirstlegDocumentNumberGenerator documentNumbers;
|
||||||
@MockBean private DropshipPalletizationMapper dropshipPallets;
|
@MockBean private DropshipPalletizationMapper dropshipPallets;
|
||||||
|
@MockBean private OrderRemarksSyncService orderRemarksSync;
|
||||||
@Resource private SmartLoadingService service;
|
@Resource private SmartLoadingService service;
|
||||||
|
|
||||||
@TestConfiguration
|
@TestConfiguration
|
||||||
@@ -119,6 +120,12 @@ class SmartLoadingServiceTest {
|
|||||||
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenReturn(
|
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenReturn(
|
||||||
ApiResponse.success(new OrderCargoClient.CargoBinding(20L, 30L, 1, "货物", 5,
|
ApiResponse.success(new OrderCargoClient.CargoBinding(20L, 30L, 1, "货物", 5,
|
||||||
new BigDecimal("10"), new BigDecimal("2"))));
|
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()))
|
when(fbaPricingService.recalculateUnlockedContainerCustomerFees(anyLong(), anyLong(), anyString()))
|
||||||
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
|
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
|
||||||
FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY, 0));
|
FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY, 0));
|
||||||
@@ -1955,6 +1962,7 @@ class SmartLoadingServiceTest {
|
|||||||
private static LoadingPlanItemEntity item(long id, long account, long detail, int boxes) {
|
private static LoadingPlanItemEntity item(long id, long account, long detail, int boxes) {
|
||||||
LoadingPlanItemEntity value = new LoadingPlanItemEntity();
|
LoadingPlanItemEntity value = new LoadingPlanItemEntity();
|
||||||
value.setId(id); value.setLoadingPlanId(1L); value.setAccountId(account);
|
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.setInboundId(10L); value.setInboundDetailId(detail); value.setPackageCount(boxes);
|
||||||
value.setVersion(0); value.setDeleted(false);
|
value.setVersion(0); value.setDeleted(false);
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
+6
-2
@@ -16,7 +16,9 @@ public final class DeliveryResponses {
|
|||||||
List<TransferDelivery> deliveries, boolean replayed) {}
|
List<TransferDelivery> deliveries, boolean replayed) {}
|
||||||
|
|
||||||
public record OutboundRow(Long id, String outboundNo, String dispatchNo, String overseasWarehouseId, String lxCodes,
|
public record OutboundRow(Long id, String outboundNo, String dispatchNo, String overseasWarehouseId, String lxCodes,
|
||||||
String status, int palletCount, int packageCount, LocalDateTime createdAt, String outboundType) {}
|
String status, int palletCount, int packageCount, BigDecimal grossWeightKg,
|
||||||
|
BigDecimal volumeWeightKg, BigDecimal volumeCbm,
|
||||||
|
LocalDateTime createdAt, String outboundType) {}
|
||||||
|
|
||||||
public record DeliveryRow(Long id, String customerId, String customerName, String deliveryNo,
|
public record DeliveryRow(Long id, String customerId, String customerName, String deliveryNo,
|
||||||
String outboundNo, String dispatchNo, String overseasWarehouseId,
|
String outboundNo, String dispatchNo, String overseasWarehouseId,
|
||||||
@@ -51,7 +53,9 @@ public final class DeliveryResponses {
|
|||||||
/** palletNo is the warehouse-authoritative physical pallet code after inbound assignment. */
|
/** palletNo is the warehouse-authoritative physical pallet code after inbound assignment. */
|
||||||
public record OutboundPalletRow(Long id, String palletNo, String cartonMarks, String inboundCode,
|
public record OutboundPalletRow(Long id, String palletNo, String cartonMarks, String inboundCode,
|
||||||
String destinationWarehouseCode, String destinationWarehouseName,
|
String destinationWarehouseCode, String destinationWarehouseName,
|
||||||
String skuCode, int packageCount, int pickedPackageCount, String status) {}
|
String skuCode, int packageCount, BigDecimal grossWeightKg,
|
||||||
|
BigDecimal volumeWeightKg, BigDecimal volumeCbm,
|
||||||
|
int pickedPackageCount, String status) {}
|
||||||
public record OutboundDetail(OutboundRow outbound, List<DeliveryRow> deliveries, List<OutboundPalletRow> pallets) {}
|
public record OutboundDetail(OutboundRow outbound, List<DeliveryRow> deliveries, List<OutboundPalletRow> pallets) {}
|
||||||
public record OneClickCandidate(Long inventoryPositionId, String orderNo, String customerId, String customerName, String overseasWarehouseId,
|
public record OneClickCandidate(Long inventoryPositionId, String orderNo, String customerId, String customerName, String overseasWarehouseId,
|
||||||
String overseasWarehouseName, String palletNo, String inboundCode,
|
String overseasWarehouseName, String palletNo, String inboundCode,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/** Outbound document created when a dispatch is transferred to delivery. */
|
/** Outbound document created when a dispatch is transferred to delivery. */
|
||||||
@@ -24,6 +25,9 @@ public class OutboundOrder {
|
|||||||
private String status;
|
private String status;
|
||||||
private Integer packageCount;
|
private Integer packageCount;
|
||||||
private Integer palletCount;
|
private Integer palletCount;
|
||||||
|
private BigDecimal grossWeightKg;
|
||||||
|
private BigDecimal volumeWeightKg;
|
||||||
|
private BigDecimal volumeCbm;
|
||||||
private String idempotencyKey;
|
private String idempotencyKey;
|
||||||
private String requestHash;
|
private String requestHash;
|
||||||
private String createdBy;
|
private String createdBy;
|
||||||
|
|||||||
+4
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/** Pallet-level immutable snapshot belonging to one outbound document. */
|
/** Pallet-level immutable snapshot belonging to one outbound document. */
|
||||||
@@ -21,6 +22,9 @@ public class OutboundPalletItem {
|
|||||||
private String destinationWarehouseCode;
|
private String destinationWarehouseCode;
|
||||||
private String destinationWarehouseName;
|
private String destinationWarehouseName;
|
||||||
private Integer packageCount;
|
private Integer packageCount;
|
||||||
|
private BigDecimal grossWeightKg;
|
||||||
|
private BigDecimal volumeWeightKg;
|
||||||
|
private BigDecimal volumeCbm;
|
||||||
private Integer pickedPackageCount;
|
private Integer pickedPackageCount;
|
||||||
private String status;
|
private String status;
|
||||||
private String skuCode;
|
private String skuCode;
|
||||||
|
|||||||
+16
-26
@@ -165,7 +165,11 @@ public class DeliveryTransferService {
|
|||||||
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND"); outbound.setOutboundType("DISPATCH_DELIVERY");
|
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND"); outbound.setOutboundType("DISPATCH_DELIVERY");
|
||||||
outbound.setLxCodes(resolveLxCodes(customerId, items));
|
outbound.setLxCodes(resolveLxCodes(customerId, items));
|
||||||
outbound.setPackageCount(items.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
outbound.setPackageCount(items.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||||
outbound.setPalletCount(items.size()); outbound.setIdempotencyKey(request.idempotencyKey().trim());
|
outbound.setPalletCount(items.size());
|
||||||
|
outbound.setGrossWeightKg(sum(items, DispatchItem::getGrossWeightKg));
|
||||||
|
outbound.setVolumeWeightKg(sum(items, DispatchItem::getVolumeWeightKg));
|
||||||
|
outbound.setVolumeCbm(sum(items, DispatchItem::getVolumeCbm));
|
||||||
|
outbound.setIdempotencyKey(request.idempotencyKey().trim());
|
||||||
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
|
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
|
||||||
try {
|
try {
|
||||||
outboundOrders.insert(outbound);
|
outboundOrders.insert(outbound);
|
||||||
@@ -190,7 +194,9 @@ public class DeliveryTransferService {
|
|||||||
item.setDestinationWarehouseId(source.getDestinationWarehouseId());
|
item.setDestinationWarehouseId(source.getDestinationWarehouseId());
|
||||||
item.setDestinationWarehouseCode(source.getDestinationWarehouseCode());
|
item.setDestinationWarehouseCode(source.getDestinationWarehouseCode());
|
||||||
item.setDestinationWarehouseName(source.getDestinationWarehouseName());
|
item.setDestinationWarehouseName(source.getDestinationWarehouseName());
|
||||||
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0)); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
|
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0));
|
||||||
|
item.setGrossWeightKg(source.getGrossWeightKg()); item.setVolumeWeightKg(source.getVolumeWeightKg());
|
||||||
|
item.setVolumeCbm(source.getVolumeCbm()); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
|
||||||
if (StringUtils.hasText(source.getCartonMarks())) {
|
if (StringUtils.hasText(source.getCartonMarks())) {
|
||||||
item.setCartonMarks(source.getCartonMarks());
|
item.setCartonMarks(source.getCartonMarks());
|
||||||
} else if (source.getWarehousePalletId() != null) {
|
} else if (source.getWarehousePalletId() != null) {
|
||||||
@@ -236,8 +242,8 @@ public class DeliveryTransferService {
|
|||||||
delivery.setSellerName(trim(request.sellerName())); delivery.setPlannedDeliveryAt(request.plannedDeliveryAt());
|
delivery.setSellerName(trim(request.sellerName())); delivery.setPlannedDeliveryAt(request.plannedDeliveryAt());
|
||||||
delivery.setPackageCount(groupItems.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
delivery.setPackageCount(groupItems.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||||
delivery.setPalletCount(groupItems.size());
|
delivery.setPalletCount(groupItems.size());
|
||||||
delivery.setTotalVolumeCbm(groupItems.stream().map(DispatchItem::getVolumeCbm).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
|
delivery.setTotalVolumeCbm(sum(groupItems, DispatchItem::getVolumeCbm));
|
||||||
delivery.setTotalWeightKg(totalWeight(customerId, groupItems)); delivery.setExpectedArrivalAt(dispatch.getEstimatedArrivalAt()); delivery.setRemark(trim(request.remark()));
|
delivery.setTotalWeightKg(sum(groupItems, DispatchItem::getGrossWeightKg)); delivery.setExpectedArrivalAt(dispatch.getEstimatedArrivalAt()); delivery.setRemark(trim(request.remark()));
|
||||||
delivery.setIdempotencyKey(childKey(request.idempotencyKey(), "DEST-" + groupIndex++)); delivery.setCreatedBy(operatorId);
|
delivery.setIdempotencyKey(childKey(request.idempotencyKey(), "DEST-" + groupIndex++)); delivery.setCreatedBy(operatorId);
|
||||||
delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery);
|
delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery);
|
||||||
recordOperation(customerId, delivery.getId(), operatorId, "CREATE_DELIVERY", "按目的FBA仓生成派送单", now);
|
recordOperation(customerId, delivery.getId(), operatorId, "CREATE_DELIVERY", "按目的FBA仓生成派送单", now);
|
||||||
@@ -1171,11 +1177,12 @@ public class DeliveryTransferService {
|
|||||||
d.getDeliveryNo(), d.getStatus(), d.getDestinationWarehouseId(), d.getDestinationWarehouseCode(),
|
d.getDeliveryNo(), d.getStatus(), d.getDestinationWarehouseId(), d.getDestinationWarehouseCode(),
|
||||||
d.getDestinationWarehouseName(), value(d.getPalletCount()), value(d.getPackageCount()))).toList(), replayed);
|
d.getDestinationWarehouseName(), value(d.getPalletCount()), value(d.getPackageCount()))).toList(), replayed);
|
||||||
}
|
}
|
||||||
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getCreatedAt(), o.getOutboundType()); }
|
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getGrossWeightKg(), o.getVolumeWeightKg(), o.getVolumeCbm(), o.getCreatedAt(), o.getOutboundType()); }
|
||||||
private DeliveryResponses.OutboundPalletRow outboundPalletRow(OutboundPalletItem item) {
|
private DeliveryResponses.OutboundPalletRow outboundPalletRow(OutboundPalletItem item) {
|
||||||
return new DeliveryResponses.OutboundPalletRow(item.getId(), item.getPalletNo(), item.getCartonMarks(),
|
return new DeliveryResponses.OutboundPalletRow(item.getId(), item.getPalletNo(), item.getCartonMarks(),
|
||||||
item.getInboundCode(), item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(),
|
item.getInboundCode(), item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(),
|
||||||
item.getSkuCode(), value(item.getPackageCount()), value(item.getPickedPackageCount()), item.getStatus());
|
item.getSkuCode(), value(item.getPackageCount()), item.getGrossWeightKg(), item.getVolumeWeightKg(),
|
||||||
|
item.getVolumeCbm(), value(item.getPickedPackageCount()), item.getStatus());
|
||||||
}
|
}
|
||||||
private DeliveryResponses.OneClickCandidate oneClickCandidate(DropshipOutboundCandidate candidate) {
|
private DeliveryResponses.OneClickCandidate oneClickCandidate(DropshipOutboundCandidate candidate) {
|
||||||
return new DeliveryResponses.OneClickCandidate(candidate.getInventoryPositionId(), candidate.getOrderNo(), candidate.getCustomerId(), candidate.getCustomerName(),
|
return new DeliveryResponses.OneClickCandidate(candidate.getInventoryPositionId(), candidate.getOrderNo(), candidate.getCustomerId(), candidate.getCustomerName(),
|
||||||
@@ -1235,26 +1242,9 @@ public class DeliveryTransferService {
|
|||||||
Math.addExact(quantity, other.quantity));
|
Math.addExact(quantity, other.quantity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private BigDecimal totalWeight(String customerId, List<DispatchItem> items) {
|
private static BigDecimal sum(List<DispatchItem> items,
|
||||||
List<Long> palletIds = items.stream().map(DispatchItem::getWarehousePalletId)
|
java.util.function.Function<DispatchItem, BigDecimal> getter) {
|
||||||
.filter(Objects::nonNull).distinct().toList();
|
return items.stream().map(getter).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
List<WarehouseTransferPallet> transferPallets = palletIds.isEmpty() ? List.of()
|
|
||||||
: warehouseTransferPallets.selectList(new LambdaQueryWrapper<WarehouseTransferPallet>()
|
|
||||||
.eq(WarehouseTransferPallet::getCustomerId, customerId)
|
|
||||||
.in(WarehouseTransferPallet::getWarehousePalletId, palletIds));
|
|
||||||
Set<Long> measuredPalletIds = transferPallets.stream()
|
|
||||||
.map(WarehouseTransferPallet::getWarehousePalletId).collect(java.util.stream.Collectors.toSet());
|
|
||||||
BigDecimal measuredWeight = transferPallets.stream().map(WarehouseTransferPallet::getGrossWeightKg)
|
|
||||||
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
List<Long> orderIds = items.stream()
|
|
||||||
.filter(item -> item.getWarehousePalletId() == null || !measuredPalletIds.contains(item.getWarehousePalletId()))
|
|
||||||
.map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList();
|
|
||||||
if (orderIds.isEmpty()) return measuredWeight;
|
|
||||||
BigDecimal orderWeight = tailOrderDetails.selectList(new LambdaQueryWrapper<LastlegOrderDetail>()
|
|
||||||
.eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, orderIds))
|
|
||||||
.stream().map(LastlegOrderDetail::getGrossWeightKg).filter(Objects::nonNull)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
return measuredWeight.add(orderWeight);
|
|
||||||
}
|
}
|
||||||
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
|
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
|
||||||
private String hash(Object value) {
|
private String hash(Object value) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public record DispatchResponse(Long id, String dispatchNo, String vehiclePlate,
|
|||||||
String overseasWarehouseId, String overseasWarehouseName,
|
String overseasWarehouseId, String overseasWarehouseName,
|
||||||
String orderNo, String orderRefs, String inboundCode, String destinationWarehouseCode,
|
String orderNo, String orderRefs, String inboundCode, String destinationWarehouseCode,
|
||||||
String destinationWarehouseName, String fbaWarehouseCode, int packageCount,
|
String destinationWarehouseName, String fbaWarehouseCode, int packageCount,
|
||||||
|
BigDecimal grossWeightKg, BigDecimal volumeWeightKg,
|
||||||
BigDecimal volumeCbm, String sourceStage) {
|
BigDecimal volumeCbm, String sourceStage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ public class DispatchItem {
|
|||||||
private String contactName;
|
private String contactName;
|
||||||
private String contactPhone;
|
private String contactPhone;
|
||||||
private Integer packageCount;
|
private Integer packageCount;
|
||||||
|
private BigDecimal grossWeightKg;
|
||||||
|
private BigDecimal volumeWeightKg;
|
||||||
private BigDecimal volumeCbm;
|
private BigDecimal volumeCbm;
|
||||||
/** PLAN for planned pallets; RECEIVED for warehouse/entity pallets. */
|
/** PLAN for planned pallets; RECEIVED for warehouse/entity pallets. */
|
||||||
private String sourceStage;
|
private String sourceStage;
|
||||||
|
|||||||
+5
-2
@@ -410,6 +410,8 @@ public class DispatchService {
|
|||||||
item.setContactName(candidate.contactName());
|
item.setContactName(candidate.contactName());
|
||||||
item.setContactPhone(candidate.contactPhone());
|
item.setContactPhone(candidate.contactPhone());
|
||||||
item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0));
|
item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0));
|
||||||
|
item.setGrossWeightKg(Objects.requireNonNullElse(candidate.grossWeightKg(), BigDecimal.ZERO));
|
||||||
|
item.setVolumeWeightKg(Objects.requireNonNullElse(candidate.volumeWeightKg(), BigDecimal.ZERO));
|
||||||
item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO));
|
item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO));
|
||||||
item.setSourceStage(candidate.sourceStage());
|
item.setSourceStage(candidate.sourceStage());
|
||||||
item.setCartonMarks(mergeCartonMarks(candidate.cartonMarks(), candidate.relabelCartonMarks()));
|
item.setCartonMarks(mergeCartonMarks(candidate.cartonMarks(), candidate.relabelCartonMarks()));
|
||||||
@@ -457,7 +459,7 @@ public class DispatchService {
|
|||||||
DispatchOrder dispatch = new DispatchOrder(); dispatch.setId(IdWorker.getId()); dispatch.setCustomerId(customerId);
|
DispatchOrder dispatch = new DispatchOrder(); dispatch.setId(IdWorker.getId()); dispatch.setCustomerId(customerId);
|
||||||
dispatch.setDispatchNo(orderNoGenerator.next("DISPATCH", warehouse, dispatch.getId())); dispatch.setOverseasWarehouseId(warehouse); dispatch.setOverseasWarehouseName(selected.get(0).overseasWarehouseName());
|
dispatch.setDispatchNo(orderNoGenerator.next("DISPATCH", warehouse, dispatch.getId())); dispatch.setOverseasWarehouseId(warehouse); dispatch.setOverseasWarehouseName(selected.get(0).overseasWarehouseName());
|
||||||
dispatch.setRoute(candidateRoute(selected)); dispatch.setFbaAppointmentNo(trimToNull(request.fbaAppointmentNo())); dispatch.setFbaAppointmentStatus(defaultStatus(request.fbaAppointmentStatus())); dispatch.setVehicleAppointmentStatus(defaultStatus(request.vehicleAppointmentStatus())); dispatch.setStatus(PENDING_DEPARTURE); dispatch.setItemCount(selected.size()); dispatch.setPalletCount(selected.size()); dispatch.setPackageCount(selected.stream().map(DispatchPalletCandidate::packageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum()); dispatch.setPickedPackageCount(0); dispatch.setShippedPackageCount(0); dispatch.setCreatedBy(trimToNull(operatorId)); dispatch.setCreatedAt(now); dispatch.setUpdatedAt(now); orders.insert(dispatch);
|
dispatch.setRoute(candidateRoute(selected)); dispatch.setFbaAppointmentNo(trimToNull(request.fbaAppointmentNo())); dispatch.setFbaAppointmentStatus(defaultStatus(request.fbaAppointmentStatus())); dispatch.setVehicleAppointmentStatus(defaultStatus(request.vehicleAppointmentStatus())); dispatch.setStatus(PENDING_DEPARTURE); dispatch.setItemCount(selected.size()); dispatch.setPalletCount(selected.size()); dispatch.setPackageCount(selected.stream().map(DispatchPalletCandidate::packageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum()); dispatch.setPickedPackageCount(0); dispatch.setShippedPackageCount(0); dispatch.setCreatedBy(trimToNull(operatorId)); dispatch.setCreatedAt(now); dispatch.setUpdatedAt(now); orders.insert(dispatch);
|
||||||
for (DispatchPalletCandidate candidate : selected) { DispatchItem item = new DispatchItem(); item.setId(IdWorker.getId()); item.setDispatchOrderId(dispatch.getId()); item.setCustomerId(customerId); item.setWarehousePalletId(candidate.warehousePalletId()); item.setOrderId(candidate.orderId()); item.setOverseasWarehouseId(candidate.overseasWarehouseId()); item.setOverseasWarehouseName(candidate.overseasWarehouseName()); item.setOrderNo(candidate.orderNo()); item.setOrderRefs(candidate.orderRefs()); item.setInboundCode(candidate.inboundCode()); item.setPalletNo(candidate.palletNo()); item.setDestinationWarehouseCode(candidate.destinationWarehouseCode()); item.setDestinationWarehouseName(candidate.destinationWarehouseName()); item.setFbaWarehouseCode(candidate.fbaWarehouseCode()); item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0)); item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO)); item.setSourceStage("RELABEL"); item.setCartonMarks(candidate.relabelCartonMarks()); item.setSourceBusinessType("RELABEL_ORDER"); item.setSourceBusinessNo(request.relabelNo()); item.setCreatedAt(now); items.insert(item); }
|
for (DispatchPalletCandidate candidate : selected) { DispatchItem item = new DispatchItem(); item.setId(IdWorker.getId()); item.setDispatchOrderId(dispatch.getId()); item.setCustomerId(customerId); item.setWarehousePalletId(candidate.warehousePalletId()); item.setOrderId(candidate.orderId()); item.setOverseasWarehouseId(candidate.overseasWarehouseId()); item.setOverseasWarehouseName(candidate.overseasWarehouseName()); item.setOrderNo(candidate.orderNo()); item.setOrderRefs(candidate.orderRefs()); item.setInboundCode(candidate.inboundCode()); item.setPalletNo(candidate.palletNo()); item.setDestinationWarehouseCode(candidate.destinationWarehouseCode()); item.setDestinationWarehouseName(candidate.destinationWarehouseName()); item.setFbaWarehouseCode(candidate.fbaWarehouseCode()); item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0)); item.setGrossWeightKg(Objects.requireNonNullElse(candidate.grossWeightKg(), BigDecimal.ZERO)); item.setVolumeWeightKg(Objects.requireNonNullElse(candidate.volumeWeightKg(), BigDecimal.ZERO)); item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO)); item.setSourceStage("RELABEL"); item.setCartonMarks(candidate.relabelCartonMarks()); item.setSourceBusinessType("RELABEL_ORDER"); item.setSourceBusinessNo(request.relabelNo()); item.setCreatedAt(now); items.insert(item); }
|
||||||
DispatchPdaTask task = new DispatchPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(customerId); task.setDispatchOrderId(dispatch.getId()); task.setTaskNo("DPDA" + task.getId()); task.setStatus("PDA_PENDING"); task.setWarehouseId(warehouse); task.setPushedAt(now); task.setCreatedAt(now); task.setUpdatedAt(now); pdaTasks.insert(task);
|
DispatchPdaTask task = new DispatchPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(customerId); task.setDispatchOrderId(dispatch.getId()); task.setTaskNo("DPDA" + task.getId()); task.setStatus("PDA_PENDING"); task.setWarehouseId(warehouse); task.setPushedAt(now); task.setCreatedAt(now); task.setUpdatedAt(now); pdaTasks.insert(task);
|
||||||
if (candidates.markRelabelPreDispatched(customerId, request.relabelNo()) != 1) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单已被其他排车占用");
|
if (candidates.markRelabelPreDispatched(customerId, request.relabelNo()) != 1) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单已被其他排车占用");
|
||||||
return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId()));
|
return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId()));
|
||||||
@@ -653,7 +655,8 @@ public class DispatchService {
|
|||||||
item.getWarehousePalletId(), item.getPalletNo(), item.getOverseasWarehouseId(), item.getOverseasWarehouseName(),
|
item.getWarehousePalletId(), item.getPalletNo(), item.getOverseasWarehouseId(), item.getOverseasWarehouseName(),
|
||||||
item.getOrderNo(), item.getOrderRefs(), item.getInboundCode(),
|
item.getOrderNo(), item.getOrderRefs(), item.getInboundCode(),
|
||||||
item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(), item.getFbaWarehouseCode(),
|
item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(), item.getFbaWarehouseCode(),
|
||||||
Objects.requireNonNullElse(item.getPackageCount(), 0), item.getVolumeCbm(), item.getSourceStage())).toList(),
|
Objects.requireNonNullElse(item.getPackageCount(), 0), item.getGrossWeightKg(),
|
||||||
|
item.getVolumeWeightKg(), item.getVolumeCbm(), item.getSourceStage())).toList(),
|
||||||
bolLineList == null ? List.of() : bolLineList);
|
bolLineList == null ? List.of() : bolLineList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ public record LastlegOrderSummary(
|
|||||||
Integer receivedPackageCount,
|
Integer receivedPackageCount,
|
||||||
@Schema(description = "实收减预报箱数;尚未完成收货时为空")
|
@Schema(description = "实收减预报箱数;尚未完成收货时为空")
|
||||||
Integer packageDifference,
|
Integer packageDifference,
|
||||||
|
@Schema(description = "头程冻结货物毛重(千克)")
|
||||||
|
BigDecimal grossWeightKg,
|
||||||
|
@Schema(description = "头程冻结货物净重(千克)")
|
||||||
|
BigDecimal netWeightKg,
|
||||||
BigDecimal volumeCbm,
|
BigDecimal volumeCbm,
|
||||||
Integer productQuantity,
|
Integer productQuantity,
|
||||||
BigDecimal volumeWeightKg,
|
BigDecimal volumeWeightKg,
|
||||||
|
|||||||
+4
-1
@@ -22,6 +22,8 @@ public record InboundPlanListResponse(
|
|||||||
String customerName,
|
String customerName,
|
||||||
Integer packageCount,
|
Integer packageCount,
|
||||||
Integer pickedPackageCount,
|
Integer pickedPackageCount,
|
||||||
|
BigDecimal grossWeightKg,
|
||||||
|
BigDecimal netWeightKg,
|
||||||
BigDecimal volumeCbm,
|
BigDecimal volumeCbm,
|
||||||
Integer palletCount,
|
Integer palletCount,
|
||||||
boolean assigned,
|
boolean assigned,
|
||||||
@@ -39,7 +41,8 @@ public record InboundPlanListResponse(
|
|||||||
public InboundPlanListResponse withReceivingWarehouse(String warehouseCode, String warehouseName) {
|
public InboundPlanListResponse withReceivingWarehouse(String warehouseCode, String warehouseName) {
|
||||||
return new InboundPlanListResponse(lineId, planId, planNo, customerId, orderId, inboundCode, lxCode,
|
return new InboundPlanListResponse(lineId, planId, planNo, customerId, orderId, inboundCode, lxCode,
|
||||||
orderType, overseasWarehouseId, warehouseCode, warehouseName, warehouseId, this.warehouseCode,
|
orderType, overseasWarehouseId, warehouseCode, warehouseName, warehouseId, this.warehouseCode,
|
||||||
this.warehouseName, fbaWarehouseCode, customerName, packageCount, pickedPackageCount, volumeCbm,
|
this.warehouseName, fbaWarehouseCode, customerName, packageCount, pickedPackageCount,
|
||||||
|
grossWeightKg, netWeightKg, volumeCbm,
|
||||||
palletCount, assigned, mergeAllowed, urgent, status, appointmentNo, appointmentStatus, appointmentAt,
|
palletCount, assigned, mergeAllowed, urgent, status, appointmentNo, appointmentStatus, appointmentAt,
|
||||||
expectedLoadingAt, dispatchStatus, createdAt, version);
|
expectedLoadingAt, dispatchStatus, createdAt, version);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -19,6 +19,8 @@ public record InboundPlanResponse(
|
|||||||
String status,
|
String status,
|
||||||
int lineCount,
|
int lineCount,
|
||||||
int palletCount,
|
int palletCount,
|
||||||
|
BigDecimal totalGrossWeightKg,
|
||||||
|
BigDecimal totalNetWeightKg,
|
||||||
BigDecimal totalVolumeCbm,
|
BigDecimal totalVolumeCbm,
|
||||||
int expectedPackageCount,
|
int expectedPackageCount,
|
||||||
int pickedPackageCount,
|
int pickedPackageCount,
|
||||||
@@ -38,7 +40,8 @@ public record InboundPlanResponse(
|
|||||||
) {
|
) {
|
||||||
public record Line(Long id, Long orderId, String inboundCode, String fbaWarehouseCode, String destinationWarehouseId,
|
public record Line(Long id, Long orderId, String inboundCode, String fbaWarehouseCode, String destinationWarehouseId,
|
||||||
String destinationWarehouseCode, String destinationWarehouseName,
|
String destinationWarehouseCode, String destinationWarehouseName,
|
||||||
BigDecimal volumeCbm, int requiredPackageCount, int plannedPalletCount) {
|
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm,
|
||||||
|
int requiredPackageCount, int plannedPalletCount) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Pallet(Long id, String palletNo, String destinationWarehouseId,
|
public record Pallet(Long id, String palletNo, String destinationWarehouseId,
|
||||||
|
|||||||
+13
@@ -17,6 +17,8 @@ public interface InboundPlanOrderMapper {
|
|||||||
record ReceivingWarehouseSnapshot(String overseasWarehouseId, String overseasWarehouseCode,
|
record ReceivingWarehouseSnapshot(String overseasWarehouseId, String overseasWarehouseCode,
|
||||||
String overseasWarehouseName) { }
|
String overseasWarehouseName) { }
|
||||||
record DetailOrderSnapshot(String orderNo, String customerName, String orderType) { }
|
record DetailOrderSnapshot(String orderNo, String customerName, String orderType) { }
|
||||||
|
record OrderWeightSnapshot(Long orderId, java.math.BigDecimal grossWeightKg,
|
||||||
|
java.math.BigDecimal netWeightKg) { }
|
||||||
record ContainerWarehouseRef(String customerId, Long orderId, String sourceContainerBookingId,
|
record ContainerWarehouseRef(String customerId, Long orderId, String sourceContainerBookingId,
|
||||||
String overseasWarehouseId) { }
|
String overseasWarehouseId) { }
|
||||||
record ContainerPreviewOrderRow(
|
record ContainerPreviewOrderRow(
|
||||||
@@ -116,6 +118,17 @@ public interface InboundPlanOrderMapper {
|
|||||||
DetailOrderSnapshot findDetailOrder(@Param("customerId") String customerId,
|
DetailOrderSnapshot findDetailOrder(@Param("customerId") String customerId,
|
||||||
@Param("orderId") Long orderId);
|
@Param("orderId") Long orderId);
|
||||||
|
|
||||||
|
@Select("<script>SELECT o.id AS orderId," +
|
||||||
|
"COALESCE(SUM(d.gross_weight_kg),MAX(i.gross_weight_kg),0) AS grossWeightKg," +
|
||||||
|
"COALESCE(SUM(d.net_weight_kg),MAX(i.net_weight_kg),0) AS netWeightKg " +
|
||||||
|
"FROM ll_order o LEFT JOIN ll_order_detail d ON d.order_id=o.id AND d.customer_id=o.customer_id " +
|
||||||
|
"LEFT JOIN ll_inbound_order i ON i.id=o.id AND i.customer_id=o.customer_id " +
|
||||||
|
"WHERE o.customer_id=#{customerId} AND o.id IN " +
|
||||||
|
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
|
||||||
|
"GROUP BY o.id</script>")
|
||||||
|
List<OrderWeightSnapshot> findOrderWeights(@Param("customerId") String customerId,
|
||||||
|
@Param("orderIds") List<Long> orderIds);
|
||||||
|
|
||||||
@Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," +
|
@Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," +
|
||||||
"pallet_merge_flag='MERGED',pallet_count=#{palletCount},updated_at=#{updatedAt} " +
|
"pallet_merge_flag='MERGED',pallet_count=#{palletCount},updated_at=#{updatedAt} " +
|
||||||
"WHERE customer_id=#{customerId} AND id=#{orderId}")
|
"WHERE customer_id=#{customerId} AND id=#{orderId}")
|
||||||
|
|||||||
+4
@@ -43,6 +43,10 @@ public interface InboundPlanQueryMapper {
|
|||||||
"l.destination_warehouse_name warehouseName,l.fba_warehouse_code fbaWarehouseCode," +
|
"l.destination_warehouse_name warehouseName,l.fba_warehouse_code fbaWarehouseCode," +
|
||||||
"o.customer_name customerName,l.required_package_count packageCount," +
|
"o.customer_name customerName,l.required_package_count packageCount," +
|
||||||
"COALESCE((SELECT SUM(pa.picked_package_count) FROM ll_inbound_plan_pallet_allocation pa WHERE pa.line_id=l.id),0) pickedPackageCount," +
|
"COALESCE((SELECT SUM(pa.picked_package_count) FROM ll_inbound_plan_pallet_allocation pa WHERE pa.line_id=l.id),0) pickedPackageCount," +
|
||||||
|
"COALESCE((SELECT SUM(d.gross_weight_kg) FROM ll_order_detail d WHERE d.order_id=l.order_id AND d.customer_id=p.customer_id)," +
|
||||||
|
"(SELECT i.gross_weight_kg FROM ll_inbound_order i WHERE i.id=l.order_id AND i.customer_id=p.customer_id),0) grossWeightKg," +
|
||||||
|
"COALESCE((SELECT SUM(d.net_weight_kg) FROM ll_order_detail d WHERE d.order_id=l.order_id AND d.customer_id=p.customer_id)," +
|
||||||
|
"(SELECT i.net_weight_kg FROM ll_inbound_order i WHERE i.id=l.order_id AND i.customer_id=p.customer_id),0) netWeightKg," +
|
||||||
"l.volume_cbm volumeCbm," +
|
"l.volume_cbm volumeCbm," +
|
||||||
"l.planned_pallet_count palletCount,p.assigned_flag assigned,p.merge_required mergeAllowed," +
|
"l.planned_pallet_count palletCount,p.assigned_flag assigned,p.merge_required mergeAllowed," +
|
||||||
"p.urgent_flag urgent,p.status status,a.appointment_no appointmentNo," +
|
"p.urgent_flag urgent,p.status status,a.appointment_no appointmentNo," +
|
||||||
|
|||||||
+14
-1
@@ -1077,6 +1077,16 @@ public class InboundPlanService {
|
|||||||
}
|
}
|
||||||
List<InboundPlanLine> lines = lineMapper.selectList(new LambdaQueryWrapper<InboundPlanLine>()
|
List<InboundPlanLine> lines = lineMapper.selectList(new LambdaQueryWrapper<InboundPlanLine>()
|
||||||
.eq(InboundPlanLine::getPlanId, plan.getId()).orderByAsc(InboundPlanLine::getId));
|
.eq(InboundPlanLine::getPlanId, plan.getId()).orderByAsc(InboundPlanLine::getId));
|
||||||
|
Map<Long, InboundPlanOrderMapper.OrderWeightSnapshot> weights = lines.isEmpty() ? Map.of()
|
||||||
|
: orderMapper.findOrderWeights(plan.getCustomerId(), lines.stream().map(InboundPlanLine::getOrderId)
|
||||||
|
.filter(Objects::nonNull).distinct().toList()).stream()
|
||||||
|
.collect(Collectors.toMap(InboundPlanOrderMapper.OrderWeightSnapshot::orderId, value -> value));
|
||||||
|
BigDecimal totalGrossWeightKg = weights.values().stream()
|
||||||
|
.map(InboundPlanOrderMapper.OrderWeightSnapshot::grossWeightKg).filter(Objects::nonNull)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
BigDecimal totalNetWeightKg = weights.values().stream()
|
||||||
|
.map(InboundPlanOrderMapper.OrderWeightSnapshot::netWeightKg).filter(Objects::nonNull)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
List<InboundPlanPallet> pallets = palletMapper.selectList(new LambdaQueryWrapper<InboundPlanPallet>()
|
List<InboundPlanPallet> pallets = palletMapper.selectList(new LambdaQueryWrapper<InboundPlanPallet>()
|
||||||
.eq(InboundPlanPallet::getPlanId, plan.getId()).orderByAsc(InboundPlanPallet::getSequenceNo));
|
.eq(InboundPlanPallet::getPlanId, plan.getId()).orderByAsc(InboundPlanPallet::getSequenceNo));
|
||||||
List<InboundPlanPalletAllocation> allocations = allocationMapper.selectList(
|
List<InboundPlanPalletAllocation> allocations = allocationMapper.selectList(
|
||||||
@@ -1116,11 +1126,14 @@ public class InboundPlanService {
|
|||||||
sourceOrder == null ? null : sourceOrder.customerName(),
|
sourceOrder == null ? null : sourceOrder.customerName(),
|
||||||
sourceOrder == null ? null : sourceOrder.orderType(), plan.getContainerNo(),
|
sourceOrder == null ? null : sourceOrder.orderType(), plan.getContainerNo(),
|
||||||
authoritativeWarehouse.warehouseCode(), authoritativeWarehouse.warehouseName(),
|
authoritativeWarehouse.warehouseCode(), authoritativeWarehouse.warehouseName(),
|
||||||
plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), plan.getTotalVolumeCbm(),
|
plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), totalGrossWeightKg,
|
||||||
|
totalNetWeightKg, plan.getTotalVolumeCbm(),
|
||||||
lines.stream().mapToInt(InboundPlanLine::getRequiredPackageCount).sum(),
|
lines.stream().mapToInt(InboundPlanLine::getRequiredPackageCount).sum(),
|
||||||
allocations.stream().mapToInt(InboundPlanPalletAllocation::getPickedPackageCount).sum(),
|
allocations.stream().mapToInt(InboundPlanPalletAllocation::getPickedPackageCount).sum(),
|
||||||
lines.stream().map(line -> new InboundPlanResponse.Line(line.getId(), line.getOrderId(), line.getInboundCode(), line.getFbaWarehouseCode(),
|
lines.stream().map(line -> new InboundPlanResponse.Line(line.getId(), line.getOrderId(), line.getInboundCode(), line.getFbaWarehouseCode(),
|
||||||
line.getDestinationWarehouseId(), line.getDestinationWarehouseCode(), line.getDestinationWarehouseName(),
|
line.getDestinationWarehouseId(), line.getDestinationWarehouseCode(), line.getDestinationWarehouseName(),
|
||||||
|
weights.containsKey(line.getOrderId()) ? weights.get(line.getOrderId()).grossWeightKg() : BigDecimal.ZERO,
|
||||||
|
weights.containsKey(line.getOrderId()) ? weights.get(line.getOrderId()).netWeightKg() : BigDecimal.ZERO,
|
||||||
line.getVolumeCbm(), line.getRequiredPackageCount(), line.getPlannedPalletCount())).toList(),
|
line.getVolumeCbm(), line.getRequiredPackageCount(), line.getPlannedPalletCount())).toList(),
|
||||||
pallets.stream().map(pallet -> new InboundPlanResponse.Pallet(pallet.getId(), pallet.getPalletNo(),
|
pallets.stream().map(pallet -> new InboundPlanResponse.Pallet(pallet.getId(), pallet.getPalletNo(),
|
||||||
pallet.getDestinationWarehouseId(), pallet.getDestinationWarehouseCode(), pallet.getDestinationWarehouseName(),
|
pallet.getDestinationWarehouseId(), pallet.getDestinationWarehouseCode(), pallet.getDestinationWarehouseName(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Select;
|
|||||||
import org.apache.ibatis.annotations.Update;
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
|
public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
|
||||||
@@ -27,6 +28,17 @@ public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
|
|||||||
|
|
||||||
record DispatchStatusRow(Long orderId, String dispatchStatus) { }
|
record DispatchStatusRow(Long orderId, String dispatchStatus) { }
|
||||||
|
|
||||||
|
record OrderWeightRow(Long orderId, BigDecimal grossWeightKg, BigDecimal netWeightKg) { }
|
||||||
|
|
||||||
|
@Select("<script>SELECT o.id AS orderId," +
|
||||||
|
"COALESCE(SUM(d.gross_weight_kg),MAX(i.gross_weight_kg),0) AS grossWeightKg," +
|
||||||
|
"COALESCE(SUM(d.net_weight_kg),MAX(i.net_weight_kg),0) AS netWeightKg " +
|
||||||
|
"FROM ll_order o LEFT JOIN ll_order_detail d ON d.order_id=o.id AND d.customer_id=o.customer_id " +
|
||||||
|
"LEFT JOIN ll_inbound_order i ON i.id=o.id AND i.customer_id=o.customer_id WHERE o.id IN " +
|
||||||
|
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
|
||||||
|
"GROUP BY o.id</script>")
|
||||||
|
List<OrderWeightRow> findOrderWeights(@Param("orderIds") List<Long> orderIds);
|
||||||
|
|
||||||
String CONTAINER_PROGRESS_FILTER = " FROM ll_order o LEFT JOIN ll_inbound_plan p " +
|
String CONTAINER_PROGRESS_FILTER = " FROM ll_order o LEFT JOIN ll_inbound_plan p " +
|
||||||
"ON p.customer_id=o.customer_id AND p.source_order_id=o.id " +
|
"ON p.customer_id=o.customer_id AND p.source_order_id=o.id " +
|
||||||
"WHERE o.source_container_booking_id IS NOT NULL AND o.source_container_booking_id<>'' " +
|
"WHERE o.source_container_booking_id IS NOT NULL AND o.source_container_booking_id<>'' " +
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -94,9 +95,11 @@ public class LastlegOrderService {
|
|||||||
Map<Long, List<LastlegOrderMapper.PalletMergeRelationRow>> mergeRelations = palletMergeRelations(page.getRecords());
|
Map<Long, List<LastlegOrderMapper.PalletMergeRelationRow>> mergeRelations = palletMergeRelations(page.getRecords());
|
||||||
Map<String, String> containerNodes = containerNodes(page.getRecords());
|
Map<String, String> containerNodes = containerNodes(page.getRecords());
|
||||||
Map<Long, String> dispatchStatuses = dispatchStatuses(page.getRecords());
|
Map<Long, String> dispatchStatuses = dispatchStatuses(page.getRecords());
|
||||||
|
Map<Long, LastlegOrderMapper.OrderWeightRow> weights = orderWeights(page.getRecords());
|
||||||
return new PageResult<>(page.getRecords().stream().map(order -> summary(order, mergeRelations.get(order.getId()),
|
return new PageResult<>(page.getRecords().stream().map(order -> summary(order, mergeRelations.get(order.getId()),
|
||||||
StringUtils.hasText(order.getSourceContainerBookingId())
|
StringUtils.hasText(order.getSourceContainerBookingId())
|
||||||
? containerNodes.get(order.getSourceContainerBookingId()) : null, dispatchStatuses.getOrDefault(order.getId(), "UNSCHEDULED"))).toList(),
|
? containerNodes.get(order.getSourceContainerBookingId()) : null,
|
||||||
|
dispatchStatuses.getOrDefault(order.getId(), "UNSCHEDULED"), weights.get(order.getId()))).toList(),
|
||||||
page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
|
page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +352,8 @@ public class LastlegOrderService {
|
|||||||
value.getNodeCode(), value.getNodeAt(), value.getSource()))
|
value.getNodeCode(), value.getNodeAt(), value.getSource()))
|
||||||
.sorted(java.util.Comparator.comparingInt(value -> MANUAL_NODE_CODES.indexOf(value.nodeCode()))).toList();
|
.sorted(java.util.Comparator.comparingInt(value -> MANUAL_NODE_CODES.indexOf(value.nodeCode()))).toList();
|
||||||
return new LastlegOrderDetailResponse(summary(order, palletMergeRelations(List.of(order)).get(order.getId()),
|
return new LastlegOrderDetailResponse(summary(order, palletMergeRelations(List.of(order)).get(order.getId()),
|
||||||
containerNode(order), dispatchStatuses(List.of(order)).getOrDefault(order.getId(), "UNSCHEDULED")), manualNodes, cargo, history.stream()
|
containerNode(order), dispatchStatuses(List.of(order)).getOrDefault(order.getId(), "UNSCHEDULED"),
|
||||||
|
orderWeights(List.of(order)).get(order.getId())), manualNodes, cargo, history.stream()
|
||||||
.map(value -> new LastlegOrderDetailResponse.History(value.getId(), value.getFromStatus(),
|
.map(value -> new LastlegOrderDetailResponse.History(value.getId(), value.getFromStatus(),
|
||||||
value.getToStatus(), value.getRemark(), value.getCreatedAt())).toList(), fees, operations.stream()
|
value.getToStatus(), value.getRemark(), value.getCreatedAt())).toList(), fees, operations.stream()
|
||||||
.map(value -> new LastlegOrderDetailResponse.Operation(value.getId(), value.getOperationNode(),
|
.map(value -> new LastlegOrderDetailResponse.Operation(value.getId(), value.getOperationNode(),
|
||||||
@@ -625,8 +629,17 @@ public class LastlegOrderService {
|
|||||||
LastlegOrderMapper.DispatchStatusRow::dispatchStatus));
|
LastlegOrderMapper.DispatchStatusRow::dispatchStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<Long, LastlegOrderMapper.OrderWeightRow> orderWeights(List<LastlegOrder> orders) {
|
||||||
|
if (orders.isEmpty()) return Map.of();
|
||||||
|
List<LastlegOrderMapper.OrderWeightRow> rows = orderMapper.findOrderWeights(
|
||||||
|
orders.stream().map(LastlegOrder::getId).toList());
|
||||||
|
if (rows == null) return Map.of();
|
||||||
|
return rows.stream().collect(Collectors.toMap(LastlegOrderMapper.OrderWeightRow::orderId, row -> row));
|
||||||
|
}
|
||||||
|
|
||||||
private LastlegOrderSummary summary(LastlegOrder o, List<LastlegOrderMapper.PalletMergeRelationRow> relations,
|
private LastlegOrderSummary summary(LastlegOrder o, List<LastlegOrderMapper.PalletMergeRelationRow> relations,
|
||||||
String containerNode, String dispatchStatus) {
|
String containerNode, String dispatchStatus,
|
||||||
|
LastlegOrderMapper.OrderWeightRow weight) {
|
||||||
List<LastlegOrderMapper.PalletMergeRelationRow> related = relations == null ? List.of() : relations;
|
List<LastlegOrderMapper.PalletMergeRelationRow> related = relations == null ? List.of() : relations;
|
||||||
return new LastlegOrderSummary(o.getId(), displayOrderNo(o), o.getOrderType(),
|
return new LastlegOrderSummary(o.getId(), displayOrderNo(o), o.getOrderType(),
|
||||||
LastlegOrderType.labelOf(o.getOrderType()), o.getDestinationType(), o.getSourceInboundId(),
|
LastlegOrderType.labelOf(o.getOrderType()), o.getDestinationType(), o.getSourceInboundId(),
|
||||||
@@ -637,7 +650,8 @@ public class LastlegOrderService {
|
|||||||
o.getAbnormal(), o.getDetained(), o.getHoldChargeable(), o.getHoldReason(),
|
o.getAbnormal(), o.getDetained(), o.getHoldChargeable(), o.getHoldReason(),
|
||||||
Boolean.TRUE.equals(o.getManualNodeOverride()), Boolean.TRUE.equals(o.getManualNodeOverride()) ? "MANUAL" : "CONTAINER",
|
Boolean.TRUE.equals(o.getManualNodeOverride()), Boolean.TRUE.equals(o.getManualNodeOverride()) ? "MANUAL" : "CONTAINER",
|
||||||
o.getVersion() == null ? 0 : o.getVersion(), o.getPalletCount(), o.getPackageCount(),
|
o.getVersion() == null ? 0 : o.getVersion(), o.getPalletCount(), o.getPackageCount(),
|
||||||
o.getReceivedPackageCount(), packageDifference(o), o.getVolumeCbm(),
|
o.getReceivedPackageCount(), packageDifference(o), weight == null ? BigDecimal.ZERO : weight.grossWeightKg(),
|
||||||
|
weight == null ? BigDecimal.ZERO : weight.netWeightKg(), o.getVolumeCbm(),
|
||||||
o.getProductQuantity(), o.getVolumeWeightKg(), o.getEstimatedArrivalAt(), o.getActualArrivalAt(),
|
o.getProductQuantity(), o.getVolumeWeightKg(), o.getEstimatedArrivalAt(), o.getActualArrivalAt(),
|
||||||
o.getEstimatedWarehouseAt(), o.getInboundOrderNo(), dispatchStatus, o.getRelabelOrderNo(), relabelBusinessStatus(o.getRelabelStatus()),
|
o.getEstimatedWarehouseAt(), o.getInboundOrderNo(), dispatchStatus, o.getRelabelOrderNo(), relabelBusinessStatus(o.getRelabelStatus()),
|
||||||
o.getPalletMergeFlag(), related.stream().map(LastlegOrderMapper.PalletMergeRelationRow::mergeGroupNo).distinct().findFirst().orElse(null),
|
o.getPalletMergeFlag(), related.stream().map(LastlegOrderMapper.PalletMergeRelationRow::mergeGroupNo).distinct().findFirst().orElse(null),
|
||||||
|
|||||||
@@ -1389,6 +1389,8 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_item (
|
|||||||
contact_name VARCHAR(128) NULL COMMENT '私人仓联系人快照',
|
contact_name VARCHAR(128) NULL COMMENT '私人仓联系人快照',
|
||||||
contact_phone VARCHAR(64) NULL COMMENT '私人仓联系电话快照',
|
contact_phone VARCHAR(64) NULL COMMENT '私人仓联系电话快照',
|
||||||
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数',
|
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数',
|
||||||
|
gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)',
|
||||||
|
volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
|
||||||
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
|
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
|
||||||
source_stage VARCHAR(32) NOT NULL COMMENT '数据来源阶段',
|
source_stage VARCHAR(32) NOT NULL COMMENT '数据来源阶段',
|
||||||
carton_marks VARCHAR(4000) NULL COMMENT '换标箱唛映射快照',
|
carton_marks VARCHAR(4000) NULL COMMENT '换标箱唛映射快照',
|
||||||
@@ -1405,6 +1407,7 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_item (
|
|||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='carton_marks'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN carton_marks VARCHAR(4000) NULL COMMENT ''换标箱唛映射快照'' AFTER source_stage')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='carton_marks'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN carton_marks VARCHAR(4000) NULL COMMENT ''换标箱唛映射快照'' AFTER source_stage')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='source_business_type'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN source_business_type VARCHAR(32) NULL COMMENT ''来源业务类型'' AFTER carton_marks')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='source_business_type'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN source_business_type VARCHAR(32) NULL COMMENT ''来源业务类型'' AFTER carton_marks')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='source_business_no'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN source_business_no VARCHAR(128) NULL COMMENT ''来源业务单号'' AFTER source_business_type')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='source_business_no'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN source_business_no VARCHAR(128) NULL COMMENT ''来源业务单号'' AFTER source_business_type')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='gross_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''毛重(千克)'' AFTER package_count, ADD COLUMN volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''材积重(千克)'' AFTER gross_weight_kg')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='destination_type'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA或PRIVATE'' AFTER fba_warehouse_code, ADD COLUMN delivery_address VARCHAR(1000) NULL COMMENT ''私人仓目的地址快照'' AFTER destination_type, ADD COLUMN postal_code VARCHAR(32) NULL COMMENT ''私人仓邮编快照'' AFTER delivery_address, ADD COLUMN contact_name VARCHAR(128) NULL COMMENT ''私人仓联系人快照'' AFTER postal_code, ADD COLUMN contact_phone VARCHAR(64) NULL COMMENT ''私人仓联系电话快照'' AFTER contact_name')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='destination_type'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA或PRIVATE'' AFTER fba_warehouse_code, ADD COLUMN delivery_address VARCHAR(1000) NULL COMMENT ''私人仓目的地址快照'' AFTER destination_type, ADD COLUMN postal_code VARCHAR(32) NULL COMMENT ''私人仓邮编快照'' AFTER delivery_address, ADD COLUMN contact_name VARCHAR(128) NULL COMMENT ''私人仓联系人快照'' AFTER postal_code, ADD COLUMN contact_phone VARCHAR(64) NULL COMMENT ''私人仓联系电话快照'' AFTER contact_name')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock (
|
CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock (
|
||||||
@@ -1507,6 +1510,8 @@ CREATE TABLE IF NOT EXISTS ll_outbound_order (
|
|||||||
outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空',
|
outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空',
|
||||||
overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
|
overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
|
||||||
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数',
|
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数',
|
||||||
|
gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
|
||||||
|
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
|
||||||
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', created_by VARCHAR(128) NULL COMMENT '创建人ID',
|
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', created_by VARCHAR(128) NULL COMMENT '创建人ID',
|
||||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||||
outbound_type VARCHAR(32) NOT NULL DEFAULT 'DISPATCH_DELIVERY' COMMENT '出库类型:派送转出库、一件代发', lx_codes VARCHAR(1000) NULL COMMENT '关联一件代发领星入库单代码汇总',
|
outbound_type VARCHAR(32) NOT NULL DEFAULT 'DISPATCH_DELIVERY' COMMENT '出库类型:派送转出库、一件代发', lx_codes VARCHAR(1000) NULL COMMENT '关联一件代发领星入库单代码汇总',
|
||||||
@@ -1521,7 +1526,7 @@ CREATE TABLE IF NOT EXISTS ll_outbound_pallet_item (
|
|||||||
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID',
|
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID',
|
||||||
warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号',
|
warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号',
|
||||||
destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
|
destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
|
||||||
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
|
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)', volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
|
||||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id),
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id),
|
||||||
UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id,inbound_code,sku_code),
|
UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id,inbound_code,sku_code),
|
||||||
KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code)
|
KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code)
|
||||||
@@ -1562,6 +1567,8 @@ CREATE TABLE IF NOT EXISTS ll_outbound_pda_pick (
|
|||||||
|
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='outbound_type'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN outbound_type VARCHAR(32) NOT NULL DEFAULT ''DISPATCH_DELIVERY'' COMMENT ''出库类型'' AFTER status'));
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='outbound_type'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN outbound_type VARCHAR(32) NOT NULL DEFAULT ''DISPATCH_DELIVERY'' COMMENT ''出库类型'' AFTER status'));
|
||||||
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
|
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
|
||||||
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='gross_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''毛重(千克)'' AFTER pallet_count, ADD COLUMN volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''材积重(千克)'' AFTER gross_weight_kg, ADD COLUMN volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''体积(立方米)'' AFTER volume_weight_kg')); PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
|
||||||
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='gross_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''毛重(千克)'' AFTER package_count, ADD COLUMN volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''材积重(千克)'' AFTER gross_weight_kg, ADD COLUMN volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''体积(立方米)'' AFTER volume_weight_kg')); PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='lx_codes'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type'));
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='lx_codes'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type'));
|
||||||
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
|
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN sku_code VARCHAR(128) NULL COMMENT ''一件代发SKU'''));
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN sku_code VARCHAR(128) NULL COMMENT ''一件代发SKU'''));
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ paths:
|
|||||||
/api/platform/v1/last-leg/orders:
|
/api/platform/v1/last-leg/orders:
|
||||||
get:
|
get:
|
||||||
summary: 分页查询尾程订单
|
summary: 分页查询尾程订单
|
||||||
description: 权限 platform:overseas-warehouse:last-mile-order:list;平台管理员省略 customerId 时查询全部客户。containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁。
|
description: 权限 platform:overseas-warehouse:last-mile-order:list;平台管理员省略 customerId 时查询全部客户。containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁;grossWeightKg/netWeightKg 由头程冻结货物明细聚合。
|
||||||
security: [{ bearerAuth: [] }]
|
security: [{ bearerAuth: [] }]
|
||||||
parameters:
|
parameters:
|
||||||
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
|
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
|
||||||
@@ -206,7 +206,7 @@ paths:
|
|||||||
/api/platform/v1/last-leg/orders/{id}:
|
/api/platform/v1/last-leg/orders/{id}:
|
||||||
get:
|
get:
|
||||||
summary: 查询尾程订单详情
|
summary: 查询尾程订单详情
|
||||||
description: 权限 platform:overseas-warehouse:last-mile-order:list;返回订单、货物、状态历史、费用明细和操作记录。订单中的 containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁。
|
description: 权限 platform:overseas-warehouse:last-mile-order:list;返回订单、货物、状态历史、费用明细和操作记录。订单中的 containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁;grossWeightKg/netWeightKg 由头程冻结货物明细聚合。
|
||||||
security: [{ bearerAuth: [] }]
|
security: [{ bearerAuth: [] }]
|
||||||
parameters:
|
parameters:
|
||||||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
@@ -463,7 +463,7 @@ paths:
|
|||||||
/api/platform/v1/last-leg/inbound-plans:
|
/api/platform/v1/last-leg/inbound-plans:
|
||||||
get:
|
get:
|
||||||
summary: 平台分页查询海外入库订单
|
summary: 平台分页查询海外入库订单
|
||||||
description: 权限 platform:overseas-warehouse:inbound-order:list;列表中的 overseasWarehouseCode/overseasWarehouseName 按入库计划冻结接柜仓ID从海外仓权威目录解析,warehouseCode/warehouseName 仍表示目的仓。
|
description: 权限 platform:overseas-warehouse:inbound-order:list;overseasWarehouseCode/overseasWarehouseName 只取尾程订单预分配接柜仓事实,并按入库计划冻结接柜仓ID从海外仓权威目录解析;warehouseCode/warehouseName 仍表示目的仓。
|
||||||
security: [{ bearerAuth: [] }]
|
security: [{ bearerAuth: [] }]
|
||||||
parameters:
|
parameters:
|
||||||
- { name: customerId, in: query, required: false, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
|
- { name: customerId, in: query, required: false, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
|
||||||
@@ -2272,6 +2272,8 @@ components:
|
|||||||
destinationWarehouseName: { type: string }
|
destinationWarehouseName: { type: string }
|
||||||
fbaWarehouseCode: { type: string }
|
fbaWarehouseCode: { type: string }
|
||||||
packageCount: { type: integer }
|
packageCount: { type: integer }
|
||||||
|
grossWeightKg: { type: number, description: 排车托盘冻结毛重(千克) }
|
||||||
|
volumeWeightKg: { type: number, description: 排车托盘冻结材积重(千克) }
|
||||||
volumeCbm: { type: number }
|
volumeCbm: { type: number }
|
||||||
sourceStage: { type: string, enum: [PLAN, RECEIVED] }
|
sourceStage: { type: string, enum: [PLAN, RECEIVED] }
|
||||||
cartonMarks: { type: string, nullable: true, description: 该托盘箱号,逗号分隔 }
|
cartonMarks: { type: string, nullable: true, description: 该托盘箱号,逗号分隔 }
|
||||||
@@ -3006,7 +3008,7 @@ components:
|
|||||||
properties: { records: { type: array, items: { $ref: '#/components/schemas/InboundPlanSummary' } } }
|
properties: { records: { type: array, items: { $ref: '#/components/schemas/InboundPlanSummary' } } }
|
||||||
InboundPlanSummary:
|
InboundPlanSummary:
|
||||||
type: object
|
type: object
|
||||||
required: [lineId, planId, planNo, customerId, orderId, inboundCode, overseasWarehouseCode, overseasWarehouseName, packageCount, pickedPackageCount, palletCount, assigned, mergeAllowed, urgent, status, createdAt, version]
|
required: [lineId, planId, planNo, customerId, orderId, inboundCode, overseasWarehouseCode, overseasWarehouseName, packageCount, pickedPackageCount, grossWeightKg, netWeightKg, palletCount, assigned, mergeAllowed, urgent, status, createdAt, version]
|
||||||
properties:
|
properties:
|
||||||
lineId: { type: string, pattern: '^[0-9]+$' }
|
lineId: { type: string, pattern: '^[0-9]+$' }
|
||||||
planId: { type: string, pattern: '^[0-9]+$' }
|
planId: { type: string, pattern: '^[0-9]+$' }
|
||||||
@@ -3025,6 +3027,8 @@ components:
|
|||||||
customerName: { type: string, nullable: true }
|
customerName: { type: string, nullable: true }
|
||||||
packageCount: { type: integer }
|
packageCount: { type: integer }
|
||||||
pickedPackageCount: { type: integer }
|
pickedPackageCount: { type: integer }
|
||||||
|
grossWeightKg: { type: number, description: 头程冻结货物毛重(千克) }
|
||||||
|
netWeightKg: { type: number, description: 头程冻结货物净重(千克) }
|
||||||
volumeCbm: { type: number, nullable: true }
|
volumeCbm: { type: number, nullable: true }
|
||||||
palletCount: { type: integer }
|
palletCount: { type: integer }
|
||||||
assigned: { type: boolean, description: 入库计划是否已分配 }
|
assigned: { type: boolean, description: 入库计划是否已分配 }
|
||||||
@@ -3040,7 +3044,7 @@ components:
|
|||||||
version: { type: integer }
|
version: { type: integer }
|
||||||
InboundPlanDetail:
|
InboundPlanDetail:
|
||||||
type: object
|
type: object
|
||||||
required: [id, planNo, customerId, overseasWarehouseCode, overseasWarehouseName, status, lineCount, palletCount, totalVolumeCbm, expectedPackageCount, pickedPackageCount, lines, pallets, allocations, version, createdAt, updatedAt]
|
required: [id, planNo, customerId, overseasWarehouseCode, overseasWarehouseName, status, lineCount, palletCount, totalGrossWeightKg, totalNetWeightKg, totalVolumeCbm, expectedPackageCount, pickedPackageCount, lines, pallets, allocations, version, createdAt, updatedAt]
|
||||||
properties:
|
properties:
|
||||||
id: { type: string, pattern: '^[0-9]+$' }
|
id: { type: string, pattern: '^[0-9]+$' }
|
||||||
planNo: { type: string }
|
planNo: { type: string }
|
||||||
@@ -3055,6 +3059,8 @@ components:
|
|||||||
status: { type: string }
|
status: { type: string }
|
||||||
lineCount: { type: integer }
|
lineCount: { type: integer }
|
||||||
palletCount: { type: integer }
|
palletCount: { type: integer }
|
||||||
|
totalGrossWeightKg: { type: number, description: 入库单关联头程货物毛重合计(千克) }
|
||||||
|
totalNetWeightKg: { type: number, description: 入库单关联头程货物净重合计(千克) }
|
||||||
totalVolumeCbm: { type: number }
|
totalVolumeCbm: { type: number }
|
||||||
expectedPackageCount: { type: integer }
|
expectedPackageCount: { type: integer }
|
||||||
pickedPackageCount: { type: integer }
|
pickedPackageCount: { type: integer }
|
||||||
@@ -3148,6 +3154,8 @@ components:
|
|||||||
destinationWarehouseId: { type: string, nullable: true }
|
destinationWarehouseId: { type: string, nullable: true }
|
||||||
destinationWarehouseCode: { type: string, nullable: true }
|
destinationWarehouseCode: { type: string, nullable: true }
|
||||||
destinationWarehouseName: { type: string, nullable: true }
|
destinationWarehouseName: { type: string, nullable: true }
|
||||||
|
grossWeightKg: { type: number, description: 本入仓码头程冻结货物毛重(千克) }
|
||||||
|
netWeightKg: { type: number, description: 本入仓码头程冻结货物净重(千克) }
|
||||||
volumeCbm: { type: number }
|
volumeCbm: { type: number }
|
||||||
requiredPackageCount: { type: integer }
|
requiredPackageCount: { type: integer }
|
||||||
plannedPalletCount: { type: integer }
|
plannedPalletCount: { type: integer }
|
||||||
|
|||||||
+9
@@ -142,6 +142,8 @@ class DeliveryTransferServiceTest {
|
|||||||
assertEquals("WAIT_OUTBOUND", outbound.getValue().getStatus());
|
assertEquals("WAIT_OUTBOUND", outbound.getValue().getStatus());
|
||||||
assertEquals(2, outbound.getValue().getPalletCount());
|
assertEquals(2, outbound.getValue().getPalletCount());
|
||||||
assertEquals(20, outbound.getValue().getPackageCount());
|
assertEquals(20, outbound.getValue().getPackageCount());
|
||||||
|
assertEquals(new BigDecimal("20"), outbound.getValue().getGrossWeightKg());
|
||||||
|
assertEquals(new BigDecimal("2.0"), outbound.getValue().getVolumeCbm());
|
||||||
assertEquals("transfer-101", outbound.getValue().getIdempotencyKey());
|
assertEquals("transfer-101", outbound.getValue().getIdempotencyKey());
|
||||||
assertTrue(outbound.getValue().getOutboundNo().matches("OB-WH-\\d{8}-\\d{6}"));
|
assertTrue(outbound.getValue().getOutboundNo().matches("OB-WH-\\d{8}-\\d{6}"));
|
||||||
assertTrue(delivery.getAllValues().stream().allMatch(value -> outbound.getValue().getId().equals(value.getOutboundOrderId())));
|
assertTrue(delivery.getAllValues().stream().allMatch(value -> outbound.getValue().getId().equals(value.getOutboundOrderId())));
|
||||||
@@ -152,6 +154,10 @@ class DeliveryTransferServiceTest {
|
|||||||
.map(OutboundPalletItem::getDestinationWarehouseCode).toList());
|
.map(OutboundPalletItem::getDestinationWarehouseCode).toList());
|
||||||
assertEquals(List.of("IN-A", "IN-B"), pallets.getAllValues().stream()
|
assertEquals(List.of("IN-A", "IN-B"), pallets.getAllValues().stream()
|
||||||
.map(OutboundPalletItem::getInboundCode).toList());
|
.map(OutboundPalletItem::getInboundCode).toList());
|
||||||
|
assertEquals(List.of(new BigDecimal("12"), new BigDecimal("8")), pallets.getAllValues().stream()
|
||||||
|
.map(OutboundPalletItem::getGrossWeightKg).toList());
|
||||||
|
assertEquals(List.of(new BigDecimal("12"), new BigDecimal("8")), delivery.getAllValues().stream()
|
||||||
|
.map(DeliveryOrder::getTotalWeightKg).toList());
|
||||||
assertEquals(20, result.packageCount());
|
assertEquals(20, result.packageCount());
|
||||||
assertEquals(2, result.palletCount());
|
assertEquals(2, result.palletCount());
|
||||||
assertEquals(2, result.deliveries().size());
|
assertEquals(2, result.deliveries().size());
|
||||||
@@ -929,6 +935,9 @@ class DeliveryTransferServiceTest {
|
|||||||
value.setDestinationWarehouseId("DEST-" + destinationCode);
|
value.setDestinationWarehouseId("DEST-" + destinationCode);
|
||||||
value.setDestinationWarehouseCode(destinationCode); value.setDestinationWarehouseName(destinationName);
|
value.setDestinationWarehouseCode(destinationCode); value.setDestinationWarehouseName(destinationName);
|
||||||
value.setInboundCode(inboundCode); value.setOrderRefs("LMO-1"); value.setPackageCount(packages);
|
value.setInboundCode(inboundCode); value.setOrderRefs("LMO-1"); value.setPackageCount(packages);
|
||||||
|
value.setGrossWeightKg(BigDecimal.valueOf(packages));
|
||||||
|
value.setVolumeWeightKg(BigDecimal.valueOf(packages * 2L));
|
||||||
|
value.setVolumeCbm(BigDecimal.valueOf(packages, 1));
|
||||||
value.setSourceStage("RECEIVED");
|
value.setSourceStage("RECEIVED");
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -434,7 +434,7 @@ class ContainerInboundPlanConversionCoordinatorTest {
|
|||||||
private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) {
|
private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) {
|
||||||
return new InboundPlanResponse(21L, planNo, "100", 11L, "FL-WH-1", "验收客户", "ONE_CLICK_DROPSHIP",
|
return new InboundPlanResponse(21L, planNo, "100", 11L, "FL-WH-1", "验收客户", "ONE_CLICK_DROPSHIP",
|
||||||
"MSCU6639870", "US-LAX-01",
|
"MSCU6639870", "US-LAX-01",
|
||||||
"Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.ONE, 10, 0,
|
"Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.TEN, new BigDecimal("8"), BigDecimal.ONE, 10, 0,
|
||||||
List.of(), pallets, List.of(), 1, LocalDate.of(2026, 9, 10), "WOOD", false,
|
List.of(), pallets, List.of(), 1, LocalDate.of(2026, 9, 10), "WOOD", false,
|
||||||
false, false, "MANUAL", "自动生成", null, null);
|
false, false, "MANUAL", "自动生成", null, null);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -52,7 +52,8 @@ class InboundPlanPaginationTest {
|
|||||||
LocalDateTime.of(2026, 8, 1, 0, 0), LocalDateTime.of(2026, 8, 31, 23, 59));
|
LocalDateTime.of(2026, 8, 1, 0, 0), LocalDateTime.of(2026, 8, 31, 23, 59));
|
||||||
InboundPlanListResponse row = new InboundPlanListResponse(9007199254740993L, 2L, "LIP2", "100",
|
InboundPlanListResponse row = new InboundPlanListResponse(9007199254740993L, 2L, "LIP2", "100",
|
||||||
3L, "IN-1", null, "FIRST_LEG", 193000000000200003L, "ACY2", "错误目的仓", "W1", "ONT", "安大略仓",
|
3L, "IN-1", null, "FIRST_LEG", 193000000000200003L, "ACY2", "错误目的仓", "W1", "ONT", "安大略仓",
|
||||||
"ONT8", "客户A", 10, 0, BigDecimal.ONE, 1, true, true, true, "PENDING_ASSIGNMENT", null, null, null,
|
"ONT8", "客户A", 10, 0, new BigDecimal("30"), new BigDecimal("22"), BigDecimal.ONE,
|
||||||
|
1, true, true, true, "PENDING_ASSIGNMENT", null, null, null,
|
||||||
null, "UNSCHEDULED", LocalDateTime.of(2026, 8, 20, 10, 0), 0);
|
null, "UNSCHEDULED", LocalDateTime.of(2026, 8, 20, 10, 0), 0);
|
||||||
when(queryMapper.count("100", query)).thenReturn(11L);
|
when(queryMapper.count("100", query)).thenReturn(11L);
|
||||||
when(queryMapper.rows("100", query, 10, 10)).thenReturn(List.of(row));
|
when(queryMapper.rows("100", query, 10, 10)).thenReturn(List.of(row));
|
||||||
@@ -87,6 +88,7 @@ class InboundPlanPaginationTest {
|
|||||||
"p.overseas_warehouse_id=#{q.overseasWarehouseId}");
|
"p.overseas_warehouse_id=#{q.overseasWarehouseId}");
|
||||||
assertThat(sql).contains("p.assigned_flag assigned", "p.merge_required mergeAllowed",
|
assertThat(sql).contains("p.assigned_flag assigned", "p.merge_required mergeAllowed",
|
||||||
"p.urgent_flag urgent");
|
"p.urgent_flag urgent");
|
||||||
|
assertThat(sql).contains("grossWeightKg", "netWeightKg", "ll_order_detail");
|
||||||
assertThat(sql).doesNotContain("l.destination_warehouse_id=#{q.overseasWarehouseId}");
|
assertThat(sql).doesNotContain("l.destination_warehouse_id=#{q.overseasWarehouseId}");
|
||||||
assertThat(sql).contains("q.planNo", "q.inboundCode", "q.status", "q.overseasWarehouseId",
|
assertThat(sql).contains("q.planNo", "q.inboundCode", "q.status", "q.overseasWarehouseId",
|
||||||
"q.destinationWarehouseCode", "q.warehouse",
|
"q.destinationWarehouseCode", "q.warehouse",
|
||||||
|
|||||||
+4
-1
@@ -94,10 +94,13 @@ class LastlegOrderServiceTest {
|
|||||||
order.setSourceContainerBookingId("BOOKING-1");
|
order.setSourceContainerBookingId("BOOKING-1");
|
||||||
|
|
||||||
LastlegOrderSummary summary = ReflectionTestUtils.invokeMethod(
|
LastlegOrderSummary summary = ReflectionTestUtils.invokeMethod(
|
||||||
service, "summary", order, List.of(), null, "UNSCHEDULED");
|
service, "summary", order, List.of(), null, "UNSCHEDULED",
|
||||||
|
new LastlegOrderMapper.OrderWeightRow(11L, new java.math.BigDecimal("30"), new java.math.BigDecimal("22")));
|
||||||
|
|
||||||
assertNotNull(summary);
|
assertNotNull(summary);
|
||||||
assertEquals("BOOKING-1", summary.sourceContainerBookingId());
|
assertEquals("BOOKING-1", summary.sourceContainerBookingId());
|
||||||
|
assertEquals(new java.math.BigDecimal("30"), summary.grossWeightKg());
|
||||||
|
assertEquals(new java.math.BigDecimal("22"), summary.netWeightKg());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ public interface FirstlegInboundClient {
|
|||||||
ApiResponse<Void> updateDeclaration(@RequestHeader("X-Internal-Service-Token") String token,
|
ApiResponse<Void> updateDeclaration(@RequestHeader("X-Internal-Service-Token") String token,
|
||||||
@RequestBody OrderDeclarationDtos.UpdatedEvent event);
|
@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")
|
@PostMapping("/order-channel-changes/validate")
|
||||||
ApiResponse<OrderChannelChangeValidationResponse> validateOrderChannelChange(
|
ApiResponse<OrderChannelChangeValidationResponse> validateOrderChannelChange(
|
||||||
@RequestHeader("X-Internal-Service-Token") String token,
|
@RequestHeader("X-Internal-Service-Token") String token,
|
||||||
@@ -51,6 +55,9 @@ public interface FirstlegInboundClient {
|
|||||||
String channelCode) {}
|
String channelCode) {}
|
||||||
record OrderChannelChangeValidationResponse(long orderId, long orderVersion,
|
record OrderChannelChangeValidationResponse(long orderId, long orderVersion,
|
||||||
String channelCode, String mainStatus) {}
|
String channelCode, String mainStatus) {}
|
||||||
|
record OrderRemarksSyncCommand(long tenantId, long orderId, String customerRemark, String internalRemark,
|
||||||
|
String warehouseRemark, String customerServiceRemark,
|
||||||
|
long operatorId, String operatorName) {}
|
||||||
|
|
||||||
record OrderApprovedInboundCommand(
|
record OrderApprovedInboundCommand(
|
||||||
long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName,
|
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.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -32,6 +34,7 @@ import java.util.stream.Collectors;
|
|||||||
@RequestMapping("/internal/order/v1/cargoes")
|
@RequestMapping("/internal/order/v1/cargoes")
|
||||||
public class InternalOrderCargoController {
|
public class InternalOrderCargoController {
|
||||||
@Resource private OrderRepository orders;
|
@Resource private OrderRepository orders;
|
||||||
|
@Resource private OrderApplicationService orderApplicationService;
|
||||||
@Resource private PrintableInboundSnapshotService printableInboundSnapshots;
|
@Resource private PrintableInboundSnapshotService printableInboundSnapshots;
|
||||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||||
private byte[] expectedToken;
|
private byte[] expectedToken;
|
||||||
@@ -146,6 +149,21 @@ public class InternalOrderCargoController {
|
|||||||
.collect(Collectors.joining("、"))));
|
.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,
|
private BigDecimal sum(List<OrderCargoEntity> cargoes,
|
||||||
java.util.function.Function<OrderCargoEntity, BigDecimal> getter) {
|
java.util.function.Function<OrderCargoEntity, BigDecimal> getter) {
|
||||||
List<BigDecimal> values = cargoes.stream().map(getter).filter(Objects::nonNull).toList();
|
List<BigDecimal> values = cargoes.stream().map(getter).filter(Objects::nonNull).toList();
|
||||||
@@ -205,4 +223,10 @@ public class InternalOrderCargoController {
|
|||||||
public record MeasurementDeclarationSnapshot(
|
public record MeasurementDeclarationSnapshot(
|
||||||
String referenceId, BigDecimal originalGrossWeightKg,
|
String referenceId, BigDecimal originalGrossWeightKg,
|
||||||
BigDecimal originalVolumeCbm, String defaultActualGoodsName) {}
|
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,
|
@PathVariable long orderId,
|
||||||
@Valid @RequestBody OrderRequests.RemarksUpdateRequest request) {
|
@Valid @RequestBody OrderRequests.RemarksUpdateRequest request) {
|
||||||
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
|
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
|
@Transactional
|
||||||
public OrderRequests.RemarksUpdateResponse updateRemarks(long tenantId, long userId, long id,
|
public OrderRequests.RemarksUpdateResponse updateRemarks(long tenantId, long userId, long id,
|
||||||
OrderRequests.RemarksUpdateRequest request) {
|
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);
|
OrderEntity order = require(tenantId, id);
|
||||||
String internalRemark = normalizeRemark(request.internalRemark());
|
String internalRemark = normalizeRemark(request.internalRemark());
|
||||||
String warehouseRemark = normalizeRemark(request.warehouseRemark());
|
String warehouseRemark = normalizeRemark(request.warehouseRemark());
|
||||||
@@ -284,10 +290,57 @@ public class OrderApplicationService {
|
|||||||
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(internalRemark);
|
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(internalRemark);
|
||||||
note.setCreatedBy(userId); note.setUpdatedBy(userId); repository.insertNote(note);
|
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(),
|
return new OrderRequests.RemarksUpdateResponse(order.getVersion() + 1, order.getCustomerRemark(),
|
||||||
internalRemark, warehouseRemark, customerServiceRemark);
|
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) {
|
private static String normalizeRemark(String value) {
|
||||||
if (value == null || value.trim().isEmpty()) return null;
|
if (value == null || value.trim().isEmpty()) return null;
|
||||||
return value.trim();
|
return value.trim();
|
||||||
|
|||||||
@@ -59,6 +59,34 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema: {$ref: '#/components/schemas/CargoLabelBindingListEnvelope'}
|
schema: {$ref: '#/components/schemas/CargoLabelBindingListEnvelope'}
|
||||||
'403': {description: 内部服务令牌无效}
|
'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:
|
/internal/order/v1/cargoes/{tenantId}/{orderId}/printable-inbound-snapshot:
|
||||||
post:
|
post:
|
||||||
security: []
|
security: []
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||||
class InternalOrderCargoControllerTest {
|
class InternalOrderCargoControllerTest {
|
||||||
@MockBean private OrderRepository orders;
|
@MockBean private OrderRepository orders;
|
||||||
|
@MockBean private OrderApplicationService orderApplicationService;
|
||||||
@MockBean private PrintableInboundSnapshotService printableInboundSnapshots;
|
@MockBean private PrintableInboundSnapshotService printableInboundSnapshots;
|
||||||
@Resource private InternalOrderCargoController controller;
|
@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