import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import { upsertChatSession } from '../api/chatSessions'; import { addProjectMember } from '../api/projectMembers'; import { createProject } from '../api/projectWorkflow'; import { buildPrivateSessionId } from '../data/storage'; import { createProjectTemplateDeliverables } from '../engine/templateFactory'; import { ensureCreatedProjectWithMembers } from '../lib/createProjectMembersPersist'; import { beginCreateProjectSubmit, endCreateProjectSubmit, resolveCreateProjectSubmitPhase, shouldCloneMedicalTemplates, type CreateProjectSubmitPhase, } from '../lib/createProjectSubmitGuard'; import type { CreateProjectConfirmData } from '../components/project/CreateProjectModal'; import type { DocumentAST } from '../types/block'; import type { UserProfile } from '../types/docgov'; import type { ChatMessage, DeliverableCategory, DeliverableItem, ProjectInfo, ProjectMember, ProjectSession, } from '../types/workbench'; export interface CreateProjectConfirmInput { currentUser: UserProfile; createProjectInFlightRef: MutableRefObject; createProjectBootstrapIdRef: MutableRefObject; setCreateProjectError: Dispatch>; setCreateProjectSubmitting: Dispatch>; setCreateProjectSubmitPhase: Dispatch>; setCreateProjectModalOpen: Dispatch>; setProjects: Dispatch>; setSessions: Dispatch>; setMessages: Dispatch>; setDeliverables: Dispatch>; setDocCache: Dispatch>>; setActiveSessionId: Dispatch>; setActiveDocId: Dispatch>; setActiveCategory: Dispatch>; } export function useCreateProjectConfirm(input: CreateProjectConfirmInput) { const { currentUser, createProjectInFlightRef, createProjectBootstrapIdRef, setCreateProjectError, setCreateProjectSubmitting, setCreateProjectSubmitPhase, setCreateProjectModalOpen, setProjects, setSessions, setMessages, setDeliverables, setDocCache, setActiveSessionId, setActiveDocId, setActiveCategory, } = input; // 发起医疗器械新项目立项 (通过 Modal 交互收集名称、品类与初始成员) return async (data: CreateProjectConfirmData) => { if (!beginCreateProjectSubmit(createProjectInFlightRef)) { return; } setCreateProjectError(null); setCreateProjectSubmitting(true); setCreateProjectSubmitPhase( resolveCreateProjectSubmitPhase(createProjectBootstrapIdRef.current), ); let newProjId: number; try { try { const ensured = await ensureCreatedProjectWithMembers({ bootstrapProjectId: createProjectBootstrapIdRef.current, createProject: async () => { const clientKey = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? `proj-${crypto.randomUUID()}` : `proj-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; const created = await createProject({ name: data.title, client_key: clientKey, device_type: data.deviceType || undefined, intended_use: data.productDomain === 'non_medical_software' ? undefined : data.description, product_domain: data.productDomain, project_type_key: data.projectTypeKey, team_mode: data.teamMode, execution_mode: data.executionMode, problem_statement: data.problemStatement, target_users: data.targetUsers, target_outcome: data.targetOutcome, initial_workflow_key: data.productDomain === 'non_medical_software' ? 'software_bootstrap' : undefined, }); // create 刚成功即写入 bootstrap:成员失败窗口内串行重试不会再 create createProjectBootstrapIdRef.current = created.id; return created; }, creatorUserId: currentUser.id, initialMembers: data.initialMembers, addMember: addProjectMember, onPhase: setCreateProjectSubmitPhase, }); newProjId = ensured.projectId; createProjectBootstrapIdRef.current = ensured.projectId; } catch (err) { const withId = err as Error & { projectId?: number }; const bootstrapId = typeof withId.projectId === 'number' && withId.projectId > 0 ? withId.projectId : createProjectBootstrapIdRef.current; if (bootstrapId) { createProjectBootstrapIdRef.current = bootstrapId; } const message = err instanceof Error ? err.message : '创建项目或同步初始成员失败,请稍后重试(不会因本地状态假成功)'; setCreateProjectError( bootstrapId ? `项目已创建(#${bootstrapId}),但初始成员未全部落库:${message}。请再次确认以幂等补齐,勿关闭后当作成功。` : message, ); return; } const nowStr = new Date().toLocaleString(); // 1. 构建项目与成员元数据(服务端成员已持久化) const members: ProjectMember[] = data.initialMembers.map((m) => ({ userId: m.userId, userName: m.userName, department: m.department, position: m.position, role: m.role, status: 'active', joinedAt: nowStr, })); const newProject: ProjectInfo = { id: newProjId, title: data.title, deviceType: data.deviceType, description: data.description, creatorId: currentUser.id, creatorName: currentUser.name, createdAt: nowStr, members, }; // 2. 为每个选中的项目成员生成独立私聊会话 const newSessionsToAppend: ProjectSession[] = []; const newMessagesToAppend: ChatMessage[] = []; let creatorSessionId = ''; members.forEach((m) => { const sessId = buildPrivateSessionId(`p${newProjId}`, m.userId); if (m.userId === currentUser.id) { creatorSessionId = sessId; } newSessionsToAppend.push({ sessionId: sessId, userId: m.userId, projectId: newProjId, title: data.title, deviceType: data.deviceType, stage: 'V0.1', stageLabel: 'V0.1 需求摸底', messageCount: 1, updatedAt: '刚刚', }); newMessagesToAppend.push({ id: `m-init-${m.userId}-${Date.now()}`, sessionId: sessId, role: 'system', content: m.userId === currentUser.id ? `【立项成功】项目《${data.title}》已成功建立!全套研发工作流与阶段文档模板已克隆就绪,协同参谋小队已就位,进入【V0.1 需求摸底】阶段。您可以点击附件图标上传临床需求 URS,或直接输入您的产品构想。` : `【项目协同通知】您已被项目创建人 ${currentUser.name} 邀请加入《${data.title}》,并分配法定职责为【${m.role}】。`, timestamp: new Date().toTimeString().substring(0, 5), }); }); // 3. 仅医疗项目克隆 7 份受控模板;软件项目走服务端 catalog / docs_only const clonedDocs = shouldCloneMedicalTemplates(data.productDomain) ? createProjectTemplateDeliverables({ projectId: newProjId, projectName: data.title, deviceType: data.deviceType, creatorId: currentUser.id, creatorName: currentUser.name, }) : { deliverables: [], docASTMap: {} }; const clonedAsts = clonedDocs.docASTMap; setProjects((prev) => [newProject, ...prev]); setSessions((prev) => [...newSessionsToAppend, ...prev]); setMessages((prev) => [...prev, ...newMessagesToAppend]); void Promise.all( newSessionsToAppend.map((sessionItem) => upsertChatSession({ session_id: sessionItem.sessionId, title: sessionItem.title, user_id: sessionItem.userId ?? currentUser.id, project_id: sessionItem.projectId, }) ) ).catch((err) => { console.warn('[sessions] 服务端会话注册失败,其他设备可能暂不可见:', err); }); setDeliverables((prev) => [...prev, ...clonedDocs.deliverables]); setDocCache((prev) => ({ ...prev, ...clonedAsts })); if (creatorSessionId) { setActiveSessionId(creatorSessionId); } if (clonedDocs.deliverables[0]?.docId) { setActiveDocId(clonedDocs.deliverables[0].docId); setActiveCategory('workflow'); } createProjectBootstrapIdRef.current = null; setCreateProjectModalOpen(false); setCreateProjectError(null); } finally { endCreateProjectSubmit(createProjectInFlightRef); setCreateProjectSubmitting(false); setCreateProjectSubmitPhase(null); } }; }