feat: complete overseas customer and PDA workflows
This commit is contained in:
@@ -67,7 +67,11 @@ VALUES
|
||||
(5107221,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:create','创建车辆主数据',NULL,'POST',921,'ACTIVE',1,0),
|
||||
(5107222,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:edit','编辑车辆主数据',NULL,'PUT',922,'ACTIVE',1,0),
|
||||
(5107223,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:status','变更车辆状态',NULL,'PATCH',923,'ACTIVE',1,0),
|
||||
(5107224,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:export','导出车辆主数据',NULL,'GET',924,'ACTIVE',1,0)
|
||||
(5107224,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:export','导出车辆主数据',NULL,'GET',924,'ACTIVE',1,0),
|
||||
(5306100,0,'MERCHANT','MENU',0,'merchant:cross-border:procurement','海外采购','/cross-border/procurement',NULL,70,'ACTIVE',1,0),
|
||||
(5306110,0,'MERCHANT','MENU',0,'merchant:cross-border:domestic-trucking','美国本土卡','/cross-border/domestic-trucking',NULL,80,'ACTIVE',1,0),
|
||||
(5306120,0,'MERCHANT','MENU',0,'merchant:cross-border:pallet-shipping','美本土散板','/cross-border/pallet-shipping',NULL,90,'ACTIVE',1,0),
|
||||
(5306130,0,'MERCHANT','MENU',0,'merchant:warehouse:inbound:list','海外仓入库订单','/orders/inbound',NULL,50,'ACTIVE',1,0)
|
||||
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name), route_path=VALUES(route_path),
|
||||
http_method=VALUES(http_method), sort_no=VALUES(sort_no), status='ACTIVE', built_in=1, deleted=0;
|
||||
|
||||
@@ -203,6 +207,52 @@ WHERE role.tenant_id=0 AND role.app_code='PLATFORM'
|
||||
AND UPPER(role.role_code)='PLATFORM_CUSTOM_ADMIN'
|
||||
AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0;
|
||||
|
||||
-- 客户默认管理员获得当前客户海外业务入口;其他客户角色按企业实际分工授权。
|
||||
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
|
||||
SELECT 0, role.id, permission.id, NOW(3)
|
||||
FROM iam_role role
|
||||
JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='MERCHANT'
|
||||
AND permission.permission_code IN (
|
||||
'merchant:cross-border:procurement',
|
||||
'merchant:cross-border:domestic-trucking',
|
||||
'merchant:cross-border:pallet-shipping',
|
||||
'merchant:warehouse:inbound:list'
|
||||
)
|
||||
WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER'
|
||||
AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_overseas_merchant_permissions;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_overseas_merchant_permissions()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM iam_permission
|
||||
WHERE tenant_id=0 AND app_code='MERCHANT' AND status='ACTIVE' AND deleted=0
|
||||
AND permission_code IN (
|
||||
'merchant:cross-border:procurement',
|
||||
'merchant:cross-border:domestic-trucking',
|
||||
'merchant:cross-border:pallet-shipping',
|
||||
'merchant:warehouse:inbound:list')) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外客户权限初始化失败:权限资源不完整';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM iam_role_permission grant_row
|
||||
JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id
|
||||
JOIN iam_permission permission ON permission.id=grant_row.permission_id
|
||||
WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER'
|
||||
AND role.status='ACTIVE' AND role.deleted=0
|
||||
AND permission.tenant_id=0 AND permission.app_code='MERCHANT'
|
||||
AND permission.status='ACTIVE' AND permission.deleted=0
|
||||
AND permission.permission_code IN (
|
||||
'merchant:cross-border:procurement',
|
||||
'merchant:cross-border:domestic-trucking',
|
||||
'merchant:cross-border:pallet-shipping',
|
||||
'merchant:warehouse:inbound:list')) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外客户权限初始化失败:客户默认管理员授权不完整';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_overseas_merchant_permissions();
|
||||
DROP PROCEDURE assert_overseas_merchant_permissions;
|
||||
|
||||
-- Tail-leg order actions are available to the current platform administrator role.
|
||||
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
|
||||
SELECT 0, role.id, permission.id, NOW(3)
|
||||
|
||||
@@ -124,7 +124,7 @@ paths:
|
||||
get:
|
||||
tags: [IAM Resources / IAM 资源]
|
||||
summary: 查询当前用户有效权限
|
||||
description: 返回当前有效平台成员权限编码;只要求有效 PLATFORM JWT,不依赖 `platform:role:list`。五期海外仓返回 `platform:overseas-warehouse:dispatch:list`、`platform:overseas-warehouse:dispatch:edit`、`platform:overseas-warehouse:dispatch:cancel`、`platform:overseas-warehouse:dispatch:transfer-delivery`、`platform:overseas-warehouse:outbound:list`、`platform:overseas-warehouse:outbound:create`、`platform:overseas-warehouse:outbound:cancel`、`platform:overseas-warehouse:delivery:list`、`platform:overseas-warehouse:delivery:return`、`platform:overseas-warehouse:delivery:status`、`platform:overseas-warehouse:exception-order:list`、`platform:overseas-warehouse:exception-order:create`、`platform:overseas-warehouse:customer-visit:list`、`platform:overseas-warehouse:customer-visit:edit`;列表权限包含详情和只读辅助接口,写操作独立授权。
|
||||
description: 返回当前有效平台成员权限编码;只要求有效 PLATFORM JWT,不依赖 `platform:role:list`。海外仓返回 `platform:overseas-warehouse:dispatch:list`、`platform:overseas-warehouse:dispatch:edit`、`platform:overseas-warehouse:dispatch:cancel`、`platform:overseas-warehouse:dispatch:transfer-delivery`、`platform:overseas-warehouse:outbound:list`、`platform:overseas-warehouse:outbound:create`、`platform:overseas-warehouse:outbound:cancel`、`platform:overseas-warehouse:delivery:list`、`platform:overseas-warehouse:delivery:return`、`platform:overseas-warehouse:delivery:status`、`platform:overseas-warehouse:exception-order:list`、`platform:overseas-warehouse:exception-order:create`、`platform:overseas-warehouse:customer-visit:list`、`platform:overseas-warehouse:customer-visit:edit`;海外 PDA 盘点、移库、换标和现场查询统一返回 `lastleg:pda:operate`,且仅限 `OVERSEAS_WAREHOUSE_PDA_OPERATOR`。列表权限包含详情和只读辅助接口,写操作独立授权。
|
||||
responses:
|
||||
'200':
|
||||
description: 当前平台成员权限查询成功
|
||||
@@ -326,7 +326,7 @@ paths:
|
||||
get:
|
||||
tags: [IAM Resources / IAM 资源]
|
||||
summary: 查询当前用户有效权限
|
||||
description: 返回当前有效客户成员权限编码。五期客户派送返回 `merchant:warehouse:delivery:list` 和 `merchant:warehouse:delivery:create`;列表权限包含详情只读,创建权限独立授权。
|
||||
description: 返回当前有效客户成员权限编码。海外采购、本土卡、本土散板和海外入库订单分别返回 `merchant:cross-border:procurement`、`merchant:cross-border:domestic-trucking`、`merchant:cross-border:pallet-shipping`、`merchant:warehouse:inbound:list`;客户派送返回 `merchant:warehouse:delivery:list` 和 `merchant:warehouse:delivery:create`。列表权限包含详情只读,创建权限独立授权。
|
||||
responses:
|
||||
'200':
|
||||
description: 当前商户成员权限查询成功
|
||||
|
||||
@@ -36,6 +36,12 @@ class IamOpenApiContractTest {
|
||||
.contains("platform:overseas-warehouse:outbound:cancel")
|
||||
.contains("merchant:warehouse:delivery:list")
|
||||
.contains("merchant:warehouse:delivery:create")
|
||||
.contains("merchant:cross-border:procurement")
|
||||
.contains("merchant:cross-border:domestic-trucking")
|
||||
.contains("merchant:cross-border:pallet-shipping")
|
||||
.contains("merchant:warehouse:inbound:list")
|
||||
.contains("lastleg:pda:operate")
|
||||
.contains("OVERSEAS_WAREHOUSE_PDA_OPERATOR")
|
||||
.contains("列表权限包含详情只读")
|
||||
.contains("PermissionCodesApiResponse:")
|
||||
.contains("TenantPageApiResponse:")
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.baiyue.iam.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class OverseasCustomerPermissionSqlContractTest {
|
||||
private static final Pattern MERCHANT_CODE = Pattern.compile("merchant:[a-z0-9:-]+");
|
||||
private static final Set<String> CUSTOMER_OVERSEAS_PERMISSIONS = Set.of(
|
||||
"merchant:cross-border:procurement",
|
||||
"merchant:cross-border:domestic-trucking",
|
||||
"merchant:cross-border:pallet-shipping",
|
||||
"merchant:warehouse:inbound:list");
|
||||
|
||||
@Test
|
||||
void declaresTheFrozenCustomerOverseasPermissionCatalog() throws Exception {
|
||||
String sql = sql();
|
||||
|
||||
assertThat(merchantCodes(sql)).containsExactlyInAnyOrderElementsOf(CUSTOMER_OVERSEAS_PERMISSIONS);
|
||||
for (String permission : CUSTOMER_OVERSEAS_PERMISSIONS) {
|
||||
assertThat(sql).contains("0,'MERCHANT','MENU'", "'" + permission + "'");
|
||||
}
|
||||
assertThat(sql).contains(
|
||||
"'merchant:cross-border:procurement','海外采购','/cross-border/procurement'",
|
||||
"'merchant:cross-border:domestic-trucking','美国本土卡','/cross-border/domestic-trucking'",
|
||||
"'merchant:cross-border:pallet-shipping','美本土散板','/cross-border/pallet-shipping'",
|
||||
"'merchant:warehouse:inbound:list','海外仓入库订单','/orders/inbound'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void grantsAllFourPermissionsOnlyToTheDefaultCustomerAdministrator() throws Exception {
|
||||
String sql = sql();
|
||||
int comment = sql.indexOf("-- 客户默认管理员获得当前客户海外业务入口");
|
||||
int grantStart = sql.indexOf("INSERT IGNORE INTO iam_role_permission", comment);
|
||||
int grantEnd = sql.indexOf(';', grantStart);
|
||||
|
||||
assertThat(comment).isGreaterThanOrEqualTo(0);
|
||||
assertThat(grantStart).isGreaterThan(comment);
|
||||
assertThat(grantEnd).isGreaterThan(grantStart);
|
||||
String grant = sql.substring(grantStart, grantEnd);
|
||||
assertThat(merchantCodes(grant)).containsExactlyInAnyOrderElementsOf(CUSTOMER_OVERSEAS_PERMISSIONS);
|
||||
assertThat(grant).contains(
|
||||
"role.app_code='MERCHANT'",
|
||||
"role.role_code='MERCHANT_OWNER'",
|
||||
"permission.app_code='MERCHANT'");
|
||||
assertThat(grant).doesNotContain("PLATFORM_ADMIN", "PLATFORM_CUSTOM_ADMIN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertsCatalogAndOwnerGrantCompleteness() throws Exception {
|
||||
String sql = sql();
|
||||
|
||||
assertThat(sql).contains(
|
||||
"assert_overseas_merchant_permissions",
|
||||
"海外客户权限初始化失败:权限资源不完整",
|
||||
"海外客户权限初始化失败:客户默认管理员授权不完整",
|
||||
"permission.app_code='MERCHANT'",
|
||||
"role.role_code='MERCHANT_OWNER'",
|
||||
")) <> 4 THEN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsAllOverseasPdaOperationsBehindTheSingleExistingPermission() throws Exception {
|
||||
String sql = sql();
|
||||
Set<String> pdaCodes = new LinkedHashSet<>();
|
||||
Pattern.compile("lastleg:pda:[a-z0-9:-]+")
|
||||
.matcher(sql).results().map(result -> result.group()).forEach(pdaCodes::add);
|
||||
|
||||
assertThat(pdaCodes).containsExactly("lastleg:pda:operate");
|
||||
assertThat(sql).contains(
|
||||
"role.id=4000005",
|
||||
"role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR'",
|
||||
"存在非海外仓 PDA 作业员授权");
|
||||
assertThat(sql).doesNotContain(
|
||||
"lastleg:pda:stocktake",
|
||||
"lastleg:pda:transfer",
|
||||
"lastleg:pda:relabel",
|
||||
"lastleg:pda:query");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesTheFrozenLastlegPermissionConstants() throws Exception {
|
||||
Path constants = Path.of(
|
||||
"lastleg-service/src/main/java/com/baiyue/lastleg/security/OverseasPermissions.java");
|
||||
if (!Files.exists(constants)) constants = Path.of("..").resolve(constants);
|
||||
|
||||
assertThat(merchantCodes(Files.readString(constants, StandardCharsets.UTF_8)))
|
||||
.containsAll(CUSTOMER_OVERSEAS_PERMISSIONS);
|
||||
}
|
||||
|
||||
private static Set<String> merchantCodes(String source) {
|
||||
Set<String> values = new LinkedHashSet<>();
|
||||
MERCHANT_CODE.matcher(source).results().map(result -> result.group()).forEach(values::add);
|
||||
return values;
|
||||
}
|
||||
|
||||
private static String sql() throws Exception {
|
||||
return new ClassPathResource("db/lastleg-permissions.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -138,17 +138,17 @@ class Phase5DeliveryPermissionSqlContractTest {
|
||||
void domesticUnifiedEntryInlinesTheCompleteModuleSqlAfterPhase4Permissions() throws Exception {
|
||||
String moduleSql = sql().trim();
|
||||
String unifiedSql = repositoryFile("deploy/sql/头程数据库统一增量-MySQL8.sql");
|
||||
int phase4 = unifiedSql.indexOf("-- 四期海外权限统一目录");
|
||||
int phase5 = unifiedSql.indexOf("-- 五期海外排车、出库与派送权限(来源:iam/db/phase5-delivery-permissions.sql)");
|
||||
int bodyStart = unifiedSql.indexOf("USE baiyue_iam;", phase5);
|
||||
int overseasCatalog = unifiedSql.indexOf("-- 海外权限统一目录");
|
||||
int outboundDelivery = unifiedSql.indexOf("-- 海外排车、出库与派送权限(来源:iam/db/phase5-delivery-permissions.sql)");
|
||||
int bodyStart = unifiedSql.indexOf("USE baiyue_iam;", outboundDelivery);
|
||||
int nextSection = unifiedSql.indexOf("-- IAM 客户提交即成功", bodyStart);
|
||||
|
||||
assertThat(phase4).isGreaterThanOrEqualTo(0);
|
||||
assertThat(phase5).isGreaterThan(phase4);
|
||||
assertThat(bodyStart).isGreaterThan(phase5);
|
||||
assertThat(overseasCatalog).isGreaterThanOrEqualTo(0);
|
||||
assertThat(outboundDelivery).isGreaterThan(overseasCatalog);
|
||||
assertThat(bodyStart).isGreaterThan(outboundDelivery);
|
||||
assertThat(nextSection).isGreaterThan(bodyStart);
|
||||
assertThat(unifiedSql.substring(bodyStart, nextSection).trim()).isEqualTo(moduleSql);
|
||||
assertThat(codes(unifiedSql.substring(phase5, nextSection)))
|
||||
assertThat(codes(unifiedSql.substring(outboundDelivery, nextSection)))
|
||||
.containsExactlyInAnyOrderElementsOf(REQUIRED);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class Phase5DeliveryPermissionSqlContractTest {
|
||||
String readme = repositoryFile("deploy/sql/README.md");
|
||||
|
||||
assertThat(readme).contains(
|
||||
"五期海外排车、出库与派送权限",
|
||||
"海外排车、出库与派送权限",
|
||||
"测试和部署只执行固定入口,不再单独执行模块脚本",
|
||||
"排车列表/编辑/取消/转派送",
|
||||
"异常订单列表/创建和客户回访列表/编辑十四项权限",
|
||||
|
||||
@@ -90,6 +90,26 @@ class AuthorizationSnapshotServiceTest {
|
||||
"merchant:warehouse:delivery:list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesCustomerOverseasBusinessPermissionsFromCurrentRoleAssignments() {
|
||||
when(mapper.countActiveIdentity(42L, 7L, 9L)).thenReturn(1);
|
||||
when(mapper.findRoles(42L, 7L, 9L, "MERCHANT")).thenReturn(List.of("MERCHANT_OWNER"));
|
||||
when(mapper.findPermissions(42L, 7L, 9L, "MERCHANT")).thenReturn(List.of(
|
||||
"merchant:cross-border:procurement",
|
||||
"merchant:cross-border:domestic-trucking",
|
||||
"merchant:cross-border:pallet-shipping",
|
||||
"merchant:warehouse:inbound:list"));
|
||||
|
||||
var result = service.currentPermissions(new AuthenticatedUser(42L, 7L, 7L, 9L,
|
||||
"owner", "MERCHANT", "MERCHANT", java.util.Set.of()), "MERCHANT");
|
||||
|
||||
assertThat(result).containsExactlyInAnyOrder(
|
||||
"merchant:cross-border:procurement",
|
||||
"merchant:cross-border:domestic-trucking",
|
||||
"merchant:cross-border:pallet-shipping",
|
||||
"merchant:warehouse:inbound:list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsIncompleteCurrentIdentityAsBusinessError() {
|
||||
assertThatThrownBy(() -> service
|
||||
|
||||
Reference in New Issue
Block a user