// U6/KTD5:RDM 管理与项目页面的纯逻辑层——能力门控(与 rdmAuditReady 同源语义: // 命令清单不可用=未就绪,fail-closed)与命令载荷构造。页面组件只消费本模块与 // api.rdmCommand,不在组件里散落命令 id / 参数拼写;身份一律经认证会话,载荷不 // 携带任何身份字段(G1-5)。 /** 系统管理「RDM 角色权限」分区消费的命令面。 */ export const RDM_ROLES_COMMANDS = [ 'rdm.role.create', 'rdm.role.action', 'rdm.role.assign', 'rdm.role.disable', 'rdm.role.list', ] as const /** 项目设置「成员」页消费的命令面。 */ export const RDM_MEMBER_COMMANDS = [ 'rdm.project.member.list', 'rdm.project.member.add', 'rdm.project.member.remove', ] as const /** 项目设置「项目权限」页消费的命令面(矩阵解释 + 成员清单)。 */ export const RDM_PERM_MATRIX_COMMANDS = [ 'rdm.perm.check', 'rdm.project.member.list', ] as const /** 项目设置「文档职责 · 整文审签」页消费的命令面。 */ export const RDM_DOC_REVIEW_COMMANDS = [ 'rdm.review.assign.doc', 'rdm.review.doc.submit', 'rdm.review.doc.approve', 'rdm.review.doc.reject', 'rdm.review.doc.final', 'rdm.review.doc.status', ] as const export type CommandListItem = { id?: string; enabled?: boolean } | null | undefined function ready(cmds: CommandListItem[] | null | undefined, need: readonly string[]): boolean { if (!Array.isArray(cmds)) return false return need.every(n => cmds.some(c => c?.id === n && c.enabled === true)) } /** 角色分区是否就绪(普通基座无 rdm.role.* → 隐藏)。 */ export const rdmRolesReady = (cmds: CommandListItem[] | null | undefined) => ready(cmds, RDM_ROLES_COMMANDS) /** 项目成员页是否就绪。 */ export const rdmMembersReady = (cmds: CommandListItem[] | null | undefined) => ready(cmds, RDM_MEMBER_COMMANDS) /** 文档职责/整文审签页是否就绪。 */ export const rdmDocReviewReady = (cmds: CommandListItem[] | null | undefined) => ready(cmds, RDM_DOC_REVIEW_COMMANDS) /** 项目权限矩阵页是否就绪。 */ export const rdmPermMatrixReady = (cmds: CommandListItem[] | null | undefined) => ready(cmds, RDM_PERM_MATRIX_COMMANDS) /** RDM 动作清单(12 项,与 RDM perms.AllActions 对齐;角色动作编辑器选项)。 */ export const ROLE_ACTIONS = [ 'project.create', 'project.archive', 'member.manage', 'duty.assign', 'stage.advance', 'doc.read', 'doc.write', 'doc.publish', 'review.participate', 'trace.edit', 'knowledge.publish', 'project.settings', ] as const // ---- 载荷构造(字段名与 RDM 命令 schema 一一对应) ---- export const roleCreatePayload = (name: string) => ({ name }) export const roleActionPayload = (roleId: string, action: string, def: boolean, cap: boolean) => ({ roleId, action, default: def, cap }) export const roleAssignPayload = (owner: string, roleId: string) => ({ owner, roleId }) export const roleDisablePayload = (roleId: string, disabled: boolean) => ({ roleId, disabled }) export const memberAddPayload = (projectId: string, member: string, role: 'owner' | 'member' = 'member') => ({ projectId, member, role }) export const memberRemovePayload = (projectId: string, member: string) => ({ projectId, member }) export const assignDocDutiesPayload = ( projectId: string, docId: string, author: string, reviewers: string[], approver: string, ) => ({ projectId, docId, author, reviewers, approver }) export const docSubmitPayload = (projectId: string, docId: string) => ({ projectId, docId }) export const docApprovePayload = (projectId: string, docId: string, note?: string) => note ? { projectId, docId, note } : { projectId, docId } export const docRejectPayload = (projectId: string, docId: string, reason?: string) => reason ? { projectId, docId, reason } : { projectId, docId } /** round=0(未指定)不携带——服务端只认在途轮次,旧轮次传参即拒。 */ export const docFinalPayload = (projectId: string, docId: string, round = 0) => round > 0 ? { projectId, docId, round } : { projectId, docId } export const docStatusPayload = (projectId: string, docId: string) => ({ projectId, docId }) export const permCheckPayload = (projectId: string, member: string) => ({ projectId, member }) /** 矩阵来源标签(人话映射;未知来源回退原文——服务端新增来源时不至于空白)。 */ export function describeSource(source: string): string { switch (source) { case 'admin': return '系统管理员' case 'prof-role-default': return '专业角色默认' case 'override': return '项目内覆盖' case 'relation-default': return '项目关系默认' case 'non-member': return '非项目成员' default: return source } } /** 审阅人 CSV 解析:去空白、去空项、保序去重(表单输入 → reviewers 数组)。 */ export function parseReviewers(csv: string): string[] { const seen = new Set() const out: string[] = [] for (const part of csv.split(',')) { const v = part.trim() if (!v || seen.has(v)) continue seen.add(v) out.push(v) } return out }