优化
This commit is contained in:
@@ -411,6 +411,7 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
id BIGINT UNSIGNED NOT NULL COMMENT '订单ID(雪花ID)',
|
id BIGINT UNSIGNED NOT NULL COMMENT '订单ID(雪花ID)',
|
||||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
||||||
order_no VARCHAR(32) NOT NULL COMMENT '租户内唯一订单号',
|
order_no VARCHAR(32) NOT NULL COMMENT '租户内唯一订单号',
|
||||||
|
inbound_code VARCHAR(40) NULL COMMENT '客户提交后生成的入仓码,草稿为空',
|
||||||
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
|
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
|
||||||
customer_name VARCHAR(128) NOT NULL COMMENT '下单时客户名称快照',
|
customer_name VARCHAR(128) NOT NULL COMMENT '下单时客户名称快照',
|
||||||
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '订单业务状态',
|
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '订单业务状态',
|
||||||
@@ -449,6 +450,7 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
UNIQUE KEY uk_order_tenant_no (tenant_id, order_no),
|
UNIQUE KEY uk_order_tenant_no (tenant_id, order_no),
|
||||||
|
UNIQUE KEY uk_order_tenant_inbound_code (tenant_id, inbound_code),
|
||||||
KEY idx_order_tenant_status_time (tenant_id, status, created_at),
|
KEY idx_order_tenant_status_time (tenant_id, status, created_at),
|
||||||
KEY idx_order_tenant_customer (tenant_id, customer_id),
|
KEY idx_order_tenant_customer (tenant_id, customer_id),
|
||||||
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
||||||
@@ -2213,6 +2215,30 @@ PREPARE carrier_name_statement FROM @carrier_name_sql;
|
|||||||
EXECUTE carrier_name_statement;
|
EXECUTE carrier_name_statement;
|
||||||
DEALLOCATE PREPARE carrier_name_statement;
|
DEALLOCATE PREPARE carrier_name_statement;
|
||||||
|
|
||||||
|
-- 客户提交时由 Order 固定入仓码;审核通过后 Firstleg 沿用该码创建正式入库单。
|
||||||
|
SET @inbound_code_exists := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'ord_order' AND column_name = 'inbound_code'
|
||||||
|
);
|
||||||
|
SET @inbound_code_sql := IF(@inbound_code_exists = 0,
|
||||||
|
'ALTER TABLE ord_order ADD COLUMN inbound_code VARCHAR(40) NULL COMMENT ''客户提交后生成的入仓码,草稿为空'' AFTER order_no',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE inbound_code_statement FROM @inbound_code_sql;
|
||||||
|
EXECUTE inbound_code_statement;
|
||||||
|
DEALLOCATE PREPARE inbound_code_statement;
|
||||||
|
|
||||||
|
SET @inbound_code_index_exists := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'ord_order'
|
||||||
|
AND index_name = 'uk_order_tenant_inbound_code'
|
||||||
|
);
|
||||||
|
SET @inbound_code_index_sql := IF(@inbound_code_index_exists = 0,
|
||||||
|
'ALTER TABLE ord_order ADD UNIQUE KEY uk_order_tenant_inbound_code (tenant_id, inbound_code)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE inbound_code_index_statement FROM @inbound_code_index_sql;
|
||||||
|
EXECUTE inbound_code_index_statement;
|
||||||
|
DEALLOCATE PREPARE inbound_code_index_statement;
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- 来源:firstleg\src\main\resources\db\phase2-inbound-schema.sql
|
-- 来源:firstleg\src\main\resources\db\phase2-inbound-schema.sql
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
@@ -2294,6 +2320,12 @@ CREATE TABLE IF NOT EXISTS fl_inbound_order (
|
|||||||
CONSTRAINT fk_inbound_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id)
|
CONSTRAINT fk_inbound_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id)
|
||||||
) ENGINE=InnoDB COMMENT='二期头程入库单聚合根和列表查询快照';
|
) ENGINE=InnoDB COMMENT='二期头程入库单聚合根和列表查询快照';
|
||||||
|
|
||||||
|
UPDATE baiyue_order.ord_order o
|
||||||
|
LEFT JOIN baiyue_firstleg.fl_inbound_order i
|
||||||
|
ON i.tenant_id = o.tenant_id AND i.order_id = o.id AND i.deleted = 0
|
||||||
|
SET o.inbound_code = COALESCE(i.inbound_code, CONCAT('RK-', o.id))
|
||||||
|
WHERE o.deleted = 0 AND o.status <> 'DRAFT' AND o.inbound_code IS NULL;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS fl_inbound_pickup_snapshot (
|
CREATE TABLE IF NOT EXISTS fl_inbound_pickup_snapshot (
|
||||||
id BIGINT UNSIGNED NOT NULL COMMENT '提货信息快照ID',
|
id BIGINT UNSIGNED NOT NULL COMMENT '提货信息快照ID',
|
||||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
||||||
@@ -3440,6 +3472,7 @@ CALL unified_assert_table('baiyue_audit', 'sys_audit_log');
|
|||||||
CALL unified_assert_table('baiyue_event_bridge', 'bridge_inbox');
|
CALL unified_assert_table('baiyue_event_bridge', 'bridge_inbox');
|
||||||
CALL unified_assert_column('baiyue_order', 'ord_order', 'carrier_id');
|
CALL unified_assert_column('baiyue_order', 'ord_order', 'carrier_id');
|
||||||
CALL unified_assert_column('baiyue_order', 'ord_order', 'carrier_name');
|
CALL unified_assert_column('baiyue_order', 'ord_order', 'carrier_name');
|
||||||
|
CALL unified_assert_column('baiyue_order', 'ord_order', 'inbound_code');
|
||||||
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id');
|
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id');
|
||||||
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no');
|
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no');
|
||||||
CALL unified_assert_column('baiyue_file', 'file_export_task', 'execution_deadline_at');
|
CALL unified_assert_column('baiyue_file', 'file_export_task', 'execution_deadline_at');
|
||||||
@@ -4733,6 +4766,21 @@ DROP PROCEDURE IF EXISTS unified_assert_final_extensions;
|
|||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE PROCEDURE unified_assert_final_extensions()
|
CREATE PROCEDURE unified_assert_final_extensions()
|
||||||
BEGIN
|
BEGIN
|
||||||
|
IF (SELECT COUNT(*) FROM information_schema.statistics
|
||||||
|
WHERE table_schema = 'baiyue_order' AND table_name = 'ord_order'
|
||||||
|
AND index_name = 'uk_order_tenant_inbound_code') <> 2 THEN
|
||||||
|
SIGNAL SQLSTATE '45000'
|
||||||
|
SET MESSAGE_TEXT = '统一迁移失败:订单入仓码唯一索引未正确创建';
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM baiyue_order.ord_order
|
||||||
|
WHERE deleted = 0
|
||||||
|
AND ((status = 'DRAFT' AND inbound_code IS NOT NULL)
|
||||||
|
OR (status <> 'DRAFT' AND inbound_code IS NULL))
|
||||||
|
) THEN
|
||||||
|
SIGNAL SQLSTATE '45000'
|
||||||
|
SET MESSAGE_TEXT = '统一迁移失败:订单状态与入仓码数据不一致';
|
||||||
|
END IF;
|
||||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||||
WHERE table_schema = 'baiyue_firstleg'
|
WHERE table_schema = 'baiyue_firstleg'
|
||||||
AND table_name IN ('fl_preload_plan', 'fl_split_operation',
|
AND table_name IN ('fl_preload_plan', 'fl_split_operation',
|
||||||
|
|||||||
+3
-1
@@ -53,7 +53,9 @@ public class InternalInboundOrderController {
|
|||||||
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
|
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
|
||||||
}
|
}
|
||||||
if (command.tenantId() <= 0 || command.orderId() <= 0 || command.warehouseId() == null
|
if (command.tenantId() <= 0 || command.orderId() <= 0 || command.warehouseId() == null
|
||||||
|| command.warehouseId() <= 0 || command.customerName() == null || command.customerName().isBlank()) {
|
|| command.warehouseId() <= 0 || command.customerName() == null || command.customerName().isBlank()
|
||||||
|
|| command.inboundCode() == null || command.inboundCode().isBlank()
|
||||||
|
|| command.inboundCode().length() > 40) {
|
||||||
throw new BizException(ResultCode.BAD_REQUEST, "订单入库快照不完整");
|
throw new BizException(ResultCode.BAD_REQUEST, "订单入库快照不完整");
|
||||||
}
|
}
|
||||||
return ApiResponse.success(service.createFromApprovedOrder(command));
|
return ApiResponse.success(service.createFromApprovedOrder(command));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public record OrderApprovedInboundCommand(
|
|||||||
long tenantId,
|
long tenantId,
|
||||||
long orderId,
|
long orderId,
|
||||||
String orderNo,
|
String orderNo,
|
||||||
|
String inboundCode,
|
||||||
Long customerId,
|
Long customerId,
|
||||||
String customerName,
|
String customerName,
|
||||||
Long warehouseId,
|
Long warehouseId,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class InboundOrderCreationService {
|
|||||||
inbound.setTenantId(command.tenantId());
|
inbound.setTenantId(command.tenantId());
|
||||||
inbound.setOrderId(command.orderId());
|
inbound.setOrderId(command.orderId());
|
||||||
inbound.setOrderNo(command.orderNo());
|
inbound.setOrderNo(command.orderNo());
|
||||||
inbound.setInboundCode("RK-" + command.orderId());
|
inbound.setInboundCode(command.inboundCode());
|
||||||
inbound.setProcessStatus("WAIT_MEASURE");
|
inbound.setProcessStatus("WAIT_MEASURE");
|
||||||
inbound.setOrderStatusSnapshot("APPROVED");
|
inbound.setOrderStatusSnapshot("APPROVED");
|
||||||
inbound.setMeasureReviewStatus("NOT_SUBMITTED");
|
inbound.setMeasureReviewStatus("NOT_SUBMITTED");
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ info:
|
|||||||
security:
|
security:
|
||||||
- bearerAuth: []
|
- bearerAuth: []
|
||||||
paths:
|
paths:
|
||||||
|
/internal/firstleg/v1/inbounds/order-approved:
|
||||||
|
post:
|
||||||
|
summary: 订单审核通过后幂等创建入库单
|
||||||
|
description: 仅供订单服务调用;入仓码由订单提交时生成,Firstleg 必须原样沿用。承运商可在后续履约或计费前补充。
|
||||||
|
security:
|
||||||
|
- internalServiceToken: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/OrderApprovedInboundCommand'}
|
||||||
|
responses:
|
||||||
|
'200': {description: 成功或业务失败;相同客户和订单重放返回已有入库单 ID}
|
||||||
/api/platform/v1/first-leg/tasks:
|
/api/platform/v1/first-leg/tasks:
|
||||||
get:
|
get:
|
||||||
summary: 分页查询履约任务
|
summary: 分页查询履约任务
|
||||||
@@ -224,6 +237,10 @@ components:
|
|||||||
in: header
|
in: header
|
||||||
name: X-Signature
|
name: X-Signature
|
||||||
description: HMAC-SHA256 小写十六进制签名
|
description: HMAC-SHA256 小写十六进制签名
|
||||||
|
internalServiceToken:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: X-Internal-Service-Token
|
||||||
parameters:
|
parameters:
|
||||||
XTimestamp:
|
XTimestamp:
|
||||||
name: X-Timestamp
|
name: X-Timestamp
|
||||||
@@ -259,6 +276,36 @@ components:
|
|||||||
in: query
|
in: query
|
||||||
schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}
|
schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}
|
||||||
schemas:
|
schemas:
|
||||||
|
OrderApprovedInboundCommand:
|
||||||
|
type: object
|
||||||
|
required: [tenantId, orderId, inboundCode, customerName, warehouseId]
|
||||||
|
properties:
|
||||||
|
tenantId: {type: string, pattern: '^[1-9][0-9]*$', description: 当前客户隔离 ID}
|
||||||
|
orderId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
orderNo: {type: string, nullable: true}
|
||||||
|
inboundCode: {type: string, minLength: 1, maxLength: 40, example: RK-2085308606829346818}
|
||||||
|
customerId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||||
|
customerName: {type: string, minLength: 1}
|
||||||
|
warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
warehouseName: {type: string, nullable: true}
|
||||||
|
deliveryWarehouseId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||||
|
deliveryWarehouseName: {type: string, nullable: true}
|
||||||
|
expectedArrivalAt: {type: string, format: date-time, nullable: true}
|
||||||
|
businessType: {type: string, nullable: true}
|
||||||
|
channelCode: {type: string, nullable: true}
|
||||||
|
channelName: {type: string, nullable: true}
|
||||||
|
carrierId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 可选,可在后续履约或计费前补充}
|
||||||
|
carrierName: {type: string, nullable: true}
|
||||||
|
taxMode: {type: string, nullable: true}
|
||||||
|
containerMode: {type: string, nullable: true}
|
||||||
|
customsInspection: {type: boolean, nullable: true}
|
||||||
|
destinationType: {type: string, nullable: true}
|
||||||
|
destinationWarehouseId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||||
|
destinationCode: {type: string, nullable: true}
|
||||||
|
destinationName: {type: string, nullable: true}
|
||||||
|
publicRemark: {type: string, nullable: true}
|
||||||
|
expressCompanyName: {type: string, nullable: true}
|
||||||
|
expressTrackingNo: {type: string, nullable: true}
|
||||||
MerchantInboundBillingEnvelope:
|
MerchantInboundBillingEnvelope:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
+25
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.controller;
|
|||||||
|
|
||||||
import com.baiyue.common.core.exception.BizException;
|
import com.baiyue.common.core.exception.BizException;
|
||||||
import com.baiyue.firstleg.dto.InboundOrderReference;
|
import com.baiyue.firstleg.dto.InboundOrderReference;
|
||||||
|
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
|
||||||
import com.baiyue.firstleg.service.InboundOrderCreationService;
|
import com.baiyue.firstleg.service.InboundOrderCreationService;
|
||||||
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
|
||||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||||
@@ -63,4 +64,28 @@ class InternalInboundOrderReferenceControllerTest {
|
|||||||
.isInstanceOf(BizException.class);
|
.isInstanceOf(BizException.class);
|
||||||
verify(unifiedQueryService).page(request);
|
verify(unifiedQueryService).page(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void approvedOrderRequiresInboundCode() {
|
||||||
|
assertThatThrownBy(() -> controller.createFromApprovedOrder(
|
||||||
|
"internal-token", approvedCommand(" ", null)))
|
||||||
|
.isInstanceOf(BizException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void approvedOrderAllowsMissingCarrier() {
|
||||||
|
OrderApprovedInboundCommand command = approvedCommand("RK-77", null);
|
||||||
|
when(service.createFromApprovedOrder(command)).thenReturn(88L);
|
||||||
|
|
||||||
|
assertThat(controller.createFromApprovedOrder("internal-token", command).data()).isEqualTo(88L);
|
||||||
|
verify(service).createFromApprovedOrder(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OrderApprovedInboundCommand approvedCommand(String inboundCode, Long carrierId) {
|
||||||
|
return new OrderApprovedInboundCommand(
|
||||||
|
10001L, 77L, "FL-77", inboundCode, 7L, "测试客户", 9L, "义乌仓",
|
||||||
|
null, null, null, "FBA", "AIR", "空运", carrierId,
|
||||||
|
carrierId == null ? null : "承运商", null, null, null, null,
|
||||||
|
null, null, null, null, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package com.baiyue.firstleg.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
|
||||||
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
|
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||||
|
import com.baiyue.firstleg.mapper.InboundOrderMapper;
|
||||||
|
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class InboundOrderCreationServiceTest {
|
||||||
|
@Mock
|
||||||
|
private InboundOrderMapper orders;
|
||||||
|
@Mock
|
||||||
|
private InboundStatusHistoryMapper histories;
|
||||||
|
@InjectMocks
|
||||||
|
private InboundOrderCreationService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseSubmittedInboundCodeAndKeepReplayIdempotentWithoutCarrier() {
|
||||||
|
OrderApprovedInboundCommand command = approvedCommand("RK-CUSTOM-77");
|
||||||
|
InboundOrderEntity existing = new InboundOrderEntity();
|
||||||
|
existing.setId(900L);
|
||||||
|
existing.setTenantId(10001L);
|
||||||
|
existing.setOrderId(77L);
|
||||||
|
existing.setInboundCode("RK-CUSTOM-77");
|
||||||
|
when(orders.selectOne(any(Wrapper.class))).thenReturn(null, existing);
|
||||||
|
|
||||||
|
long createdId = service.createFromApprovedOrder(command);
|
||||||
|
long replayedId = service.createFromApprovedOrder(command);
|
||||||
|
|
||||||
|
ArgumentCaptor<InboundOrderEntity> inboundCaptor = ArgumentCaptor.forClass(InboundOrderEntity.class);
|
||||||
|
verify(orders, times(1)).insert(inboundCaptor.capture());
|
||||||
|
verify(histories, times(1)).insert(any(InboundStatusHistoryEntity.class));
|
||||||
|
assertThat(inboundCaptor.getValue().getInboundCode()).isEqualTo("RK-CUSTOM-77");
|
||||||
|
assertThat(inboundCaptor.getValue().getCarrierId()).isNull();
|
||||||
|
assertThat(createdId).isEqualTo(inboundCaptor.getValue().getId());
|
||||||
|
assertThat(replayedId).isEqualTo(900L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OrderApprovedInboundCommand approvedCommand(String inboundCode) {
|
||||||
|
return new OrderApprovedInboundCommand(
|
||||||
|
10001L, 77L, "FL-77", inboundCode, 7L, "测试客户", 9L, "义乌仓",
|
||||||
|
null, null, null, "FBA", "AIR", "空运", null, null,
|
||||||
|
null, null, null, null, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ public interface FirstlegInboundClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record OrderApprovedInboundCommand(
|
record OrderApprovedInboundCommand(
|
||||||
long tenantId, long orderId, String orderNo, Long customerId, String customerName,
|
long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName,
|
||||||
Long warehouseId, String warehouseName, Long deliveryWarehouseId, String deliveryWarehouseName,
|
Long warehouseId, String warehouseName, Long deliveryWarehouseId, String deliveryWarehouseName,
|
||||||
LocalDateTime expectedArrivalAt, String businessType, String channelCode, String channelName,
|
LocalDateTime expectedArrivalAt, String businessType, String channelCode, String channelName,
|
||||||
Long carrierId, String carrierName, String taxMode, String containerMode,
|
Long carrierId, String carrierName, String taxMode, String containerMode,
|
||||||
|
|||||||
+2
-2
@@ -146,7 +146,7 @@ public class PlatformFirstLegOrderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@BusinessAudit(event = "ORDER_APPROVE", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "Approve order / 审核通过")
|
@BusinessAudit(event = "ORDER_APPROVE", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "Approve order / 审核通过")
|
||||||
@Operation(summary = "Approve / 审核通过", description = "Approves a submitted order and records its status history. / 审核通过已提交订单,并记录状态历史。")
|
@Operation(summary = "Approve / 审核通过", description = "Approves a submitted order without requiring carrier assignment and records its status history. / 审核通过已提交订单,不要求预先指派承运商,并记录状态历史。")
|
||||||
@PostMapping("/{id}/approve")
|
@PostMapping("/{id}/approve")
|
||||||
@PreAuthorize("hasAuthority('platform:order:approve')")
|
@PreAuthorize("hasAuthority('platform:order:approve')")
|
||||||
public ApiResponse<Void> approve(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<Void> approve(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@@ -156,7 +156,7 @@ public class PlatformFirstLegOrderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@BusinessAudit(event = "ORDER_ASSIGN_CARRIER", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "指派订单承运商")
|
@BusinessAudit(event = "ORDER_ASSIGN_CARRIER", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "指派订单承运商")
|
||||||
@Operation(summary = "指派承运商", description = "为待审核订单选择启用的公共承运商,名称快照由服务端主数据回填。")
|
@Operation(summary = "指派承运商", description = "可选地为待审核订单选择启用的公共承运商,名称快照由服务端主数据回填;该动作不是审核前置条件。")
|
||||||
@PutMapping("/{id}/carrier")
|
@PutMapping("/{id}/carrier")
|
||||||
@PreAuthorize("hasAuthority('platform:order:approve')")
|
@PreAuthorize("hasAuthority('platform:order:approve')")
|
||||||
public ApiResponse<Void> assignCarrier(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<Void> assignCarrier(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class OrderEntity extends BaseEntity {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
private String orderNo;
|
private String orderNo;
|
||||||
|
private String inboundCode;
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
private String customerName;
|
private String customerName;
|
||||||
private OrderStatus status;
|
private OrderStatus status;
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ public class OrderRepository {
|
|||||||
.eq(tenantId != null, OrderEntity::getTenantId, tenantId)
|
.eq(tenantId != null, OrderEntity::getTenantId, tenantId)
|
||||||
.in(OrderEntity::getStatus, statuses)
|
.in(OrderEntity::getStatus, statuses)
|
||||||
.like(StringUtils.hasText(query.orderNo()), OrderEntity::getOrderNo, query.orderNo())
|
.like(StringUtils.hasText(query.orderNo()), OrderEntity::getOrderNo, query.orderNo())
|
||||||
|
.like(StringUtils.hasText(query.inboundCode()), OrderEntity::getInboundCode, query.inboundCode())
|
||||||
.eq(query.customerId() != null, OrderEntity::getCustomerId, query.customerId())
|
.eq(query.customerId() != null, OrderEntity::getCustomerId, query.customerId())
|
||||||
.eq(query.receivingWarehouseId() != null, OrderEntity::getReceivingWarehouseId,
|
.eq(query.receivingWarehouseId() != null, OrderEntity::getReceivingWarehouseId,
|
||||||
query.receivingWarehouseId())
|
query.receivingWarehouseId())
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ public class OrderApplicationService {
|
|||||||
throw new BizException("当前订单状态不允许编辑订单资料");
|
throw new BizException("当前订单状态不允许编辑订单资料");
|
||||||
}
|
}
|
||||||
apply(order, request, snapshots); order.setStatus(OrderStatus.DRAFT); order.setUpdatedBy(userId);
|
apply(order, request, snapshots); order.setStatus(OrderStatus.DRAFT); order.setUpdatedBy(userId);
|
||||||
|
if (before == OrderStatus.REJECTED) order.setInboundCode(null);
|
||||||
if (before == OrderStatus.SUBMITTED) {
|
if (before == OrderStatus.SUBMITTED) {
|
||||||
order.setStatus(OrderStatus.SUBMITTED);
|
order.setStatus(OrderStatus.SUBMITTED);
|
||||||
}
|
}
|
||||||
@@ -118,11 +119,7 @@ public class OrderApplicationService {
|
|||||||
masterDataGuard.validatePersisted(tenantId, order.getCustomerId(), order.getReceivingWarehouseId(),
|
masterDataGuard.validatePersisted(tenantId, order.getCustomerId(), order.getReceivingWarehouseId(),
|
||||||
order.getDestinationWarehouseId(), order.getDestinationType());
|
order.getDestinationWarehouseId(), order.getDestinationType());
|
||||||
}
|
}
|
||||||
if (target == OrderStatus.APPROVED) {
|
if (target == OrderStatus.APPROVED && order.getCarrierId() != null) {
|
||||||
if (order.getCarrierId() == null || !StringUtils.hasText(order.getCarrierName())) {
|
|
||||||
throw new BizException(com.baiyue.common.core.api.ResultCode.VALIDATION_FAILED,
|
|
||||||
"请先选择有效承运商后再审核通过");
|
|
||||||
}
|
|
||||||
var carrier = masterDataGuard.validateCarrier(order.getCarrierId());
|
var carrier = masterDataGuard.validateCarrier(order.getCarrierId());
|
||||||
order.setCarrierName(carrier.partnerName());
|
order.setCarrierName(carrier.partnerName());
|
||||||
}
|
}
|
||||||
@@ -130,7 +127,11 @@ public class OrderApplicationService {
|
|||||||
throw new BizException("Reason is required");
|
throw new BizException("Reason is required");
|
||||||
}
|
}
|
||||||
order.setStatus(target); order.setUpdatedBy(userId);
|
order.setStatus(target); order.setUpdatedBy(userId);
|
||||||
if (target == OrderStatus.SUBMITTED) order.setSubmittedAt(LocalDateTime.now());
|
if (target == OrderStatus.SUBMITTED) {
|
||||||
|
if (!StringUtils.hasText(order.getInboundCode())) order.setInboundCode("RK-" + order.getId());
|
||||||
|
order.setSubmittedAt(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
if (target == OrderStatus.DRAFT) order.setInboundCode(null);
|
||||||
if (target == OrderStatus.COMPLETED) order.setCompletedAt(LocalDateTime.now());
|
if (target == OrderStatus.COMPLETED) order.setCompletedAt(LocalDateTime.now());
|
||||||
if (repository.saveOrder(tenantId, order) != 1) throw new BizException("Concurrent order update");
|
if (repository.saveOrder(tenantId, order) != 1) throw new BizException("Concurrent order update");
|
||||||
record(tenantId, id, from, target, target.name(), reason, userId, username);
|
record(tenantId, id, from, target, target.name(), reason, userId, username);
|
||||||
@@ -423,7 +424,8 @@ public class OrderApplicationService {
|
|||||||
}
|
}
|
||||||
private FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) {
|
private FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) {
|
||||||
return new FirstlegInboundClient.OrderApprovedInboundCommand(
|
return new FirstlegInboundClient.OrderApprovedInboundCommand(
|
||||||
order.getTenantId(), order.getId(), order.getOrderNo(), order.getCustomerId(), order.getCustomerName(),
|
order.getTenantId(), order.getId(), order.getOrderNo(), order.getInboundCode(),
|
||||||
|
order.getCustomerId(), order.getCustomerName(),
|
||||||
order.getReceivingWarehouseId(), order.getReceivingWarehouseName(), order.getDestinationWarehouseId(),
|
order.getReceivingWarehouseId(), order.getReceivingWarehouseName(), order.getDestinationWarehouseId(),
|
||||||
order.getDestinationWarehouseName(), order.getExpectedShipDate() == null ? null : order.getExpectedShipDate().atStartOfDay(),
|
order.getDestinationWarehouseName(), order.getExpectedShipDate() == null ? null : order.getExpectedShipDate().atStartOfDay(),
|
||||||
order.getDestinationType() + "_" + order.getTransportMode(), order.getChannelCode(), order.getChannelName(),
|
order.getDestinationType() + "_" + order.getTransportMode(), order.getChannelCode(), order.getChannelName(),
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ public class UnifiedOrderQueryService {
|
|||||||
List<OrderStatus> statuses = "PENDING_REVIEW".equals(query.mainStatus())
|
List<OrderStatus> statuses = "PENDING_REVIEW".equals(query.mainStatus())
|
||||||
? List.of(OrderStatus.SUBMITTED) : ACTIVE_ORDER_STATUSES;
|
? List.of(OrderStatus.SUBMITTED) : ACTIVE_ORDER_STATUSES;
|
||||||
PageResult<OrderEntity> source = orders.unifiedPage(tenantId, query, statuses);
|
PageResult<OrderEntity> source = orders.unifiedPage(tenantId, query, statuses);
|
||||||
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = summaries(
|
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = "PENDING_REVIEW".equals(query.mainStatus())
|
||||||
tenantId, source.records().stream().map(OrderEntity::getId).toList());
|
? Map.of()
|
||||||
|
: summaries(tenantId, source.records().stream().map(OrderEntity::getId).toList());
|
||||||
List<UnifiedOrderResponse.Row> rows = source.records().stream()
|
List<UnifiedOrderResponse.Row> rows = source.records().stream()
|
||||||
.map(order -> row(order, inboundByOrder.get(order.getId()))).toList();
|
.map(order -> row(order, inboundByOrder.get(order.getId()))).toList();
|
||||||
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
|
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
|
||||||
@@ -64,7 +65,8 @@ public class UnifiedOrderQueryService {
|
|||||||
OrderDetailResponse orderDetail = new OrderDetailResponse(order, orders.findCargo(tenantId, orderId),
|
OrderDetailResponse orderDetail = new OrderDetailResponse(order, orders.findCargo(tenantId, orderId),
|
||||||
orders.findParties(tenantId, orderId), includeInternalNotes ? orders.findNotes(tenantId, orderId) : List.of(),
|
orders.findParties(tenantId, orderId), includeInternalNotes ? orders.findNotes(tenantId, orderId) : List.of(),
|
||||||
orders.findHistory(tenantId, orderId));
|
orders.findHistory(tenantId, orderId));
|
||||||
FirstlegUnifiedOrderClient.Detail inbound = firstlegDetail(tenantId, orderId);
|
FirstlegUnifiedOrderClient.Detail inbound = order.getStatus() == OrderStatus.SUBMITTED
|
||||||
|
? null : firstlegDetail(tenantId, orderId);
|
||||||
UnifiedOrderResponse.Row summary = row(order, inbound == null ? null : inbound.summary());
|
UnifiedOrderResponse.Row summary = row(order, inbound == null ? null : inbound.summary());
|
||||||
List<UnifiedOrderResponse.TimelineEvent> timeline = new ArrayList<>();
|
List<UnifiedOrderResponse.TimelineEvent> timeline = new ArrayList<>();
|
||||||
orderDetail.statusHistory().forEach(item -> timeline.add(new UnifiedOrderResponse.TimelineEvent(
|
orderDetail.statusHistory().forEach(item -> timeline.add(new UnifiedOrderResponse.TimelineEvent(
|
||||||
@@ -121,7 +123,8 @@ public class UnifiedOrderQueryService {
|
|||||||
order.getStatus() == OrderStatus.SUBMITTED ? "APPROVE_ORDER" : null,
|
order.getStatus() == OrderStatus.SUBMITTED ? "APPROVE_ORDER" : null,
|
||||||
order.getStatus(), order.getCustomerId(), order.getCustomerName(),
|
order.getStatus(), order.getCustomerId(), order.getCustomerName(),
|
||||||
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
||||||
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), null, null, null,
|
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), null, null,
|
||||||
|
order.getInboundCode(),
|
||||||
order.getExpectedShipDate(), null, null, null, null, null, null, null, null,
|
order.getExpectedShipDate(), null, null, null, null, null, null, null, null,
|
||||||
order.getCreatedAt(), order.getUpdatedAt());
|
order.getCreatedAt(), order.getUpdatedAt());
|
||||||
}
|
}
|
||||||
@@ -213,7 +216,7 @@ public class UnifiedOrderQueryService {
|
|||||||
|
|
||||||
private boolean requiresFirstlegQuery(UnifiedOrderQuery query) {
|
private boolean requiresFirstlegQuery(UnifiedOrderQuery query) {
|
||||||
return (StringUtils.hasText(query.mainStatus()) && !"PENDING_REVIEW".equals(query.mainStatus()))
|
return (StringUtils.hasText(query.mainStatus()) && !"PENDING_REVIEW".equals(query.mainStatus()))
|
||||||
|| StringUtils.hasText(query.inboundCode()) || query.expectedArrivalDate() != null
|
|| query.expectedArrivalDate() != null
|
||||||
|| StringUtils.hasText(query.feeReviewStatus()) || StringUtils.hasText(query.containerNo())
|
|| StringUtils.hasText(query.feeReviewStatus()) || StringUtils.hasText(query.containerNo())
|
||||||
|| StringUtils.hasText(query.cabinetNo()) || query.sailingDate() != null || query.loaded() != null
|
|| StringUtils.hasText(query.cabinetNo()) || query.sailingDate() != null || query.loaded() != null
|
||||||
|| StringUtils.hasText(query.measurementReviewStatus());
|
|| StringUtils.hasText(query.measurementReviewStatus());
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
id BIGINT UNSIGNED NOT NULL COMMENT '订单ID(雪花ID)',
|
id BIGINT UNSIGNED NOT NULL COMMENT '订单ID(雪花ID)',
|
||||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
||||||
order_no VARCHAR(32) NOT NULL COMMENT '租户内唯一订单号',
|
order_no VARCHAR(32) NOT NULL COMMENT '租户内唯一订单号',
|
||||||
|
inbound_code VARCHAR(40) NULL COMMENT '客户提交后生成的入仓码,草稿为空',
|
||||||
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
|
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
|
||||||
customer_name VARCHAR(128) NOT NULL COMMENT '下单时客户名称快照',
|
customer_name VARCHAR(128) NOT NULL COMMENT '下单时客户名称快照',
|
||||||
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '订单业务状态',
|
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '订单业务状态',
|
||||||
@@ -47,6 +48,7 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
UNIQUE KEY uk_order_tenant_no (tenant_id, order_no),
|
UNIQUE KEY uk_order_tenant_no (tenant_id, order_no),
|
||||||
|
UNIQUE KEY uk_order_tenant_inbound_code (tenant_id, inbound_code),
|
||||||
KEY idx_order_tenant_status_time (tenant_id, status, created_at),
|
KEY idx_order_tenant_status_time (tenant_id, status, created_at),
|
||||||
KEY idx_order_tenant_customer (tenant_id, customer_id),
|
KEY idx_order_tenant_customer (tenant_id, customer_id),
|
||||||
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ paths:
|
|||||||
summary: 平台统一订单列表
|
summary: 平台统一订单列表
|
||||||
description: >-
|
description: >-
|
||||||
统一返回七态产品主状态、下一步动作及测量/预装/装柜/派送摘要,需要 platform:order:list。
|
统一返回七态产品主状态、下一步动作及测量/预装/装柜/派送摘要,需要 platform:order:list。
|
||||||
有入库单时同时返回 inboundId、inboundVersion、loadingVersion、pickingVersion;inboundVersion
|
草稿不生成 inboundCode,客户提交进入待审核后即返回固定 inboundCode;有入库单时同时返回
|
||||||
|
inboundId、inboundVersion、loadingVersion、pickingVersion;inboundVersion
|
||||||
才是 C04 loading/complete 的 expectedVersion,loadingVersion 当前等于 pickingVersion(兼容别名)。
|
才是 C04 loading/complete 的 expectedVersion,loadingVersion 当前等于 pickingVersion(兼容别名)。
|
||||||
tenantId 省略时跨全部客户;mainStatus 省略时按订单创建时间分页,指定 PENDING_REVIEW 时由 Order 查询,
|
tenantId 省略时跨全部客户;mainStatus 省略时按订单创建时间分页,指定 PENDING_REVIEW 时由 Order 查询,
|
||||||
指定其他主状态时由 Firstleg 精确分页。旧 /orders/first-leg 接口保持兼容。
|
指定其他主状态时由 Firstleg 精确分页。旧 /orders/first-leg 接口保持兼容。
|
||||||
@@ -45,7 +46,7 @@ paths:
|
|||||||
/api/platform/v1/orders/{orderId}:
|
/api/platform/v1/orders/{orderId}:
|
||||||
get:
|
get:
|
||||||
summary: 平台统一订单详情
|
summary: 平台统一订单详情
|
||||||
description: 合并 Order 与 Firstleg 时间线并按发生时间升序返回;平台可见内部备注。
|
description: 合并 Order 与 Firstleg 时间线并按发生时间升序返回;待审核订单直接返回 Order 在提交时生成的 inboundCode;平台可见内部备注。
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/UnifiedOrderId'
|
- $ref: '#/components/parameters/UnifiedOrderId'
|
||||||
- $ref: '#/components/parameters/UnifiedTenantId'
|
- $ref: '#/components/parameters/UnifiedTenantId'
|
||||||
@@ -222,7 +223,7 @@ paths:
|
|||||||
/api/platform/v1/orders/first-leg/{id}/carrier:
|
/api/platform/v1/orders/first-leg/{id}/carrier:
|
||||||
put:
|
put:
|
||||||
summary: 为待审核订单指派承运商
|
summary: 为待审核订单指派承运商
|
||||||
description: 只接收承运商 ID;服务端从公共往来单位主数据校验 CARRIER 类型并写入可信名称快照。审核通过前必须完成指派。
|
description: 可选的独立指派动作;服务端从公共往来单位主数据校验 CARRIER 类型并写入可信名称快照,不作为审核通过的前置条件。
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/OrderId'
|
- $ref: '#/components/parameters/OrderId'
|
||||||
- $ref: '#/components/parameters/TenantId'
|
- $ref: '#/components/parameters/TenantId'
|
||||||
@@ -263,7 +264,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, WAIT_SPLIT, LOADING, LOADED, DELIVERED]
|
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, WAIT_SPLIT, LOADING, LOADED, DELIVERED]
|
||||||
UnifiedInboundCode:
|
UnifiedInboundCode:
|
||||||
{name: inboundCode, in: query, required: false, schema: {type: string, maxLength: 64}}
|
{name: inboundCode, in: query, required: false, description: 客户提交后生成的入仓码,待审核订单同样可查询, schema: {type: string, maxLength: 40}}
|
||||||
UnifiedInternalOrderStatus:
|
UnifiedInternalOrderStatus:
|
||||||
name: internalOrderStatus
|
name: internalOrderStatus
|
||||||
in: query
|
in: query
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class OrderApplicationServiceStateTest {
|
|||||||
|
|
||||||
assertThat(orderId).isNotNull();
|
assertThat(orderId).isNotNull();
|
||||||
verify(eligibility, never()).requireApproved(anyLong());
|
verify(eligibility, never()).requireApproved(anyLong());
|
||||||
verify(repository).insertOrder(argThat(order -> order.getStatus() == OrderStatus.DRAFT));
|
verify(repository).insertOrder(argThat(order -> order.getStatus() == OrderStatus.DRAFT
|
||||||
|
&& order.getInboundCode() == null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -120,17 +121,20 @@ class OrderApplicationServiceStateTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void approvalRequiresCarrierAssignment() {
|
void approvalDoesNotRequireCarrierAssignmentAndPublishesInboundCode() {
|
||||||
OrderRepository repository = mock(OrderRepository.class);
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
OrderEntity order = persistedOrder(OrderStatus.SUBMITTED);
|
OrderEntity order = persistedOrder(OrderStatus.SUBMITTED);
|
||||||
|
order.setOrderNo("FL1");
|
||||||
|
order.setInboundCode("RK-1");
|
||||||
when(repository.findOwned(10001L, 1L)).thenReturn(order);
|
when(repository.findOwned(10001L, 1L)).thenReturn(order);
|
||||||
|
when(repository.saveOrder(10001L, order)).thenReturn(1);
|
||||||
|
|
||||||
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
|
service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
|
||||||
.transit(10001L, 2L, "u", 1L, OrderStatus.APPROVED, null))
|
.transit(10001L, 2L, "u", 1L, OrderStatus.APPROVED, null);
|
||||||
.isInstanceOf(BizException.class)
|
|
||||||
.hasMessageContaining("请先选择有效承运商");
|
|
||||||
|
|
||||||
verify(repository, never()).saveOrder(anyLong(), any());
|
assertThat(order.getStatus()).isEqualTo(OrderStatus.APPROVED);
|
||||||
|
verify(repository).insertOutbox(argThat(event -> event.getPayloadJson().contains("\"inboundCode\":\"RK-1\"")
|
||||||
|
&& event.getPayloadJson().contains("\"carrierId\":null")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -153,6 +157,25 @@ class OrderApplicationServiceStateTest {
|
|||||||
verify(masterData).validateCarrier(9L);
|
verify(masterData).validateCarrier(9L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void submittingDraftGeneratesAndPersistsInboundCode() {
|
||||||
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
|
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
|
||||||
|
OrderEntity order = persistedDraft("SELF_EXPRESS", "OVERSEAS");
|
||||||
|
when(repository.findOwned(20002L, 1L)).thenReturn(order);
|
||||||
|
when(repository.findCargo(20002L, 1L)).thenReturn(List.of(new OrderCargoEntity()));
|
||||||
|
when(repository.findParties(20002L, 1L)).thenReturn(List.of());
|
||||||
|
when(repository.saveOrder(20002L, order)).thenReturn(1);
|
||||||
|
|
||||||
|
service(repository, mock(OrderEligibilityGuard.class), masterData)
|
||||||
|
.transit(20002L, 2L, "u", 1L, OrderStatus.SUBMITTED, null);
|
||||||
|
|
||||||
|
assertThat(order.getStatus()).isEqualTo(OrderStatus.SUBMITTED);
|
||||||
|
assertThat(order.getInboundCode()).isEqualTo("RK-1");
|
||||||
|
assertThat(order.getSubmittedAt()).isNotNull();
|
||||||
|
verify(repository).saveOrder(20002L, order);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void merchantDraftEditorStillCannotEditSubmittedOrder() {
|
void merchantDraftEditorStillCannotEditSubmittedOrder() {
|
||||||
OrderRepository repository = mock(OrderRepository.class);
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class UnifiedOrderQueryServiceTest {
|
|||||||
void pendingReviewIsPagedFromOrderAndMappedToApproveAction() {
|
void pendingReviewIsPagedFromOrderAndMappedToApproveAction() {
|
||||||
UnifiedOrderQuery query = query("PENDING_REVIEW");
|
UnifiedOrderQuery query = query("PENDING_REVIEW");
|
||||||
OrderEntity order = order(OrderStatus.SUBMITTED);
|
OrderEntity order = order(OrderStatus.SUBMITTED);
|
||||||
|
order.setInboundCode("RK-77");
|
||||||
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
|
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
|
||||||
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
|
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
|
||||||
when(firstleg.summaries(eq("internal-token"), any()))
|
when(firstleg.summaries(eq("internal-token"), any()))
|
||||||
@@ -48,7 +49,58 @@ class UnifiedOrderQueryServiceTest {
|
|||||||
assertThat(result.records()).singleElement().satisfies(row -> {
|
assertThat(result.records()).singleElement().satisfies(row -> {
|
||||||
assertThat(row.mainStatus()).isEqualTo("PENDING_REVIEW");
|
assertThat(row.mainStatus()).isEqualTo("PENDING_REVIEW");
|
||||||
assertThat(row.nextAction()).isEqualTo("APPROVE_ORDER");
|
assertThat(row.nextAction()).isEqualTo("APPROVE_ORDER");
|
||||||
|
assertThat(row.inboundCode()).isEqualTo("RK-77");
|
||||||
|
assertThat(row.inboundId()).isNull();
|
||||||
});
|
});
|
||||||
|
verify(firstleg, never()).summaries(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pendingReviewInboundCodeFilterStaysInOrderQuery() {
|
||||||
|
UnifiedOrderQuery query = new UnifiedOrderQuery(
|
||||||
|
null, null, "PENDING_REVIEW", null, null, null, "RK-77", null,
|
||||||
|
null, null, null, null, null, null, null, null, null, null,
|
||||||
|
null, null, 1, 20);
|
||||||
|
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
|
||||||
|
.thenReturn(new PageResult<>(List.of(), 1, 20, 0, 0));
|
||||||
|
|
||||||
|
service.page(10001L, query);
|
||||||
|
|
||||||
|
verify(orders).unifiedPage(10001L, query, List.of(OrderStatus.SUBMITTED));
|
||||||
|
verify(firstleg, never()).page(any(), any());
|
||||||
|
verify(firstleg, never()).summaries(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allStatusInboundCodeFilterIncludesPendingOrdersFromOrderQuery() {
|
||||||
|
UnifiedOrderQuery query = new UnifiedOrderQuery(
|
||||||
|
null, null, null, null, null, null, "RK-77", null,
|
||||||
|
null, null, null, null, null, null, null, null, null, null,
|
||||||
|
null, null, 1, 20);
|
||||||
|
when(orders.unifiedPage(eq(10001L), eq(query), any()))
|
||||||
|
.thenReturn(new PageResult<>(List.of(), 1, 20, 0, 0));
|
||||||
|
|
||||||
|
service.page(10001L, query);
|
||||||
|
|
||||||
|
verify(orders).unifiedPage(eq(10001L), eq(query), any());
|
||||||
|
verify(firstleg, never()).page(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pendingReviewDetailUsesOrderInboundCodeWithoutCallingFirstleg() {
|
||||||
|
OrderEntity order = order(OrderStatus.SUBMITTED);
|
||||||
|
order.setInboundCode("RK-77");
|
||||||
|
when(orders.findOwned(10001L, 77L)).thenReturn(order);
|
||||||
|
when(orders.findCargo(10001L, 77L)).thenReturn(List.of());
|
||||||
|
when(orders.findParties(10001L, 77L)).thenReturn(List.of());
|
||||||
|
when(orders.findNotes(10001L, 77L)).thenReturn(List.of());
|
||||||
|
when(orders.findHistory(10001L, 77L)).thenReturn(List.of());
|
||||||
|
|
||||||
|
var result = service.detail(10001L, 77L, true);
|
||||||
|
|
||||||
|
assertThat(result.summary().inboundCode()).isEqualTo("RK-77");
|
||||||
|
assertThat(result.order().order().getInboundCode()).isEqualTo("RK-77");
|
||||||
|
verify(firstleg, never()).detail(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user