using Rmsys.Domain; using Rmsys.Domain.Entities; namespace Rmsys.Api.Auth; /// /// 会议状态守卫与状态流转规则。 /// /// 背景:此前只有「删除会议」检查了状态(已上会不许删), /// 上传材料、改上会结果、写批注、改数据包设置全都没有判断, /// 导致已归档的会议还能被追加材料、篡改表决结果,且审计里看不出是会后补的。 /// public static class MeetingGuard { /// 允许修改内容的状态(结果登记后即冻结,归档后彻底冻结) private static readonly string[] Writable = { MeetingStatus.Draft, MeetingStatus.Reviewing, MeetingStatus.Delivered, MeetingStatus.Ongoing }; /// 返回 null 表示可写;否则返回拒绝原因 public static string? CheckWritable(Meeting m) { if (Writable.Contains(m.Status)) return null; return m.Status switch { MeetingStatus.Registered => "会议结果已登记,材料与表决不允许再修改(如需修改请先将状态撤回为「会议召开中」)", MeetingStatus.Archived => "会议已归档,不允许修改(如需修改请先「撤回归档」)", _ => $"会议当前状态({MeetingStatus.Label(m.Status)})不允许修改" }; } /// /// 状态流转表。只允许相邻推进/回退,不能从「已归档」直接跳回「筹备中」。 /// 「撤回归档」是唯一合法的逆向出口。 /// private static readonly Dictionary Transitions = new() { [MeetingStatus.Draft] = new[] { MeetingStatus.Reviewing }, [MeetingStatus.Reviewing] = new[] { MeetingStatus.Draft, MeetingStatus.Delivered }, [MeetingStatus.Delivered] = new[] { MeetingStatus.Reviewing, MeetingStatus.Ongoing }, [MeetingStatus.Ongoing] = new[] { MeetingStatus.Delivered, MeetingStatus.Registered }, [MeetingStatus.Registered] = new[] { MeetingStatus.Ongoing, MeetingStatus.Archived }, [MeetingStatus.Archived] = new[] { MeetingStatus.Registered } }; public static bool CanTransition(string from, string to) => !string.Equals(from, to, StringComparison.Ordinal) && Transitions.TryGetValue(from, out var allowed) && allowed.Contains(to); /// 允许的目标状态(给前端下拉/提示用) public static string[] AllowedNext(string from) => Transitions.TryGetValue(from, out var allowed) ? allowed : Array.Empty(); public static string TransitionDenied(string from, string to) => $"不能从「{MeetingStatus.Label(from)}」直接变更为「{MeetingStatus.Label(to)}」" + $"(允许的下一步:{string.Join("、", AllowedNext(from).Select(MeetingStatus.Label))})"; }