Merge branch 'codex/xuxiake-sabc-sync-20260929' into codex/xuxiake-mainline
This commit is contained in:
commit
b2ca7bf7f1
@ -406,7 +406,7 @@ export function DepartmentDistributionView({
|
||||
<textarea value={draft.profitCenterAlias} onChange={(event) => setDraft((current) => ({ ...current, profitCenterAlias: event.target.value }))} />
|
||||
</label>
|
||||
<label className={styles.deptConfigField}>
|
||||
数据权限例外(一行一条:花名=范围;范围支持 全部(看所有利润中心)/ 利润中心:xx / 事业部:xx / 品类部:xx / 负责人:xx,多个用逗号)
|
||||
数据权限例外(默认不需要配置:所有人都能看全部利润中心;只有这里写了花名的人才会被收窄。一行一条:花名=范围;范围支持 全部(看所有利润中心)/ 利润中心:xx / 事业部:xx / 品类部:xx / 负责人:xx,多个用逗号)
|
||||
<textarea value={draft.dataScopeGrants} onChange={(event) => setDraft((current) => ({ ...current, dataScopeGrants: event.target.value }))} />
|
||||
</label>
|
||||
<div className={styles.deptConfigField} style={{ gridColumn: '1 / -1' }}>
|
||||
|
||||
@ -385,12 +385,12 @@ export type DepartmentViewerScope = {
|
||||
}
|
||||
|
||||
/**
|
||||
* 方案 C 数据权限:
|
||||
* 1) 管理员看全部;
|
||||
* 2) 部门负责人(人员信息「部门负责人」)看本部门全部数据;
|
||||
* 3) 事业部/总负责人(「总负责人」)看本事业部全部数据;
|
||||
* 4) 配置里的例外授权可按 利润中心 / 事业部 / 品类部 / 负责人 追加范围;
|
||||
* 5) 其余人员只看自己名下的数据。
|
||||
* 「各部门数据分类」数据权限:
|
||||
* 1) 不做任何配置时,所有人都能看到全部利润中心数据(查看/导出本身由「用户管理后台」的
|
||||
* 「商品SABC打标 · 各部门数据分类」节点控制,默认开放);
|
||||
* 2) 管理员始终全部可见;
|
||||
* 3) 只有在「数据权限例外」里显式给某人配置了范围(利润中心 / 事业部 / 品类部 / 负责人)
|
||||
* 时才会收窄;写 全部 / 所有 / * / 全公司 表示不受限。
|
||||
*/
|
||||
export async function resolveDepartmentViewerScope(
|
||||
user: AuthUser,
|
||||
@ -399,13 +399,7 @@ export async function resolveDepartmentViewerScope(
|
||||
): Promise<DepartmentViewerScope> {
|
||||
if (isAdmin(user)) return { nickname: '', isAdmin: true, allowedOwners: null, scopeLabel: '全部利润中心(管理员)' }
|
||||
const nickname = await resolveNickname(user, configs)
|
||||
const allowed = new Set<string>(nickname ? [nickname] : [])
|
||||
const scopes = new Set<string>()
|
||||
for (const config of configs) {
|
||||
if (!config.operationOwner) continue
|
||||
if (nickname && config.departmentOwner === nickname && config.department) scopes.add(`事业部:${config.department}`)
|
||||
if (nickname && config.generalOwner === nickname && config.department) scopes.add(`事业部:${config.department}`)
|
||||
}
|
||||
for (const grant of settings.dataScopeGrants) {
|
||||
if (grant.user !== nickname) continue
|
||||
for (const scope of grant.scopes) scopes.add(scope)
|
||||
@ -418,6 +412,16 @@ export async function resolveDepartmentViewerScope(
|
||||
if (grantsAllData) {
|
||||
return { nickname, isAdmin: false, allowedOwners: null, scopeLabel: nickname ? `全部数据(授权给 ${nickname})` : '全部数据' }
|
||||
}
|
||||
// 没有任何例外配置:默认全部利润中心可见,不做收窄
|
||||
if (scopes.size === 0) {
|
||||
return {
|
||||
nickname,
|
||||
isAdmin: false,
|
||||
allowedOwners: null,
|
||||
scopeLabel: nickname ? `全部利润中心(默认开放 · ${nickname})` : '全部利润中心(默认开放)',
|
||||
}
|
||||
}
|
||||
const allowed = new Set<string>(nickname ? [nickname] : [])
|
||||
for (const scope of scopes) {
|
||||
const [kind, rawValue] = scope.split(':')
|
||||
const value = String(rawValue ?? '').trim()
|
||||
@ -430,9 +434,7 @@ export async function resolveDepartmentViewerScope(
|
||||
if (kind === '利润中心' && config.profitCenter === value) allowed.add(config.operationOwner)
|
||||
}
|
||||
}
|
||||
const scopeLabel = scopes.size > 0
|
||||
? [nickname ? `本人(${nickname})` : '', ...scopes].filter(Boolean).join('、')
|
||||
: nickname ? `本人(${nickname})` : '未匹配到人员信息(无数据)'
|
||||
const scopeLabel = [nickname ? `本人(${nickname})` : '', ...scopes].filter(Boolean).join('、')
|
||||
return { nickname, isAdmin: false, allowedOwners: allowed, scopeLabel }
|
||||
}
|
||||
|
||||
|
||||
@ -82,7 +82,7 @@ describe('supply-chain department distribution', () => {
|
||||
expect(settings.targets.newProductSuccess).toBe(0.3)
|
||||
})
|
||||
|
||||
it('keeps admin, department owner, general owner and grant scopes apart', async () => {
|
||||
it('opens all profit centers by default and only narrows when a grant is configured', async () => {
|
||||
const configs = [
|
||||
config({ operationOwner: '比干', profitCenter: '运营中心A', department: '事业一部', departmentOwner: '比干', generalOwner: '比干' }),
|
||||
config({ operationOwner: '西门吹雪', profitCenter: '运营中心A', department: '事业二部', secondaryDepartment: '品类三部', departmentOwner: '西门吹雪', generalOwner: '無尘' }),
|
||||
@ -98,23 +98,23 @@ describe('supply-chain department distribution', () => {
|
||||
)
|
||||
expect(admin.allowedOwners).toBeNull()
|
||||
|
||||
// 部门负责人:比干 管 事业一部
|
||||
// 默认(没有任何例外配置):所有人都能看全部利润中心,不做收窄
|
||||
const departmentOwner = await resolveDepartmentViewerScope(
|
||||
{ id: 'u', email: '', name: '比干', role: 'USER', status: 'ACTIVE' },
|
||||
configs,
|
||||
settings,
|
||||
)
|
||||
expect([...(departmentOwner.allowedOwners ?? [])].sort()).toEqual(['比干'])
|
||||
expect(departmentOwner.allowedOwners).toBeNull()
|
||||
expect(departmentOwner.scopeLabel).toContain('全部利润中心')
|
||||
|
||||
// 总负责人:無尘 管 事业二部 → 西门吹雪 + 玄女
|
||||
const generalOwner = await resolveDepartmentViewerScope(
|
||||
{ id: 'u', email: '', name: '無尘', role: 'USER', status: 'ACTIVE' },
|
||||
configs,
|
||||
settings,
|
||||
)
|
||||
expect([...(generalOwner.allowedOwners ?? [])].sort()).toEqual(['無尘', '玄女', '西门吹雪'])
|
||||
expect(generalOwner.allowedOwners).toBeNull()
|
||||
|
||||
// 例外授权:香茗 额外看 利润中心:运营中心A
|
||||
// 例外授权(显式配置才收窄):香茗 只配了 利润中心:运营中心A
|
||||
const granted = await resolveDepartmentViewerScope(
|
||||
{ id: 'u', email: '', name: '香茗', role: 'USER', status: 'ACTIVE' },
|
||||
configs,
|
||||
@ -295,15 +295,31 @@ describe('supply-chain department distribution', () => {
|
||||
expect(adminResult.profitCenters.map((center) => center.profitCenter)).toEqual(['运营中心A', '运营中心B'])
|
||||
expect(adminResult.designerTotal.productTotal).toBe(10)
|
||||
|
||||
// 普通人员只能看到自己那部分,小计按可见数据重算
|
||||
// 默认不做任何配置:普通人员也能看到全部利润中心(查看/导出由用户管理后台控制)
|
||||
clearDepartmentDistributionCache()
|
||||
const selfResult = await supplyChainDepartmentDistributionService.build({
|
||||
year: 2026, month: 8,
|
||||
user: { id: 'u', email: '', name: '比干', role: 'USER', status: 'ACTIVE' },
|
||||
canExport: false,
|
||||
})
|
||||
expect(selfResult.summary.productTotal).toBe(10)
|
||||
expect(selfResult.profitCenters.map((center) => center.profitCenter)).toEqual(['运营中心A'])
|
||||
expect(selfResult.summary.productTotal).toBe(20)
|
||||
expect(selfResult.profitCenters.map((center) => center.profitCenter)).toEqual(['运营中心A', '运营中心B'])
|
||||
expect(selfResult.designerTotal.productTotal).toBe(10)
|
||||
|
||||
// 只有在「数据权限例外」里显式配置了范围,才会收窄(小计按可见数据重算)
|
||||
clearDepartmentDistributionCache()
|
||||
settingFindUnique.mockResolvedValue({
|
||||
value: {
|
||||
...normalizeDepartmentSettings(null),
|
||||
dataScopeGrants: [{ user: '比干', scopes: ['负责人:比干'] }],
|
||||
},
|
||||
})
|
||||
const narrowedResult = await supplyChainDepartmentDistributionService.build({
|
||||
year: 2026, month: 8,
|
||||
user: { id: 'u', email: '', name: '比干', role: 'USER', status: 'ACTIVE' },
|
||||
canExport: false,
|
||||
})
|
||||
expect(narrowedResult.summary.productTotal).toBe(10)
|
||||
expect(narrowedResult.profitCenters.map((center) => center.profitCenter)).toEqual(['运营中心A'])
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user