fix: bind overseas receiving to shared warehouse
This commit is contained in:
@@ -40,5 +40,13 @@ public final class AuthorizationDtos {
|
||||
private Set<String> roles;
|
||||
private Set<String> permissions;
|
||||
private Long permissionVersion;
|
||||
private Long overseasWarehouseId;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,28 @@ public final class PlatformUserManagementDtos {
|
||||
@Email @Size(max = 128) String email,
|
||||
@NotNull Long tenantId,
|
||||
@NotNull List<Long> roleIds,
|
||||
Long overseasWarehouseId,
|
||||
@NotBlank @Size(min = 8, max = 64)
|
||||
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String password,
|
||||
@NotBlank @Pattern(regexp = "ACTIVE|DISABLED") String status) { }
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
public record UpdateUserRequest(
|
||||
@NotBlank @Size(max = 64) String displayName,
|
||||
@NotBlank @Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确") String phone,
|
||||
@Email @Size(max = 128) String email,
|
||||
@NotNull Long tenantId,
|
||||
@NotNull List<Long> roleIds) { }
|
||||
@NotNull List<Long> roleIds,
|
||||
Long overseasWarehouseId) {
|
||||
public UpdateUserRequest(String displayName, String phone, String email,
|
||||
Long tenantId, List<Long> roleIds) {
|
||||
this(displayName, phone, email, tenantId, roleIds, null);
|
||||
}
|
||||
}
|
||||
|
||||
public record ChangeStatusRequest(@NotBlank @Pattern(regexp = "ACTIVE|DISABLED") String status) { }
|
||||
public record ResetPasswordRequest(@NotBlank @Size(min = 8, max = 64)
|
||||
@@ -40,7 +52,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) { }
|
||||
boolean protectedAccount, Long overseasWarehouseId) { }
|
||||
|
||||
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 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 String status; private LocalDateTime joinedAt; private LocalDateTime leftAt;
|
||||
|
||||
}
|
||||
|
||||
@@ -65,4 +65,8 @@ public interface AuthorizationQueryMapper {
|
||||
|
||||
@Select("SELECT COALESCE(MAX(permission_version),0) FROM iam_security_subject_version WHERE auth_user_id=#{userId}")
|
||||
long findPermissionVersion(@Param("userId") long userId);
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public class AuthorizationSnapshotService {
|
||||
request.getUserId(), request.getTenantId(), request.getMembershipId(), appCode));
|
||||
return new SnapshotResponse(request.getUserId(), request.getTenantId(), request.getMembershipId(),
|
||||
appCode, appCode, Set.copyOf(roles), Set.copyOf(permissions),
|
||||
mapper.findPermissionVersion(request.getUserId()));
|
||||
mapper.findPermissionVersion(request.getUserId()),
|
||||
mapper.findOverseasWarehouseId(request.getTenantId(), request.getMembershipId()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,6 +75,7 @@ public class PlatformUserManagementService {
|
||||
long tenantId = request.tenantId();
|
||||
requirePlatformTenant(tenantId);
|
||||
List<Long> roleIds = validateRoles(tenantId, request.roleIds());
|
||||
Long overseasWarehouseId = validateWarehouseBinding(roleIds, request.overseasWarehouseId());
|
||||
long authUserId = IdWorker.getId();
|
||||
long userId = IdWorker.getId();
|
||||
long membershipId = IdWorker.getId();
|
||||
@@ -99,6 +100,7 @@ public class PlatformUserManagementService {
|
||||
membership.setUserId(userId);
|
||||
membership.setMemberType("EMPLOYEE");
|
||||
membership.setMemberNo(memberNo(tenantId, membershipId));
|
||||
membership.setOverseasWarehouseId(overseasWarehouseId);
|
||||
membership.setStatus(request.status());
|
||||
membership.setJoinedAt(LocalDateTime.now());
|
||||
membership.setLeftAt("DISABLED".equals(request.status()) ? LocalDateTime.now() : null);
|
||||
@@ -121,6 +123,7 @@ public class PlatformUserManagementService {
|
||||
long newTenantId = request.tenantId();
|
||||
requirePlatformTenant(newTenantId);
|
||||
List<Long> roleIds = validateRoles(newTenantId, request.roleIds());
|
||||
Long overseasWarehouseId = validateWarehouseBinding(roleIds, request.overseasWarehouseId());
|
||||
List<Long> oldRoleIds = members.findRoleIds(oldTenantId, membershipId);
|
||||
if (isProtectedPlatformAdmin(membership, oldRoleIds)
|
||||
&& (newTenantId != PLATFORM_TENANT_ID || !containsPlatformAdminRole(roleIds))) {
|
||||
@@ -134,6 +137,7 @@ public class PlatformUserManagementService {
|
||||
boolean profileChanged = !Objects.equals(user.getDisplayName(), request.displayName().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);
|
||||
|
||||
if (profileChanged) call(() -> authClient.profile(internalToken, user.getAuthUserId(),
|
||||
new AuthAccountClient.ProfileRequest(request.displayName().trim())));
|
||||
@@ -145,6 +149,7 @@ public class PlatformUserManagementService {
|
||||
user.setEmail(trimToNull(request.email()));
|
||||
user.setUserType(userType(newTenantId));
|
||||
membership.setTenantId(newTenantId);
|
||||
membership.setOverseasWarehouseId(overseasWarehouseId);
|
||||
if (scopeChanged && members.existsMemberNo(newTenantId, membership.getMemberNo(), membershipId)) {
|
||||
membership.setMemberNo(memberNo(newTenantId, membershipId));
|
||||
}
|
||||
@@ -152,7 +157,7 @@ public class PlatformUserManagementService {
|
||||
members.save(membership);
|
||||
if (scopeChanged) members.replaceRoles(oldTenantId, membershipId, List.of());
|
||||
members.replaceRoles(newTenantId, membershipId, roleIds);
|
||||
if (rolesChanged && !profileChanged && !scopeChanged) {
|
||||
if ((rolesChanged || warehouseChanged) && !profileChanged && !scopeChanged) {
|
||||
call(() -> authClient.revokeSessions(internalToken,
|
||||
new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(user.getAuthUserId()))));
|
||||
}
|
||||
@@ -222,7 +227,8 @@ public class PlatformUserManagementService {
|
||||
return new UserResponse(membership.getId(), user.getId(), user.getAuthUserId(), account.username(),
|
||||
user.getDisplayName(), user.getPhone(), user.getEmail(), membership.getTenantId(), tenantName,
|
||||
membership.getMemberType(), membership.getMemberNo(), membership.getStatus(), roleIds,
|
||||
roleNames, account.lastLoginAt(), isProtectedPlatformAdmin(membership, roleIds));
|
||||
roleNames, account.lastLoginAt(), isProtectedPlatformAdmin(membership, roleIds),
|
||||
membership.getOverseasWarehouseId());
|
||||
}
|
||||
|
||||
private List<IamRole> platformRoles() {
|
||||
@@ -245,6 +251,18 @@ public class PlatformUserManagementService {
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
private Long validateWarehouseBinding(List<Long> roleIds, Long overseasWarehouseId) {
|
||||
boolean picker = roleIds.stream().map(roleMapper::selectById).filter(Objects::nonNull)
|
||||
.anyMatch(role -> "WAREHOUSE_PICKER".equals(role.getRoleCode()));
|
||||
if (picker && (overseasWarehouseId == null || overseasWarehouseId <= 0)) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "海外仓拣货员必须绑定一个有效海外仓");
|
||||
}
|
||||
if (!picker && overseasWarehouseId != null) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "仅海外仓拣货员可绑定海外仓");
|
||||
}
|
||||
return overseasWarehouseId;
|
||||
}
|
||||
|
||||
private boolean isProtectedPlatformAdmin(IamMembership membership, List<Long> roleIds) {
|
||||
return membership.getTenantId() == PLATFORM_TENANT_ID && containsPlatformAdminRole(roleIds);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ CREATE TABLE IF NOT EXISTS iam_membership (
|
||||
user_id BIGINT NOT NULL COMMENT 'IAM用户资料ID',
|
||||
member_type VARCHAR(20) NOT NULL DEFAULT 'EMPLOYEE' COMMENT '成员类型:EMPLOYEE企业员工等',
|
||||
member_no VARCHAR(64) NULL COMMENT '客户企业内部成员编号',
|
||||
overseas_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 '离开客户企业时间',
|
||||
|
||||
@@ -1279,6 +1279,7 @@ components:
|
||||
email: {type: string, format: email, maxLength: 128}
|
||||
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: 选择WAREHOUSE_PICKER角色时必填且只能绑定一个实际海外仓;其他角色必须为空}
|
||||
password: {type: string, format: password, minLength: 8, maxLength: 64, writeOnly: true}
|
||||
status: {type: string, enum: [ACTIVE, DISABLED]}
|
||||
UpdatePlatformManagedUserRequest:
|
||||
@@ -1290,6 +1291,7 @@ components:
|
||||
email: {type: string, format: email, maxLength: 128}
|
||||
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: 选择WAREHOUSE_PICKER角色时必填且只能绑定一个实际海外仓;其他角色必须为空}
|
||||
CreateInternalUserRequest:
|
||||
type: object
|
||||
required: [displayName]
|
||||
@@ -1362,6 +1364,7 @@ components:
|
||||
roleNames: {type: array, items: {type: string}}
|
||||
lastLoginAt: {type: string, format: date-time, nullable: true}
|
||||
protectedAccount: {type: boolean}
|
||||
overseasWarehouseId: {type: string, pattern: '^\d+$', nullable: true, description: 海外仓PDA操作员绑定的唯一实际海外仓ID}
|
||||
CreateMemberRequest:
|
||||
type: object
|
||||
required: [username, password, displayName, memberType]
|
||||
|
||||
@@ -29,6 +29,7 @@ class AuthorizationSnapshotServiceTest {
|
||||
.thenReturn(List.of("ADMIN", "OPERATOR"));
|
||||
when(mapper.findPermissions(42L, 7L, 9L, "MERCHANT"))
|
||||
.thenReturn(List.of("order:read", "order:create"));
|
||||
when(mapper.findOverseasWarehouseId(7L, 9L)).thenReturn(900L);
|
||||
|
||||
var result = service
|
||||
.load(new SnapshotRequest(42L, 7L, 9L, "merchant"));
|
||||
@@ -37,6 +38,7 @@ class AuthorizationSnapshotServiceTest {
|
||||
assertThat(result.getClientType()).isEqualTo("MERCHANT");
|
||||
assertThat(result.getRoles()).containsExactlyInAnyOrder("ADMIN", "OPERATOR");
|
||||
assertThat(result.getPermissions()).containsExactlyInAnyOrder("order:read", "order:create");
|
||||
assertThat(result.getOverseasWarehouseId()).isEqualTo(900L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -57,6 +57,7 @@ class PlatformUserManagementServiceTest {
|
||||
when(auth.summary("internal-token", 10L)).thenReturn(ApiResponse.success(
|
||||
new AuthAccountClient.AccountSummary(10L, "zhangwei", "张伟", "ACTIVE",
|
||||
LocalDateTime.of(2026, 6, 1, 10, 30), 0L, 30L, "PLATFORM")));
|
||||
when(auth.revokeSessions(eq("internal-token"), any())).thenReturn(ApiResponse.success(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,6 +118,23 @@ class PlatformUserManagementServiceTest {
|
||||
verify(auth, never()).scope(anyString(), anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void warehousePickerRequiresAndPersistsSingleOverseasWarehouseBinding() {
|
||||
platformRole.setRoleCode("WAREHOUSE_PICKER");
|
||||
UpdateUserRequest missing = new UpdateUserRequest("张伟", "13800138000", null,
|
||||
0L, List.of(40001L), null);
|
||||
assertThrows(BizException.class,
|
||||
() -> service.update(30L, missing, Set.of("platform:user:grant-role")));
|
||||
|
||||
UpdateUserRequest bound = new UpdateUserRequest("张伟", "13800138000", null,
|
||||
0L, List.of(40001L), 900L);
|
||||
UserResponse response = service.update(30L, bound, Set.of("platform:user:grant-role"));
|
||||
|
||||
assertEquals(900L, membership.getOverseasWarehouseId());
|
||||
assertEquals(900L, response.overseasWarehouseId());
|
||||
verify(auth).revokeSessions(eq("internal-token"), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentPlatformOperatorCannotDisableSelf() {
|
||||
assertThrows(BizException.class,
|
||||
|
||||
Reference in New Issue
Block a user