Files
ftb-project-management/apps/web/lib/version-members.ts
2e595c7e72
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
refactor(data): 收口关系表运行时数据源
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读
- 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移
- 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-07-09 14:59:49 +08:00

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);
}