货柜pda 添加操作人

This commit is contained in:
2026-09-22 17:27:32 +08:00
parent abca6882c3
commit 2555eda797
31 changed files with 384 additions and 59 deletions
@@ -18,6 +18,8 @@ import com.baiyue.iam.service.TenantService;
import com.baiyue.iam.dto.CustomerExportDtos.CustomerExportBatch;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.iam.dto.TenantDtos.FinanceCustomerSummary;
import com.baiyue.iam.dto.DomesticPdaOperatorDtos.OperatorOption;
import com.baiyue.iam.service.DomesticPdaOperatorService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
@@ -38,6 +40,7 @@ public class InternalTenantController {
@Resource private MerchantOnboardingService onboardingService;
@Resource private ProfileResourceService profileResourceService;
@Resource private TenantService tenantService;
@Resource private DomesticPdaOperatorService domesticPdaOperatorService;
@Value("${baiyue.security.internal-service-token}") private String internalTokenValue;
private byte[] internalToken;
@@ -128,6 +131,15 @@ public class InternalTenantController {
return ApiResponse.success(tenantService.pageFinanceCustomers(page, size, keyword, status, customerId));
}
@GetMapping("/domestic-pda-operators/{authUserId}")
public ApiResponse<OperatorOption> domesticPdaOperator(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@PathVariable long authUserId,
@RequestParam long warehouseId) {
verify(token);
return ApiResponse.success(domesticPdaOperatorService.required(authUserId, warehouseId));
}
@GetMapping("/exports/customers")
public ApiResponse<CustomerExportBatch> exportCustomers(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@@ -6,6 +6,9 @@ import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.iam.dto.PlatformUserManagementDtos.*;
import com.baiyue.iam.service.PlatformUserManagementService;
import com.baiyue.iam.service.TenantGuard;
import com.baiyue.iam.service.DomesticPdaOperatorService;
import com.baiyue.iam.dto.DomesticPdaOperatorDtos.OperatorOption;
import java.util.List;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
@@ -26,6 +29,19 @@ import org.springframework.web.bind.annotation.*;
public class PlatformUserManagementController {
@Resource
private PlatformUserManagementService service;
@Resource
private DomesticPdaOperatorService domesticPdaOperators;
@GetMapping("/domestic-pda-operators")
@PreAuthorize("hasAuthority('platform:firstleg:pda:assign')")
@Operation(summary = "查询启用的国内头程 PDA 作业员候选")
public ApiResponse<List<OperatorOption>> domesticPdaOperators(
@RequestParam long warehouseId,
@RequestParam(required = false) String keyword,
@AuthenticationPrincipal AuthenticatedUser user) {
TenantGuard.tenant(user, "PLATFORM");
return ApiResponse.success(domesticPdaOperators.options(warehouseId, keyword));
}
@GetMapping
@PreAuthorize("hasAuthority('platform:user:list')")
@@ -41,12 +41,13 @@ public final class AuthorizationDtos {
private Set<String> permissions;
private Long permissionVersion;
private Long overseasWarehouseId;
private Long domesticWarehouseId;
public SnapshotResponse(Long userId, Long tenantId, Long membershipId, String appCode,
String clientType, Set<String> roles, Set<String> permissions,
Long permissionVersion) {
this(userId, tenantId, membershipId, appCode, clientType, roles, permissions,
permissionVersion, null);
permissionVersion, null, null);
}
}
}
@@ -0,0 +1,8 @@
package com.baiyue.iam.dto;
/** 国内头程 PDA 作业员候选契约。 */
public final class DomesticPdaOperatorDtos {
private DomesticPdaOperatorDtos() { }
public record OperatorOption(Long userId, String displayName) { }
}
@@ -23,12 +23,13 @@ public final class PlatformUserManagementDtos {
@NotNull Long tenantId,
@NotNull List<Long> roleIds,
Long overseasWarehouseId,
Long domesticWarehouseId,
@NotBlank @Size(min = 8, max = 64)
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String password,
@NotBlank @Pattern(regexp = "ACTIVE|DISABLED") String status) {
public CreateUserRequest(String username, String displayName, String phone, String email,
Long tenantId, List<Long> roleIds, String password, String status) {
this(username, displayName, phone, email, tenantId, roleIds, null, password, status);
this(username, displayName, phone, email, tenantId, roleIds, null, null, password, status);
}
}
@@ -39,10 +40,15 @@ public final class PlatformUserManagementDtos {
@Email @Size(max = 128) String email,
@NotNull Long tenantId,
@NotNull List<Long> roleIds,
Long overseasWarehouseId) {
Long overseasWarehouseId,
Long domesticWarehouseId) {
public UpdateUserRequest(String displayName, String phone, String email,
Long tenantId, List<Long> roleIds, Long overseasWarehouseId) {
this(displayName, phone, email, tenantId, roleIds, overseasWarehouseId, null);
}
public UpdateUserRequest(String displayName, String phone, String email,
Long tenantId, List<Long> roleIds) {
this(displayName, phone, email, tenantId, roleIds, null);
this(displayName, phone, email, tenantId, roleIds, null, null);
}
}
@@ -55,7 +61,7 @@ public final class PlatformUserManagementDtos {
String displayName, String phone, String email, Long tenantId,
String tenantName, String memberType, String memberNo, String status,
List<Long> roleIds, List<String> roleNames, LocalDateTime lastLoginAt,
boolean protectedAccount, Long overseasWarehouseId) { }
boolean protectedAccount, Long overseasWarehouseId, Long domesticWarehouseId) { }
public record TenantOption(Long id, String name, String status) { }
public record RoleOption(Long id, Long tenantId, String name, String status) { }
@@ -9,6 +9,6 @@ import com.baiyue.common.core.model.BaseEntity; import com.baomidou.mybatisplus.
@Getter
@Setter
@TableName("iam_membership") public class IamMembership extends BaseEntity {
@TableId private Long id; private Long tenantId; private Long userId; private String memberType; private String memberNo; private Long overseasWarehouseId; private String status; private LocalDateTime joinedAt; private LocalDateTime leftAt;
@TableId private Long id; private Long tenantId; private Long userId; private String memberType; private String memberNo; private Long overseasWarehouseId; private Long domesticWarehouseId; private String status; private LocalDateTime joinedAt; private LocalDateTime leftAt;
}
@@ -105,4 +105,8 @@ public interface AuthorizationQueryMapper {
@Select("SELECT overseas_warehouse_id FROM iam_membership WHERE id=#{membershipId} AND tenant_id=#{tenantId} AND status='ACTIVE' AND deleted=0")
Long findOverseasWarehouseId(@Param("tenantId") long tenantId,
@Param("membershipId") long membershipId);
@Select("SELECT domestic_warehouse_id FROM iam_membership WHERE id=#{membershipId} AND tenant_id=#{tenantId} AND status='ACTIVE' AND deleted=0")
Long findDomesticWarehouseId(@Param("tenantId") long tenantId,
@Param("membershipId") long membershipId);
}
@@ -10,6 +10,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import com.baiyue.iam.dto.DomesticPdaOperatorDtos.OperatorOption;
@Mapper
public interface IamMembershipRoleMapper extends BaseMapper<IamMembershipRole> {
@@ -26,4 +27,31 @@ public interface IamMembershipRoleMapper extends BaseMapper<IamMembershipRole> {
""")
List<Long> findAssignedAuthUserIds(@Param("tenantId") long tenantId,
@Param("roleId") Long roleId);
@Select("""
<script>
SELECT DISTINCT u.auth_user_id AS userId, u.display_name AS displayName
FROM iam_membership_role mr
JOIN iam_role role ON role.id = mr.role_id AND role.tenant_id = mr.tenant_id
JOIN iam_membership m ON m.id = mr.membership_id AND m.tenant_id = mr.tenant_id
JOIN iam_user u ON u.id = m.user_id
WHERE mr.tenant_id = #{tenantId}
AND role.role_code = #{roleCode} AND role.app_code = 'PLATFORM'
AND role.status = 'ACTIVE' AND role.deleted = 0
AND m.status = 'ACTIVE' AND m.deleted = 0
AND u.status = 'ACTIVE' AND u.deleted = 0
<if test="warehouseId != null">AND m.domestic_warehouse_id = #{warehouseId}</if>
<if test="authUserId != null">AND u.auth_user_id = #{authUserId}</if>
<if test="keyword != null and keyword != ''">
AND u.display_name LIKE CONCAT('%', #{keyword}, '%')
</if>
ORDER BY u.display_name, u.auth_user_id
LIMIT 200
</script>
""")
List<OperatorOption> findActiveUsersByRoleCode(@Param("tenantId") long tenantId,
@Param("roleCode") String roleCode,
@Param("keyword") String keyword,
@Param("authUserId") Long authUserId,
@Param("warehouseId") Long warehouseId);
}
@@ -48,7 +48,8 @@ public class AuthorizationSnapshotService {
return new SnapshotResponse(request.getUserId(), request.getTenantId(), request.getMembershipId(),
appCode, appCode, Set.copyOf(roles), Set.copyOf(permissions),
mapper.findPermissionVersion(request.getUserId()),
mapper.findOverseasWarehouseId(request.getTenantId(), request.getMembershipId()));
mapper.findOverseasWarehouseId(request.getTenantId(), request.getMembershipId()),
mapper.findDomesticWarehouseId(request.getTenantId(), request.getMembershipId()));
}
/**
@@ -0,0 +1,32 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.dto.DomesticPdaOperatorDtos.OperatorOption;
import com.baiyue.iam.mapper.IamMembershipRoleMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/** 提供启用的国内头程 PDA 作业员,并作为派单时的权威角色校验。 */
@Service
public class DomesticPdaOperatorService {
@Resource
private IamMembershipRoleMapper membershipRoles;
@Transactional(readOnly = true)
public List<OperatorOption> options(long warehouseId, String keyword) {
String normalized = keyword == null || keyword.isBlank() ? null : keyword.trim();
return membershipRoles.findActiveUsersByRoleCode(0L, "CN_FIRSTLEG_PDA_OPERATOR", normalized, null, warehouseId);
}
@Transactional(readOnly = true)
public OperatorOption required(long authUserId, long warehouseId) {
return membershipRoles.findActiveUsersByRoleCode(
0L, "CN_FIRSTLEG_PDA_OPERATOR", null, authUserId, warehouseId).stream().findFirst()
.orElseThrow(() -> new BizException(ResultCode.BAD_REQUEST,
"所选人员不是该仓库启用的国内头程 PDA 作业员"));
}
}
@@ -75,7 +75,7 @@ public class PlatformUserManagementService {
long tenantId = request.tenantId();
requirePlatformTenant(tenantId);
List<Long> roleIds = validateRoles(tenantId, request.roleIds());
Long overseasWarehouseId = validateWarehouseBinding(roleIds, request.overseasWarehouseId());
WarehouseBindings warehouses = validateWarehouseBinding(roleIds, request.overseasWarehouseId(), request.domesticWarehouseId());
long authUserId = IdWorker.getId();
long userId = IdWorker.getId();
long membershipId = IdWorker.getId();
@@ -100,7 +100,8 @@ public class PlatformUserManagementService {
membership.setUserId(userId);
membership.setMemberType("EMPLOYEE");
membership.setMemberNo(memberNo(tenantId, membershipId));
membership.setOverseasWarehouseId(overseasWarehouseId);
membership.setOverseasWarehouseId(warehouses.overseasWarehouseId());
membership.setDomesticWarehouseId(warehouses.domesticWarehouseId());
membership.setStatus(request.status());
membership.setJoinedAt(LocalDateTime.now());
membership.setLeftAt("DISABLED".equals(request.status()) ? LocalDateTime.now() : null);
@@ -125,7 +126,7 @@ public class PlatformUserManagementService {
long newTenantId = request.tenantId();
requirePlatformTenant(newTenantId);
List<Long> roleIds = validateRoles(newTenantId, request.roleIds());
Long overseasWarehouseId = validateWarehouseBinding(roleIds, request.overseasWarehouseId());
WarehouseBindings warehouses = validateWarehouseBinding(roleIds, request.overseasWarehouseId(), request.domesticWarehouseId());
List<Long> oldRoleIds = members.findRoleIds(oldTenantId, membershipId);
if (isProtectedPlatformAdmin(membership, oldRoleIds)
&& (newTenantId != PLATFORM_TENANT_ID || !containsPlatformAdminRole(roleIds))) {
@@ -140,7 +141,8 @@ public class PlatformUserManagementService {
boolean phoneChanged = !Objects.equals(user.getPhone(), request.phone().trim());
boolean scopeChanged = oldTenantId != newTenantId;
boolean rolesChanged = !new java.util.HashSet<>(oldRoleIds).equals(new java.util.HashSet<>(roleIds));
boolean warehouseChanged = !Objects.equals(membership.getOverseasWarehouseId(), overseasWarehouseId);
boolean warehouseChanged = !Objects.equals(membership.getOverseasWarehouseId(), warehouses.overseasWarehouseId())
|| !Objects.equals(membership.getDomesticWarehouseId(), warehouses.domesticWarehouseId());
if (profileChanged) call(() -> authClient.profile(internalToken, user.getAuthUserId(),
new AuthAccountClient.ProfileRequest(request.displayName().trim())));
@@ -154,7 +156,8 @@ public class PlatformUserManagementService {
user.setEmail(trimToNull(request.email()));
user.setUserType(userType(newTenantId));
membership.setTenantId(newTenantId);
membership.setOverseasWarehouseId(overseasWarehouseId);
membership.setOverseasWarehouseId(warehouses.overseasWarehouseId());
membership.setDomesticWarehouseId(warehouses.domesticWarehouseId());
if (scopeChanged && members.existsMemberNo(newTenantId, membership.getMemberNo(), membershipId)) {
membership.setMemberNo(memberNo(newTenantId, membershipId));
}
@@ -239,7 +242,7 @@ public class PlatformUserManagementService {
user.getDisplayName(), user.getPhone(), user.getEmail(), membership.getTenantId(), tenantName,
membership.getMemberType(), membership.getMemberNo(), membership.getStatus(), roleIds,
roleNames, account.lastLoginAt(), isProtectedPlatformAdmin(membership, roleIds),
membership.getOverseasWarehouseId());
membership.getOverseasWarehouseId(), membership.getDomesticWarehouseId());
}
private List<IamRole> platformRoles() {
@@ -262,7 +265,8 @@ public class PlatformUserManagementService {
return roleIds;
}
private Long validateWarehouseBinding(List<Long> roleIds, Long overseasWarehouseId) {
private WarehouseBindings validateWarehouseBinding(List<Long> roleIds, Long overseasWarehouseId,
Long domesticWarehouseId) {
Set<String> roleCodes = roleIds.stream().map(roleMapper::selectById).filter(Objects::nonNull)
.map(IamRole::getRoleCode).collect(Collectors.toSet());
boolean overseasPda = roleCodes.contains("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
@@ -276,9 +280,17 @@ public class PlatformUserManagementService {
if (!overseasPda && overseasWarehouseId != null) {
throw new BizException(ResultCode.BAD_REQUEST, "仅海外仓 PDA 作业员可绑定海外仓");
}
return overseasWarehouseId;
if (domesticPda && (domesticWarehouseId == null || domesticWarehouseId <= 0)) {
throw new BizException(ResultCode.BAD_REQUEST, "国内头程 PDA 作业员必须绑定一个有效国内仓");
}
if (!domesticPda && domesticWarehouseId != null) {
throw new BizException(ResultCode.BAD_REQUEST, "仅国内头程 PDA 作业员可绑定国内仓");
}
return new WarehouseBindings(overseasWarehouseId, domesticWarehouseId);
}
private record WarehouseBindings(Long overseasWarehouseId, Long domesticWarehouseId) { }
private boolean isProtectedPlatformAdmin(IamMembership membership, List<Long> roleIds) {
return membership.getTenantId() == PLATFORM_TENANT_ID && containsPlatformAdminRole(roleIds);
}
+1
View File
@@ -149,6 +149,7 @@ CREATE TABLE IF NOT EXISTS iam_membership (
member_type VARCHAR(20) NOT NULL DEFAULT 'EMPLOYEE' COMMENT '成员类型:EMPLOYEE企业员工等',
member_no VARCHAR(64) NULL COMMENT '客户企业内部成员编号',
overseas_warehouse_id BIGINT NULL COMMENT '海外仓PDA操作员绑定的唯一实际海外仓ID',
domestic_warehouse_id BIGINT NULL COMMENT '国内头程PDA操作员绑定的唯一国内作业仓ID',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '成员状态:ACTIVE在职、DISABLED停用、LEFT已离开',
joined_at DATETIME(3) NULL COMMENT '加入客户企业时间',
left_at DATETIME(3) NULL COMMENT '离开客户企业时间',
@@ -3,6 +3,26 @@
USE baiyue_iam;
SET NAMES utf8mb4;
DROP PROCEDURE IF EXISTS ensure_domestic_pda_operator_warehouse_column;
DELIMITER $$
CREATE PROCEDURE ensure_domestic_pda_operator_warehouse_column()
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'iam_membership'
AND column_name = 'domestic_warehouse_id'
) THEN
ALTER TABLE iam_membership
ADD COLUMN domestic_warehouse_id BIGINT NULL
COMMENT '国内头程PDA操作员绑定的唯一国内作业仓ID'
AFTER overseas_warehouse_id,
ADD KEY idx_membership_domestic_warehouse (tenant_id, domestic_warehouse_id, status);
END IF;
END$$
DELIMITER ;
CALL ensure_domestic_pda_operator_warehouse_column();
DROP PROCEDURE ensure_domestic_pda_operator_warehouse_column;
UPDATE iam_role
SET role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR',
role_name = '海外仓 PDA 作业员',
@@ -72,13 +92,16 @@ CREATE TEMPORARY TABLE tmp_phase3_warehouse_role_permission (
PRIMARY KEY (role_code, permission_code)
) ENGINE=MEMORY;
-- 操作员:查看入仓业务并维护后台装柜证据,不授予完成装柜、任务分派或出仓动作。
-- 仓管:可查看并确认预装、指定国内 PDA 作业员,不授予 PDA 执行或出仓动作。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
('WAREHOUSE_OPERATOR', 'platform:order'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:list'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:view'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:view'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:edit');
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:edit'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:preload:list'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:preload:edit'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:pda:assign');
-- 国内头程 PDA 作业员:只允许国内任务查询、作业和现场照片,不授予后台、海外、分派或复核权限。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
@@ -112,7 +135,7 @@ BEGIN
COALESCE(SUM(role_code = 'WAREHOUSE_TALLY'), 0)
INTO operator_permission_count, domestic_pda_permission_count, tally_permission_count
FROM tmp_phase3_warehouse_role_permission;
IF operator_permission_count <> 5
IF operator_permission_count <> 8
OR domestic_pda_permission_count <> 2
OR tally_permission_count <> 12 THEN
SIGNAL SQLSTATE '45000'
@@ -208,7 +231,7 @@ BEGIN
JOIN iam_role_permission grant_row
ON grant_row.role_id = role.id
AND grant_row.permission_id = permission.id
AND grant_row.tenant_id = 0) <> 19 THEN
AND grant_row.tenant_id = 0) <> 22 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:最小权限授权未完整写入';
END IF;
@@ -435,6 +435,18 @@ paths:
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
/api/platform/v1/iam/users/domestic-pda-operators:
get:
tags: [IAM Resources / IAM 资源]
summary: 查询启用的国内头程 PDA 作业员候选
description: 仅返回具有 CN_FIRSTLEG_PDA_OPERATOR 角色且账号、成员、角色均启用的平台人员。
x-permission: platform:firstleg:pda:assign
parameters:
- {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1, description: 仅返回绑定到该国内仓的国内头程 PDA 作业员}}
- {name: keyword, in: query, required: false, schema: {type: string}}
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
/api/platform/v1/iam/users/{membershipId}:
parameters:
- {$ref: '#/components/parameters/MembershipId'}
@@ -993,6 +1005,19 @@ paths:
total: {type: integer, format: int64}
pages: {type: integer}
'403': {$ref: '#/components/responses/Forbidden'}
/internal/iam/domestic-pda-operators/{authUserId}:
get:
tags: [IAM Resources / IAM 资源]
summary: 校验并返回启用的国内头程 PDA 作业员
security: []
parameters:
- {$ref: '#/components/parameters/InternalServiceToken'}
- {name: authUserId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
- {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/internal/exports/v1/CUSTOMER_LIST/batches:
post:
tags: [Internal Export Provider]
@@ -1550,6 +1575,7 @@ components:
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0}
roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}}
overseasWarehouseId: {type: integer, format: int64, nullable: true, description: 选择 OVERSEAS_WAREHOUSE_PDA_OPERATOR 时必填且只能绑定一个实际海外仓;CN_FIRSTLEG_PDA_OPERATOR 与其他角色必须为空;两个 PDA 角色不能同时分配}
domesticWarehouseId: {type: integer, format: int64, nullable: true, description: 选择 CN_FIRSTLEG_PDA_OPERATOR 时必填且只能绑定一个有效国内作业仓;非国内头程 PDA 角色时必须为空;两个 PDA 角色不能同时分配}
password: {type: string, format: password, minLength: 8, maxLength: 64, writeOnly: true}
status: {type: string, enum: [ACTIVE, DISABLED]}
UpdatePlatformManagedUserRequest:
@@ -1562,6 +1588,7 @@ components:
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0}
roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}}
overseasWarehouseId: {type: integer, format: int64, nullable: true, description: 选择 OVERSEAS_WAREHOUSE_PDA_OPERATOR 时必填且只能绑定一个实际海外仓;CN_FIRSTLEG_PDA_OPERATOR 与其他角色必须为空;两个 PDA 角色不能同时分配}
domesticWarehouseId: {type: integer, format: int64, nullable: true, description: 选择 CN_FIRSTLEG_PDA_OPERATOR 时必填且只能绑定一个有效国内作业仓;非国内头程 PDA 角色时必须为空;两个 PDA 角色不能同时分配}
CreateInternalUserRequest:
type: object
required: [displayName]
@@ -1635,6 +1662,7 @@ components:
lastLoginAt: {type: string, format: date-time, nullable: true}
protectedAccount: {type: boolean}
overseasWarehouseId: {type: string, pattern: '^\d+$', nullable: true, description: 海外仓PDA操作员绑定的唯一实际海外仓ID}
domesticWarehouseId: {type: string, pattern: '^\d+$', nullable: true, description: 国内头程PDA操作员绑定的唯一国内作业仓ID}
CreateMemberRequest:
type: object
required: [username, password, displayName, memberType]
@@ -0,0 +1,20 @@
package com.baiyue.iam.repository;
import com.baiyue.iam.mapper.IamMembershipRoleMapper;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class DomesticPdaOperatorSqlContractTest {
@Test
void candidatesUseActivePlatformRoleAndAuthAccountIdentity() throws Exception {
String sql = IamMembershipRoleMapper.class.getDeclaredMethod("findActiveUsersByRoleCode",
long.class, String.class, String.class, Long.class).getAnnotation(Select.class).value()[0];
assertThat(sql).contains("u.auth_user_id AS userId", "role.role_code = #{roleCode}",
"role.app_code = 'PLATFORM'", "role.status = 'ACTIVE'", "m.status = 'ACTIVE'",
"u.status = 'ACTIVE'", "u.auth_user_id = #{authUserId}")
.doesNotContain("baiyue_auth");
}
}
@@ -46,7 +46,7 @@ class WarehouseRoleSqlContractTest {
.contains("'WAREHOUSE_TALLY', 'platform:firstleg:pda:review'")
.contains("'WAREHOUSE_TALLY', 'platform:firstleg:inbound:loading:edit'")
.contains("'WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:edit'")
.contains("operator_permission_count <> 5")
.contains("operator_permission_count <> 8")
.contains("domestic_pda_permission_count <> 2")
.contains("tally_permission_count <> 12")
.contains("依赖的三期平台权限尚未完整部署")
@@ -54,7 +54,7 @@ class WarehouseRoleSqlContractTest {
.contains("expected.permission_code IS NULL")
.contains("role.role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND permission.permission_code = 'lastleg:pda:operate'")
.contains("最小权限授权未完整写入")
.contains("grant_row.tenant_id = 0) <> 19")
.contains("grant_row.tenant_id = 0) <> 22")
.contains("(6000004, 4000004, 0, 'TENANT', NULL)")
.contains("(6000005, 4000005, 0, 'TENANT', NULL)")
.contains("(6000006, 4000006, 0, 'TENANT', NULL)")
@@ -80,7 +80,7 @@ class WarehouseRoleSqlContractTest {
.doesNotContain("platform:firstleg:inbound:picking:view")
.doesNotContain("platform:firstleg:outbound-dispatch:confirm");
assertThat(sql)
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:pda:assign'")
.contains("'WAREHOUSE_OPERATOR', 'platform:firstleg:pda:assign'")
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:complete'")
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:outbound-dispatch:confirm'")
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:outbound-dispatch:reverse'")