Files
baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/controller/InternalInboundOrderController.java
T
2026-11-04 11:13:29 +08:00

235 lines
12 KiB
Java

package com.baiyue.firstleg.controller;
import jakarta.annotation.Resource;
import jakarta.annotation.PostConstruct;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
import com.baiyue.firstleg.dto.InboundOrderReference;
import com.baiyue.firstleg.service.InboundOrderCreationService;
import com.baiyue.firstleg.service.InboundCartonLabelService;
import com.baiyue.firstleg.service.UnifiedInboundQueryService;
import com.baiyue.firstleg.service.OrderDeclarationSyncService;
import com.baiyue.firstleg.service.OrderRemarksSyncService;
import com.baiyue.firstleg.dto.OrderDeclarationSync;
import com.baiyue.firstleg.dto.OrderRemarksSync;
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
import com.baiyue.firstleg.dto.UnifiedInboundRequest;
import com.baiyue.firstleg.dto.UnifiedInboundSummary;
import com.baiyue.common.core.api.PageResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import java.util.List;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/** 接收订单服务审核通过事件,创建入库单。 */
@RestController
@RequestMapping("/internal/firstleg/v1/inbounds")
public class InternalInboundOrderController {
/** 海外废弃溯源只读取已生成的有效箱唛,不触发打印或补建。 */
@org.springframework.web.bind.annotation.GetMapping("/{inboundId}/existing-carton-marks")
public ApiResponse<List<String>> existingCartonMarks(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@org.springframework.web.bind.annotation.PathVariable long inboundId,
@org.springframework.web.bind.annotation.RequestParam long customerId,
@org.springframework.web.bind.annotation.RequestParam String inboundCode) {
requireToken(token);
return ApiResponse.success(cartonLabels.existingCartonMarks(customerId, inboundId, inboundCode));
}
@Resource
private InboundOrderCreationService service;
@Resource
private InboundCartonLabelService cartonLabels;
@Resource
private UnifiedInboundQueryService unifiedQueryService;
@Resource private com.baiyue.firstleg.service.MerchantWorkbenchFilterService workbenchFilters;
@Resource
private OrderDeclarationSyncService declarationSync;
@Resource
private OrderRemarksSyncService remarksSync;
@Resource private com.baiyue.firstleg.service.OrderFulfillmentSnapshotService fulfillmentSnapshot;
@Value("${baiyue.security.internal-service-token}")
private String internalToken;
private byte[] expectedToken;
@PostConstruct
void initializeToken() {
expectedToken = internalToken.getBytes(StandardCharsets.UTF_8);
}
@PostMapping("/order-fulfillment-completed")
public ApiResponse<Integer> completeFulfillmentSnapshot(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody com.baiyue.firstleg.dto.OrderFulfillmentCompleted command) {
requireToken(token);
return ApiResponse.success(fulfillmentSnapshot.complete(command));
}
@PostMapping("/order-approved")
public ApiResponse<Long> createFromApprovedOrder(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestBody OrderApprovedInboundCommand command) {
if (token == null || !MessageDigest.isEqual(expectedToken, token.getBytes(StandardCharsets.UTF_8))) {
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
}
if (command.tenantId() <= 0 || command.orderId() <= 0 || command.warehouseId() == null
|| command.warehouseId() <= 0 || command.customerName() == null || command.customerName().isBlank()
|| command.inboundCode() == null || command.inboundCode().isBlank()
|| command.inboundCode().length() > 40
|| command.fulfillmentType() == null
|| (!"STANDARD".equals(command.fulfillmentType())
&& !"ONE_CLICK_DROPSHIP".equals(command.fulfillmentType()))
|| command.totalPackageCount() != null && command.totalPackageCount() < 1) {
throw new BizException(ResultCode.BAD_REQUEST, "订单入库快照不完整");
}
long inboundId = service.createFromApprovedOrder(command);
cartonLabels.getOrCreate(command.tenantId(), inboundId);
return ApiResponse.success(inboundId);
}
@PostMapping("/order-preparing")
public ApiResponse<Long> prepareFromDraft(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestBody OrderApprovedInboundCommand command) {
requireToken(token);
requireSnapshot(command);
return ApiResponse.success(service.prepareFromDraft(command));
}
@PostMapping("/order-preparing/batch")
public ApiResponse<List<InboundOrderReference.Item>> prepareFromDraftBatch(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestBody List<OrderApprovedInboundCommand> commands) {
requireToken(token);
if (commands == null || commands.isEmpty() || commands.size() > 200) {
throw new BizException(ResultCode.BAD_REQUEST, "预入库订单批次必须为1至200条");
}
commands.forEach(this::requireSnapshot);
long tenantId = commands.get(0).tenantId();
if (commands.stream().anyMatch(command -> command.tenantId() != tenantId)) {
throw new BizException(ResultCode.BAD_REQUEST, "预入库订单批次必须属于同一客户");
}
commands.forEach(service::prepareFromDraft);
return ApiResponse.success(service.findReferences(tenantId,
commands.stream().map(OrderApprovedInboundCommand::orderId).distinct().toList()));
}
@PostMapping("/order-references")
public ApiResponse<List<InboundOrderReference.Item>> references(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody InboundOrderReference.BatchRequest request) {
requireToken(token);
return ApiResponse.success(service.findReferences(
request.tenantId(), request.orderIds().stream().distinct().toList()));
}
@PostMapping("/order-channel-changes/validate")
public ApiResponse<OrderDeclarationSync.ChannelChangeValidationResponse> validateOrderChannelChange(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestBody OrderDeclarationSync.ChannelChangeValidationRequest request) {
requireToken(token);
return ApiResponse.success(declarationSync.validateChannelChange(request));
}
@PostMapping("/order-declaration-updated")
public ApiResponse<Void> updateOrderDeclaration(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestBody OrderDeclarationSync.UpdatedEvent event) {
requireToken(token);
declarationSync.consume(event);
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/workbench-filters")
public ApiResponse<com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Response> workbenchFilters(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Request request) {
requireToken(token);
return ApiResponse.success(workbenchFilters.read(request));
}
/** 当前页摘要继续应用原筛选,防止父子或分片入库投影错配。 */
@PostMapping("/unified-orders/status-summaries")
public ApiResponse<List<UnifiedInboundSummary>> unifiedStatusSummaries(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody UnifiedInboundQuery request) {
requireToken(token);
return ApiResponse.success(unifiedQueryService.statusSummaries(request));
}
/** 服务令牌保护的轻量状态投影,供 Order 统计和分页校正。 */
@PostMapping("/unified-orders/status-rows")
public ApiResponse<List<UnifiedInboundQueryService.StatusRow>> unifiedStatusRows(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody UnifiedInboundQuery request) {
requireToken(token);
return ApiResponse.success(unifiedQueryService.statusRows(request));
}
@PostMapping("/unified-orders/query")
public ApiResponse<PageResult<UnifiedInboundSummary>> unifiedPage(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody UnifiedInboundQuery request) {
requireToken(token);
return ApiResponse.success(unifiedQueryService.page(request));
}
@PostMapping("/unified-orders/summaries")
public ApiResponse<List<UnifiedInboundSummary>> unifiedSummaries(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody UnifiedInboundRequest.BatchRequest request) {
requireToken(token);
return ApiResponse.success(unifiedQueryService.summaries(
request.tenantId(), request.orderIds().stream().distinct().toList()));
}
@PostMapping("/unified-orders/detail")
public ApiResponse<UnifiedInboundDetail> unifiedDetail(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody UnifiedInboundRequest.DetailRequest request) {
requireToken(token);
return ApiResponse.success(unifiedQueryService.detail(request.tenantId(), request.orderId()));
}
private void requireToken(String token) {
if (token == null || !MessageDigest.isEqual(expectedToken, token.getBytes(StandardCharsets.UTF_8))) {
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
}
}
private void requireSnapshot(OrderApprovedInboundCommand command) {
if (command == null || command.tenantId() <= 0 || command.orderId() <= 0
|| command.orderNo() == null || command.orderNo().isBlank()
|| command.inboundCode() == null || command.inboundCode().isBlank()
|| command.inboundCode().length() > 40
|| command.customerName() == null || command.customerName().isBlank()
|| command.warehouseId() == null || command.warehouseId() <= 0
|| command.warehouseName() == null || command.warehouseName().isBlank()
|| command.businessType() == null || command.businessType().isBlank()
|| command.fulfillmentType() == null
|| (!"STANDARD".equals(command.fulfillmentType())
&& !"ONE_CLICK_DROPSHIP".equals(command.fulfillmentType()))) {
throw new BizException(ResultCode.BAD_REQUEST, "草稿预入库快照不完整");
}
}
}