Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pac
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tools
pac
Commits
ffeb9083
Commit
ffeb9083
authored
Oct 14, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Plain Diff
Merge whole-tenant FridayAI host administration fix into test
parents
668da7d0
4bd0be51
Pipeline
#3808
failed in 0 seconds
Changes
4
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
92 additions
and
3 deletions
+92
-3
apps/ai-service/src/modules/scope/grant-policy.ts
+5
-1
apps/ai-service/src/modules/scope/scope.service.ts
+2
-1
apps/ai-service/src/modules/workbench/admin-common.ts
+2
-1
apps/ai-service/tests/host-admin-reach.spec.ts
+83
-0
No files found.
apps/ai-service/src/modules/scope/grant-policy.ts
View file @
ffeb9083
...
@@ -160,7 +160,11 @@ export function canManage(
...
@@ -160,7 +160,11 @@ export function canManage(
return
{
ok
:
true
};
return
{
ok
:
true
};
}
}
export
function
hostOperatorReach
(
ids
:
readonly
string
[],
nodes
:
readonly
OrgNode
[]):
OperatorReach
{
export
function
hostOperatorReach
(
ids
:
readonly
string
[],
nodes
:
readonly
OrgNode
[]
,
tenantGrant
=
false
):
OperatorReach
{
const
visible
=
new
Set
(
ids
);
const
visible
=
new
Set
(
ids
);
// 全机构管理要求 AI 常设全租户授权,并且宿主本次范围覆盖该客户的全部有效节点。
// ⛔ 仅覆盖所有节点的 scoped 用户不能据此升级为全机构管理员。
const
active
=
nodes
.
filter
((
node
)
=>
node
.
isActive
);
if
(
tenantGrant
&&
active
.
length
>
0
&&
active
.
every
((
node
)
=>
visible
.
has
(
node
.
id
)))
return
{
all
:
true
};
return
{
all
:
false
,
visible
,
subtree
:
new
Set
(
ids
.
filter
((
id
)
=>
[...
expandSubtree
(
nodes
,
[
id
])].
every
((
child
)
=>
visible
.
has
(
child
))))
};
return
{
all
:
false
,
visible
,
subtree
:
new
Set
(
ids
.
filter
((
id
)
=>
[...
expandSubtree
(
nodes
,
[
id
])].
every
((
child
)
=>
visible
.
has
(
child
))))
};
}
}
apps/ai-service/src/modules/scope/scope.service.ts
View file @
ffeb9083
...
@@ -201,7 +201,8 @@ export class ScopeService {
...
@@ -201,7 +201,8 @@ export class ScopeService {
async
operatorReach
(
op
:
AuthUser
):
Promise
<
OperatorReach
>
{
async
operatorReach
(
op
:
AuthUser
):
Promise
<
OperatorReach
>
{
if
(
op
.
hostScope
)
{
if
(
op
.
hostScope
)
{
const
nodes
=
await
this
.
prisma
.
orgUnit
.
findMany
({
where
:
{
tenantId
:
op
.
tenantId
},
select
:
{
id
:
true
,
parentId
:
true
,
name
:
true
,
kind
:
true
,
isActive
:
true
}
});
const
nodes
=
await
this
.
prisma
.
orgUnit
.
findMany
({
where
:
{
tenantId
:
op
.
tenantId
},
select
:
{
id
:
true
,
parentId
:
true
,
name
:
true
,
kind
:
true
,
isActive
:
true
}
});
return
hostOperatorReach
(
op
.
hostScope
.
orgUnitIds
,
nodes
);
return
hostOperatorReach
(
op
.
hostScope
.
orgUnitIds
,
nodes
,
op
.
dataScope
===
'tenant'
&&
op
.
hostScope
.
wholeTenant
&&
op
.
hostScope
.
accountAll
);
}
}
if
(
op
.
dataScope
===
'tenant'
)
return
{
all
:
true
};
if
(
op
.
dataScope
===
'tenant'
)
return
{
all
:
true
};
const
[
snap
,
anchors
]
=
await
Promise
.
all
([
const
[
snap
,
anchors
]
=
await
Promise
.
all
([
...
...
apps/ai-service/src/modules/workbench/admin-common.ts
View file @
ffeb9083
...
@@ -68,7 +68,8 @@ export async function reachOf(
...
@@ -68,7 +68,8 @@ export async function reachOf(
where
:
{
tenantId
:
op
.
tenantId
,
userId
:
op
.
id
},
where
:
{
tenantId
:
op
.
tenantId
,
userId
:
op
.
id
},
select
:
{
orgUnitId
:
true
,
includeSubtree
:
true
},
select
:
{
orgUnitId
:
true
,
includeSubtree
:
true
},
});
});
return
{
reach
:
op
.
hostScope
?
hostOperatorReach
(
op
.
hostScope
.
orgUnitIds
,
nodes
)
:
operatorReach
(
op
,
anchors
,
nodes
),
anchors
};
return
{
reach
:
op
.
hostScope
?
hostOperatorReach
(
op
.
hostScope
.
orgUnitIds
,
nodes
,
op
.
dataScope
===
'tenant'
&&
op
.
hostScope
.
wholeTenant
&&
op
.
hostScope
.
accountAll
)
:
operatorReach
(
op
,
anchors
,
nodes
),
anchors
};
}
}
/**
/**
...
...
apps/ai-service/tests/host-admin-reach.spec.ts
0 → 100644
View file @
ffeb9083
import
type
{
AuthUser
}
from
'../src/common/guards/jwt-auth.guard'
;
import
{
permissionsOf
}
from
'../src/common/permissions'
;
import
{
adminCan
,
reachOf
}
from
'../src/modules/workbench/admin-common'
;
import
{
hostOperatorReach
}
from
'../src/modules/scope/grant-policy'
;
import
{
ScopeService
}
from
'../src/modules/scope/scope.service'
;
import
type
{
OrgNode
}
from
'../src/modules/scope/org-tree'
;
import
{
NotifySettingsService
}
from
'../src/modules/workbench/notify-settings.service'
;
const
nodes
:
OrgNode
[]
=
[
{
id
:
'root'
,
parentId
:
null
,
name
:
'普尔'
,
kind
:
'group'
,
isActive
:
true
},
{
id
:
'clinic'
,
parentId
:
'root'
,
name
:
'门店'
,
kind
:
'clinic'
,
isActive
:
true
},
];
const
owner
=
():
AuthUser
=>
({
id
:
'user'
,
tenantId
:
'puer'
,
role
:
'owner'
,
displayName
:
'管理员'
,
dataScope
:
'tenant'
,
orgUnitId
:
null
,
jobTitle
:
null
,
mustChangePassword
:
false
,
permissions
:
permissionsOf
(
'owner'
),
hostScope
:
{
version
:
2
,
appId
:
'test'
,
dataTenantId
:
'friday-market'
,
sourceUserId
:
'5979'
,
launchId
:
'launch'
,
rootOrgId
:
'root'
,
orgScope
:
[
'root'
],
scopeOrgIds
:
[
'root'
],
role
:
'admin'
,
capabilityCeiling
:
[],
readonlyCeiling
:
false
,
capabilities
:
[],
modules
:
[
'workbench'
],
readOnly
:
false
,
orgUnitIds
:
[
'root'
,
'clinic'
],
wholeTenant
:
true
,
accountAll
:
true
,
clinicNames
:
{},
},
});
function
scopeService
()
{
const
db
=
{
orgUnit
:
{
findMany
:
async
()
=>
nodes
}
};
return
new
ScopeService
(
db
as
never
);
}
it
(
'人工全租户授权与宿主完整客户范围同时成立,才允许全机构管理'
,
()
=>
{
expect
(
hostOperatorReach
([
'root'
,
'clinic'
],
nodes
,
true
)).
toEqual
({
all
:
true
});
expect
(
hostOperatorReach
([
'root'
,
'clinic'
],
nodes
).
all
).
toBe
(
false
);
expect
(
hostOperatorReach
([
'clinic'
],
nodes
,
true
).
all
).
toBe
(
false
);
expect
(
hostOperatorReach
([],
[],
true
).
all
).
toBe
(
false
);
});
it
(
'只覆盖所有节点但仍是 scoped 的宿主管理员,不能升级为全机构'
,
async
()
=>
{
const
user
=
{
...
owner
(),
dataScope
:
'scoped'
};
expect
((
await
scopeService
().
operatorReach
(
user
)).
all
).
toBe
(
false
);
expect
(
adminCan
(
user
).
editTree
).
toBe
(
false
);
});
it
(
'AI 全租户管理员经宿主缩窄到门店或失去全机构标记,仍不能改全机构'
,
async
()
=>
{
for
(
const
override
of
[
{
orgUnitIds
:
[
'clinic'
],
wholeTenant
:
false
,
accountAll
:
false
},
{
wholeTenant
:
false
},
{
accountAll
:
false
},
{
orgUnitIds
:
[
'clinic'
]
},
])
{
const
user
=
owner
();
user
.
hostScope
=
{
...
user
.
hostScope
!
,
...
override
};
expect
((
await
scopeService
().
operatorReach
(
user
)).
all
).
toBe
(
false
);
}
});
it
(
'组织授权事务与提醒设置共用全机构判据'
,
async
()
=>
{
const
user
=
owner
();
const
db
=
{
userOrgScope
:
{
findMany
:
async
()
=>
[]
}
};
expect
((
await
reachOf
(
db
as
never
,
user
,
nodes
)).
reach
).
toEqual
({
all
:
true
});
expect
(
adminCan
(
user
)).
toMatchObject
({
editTree
:
true
,
tenantScope
:
true
});
const
scope
=
scopeService
();
const
notify
=
new
NotifySettingsService
({}
as
never
,
scope
);
expect
(
await
notify
[
'can'
](
user
)).
toMatchObject
({
edit
:
true
,
pick
:
true
});
user
.
hostScope
!
.
orgUnitIds
=
[
'clinic'
];
expect
((
await
reachOf
(
db
as
never
,
user
,
nodes
)).
reach
.
all
).
toBe
(
false
);
expect
(
await
notify
[
'can'
](
user
)).
toMatchObject
({
edit
:
true
,
pick
:
false
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment