This commit is contained in:
2026-08-05 09:11:26 +08:00
parent d544bbd0c5
commit 3791fee285
45 changed files with 1187 additions and 126 deletions
@@ -2,6 +2,8 @@ package com.baiyue.firstleg.controller;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.FbaPricingDtos.CarrierRateVersionRequest;
import com.baiyue.firstleg.dto.FbaPricingDtos.CustomerRuleVersionRequest;
@@ -36,10 +38,12 @@ public class PlatformFbaMasterDataController {
@GetMapping("/customer-rules")
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rule:view')")
@Operation(summary = "查询客户计费规则版本", description = "默认查询全部企业;tenantId 可用于筛选目标企业")
public ApiResponse<List<CustomerChargeRuleEntity>> rules(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId,
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) Long tenantId,
@RequestParam(required = false) String ruleCode) {
return ApiResponse.success(service.listRules(FirstlegScopeGuard.platform(user, tenantId), ruleCode));
return ApiResponse.success(service.listRules(optionalPlatformTenant(user, tenantId), ruleCode));
}
@GetMapping("/customer-rules/{id}")
@@ -105,4 +109,11 @@ public class PlatformFbaMasterDataController {
service.retireRate(FirstlegScopeGuard.platform(user, tenantId), id, user.userId());
return ApiResponse.success(null);
}
private static Long optionalPlatformTenant(AuthenticatedUser user, Long tenantId) {
if (user == null || !"PLATFORM".equals(user.appCode())) {
throw new BizException(ResultCode.FORBIDDEN, "仅允许平台端访问");
}
return tenantId == null ? null : FirstlegScopeGuard.platform(user, tenantId);
}
}
@@ -10,10 +10,13 @@ import com.baiyue.firstleg.dto.InboundOrderQuery;
import com.baiyue.firstleg.dto.InboundOrderSummary;
import com.baiyue.firstleg.service.InboundOrderQueryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -22,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
/** 平台端入库单列表和详情只读接口。 */
@Tag(name = "平台入库单", description = "二期入库单列表、页签、筛选和只读聚合详情")
@Validated
@RestController
@RequestMapping("/api/platform/inbounds")
public class PlatformInboundOrderController {
@@ -31,14 +35,16 @@ public class PlatformInboundOrderController {
this.service = service;
}
@Operation(summary = "分页查询入库单", description = "按目标租户和二期状态页签分页查询入库单快照。")
@Operation(summary = "分页查询入库单",
description = "默认分页查询全部企业入库单;可按目标租户和二期状态页签筛选入库单快照。")
@GetMapping
@PreAuthorize("hasAuthority('platform:firstleg:inbound:list')")
public ApiResponse<PageResult<InboundOrderSummary>> page(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@Parameter(description = "可选目标企业租户ID;不传时查询全部企业")
@RequestParam(required = false) @Min(1) Long tenantId,
@Valid InboundOrderQuery query) {
requirePlatformTenant(user, tenantId);
requirePlatform(user);
return ApiResponse.success(service.page(tenantId, query));
}
@@ -57,11 +63,15 @@ public class PlatformInboundOrderController {
}
private static void requirePlatformTenant(AuthenticatedUser user, long tenantId) {
if (user == null || !"PLATFORM".equals(user.appCode())) {
throw new BizException(ResultCode.FORBIDDEN, "仅允许平台端访问");
}
requirePlatform(user);
if (tenantId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "必须指定有效目标租户");
}
}
private static void requirePlatform(AuthenticatedUser user) {
if (user == null || !"PLATFORM".equals(user.appCode())) {
throw new BizException(ResultCode.FORBIDDEN, "仅允许平台端访问");
}
}
}
@@ -8,7 +8,7 @@ import java.time.LocalDateTime;
/** 入库列表行,所有名称和运输信息均为业务快照。 */
public record InboundOrderSummary(
Long id, Long orderId, String orderNo, String inboundCode,
Long id, Long tenantId, Long orderId, String orderNo, String inboundCode,
String processStatus, String orderStatusSnapshot, String measureReviewStatus,
String feeStatus, String feeReviewStatus, String notificationStatus,
Integer version,
@@ -33,7 +33,7 @@ public record InboundOrderSummary(
public static InboundOrderSummary from(
InboundOrderEntity order, InboundTransportSnapshotEntity transport, String locationCodes) {
return new InboundOrderSummary(
order.getId(), order.getOrderId(), order.getOrderNo(), order.getInboundCode(),
order.getId(), order.getTenantId(), order.getOrderId(), order.getOrderNo(), order.getInboundCode(),
order.getProcessStatus(), order.getOrderStatusSnapshot(), order.getMeasureReviewStatus(),
order.getFeeStatus(), order.getFeeReviewStatus(), order.getNotificationStatus(),
order.getVersion(),
@@ -21,7 +21,7 @@ import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
/** FBA 计费仓储,所有业务查询都强制带租户条件。 */
/** FBA 计费仓储;平台客户规则列表可跨企业读取,其他业务操作强制带租户条件。 */
@Repository
public class FbaPricingRepository {
private final CustomerChargeRuleMapper rules;
@@ -61,9 +61,9 @@ public class FbaPricingRepository {
.eq(CarrierFbaRateEntity::getId, id));
}
public List<CustomerChargeRuleEntity> listRules(long tenantId, String ruleCode) {
public List<CustomerChargeRuleEntity> listRules(Long tenantId, String ruleCode) {
return rules.selectList(new LambdaQueryWrapper<CustomerChargeRuleEntity>()
.eq(CustomerChargeRuleEntity::getTenantId, tenantId)
.eq(tenantId != null, CustomerChargeRuleEntity::getTenantId, tenantId)
.eq(ruleCode != null && !ruleCode.isBlank(), CustomerChargeRuleEntity::getRuleCode, ruleCode)
.orderByAsc(CustomerChargeRuleEntity::getRuleCode)
.orderByDesc(CustomerChargeRuleEntity::getRuleVersion));
@@ -26,7 +26,7 @@ import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/** 入库单只读仓储,所有查询均以租户作为首要隔离条件。 */
/** 入库单只读仓储;平台分页可跨企业查询,详情继续强制租户隔离。 */
@Repository
public class InboundOrderRepository {
private final InboundOrderMapper orders;
@@ -51,9 +51,9 @@ public class InboundOrderRepository {
this.histories = histories;
}
public PageResult<InboundOrderSummary> page(long tenantId, InboundOrderQuery query) {
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
LambdaQueryWrapper<InboundOrderEntity> wrapper = new LambdaQueryWrapper<InboundOrderEntity>()
.eq(InboundOrderEntity::getTenantId, tenantId)
.eq(tenantId != null, InboundOrderEntity::getTenantId, tenantId)
.likeRight(StringUtils.hasText(query.inboundCode()), InboundOrderEntity::getInboundCode,
query.inboundCode())
.likeRight(StringUtils.hasText(query.orderNo()), InboundOrderEntity::getOrderNo, query.orderNo())
@@ -85,9 +85,10 @@ public class InboundOrderRepository {
.orderByDesc(InboundOrderEntity::getCreatedAt)
.orderByDesc(InboundOrderEntity::getId);
Page<InboundOrderEntity> result = orders.selectPage(new Page<>(query.page(), query.size()), wrapper);
List<Long> ids = result.getRecords().stream().map(InboundOrderEntity::getId).toList();
Map<Long, InboundTransportSnapshotEntity> transportByInbound = transportMap(tenantId, ids);
Map<Long, String> locationCodesByInbound = locationCodesMap(tenantId, ids);
Map<Long, Long> tenantByInbound = result.getRecords().stream()
.collect(Collectors.toMap(InboundOrderEntity::getId, InboundOrderEntity::getTenantId));
Map<Long, InboundTransportSnapshotEntity> transportByInbound = transportMap(tenantId, tenantByInbound);
Map<Long, String> locationCodesByInbound = locationCodesMap(tenantId, tenantByInbound);
List<InboundOrderSummary> records = result.getRecords().stream()
.map(order -> InboundOrderSummary.from(order, transportByInbound.get(order.getId()),
locationCodesByInbound.get(order.getId())))
@@ -156,27 +157,31 @@ public class InboundOrderRepository {
palletSummaries, fileSummaries, noteSummaries, historySummaries);
}
private Map<Long, InboundTransportSnapshotEntity> transportMap(long tenantId, List<Long> inboundIds) {
if (inboundIds.isEmpty()) {
private Map<Long, InboundTransportSnapshotEntity> transportMap(
Long tenantId, Map<Long, Long> tenantByInbound) {
if (tenantByInbound.isEmpty()) {
return Map.of();
}
return transports.selectList(new LambdaQueryWrapper<InboundTransportSnapshotEntity>()
.eq(InboundTransportSnapshotEntity::getTenantId, tenantId)
.in(InboundTransportSnapshotEntity::getInboundId, inboundIds))
.stream().collect(Collectors.toMap(InboundTransportSnapshotEntity::getInboundId,
.eq(tenantId != null, InboundTransportSnapshotEntity::getTenantId, tenantId)
.in(InboundTransportSnapshotEntity::getInboundId, tenantByInbound.keySet()))
.stream()
.filter(item -> owns(tenantByInbound, item.getInboundId(), item.getTenantId()))
.collect(Collectors.toMap(InboundTransportSnapshotEntity::getInboundId,
Function.identity(), (left, right) -> left));
}
private Map<Long, String> locationCodesMap(long tenantId, List<Long> inboundIds) {
if (inboundIds.isEmpty()) {
private Map<Long, String> locationCodesMap(Long tenantId, Map<Long, Long> tenantByInbound) {
if (tenantByInbound.isEmpty()) {
return Map.of();
}
return pallets.selectList(new LambdaQueryWrapper<InboundPalletAllocationEntity>()
.eq(InboundPalletAllocationEntity::getTenantId, tenantId)
.in(InboundPalletAllocationEntity::getInboundId, inboundIds)
.eq(tenantId != null, InboundPalletAllocationEntity::getTenantId, tenantId)
.in(InboundPalletAllocationEntity::getInboundId, tenantByInbound.keySet())
.orderByAsc(InboundPalletAllocationEntity::getInboundId)
.orderByAsc(InboundPalletAllocationEntity::getAllocationNo))
.stream()
.filter(item -> owns(tenantByInbound, item.getInboundId(), item.getTenantId()))
.filter(item -> StringUtils.hasText(item.getLocationCode()))
.collect(Collectors.groupingBy(InboundPalletAllocationEntity::getInboundId,
java.util.LinkedHashMap::new,
@@ -184,6 +189,10 @@ public class InboundOrderRepository {
Collectors.joining("、"))));
}
private static boolean owns(Map<Long, Long> tenantByInbound, Long inboundId, Long tenantId) {
return inboundId != null && tenantId != null && tenantId.equals(tenantByInbound.get(inboundId));
}
private static <T> LambdaQueryWrapper<T> owned(
com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, ?> tenantColumn,
com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, ?> inboundColumn,
@@ -98,7 +98,7 @@ public class FbaMasterDataService {
return value;
}
public List<CustomerChargeRuleEntity> listRules(long tenantId, String code) {
public List<CustomerChargeRuleEntity> listRules(Long tenantId, String code) {
return repository.listRules(tenantId, code);
}
@@ -21,7 +21,7 @@ public class InboundOrderQueryService {
this.repository = repository;
}
public PageResult<InboundOrderSummary> page(long tenantId, InboundOrderQuery query) {
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
validateQuery(query);
return repository.page(tenantId, query);
}