Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读 - 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移 - 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import type { Role } from './stage';
|
|
|
|
export interface VersionMember {
|
|
name: string;
|
|
role: Role;
|
|
}
|
|
|
|
export interface VersionMemberCandidate {
|
|
id: string;
|
|
name: string;
|
|
username?: string;
|
|
departmentName?: string;
|
|
}
|
|
|
|
export const DEFAULT_VERSION_MEMBER_ROLE: Role = 'frontend';
|
|
|
|
export function inferVersionMemberRole(
|
|
member: Partial<VersionMemberCandidate>,
|
|
defaultRole: Role = DEFAULT_VERSION_MEMBER_ROLE,
|
|
): Role {
|
|
const departmentName = member.departmentName?.toLowerCase() ?? '';
|
|
if (!departmentName) return defaultRole;
|
|
if (departmentName.includes('产品') || departmentName.includes('product')) return 'product';
|
|
if (departmentName.includes('设计') || departmentName.includes('ui') || departmentName.includes('ux')) return 'ui';
|
|
if (departmentName.includes('测试') || departmentName.includes('质量') || departmentName.includes('qa')) return 'testing';
|
|
if (departmentName.includes('后端') || departmentName.includes('backend') || departmentName.includes('server')) return 'backend';
|
|
if (departmentName.includes('前端') || departmentName.includes('frontend') || departmentName.includes('web') || departmentName.includes('client')) return 'frontend';
|
|
return defaultRole;
|
|
}
|
|
|
|
export function filterVersionMemberCandidates(
|
|
allMembers: VersionMemberCandidate[],
|
|
currentMembers: VersionMember[],
|
|
query: string,
|
|
): VersionMemberCandidate[] {
|
|
const existingNames = new Set(currentMembers.map((member) => member.name));
|
|
const normalizedQuery = query.trim().toLowerCase();
|
|
|
|
return allMembers.filter((member) => {
|
|
if (existingNames.has(member.name)) return false;
|
|
if (!normalizedQuery) return true;
|
|
return member.name.toLowerCase().includes(normalizedQuery);
|
|
});
|
|
}
|
|
|
|
export function addVersionMembers(
|
|
currentMembers: VersionMember[],
|
|
selectedNames: Iterable<string>,
|
|
allMembers: VersionMemberCandidate[],
|
|
defaultRole: Role = DEFAULT_VERSION_MEMBER_ROLE,
|
|
): VersionMember[] {
|
|
const existingNames = new Set(currentMembers.map((member) => member.name));
|
|
const selectedNameSet = new Set(selectedNames);
|
|
const additions = allMembers
|
|
.filter((member) => selectedNameSet.has(member.name) && !existingNames.has(member.name))
|
|
.map((member) => ({ name: member.name, role: inferVersionMemberRole(member, defaultRole) }));
|
|
|
|
return [...currentMembers, ...additions];
|
|
}
|
|
|
|
export function isVersionMemberName(currentUserName: string | undefined, versionMembers: VersionMember[]): boolean {
|
|
const name = currentUserName?.trim();
|
|
if (!name) return false;
|
|
return versionMembers.some((member) => member.name === name);
|
|
}
|
|
|
|
export function canWriteVersionWork(input: {
|
|
role?: { permissions?: string[] } | null;
|
|
permission: string;
|
|
currentUserName?: string;
|
|
versionMembers: VersionMember[];
|
|
readOnly?: boolean;
|
|
}): boolean {
|
|
if (input.readOnly) return false;
|
|
const permissions = input.role?.permissions ?? [];
|
|
if (permissions.includes('*')) return true;
|
|
if (!permissions.includes(input.permission)) return false;
|
|
return isVersionMemberName(input.currentUserName, input.versionMembers);
|
|
}
|