This commit is contained in:
2026-08-30 17:02:19 +08:00
parent 0aa77327d5
commit ca9e7b60e8
40 changed files with 1471 additions and 6 deletions
@@ -10,11 +10,13 @@ import com.baiyue.order.dto.MerchantBatchDraftDtos;
import com.baiyue.order.dto.MerchantInboundOrderQuery;
import com.baiyue.order.dto.MerchantInboundOrderRow;
import com.baiyue.order.dto.InboundCodePolicyResponse;
import com.baiyue.order.dto.InboundCodeAssociationSummary;
import com.baiyue.order.entity.OrderStatus;
import com.baiyue.order.service.OrderApplicationService;
import com.baiyue.order.service.OrderScopeGuard;
import com.baiyue.order.service.MerchantInboundOrderListService;
import com.baiyue.order.service.MerchantBatchDraftService;
import com.baiyue.order.service.InboundCodeAssociationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -50,6 +52,8 @@ public class MerchantFirstLegOrderController {
private MerchantInboundOrderListService inboundOrderListService;
@Resource
private MerchantBatchDraftService batchDraftService;
@Resource
private InboundCodeAssociationService inboundCodeAssociations;
@GetMapping("/inbound-code-policy")
@PreAuthorize("hasAnyAuthority('merchant:order:list', 'merchant:order:create', 'merchant:order:edit')")
@@ -57,6 +61,17 @@ public class MerchantFirstLegOrderController {
return ApiResponse.success(service.inboundCodePolicy());
}
@GetMapping("/inbound-code-associations/{inboundCode}")
@PreAuthorize("hasAuthority('merchant:order:list')")
@Operation(summary = "按入仓码查询本客户订单基础关联",
description = "客户范围只取登录态;其他客户的入仓码按不存在处理。")
public ApiResponse<InboundCodeAssociationSummary> inboundCodeAssociation(
@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String inboundCode) {
return ApiResponse.success(inboundCodeAssociations.findForCustomer(
OrderScopeGuard.merchant(user), inboundCode));
}
@BusinessAudit(event = "ORDER_CREATE", resourceType = "FIRST_LEG_ORDER", description = "Create draft / 新建草稿")
@Operation(summary = "Create draft / 新建草稿", description = "Creates a first-leg draft order in the authenticated merchant tenant. / 在当前认证商户租户下创建头程订单草稿。")
@ApiResponses({
@@ -9,12 +9,14 @@ import com.baiyue.order.dto.BatchOrderReviewResponse;
import com.baiyue.order.dto.OrderDetailResponse;
import com.baiyue.order.dto.OrderRequests;
import com.baiyue.order.dto.InboundCodePolicyResponse;
import com.baiyue.order.dto.InboundCodeAssociationSummary;
import com.baiyue.order.dto.TenantOrderCountResponse;
import com.baiyue.order.dto.TenantOrderStatisticsResponse;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.entity.OrderStatus;
import com.baiyue.order.service.OrderApplicationService;
import com.baiyue.order.service.OrderScopeGuard;
import com.baiyue.order.service.InboundCodeAssociationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -47,6 +49,8 @@ import java.util.List;
public class PlatformFirstLegOrderController {
@Resource
private OrderApplicationService service;
@Resource
private InboundCodeAssociationService inboundCodeAssociations;
@GetMapping("/inbound-code-policy")
@PreAuthorize("hasAnyAuthority('platform:order:list', 'platform:order:create', 'platform:order:edit', 'platform:order:submit')")
@@ -54,6 +58,17 @@ public class PlatformFirstLegOrderController {
return ApiResponse.success(service.inboundCodePolicy());
}
@GetMapping("/inbound-code-associations/{inboundCode}")
@PreAuthorize("hasAuthority('platform:order:list')")
@Operation(summary = "按入仓码查询订单基础关联",
description = "入仓码和订单号是主要展示键;Long ID 仅供路由关联。")
public ApiResponse<InboundCodeAssociationSummary> inboundCodeAssociation(
@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String inboundCode) {
OrderScopeGuard.platform(user);
return ApiResponse.success(inboundCodeAssociations.findForPlatform(inboundCode));
}
@BusinessAudit(event = "ORDER_CREATE", resourceType = "FIRST_LEG_ORDER", description = "平台代客创建草稿")
@Operation(summary = "平台代客创建草稿", description = "在明确指定的目标租户内创建头程订单草稿,必须携带幂等键。")
@ApiResponses({
@@ -0,0 +1,19 @@
package com.baiyue.order.dto;
import io.swagger.v3.oas.annotations.media.Schema;
/** 以全局唯一入仓码定位的订单基础关联摘要;技术 ID 统一使用字符串。 */
public record InboundCodeAssociationSummary(
@Schema(description = "主要展示键:全局唯一入仓码") String inboundCode,
@Schema(description = "主要展示键:订单业务号") String orderNo,
@Schema(description = "订单交易状态") String orderStatus,
@Schema(description = "订单内部 ID,仅供路由关联") String orderId,
@Schema(description = "客户 ID,字符串避免前端精度损失") String customerId,
String customerName,
String destinationType,
String destinationCountryCode,
@Schema(description = "目的仓内部 ID,仅供路由关联") String destinationWarehouseId,
@Schema(description = "目的仓业务编码;FBA 场景为 Amazon 仓码") String destinationWarehouseCode,
String destinationWarehouseName
) {
}
@@ -121,6 +121,21 @@ public class OrderRepository {
.eq(OrderEntity::getId, orderId));
}
/** 平台按数据库全局唯一键查询,不接受客户筛选参数。 */
public OrderEntity findByInboundCode(String inboundCode) {
return orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>()
.eq(OrderEntity::getInboundCode, inboundCode)
.eq(OrderEntity::getDeleted, false));
}
/** 客户查询必须在 SQL 条件中同时绑定登录客户与入仓码。 */
public OrderEntity findOwnedByInboundCode(long tenantId, String inboundCode) {
return orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>()
.eq(OrderEntity::getTenantId, tenantId)
.eq(OrderEntity::getInboundCode, inboundCode)
.eq(OrderEntity::getDeleted, false));
}
public boolean inboundCodeExists(String inboundCode, Long excludedOrderId) {
if (!StringUtils.hasText(inboundCode)) return false;
return orderMapper.selectCount(new LambdaQueryWrapper<OrderEntity>()
@@ -0,0 +1,65 @@
package com.baiyue.order.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.dto.InboundCodeAssociationSummary;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.repository.OrderRepository;
import org.springframework.stereotype.Service;
import java.util.regex.Pattern;
/** 从 Order 权威快照按入仓码读取只读基础关联。 */
@Service
public class InboundCodeAssociationService {
private static final Pattern INBOUND_CODE = Pattern.compile("[A-Za-z0-9._:-]{1,40}");
private final OrderRepository repository;
public InboundCodeAssociationService(OrderRepository repository) {
this.repository = repository;
}
public InboundCodeAssociationSummary findForPlatform(String inboundCode) {
String normalized = normalize(inboundCode);
return summary(require(repository.findByInboundCode(normalized)), normalized);
}
public InboundCodeAssociationSummary findForCustomer(long tenantId, String inboundCode) {
if (tenantId <= 0) throw new BizException(ResultCode.FORBIDDEN, "Customer scope is required");
String normalized = normalize(inboundCode);
return summary(require(repository.findOwnedByInboundCode(tenantId, normalized)), normalized);
}
private String normalize(String value) {
String normalized = value == null ? "" : value.trim();
if (!INBOUND_CODE.matcher(normalized).matches()) {
throw new BizException(ResultCode.BAD_REQUEST, "入仓码格式无效");
}
return normalized;
}
private OrderEntity require(OrderEntity order) {
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "入仓码关联订单不存在");
return order;
}
private InboundCodeAssociationSummary summary(OrderEntity order, String inboundCode) {
return new InboundCodeAssociationSummary(
inboundCode,
order.getOrderNo(),
order.getStatus() == null ? null : order.getStatus().name(),
string(order.getId()),
string(order.getCustomerId()),
order.getCustomerName(),
order.getDestinationType(),
order.getDestinationCountryCode(),
string(order.getDestinationWarehouseId()),
order.getAmazonWarehouseCode(),
order.getDestinationWarehouseName());
}
private String string(Long value) {
return value == null ? null : value.toString();
}
}
+58 -1
View File
@@ -2,7 +2,7 @@ openapi: 3.0.3
info:
title: 柏悦头程订单 API
description: 业务成功和失败均使用 HTTP 200;仅响应体 code 为字符串 "200" 时表示成功,其他 code 表示失败。
version: 1.6.0
version: 1.7.0
security:
- bearerAuth: []
paths:
@@ -21,6 +21,21 @@ paths:
code: {type: string, example: '200'}
data: {$ref: '#/components/schemas/InboundCodePolicy'}
'403': {description: 需要平台订单列表、创建、编辑或提交权限之一}
/api/platform/v1/orders/first-leg/inbound-code-associations/{inboundCode}:
get:
summary: 平台按全局入仓码查询订单基础关联
description: >-
需要 platform:order:list。入仓码和订单业务号是主要展示键;Long ID 均以字符串返回,
只供页面路由和后续接口关联。查询直接使用 Order 全局唯一入仓码事实,不跨库读取 Firstleg。
parameters:
- $ref: '#/components/parameters/InboundCodePath'
responses:
'200':
description: 查询成功或业务失败,按响应体 code 判断
content:
application/json:
schema: {$ref: '#/components/schemas/InboundCodeAssociationEnvelope'}
'403': {description: 需要平台身份和 platform:order:list 权限}
/api/platform/v1/orders:
get:
summary: 平台统一订单列表
@@ -187,6 +202,21 @@ paths:
schema: {$ref: '#/components/schemas/FirstLegOrderDraft'}
responses:
'200': {description: 成功或业务失败,按响应体 code 判断}
/api/merchant/v1/orders/first-leg/inbound-code-associations/{inboundCode}:
get:
summary: 客户按入仓码查询本客户订单基础关联
description: >-
需要 merchant:order:list。客户范围仅取登录态,不接受客户 ID 参数;其他客户的入仓码统一按不存在处理。
入仓码和订单业务号是主要展示键,Long ID 只供路由关联。
parameters:
- $ref: '#/components/parameters/InboundCodePath'
responses:
'200':
description: 查询成功、不存在或其他业务失败,按响应体 code 判断
content:
application/json:
schema: {$ref: '#/components/schemas/InboundCodeAssociationEnvelope'}
'403': {description: 需要客户身份和 merchant:order:list 权限}
/api/merchant/v1/orders/first-leg/batch:
post:
summary: 批量创建头程订单草稿
@@ -671,6 +701,27 @@ components:
properties:
mode: {type: string, enum: [AUTO, LEGACY_MANUAL]}
manualEntryRequired: {type: boolean}
InboundCodeAssociationEnvelope:
type: object
properties:
code: {type: string, example: '200'}
message: {type: string}
data: {$ref: '#/components/schemas/InboundCodeAssociationSummary'}
InboundCodeAssociationSummary:
type: object
required: [inboundCode, orderNo, orderStatus, orderId, customerId, customerName, destinationType]
properties:
inboundCode: {type: string, maxLength: 40, pattern: '^[A-Za-z0-9._:-]+$', description: 主要展示键:全局唯一入仓码}
orderNo: {type: string, description: 主要展示键:订单业务号}
orderStatus: {type: string, description: Order 权威交易状态}
orderId: {type: string, pattern: '^[1-9][0-9]*$', description: 内部 ID,仅供路由关联}
customerId: {type: string, pattern: '^[1-9][0-9]*$', description: 客户 ID,字符串避免精度损失}
customerName: {type: string}
destinationType: {type: string}
destinationCountryCode: {type: string, nullable: true}
destinationWarehouseId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 内部 ID,仅供路由关联}
destinationWarehouseCode: {type: string, nullable: true, description: 目的仓业务编码;FBA 场景为 Amazon 仓码}
destinationWarehouseName: {type: string, nullable: true}
FirstLegBatchDraftItem:
type: object
required: [clientRowId, draft]
@@ -778,6 +829,12 @@ components:
packageCount: {type: integer}
loadingPlanId: {type: string}
parameters:
InboundCodePath:
name: inboundCode
in: path
required: true
schema: {type: string, minLength: 1, maxLength: 40, pattern: '^[A-Za-z0-9._:-]+$'}
description: 全局唯一业务入仓码
InternalToken:
name: X-Internal-Service-Token
in: header