/** * 立项成功后将非创建者初始成员持久化到受权 project_members API。 * 禁止仅写本地 allUsers/sessions 造成「页面勾选成功、库表无成员」假成功。 */ import type { UpsertProjectMemberPayload } from '../api/projectMembers'; import { toApiProjectRole } from './projectMemberAdapter'; import { documentDutyFromMemberRole } from './projectDocumentDuty'; import type { ProjectMemberRole } from '../types/workbench'; export type InitialProjectMemberInput = { userId: number; userName: string; department?: string; position?: string; role: ProjectMemberRole; }; export function selectNonCreatorInitialMembers( initialMembers: InitialProjectMemberInput[], creatorUserId: number, ): InitialProjectMemberInput[] { return initialMembers.filter((m) => m.userId !== creatorUserId); } export function buildInitialMemberUpsertPayload( member: InitialProjectMemberInput, creatorUserId: number, ): UpsertProjectMemberPayload { return buildManagedMemberUpsertPayload( { userId: member.userId, role: member.role }, creatorUserId, ); } export function buildManagedMemberUpsertPayload( input: { userId: number; role: ProjectMemberRole; status?: 'active' | 'exited' }, creatorUserId: number, ): UpsertProjectMemberPayload { const payload: UpsertProjectMemberPayload = { user_id: input.userId, project_role: toApiProjectRole(input.role, input.userId, creatorUserId), document_duty: documentDutyFromMemberRole(input.role), }; if (input.status) { payload.status = input.status; } return payload; } export type AddProjectMemberFn = ( projectId: number, payload: UpsertProjectMemberPayload, ) => Promise; /** * 逐一 upsert 非创建者;任一失败抛错(调用方保留已创建 projectId 以便重试幂等)。 * Upsert 语义下重复确认不会因已存在成员而假失败。 */ export async function persistInitialProjectMembers(params: { projectId: number; creatorUserId: number; initialMembers: InitialProjectMemberInput[]; addMember: AddProjectMemberFn; }): Promise<{ persistedUserIds: number[] }> { const targets = selectNonCreatorInitialMembers(params.initialMembers, params.creatorUserId); const persistedUserIds: number[] = []; for (const member of targets) { const payload = buildInitialMemberUpsertPayload(member, params.creatorUserId); await params.addMember(params.projectId, payload); persistedUserIds.push(member.userId); } return { persistedUserIds }; } export type CreateProjectEnsurePhase = 'creating' | 'syncing_members'; /** * 若已有 bootstrapProjectId(上次 create 成功但成员失败),跳过再次 createProject,仅补成员。 */ export async function ensureCreatedProjectWithMembers(params: { bootstrapProjectId: number | null; createProject: () => Promise<{ id: number }>; creatorUserId: number; initialMembers: InitialProjectMemberInput[]; addMember: AddProjectMemberFn; /** 可选:驱动 UI「立项中 / 同步成员中」文案(不参与控制流)。 */ onPhase?: (phase: CreateProjectEnsurePhase) => void; }): Promise<{ projectId: number; createdNow: boolean; persistedUserIds: number[] }> { let projectId = params.bootstrapProjectId; let createdNow = false; if (projectId == null || projectId <= 0) { params.onPhase?.('creating'); const created = await params.createProject(); projectId = created.id; createdNow = true; } params.onPhase?.('syncing_members'); try { const { persistedUserIds } = await persistInitialProjectMembers({ projectId, creatorUserId: params.creatorUserId, initialMembers: params.initialMembers, addMember: params.addMember, }); return { projectId, createdNow, persistedUserIds }; } catch (err) { const wrapped = err instanceof Error ? err : new Error(String(err)); (wrapped as Error & { projectId?: number }).projectId = projectId; throw wrapped; } }