Files
railwaysystem/docs/visual/business-flow.html
T

689 lines
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>业务全景与状态机 - RailwaySystem SYFJGYD学员管理系统</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%231d4ed8' d='M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2.23l2-2H14l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4z'/></svg>">
<link rel="stylesheet" href="assets/docs.css">
<style>
/* 业务流图专属容器与样式 */
.flow-diagram-card {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
padding: 2rem;
box-shadow: var(--shadow-card);
margin-bottom: 2.5rem;
}
.flow-canvas-wrap {
width: 100%;
overflow-x: auto;
padding: 1rem 0;
}
.flow-svg {
min-width: 980px;
width: 100%;
height: auto;
display: block;
}
/* 节点可点击交互效果 */
.flow-node {
cursor: pointer;
transition: transform 0.15s ease, filter 0.15s ease;
}
.flow-node:hover {
filter: drop-shadow(0 4px 8px rgba(30, 64, 175, 0.18));
}
.flow-node:hover rect {
stroke: var(--rail-blue);
stroke-width: 2px;
}
.flow-node text {
pointer-events: none;
user-select: none;
}
/* 节点卡片速览网格 */
.flow-step-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.25rem;
margin-top: 1.5rem;
}
.step-clickable-card {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 1.25rem;
cursor: pointer;
box-shadow: var(--shadow-card);
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.step-clickable-card:hover {
border-color: var(--rail-blue-border);
transform: translateY(-2px);
box-shadow: var(--shadow-card-hover);
}
.step-clickable-card.side-branch {
border-left: 4px solid #0284c7;
background: #fcfdfe;
}
.step-clickable-card.support-layer {
border-left: 4px solid #6366f1;
background: #fbfbfe;
}
</style>
</head>
<body>
<!-- 顶部主导航 -->
<nav class="navbar">
<div class="nav-brand-wrap">
<a href="index.html" class="nav-brand">
<svg viewBox="0 0 24 24"><path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2.23l2-2H14l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4zM7.5 17c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm9 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6H6V6h12v5z"/></svg>
<div>
<div class="brand-title">RailwaySystem</div>
<div class="brand-sub">SYFJGYD学员管理系统</div>
</div>
</a>
<span class="brand-pill">Final Candidate · 待正式部署</span>
</div>
<ul class="nav-menu">
<li><a href="index.html" class="nav-link">首页</a></li>
<li><a href="business-flow.html" class="nav-link active">业务</a></li>
<li><a href="architecture.html" class="nav-link">系统</a></li>
<li><a href="testing.html" class="nav-link">质量</a></li>
<li><a href="release-status.html" class="nav-link">发布</a></li>
<li><a href="modules.html" class="nav-link">14大业务模块清单</a></li>
<li><a href="data-model.html" class="nav-link">实体关系模型(ER)</a></li>
<li><a href="deployment.html" class="nav-link">部署拓扑与脚本Delta</a></li>
<li><a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/handbook/08-deployment-and-release.md" target="_blank" rel="noopener noreferrer" class="nav-handbook-link">&gt; 查阅部署发布手册</a></li>
<li><a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/README.md" target="_blank" rel="noopener noreferrer" class="nav-back-link">&larr; 文档中心</a></li>
</ul>
</nav>
<!-- 页面主体容器 -->
<main class="page-container">
<header class="page-hero">
<div class="hero-pretitle">Business Process Architecture</div>
<h1 class="hero-title-main">业务全景流程与状态机</h1>
<p class="hero-lead-text">
主视觉聚焦于基础资源编排至结训考核的线性主脉络,解耦呈现后勤住宿支线与电子班牌硬件支撑层。点击任意节点可展开查看其状态机、业务约束与技术契约。
</p>
</header>
<!-- 流程图视觉卡片 -->
<section class="flow-diagram-card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 0.75rem;">
<div>
<h2 style="font-size: 1.25rem; font-weight: 700; color: var(--navy-900);">全业务生命周期协同拓扑</h2>
<p style="font-size: 0.82rem; color: var(--text-muted); margin-top: 0.2rem;">点击拓扑节点查看抽屉详情,支持键盘 ESC 快速退出</p>
</div>
<div style="display: flex; gap: 0.85rem; align-items: center; font-size: 0.8rem;">
<span style="display: inline-flex; align-items: center; gap: 4px;"><span style="width: 12px; height: 12px; background: #eff6ff; border: 1px solid #1d4ed8; border-radius: 2px;"></span> 主干链路</span>
<span style="display: inline-flex; align-items: center; gap: 4px;"><span style="width: 12px; height: 12px; background: #f0f9ff; border: 1px solid #0284c7; border-radius: 2px;"></span> 住宿支线</span>
<span style="display: inline-flex; align-items: center; gap: 4px;"><span style="width: 12px; height: 12px; background: #eef2ff; border: 1px solid #6366f1; border-radius: 2px;"></span> 班牌支撑</span>
</div>
</div>
<div class="flow-canvas-wrap">
<svg class="flow-svg" viewBox="0 0 1120 420" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow-blue" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
<path d="M0,1 L7,4 L0,7 Z" fill="#1d4ed8" />
</marker>
<marker id="arrow-cyan" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
<path d="M0,1 L7,4 L0,7 Z" fill="#0284c7" />
</marker>
<marker id="arrow-purple" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
<path d="M0,1 L7,4 L0,7 Z" fill="#6366f1" />
</marker>
</defs>
<!-- 区域背景标注 -->
<rect x="10" y="20" width="1100" height="150" rx="12" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="4 4" />
<text x="25" y="42" font-size="12" font-weight="700" fill="#64748b">CORE TRAINING LIFECYCLE (核心业务主脉络)</text>
<rect x="420" y="195" width="240" height="95" rx="8" fill="#f0f9ff" stroke="#bae6fd" stroke-width="1" />
<text x="435" y="218" font-size="11" font-weight="700" fill="#0284c7">LOGISTICS BRANCH (后勤住宿支线)</text>
<rect x="420" y="310" width="460" height="95" rx="8" fill="#eef2ff" stroke="#c7d2fe" stroke-width="1" />
<text x="435" y="333" font-size="11" font-weight="700" fill="#6366f1">FACILITIES LAYER (电子班牌显示支撑层)</text>
<!-- 主干流程连接线 -->
<!-- 1 -> 2 -->
<line x1="115" y1="95" x2="145" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 2 -> 3 -->
<line x1="245" y1="95" x2="275" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 3 -> 4 -->
<line x1="375" y1="95" x2="405" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 4 -> 5 -->
<line x1="505" y1="95" x2="535" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 5 -> 6 -->
<line x1="635" y1="95" x2="665" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 6 -> 7 -->
<line x1="765" y1="95" x2="795" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 7 -> 8 -->
<line x1="895" y1="95" x2="925" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 8 -> 9 -->
<line x1="1025" y1="95" x2="1045" y2="95" stroke="#1d4ed8" stroke-width="2" marker-end="url(#arrow-blue)" />
<!-- 支线与支撑连接线 -->
<!-- 4 -> 住宿支线 -->
<path d="M 455 125 L 455 235 L 475 235" fill="none" stroke="#0284c7" stroke-width="1.8" stroke-dasharray="3 3" marker-end="url(#arrow-cyan)" />
<!-- 5 -> 班牌支撑 -->
<path d="M 585 125 L 585 350 L 595 350" fill="none" stroke="#6366f1" stroke-width="1.8" stroke-dasharray="3 3" marker-end="url(#arrow-purple)" />
<!-- 班牌 -> 7 课次 -->
<path d="M 720 350 L 720 125" fill="none" stroke="#6366f1" stroke-width="1.8" stroke-dasharray="3 3" marker-end="url(#arrow-purple)" />
<!-- 节点 1: 基础资源 -->
<g class="flow-node" onclick="handleNodeClick('resource')">
<rect x="25" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="70" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">1. 基础资源</text>
<text x="70" y="110" text-anchor="middle" font-size="10" fill="#64748b">师资/教室/场地</text>
</g>
<!-- 节点 2: 开班计划 -->
<g class="flow-node" onclick="handleNodeClick('plan')">
<rect x="155" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="200" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">2. 开班计划</text>
<text x="200" y="110" text-anchor="middle" font-size="10" fill="#64748b">拟定班期/容量</text>
</g>
<!-- 节点 3: 本期课程 -->
<g class="flow-node" onclick="handleNodeClick('course')">
<rect x="285" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="330" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">3. 本期课程</text>
<text x="330" y="110" text-anchor="middle" font-size="10" fill="#64748b">师资绑定/学时</text>
</g>
<!-- 节点 4: 参训人员 -->
<g class="flow-node" onclick="handleNodeClick('enrollment')">
<rect x="415" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="460" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">4. 参训人员</text>
<text x="460" y="110" text-anchor="middle" font-size="10" fill="#64748b">名单导入/防超限</text>
</g>
<!-- 节点 5: 排课准备 -->
<g class="flow-node" onclick="handleNodeClick('scheduling')">
<rect x="545" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="590" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">5. 排课准备</text>
<text x="590" y="110" text-anchor="middle" font-size="10" fill="#64748b">四维预检/XOR互斥</text>
</g>
<!-- 节点 6: 开班报到 -->
<g class="flow-node" onclick="handleNodeClick('report')">
<rect x="675" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="720" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">6. 开班报到</text>
<text x="720" y="110" text-anchor="middle" font-size="10" fill="#64748b">现场核验/状态联动</text>
</g>
<!-- 节点 7: 正式培训 -->
<g class="flow-node" onclick="handleNodeClick('training')">
<rect x="805" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="850" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">7. 正式培训</text>
<text x="850" y="110" text-anchor="middle" font-size="10" fill="#64748b">课表执行/课次授课</text>
</g>
<!-- 节点 8: 课程考勤 -->
<g class="flow-node" onclick="handleNodeClick('attendance')">
<rect x="935" y="65" width="90" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="980" y="92" text-anchor="middle" font-size="13" font-weight="700" fill="#0f172a">8. 课程考勤</text>
<text x="980" y="110" text-anchor="middle" font-size="10" fill="#64748b">签到签退/留痕修正</text>
</g>
<!-- 节点 9: 结训与分析 -->
<g class="flow-node" onclick="handleNodeClick('completion')">
<rect x="1055" y="65" width="45" height="60" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.5" />
<text x="1077" y="92" text-anchor="middle" font-size="11" font-weight="700" fill="#0f172a">9.结训</text>
<text x="1077" y="110" text-anchor="middle" font-size="9" fill="#64748b">保护</text>
</g>
<!-- 支线节点: 后勤住宿 -->
<g class="flow-node" onclick="handleNodeClick('dormitory')">
<rect x="485" y="225" width="160" height="55" rx="8" fill="#ffffff" stroke="#0284c7" stroke-width="1.5" />
<text x="565" y="250" text-anchor="middle" font-size="12" font-weight="700" fill="#0284c7">后勤住宿准备 (支线)</text>
<text x="565" y="268" text-anchor="middle" font-size="10" fill="#64748b">住宿标记 · 房间分配 · 入住</text>
</g>
<!-- 支撑层节点: 电子班牌 -->
<g class="flow-node" onclick="handleNodeClick('board')">
<rect x="585" y="340" width="280" height="55" rx="8" fill="#ffffff" stroke="#6366f1" stroke-width="1.5" />
<text x="725" y="363" text-anchor="middle" font-size="12" font-weight="700" fill="#4f46e5">电子班牌终端映射 (支撑层)</text>
<text x="725" y="381" text-anchor="middle" font-size="10" fill="#64748b">UUID主键 · 教室绑定0/1/N · 课表降级</text>
</g>
</svg>
</div>
</section>
<!-- 节点图文卡片清单(供直观速查与点击唤起抽屉) -->
<section class="content-section">
<div class="section-header">
<div>
<h2 class="section-title">业务主链环节与支线速览</h2>
<p class="section-subtitle">点击任意卡片即可在右侧展开该环节的角色、规则约束、状态机流转与物理接口。</p>
</div>
</div>
<div class="flow-step-grid">
<div class="step-clickable-card" onclick="handleNodeClick('resource')">
<div class="card-top">
<span class="badge badge-pass">环节 1 · 主干</span>
<span class="card-code">Master Data</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">基础资源录入与治理</h3>
<p class="card-body-text">建立教师、学员主档、标准化课程、理论教室与演练场地。强校验手机号唯一性与脱敏存储。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('plan')">
<div class="card-top">
<span class="badge badge-pass">环节 2 · 主干</span>
<span class="card-code">Plan Lifecycle</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">开班计划拟定</h3>
<p class="card-body-text">生成 32 位 UUID 计划,强制初始状态为 0:草稿。校验起止日期有效性与招生容量。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('course')">
<div class="card-top">
<span class="badge badge-pass">环节 3 · 主干</span>
<span class="card-code">Plan Courses</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">本期课程与师资绑定</h3>
<p class="card-body-text">建立计划与课程的 N:M 关联,指定主讲教师,系统自动汇总开班总学时并保持数据一致性。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('enrollment')">
<div class="card-top">
<span class="badge badge-pass">环节 4 · 主干</span>
<span class="card-code">Enrollment Guard</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">参训人员入班与防超额</h3>
<p class="card-body-text">学员初始参训状态为 0:待报到。严格校验容量上限 (40031) 及重复报名拦截 (40030)。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('scheduling')">
<div class="card-top">
<span class="badge badge-pass">环节 5 · 主干</span>
<span class="card-code">Scheduling Engine</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">排课四维冲突预检</h3>
<p class="card-body-text">智能检测教师时段、场地占用与班级重叠。支持 Warning 级别确认保存,硬阻断场地 XOR 互斥。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('report')">
<div class="card-top">
<span class="badge badge-pass">环节 6 · 主干</span>
<span class="card-code">Report Confirm</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">现场开班报到确认</h3>
<p class="card-body-text">核验报到时限与资格,强事务联动学员状态至 1:已报到,并记录精确报到时间,防重复报到。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('attendance')">
<div class="card-top">
<span class="badge badge-pass">环节 8 · 主干</span>
<span class="card-code">Attendance Actions</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">课堂考勤打卡与审计</h3>
<p class="card-body-text">签到签退幂等性保证,重复签到签退保留初次时间。人工代签/补签自动记录 manual_flag=1 审计留痕。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card" onclick="handleNodeClick('completion')">
<div class="card-top">
<span class="badge badge-pass">环节 9 · 主干</span>
<span class="card-code">Plan Complete</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">结训流转与退训保护</h3>
<p class="card-body-text">计划流转为 2:已结束,在读活动学员自动批量流转为 4:已完成。历史退训记录严格锁定防破坏。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">点击查看规则 &rarr;</div>
</div>
<div class="step-clickable-card side-branch" onclick="handleNodeClick('dormitory')">
<div class="card-top">
<span class="badge badge-info">支线 · 后勤</span>
<span class="card-code">Accommodation</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">后勤住宿管理支线</h3>
<p class="card-body-text">学员报名时勾选 accommodationRequired 触发宿舍分配,管理房间可用床位与入住注销。</p>
<div style="font-size: 0.78rem; color: var(--rail-cyan); font-weight: 600;">点击查看支线 &rarr;</div>
</div>
<div class="step-clickable-card support-layer" onclick="handleNodeClick('board')">
<div class="card-top">
<span class="badge badge-neutral" style="color: #6366f1; background: #eef2ff; border-color: #c7d2fe;">支撑 · 物联</span>
<span class="card-code">Electronic Board</span>
</div>
<h3 class="card-title" style="margin-bottom: 0.4rem;">电子班牌显示支撑层</h3>
<p class="card-body-text">教室绑定 0/1/N 班牌关系,课表 timetable 智能降级映射 boardSn,支持逻辑删除保护。</p>
<div style="font-size: 0.78rem; color: #6366f1; font-weight: 600;">点击查看支撑 &rarr;</div>
</div>
</div>
</section>
</main>
<!-- 侧边抽屉组件 (Drawer) -->
<div class="drawer-backdrop" id="drawer-backdrop">
<div class="drawer-panel">
<div class="drawer-header">
<h3 class="drawer-title" id="drawer-title">节点详情</h3>
<button class="drawer-close" id="drawer-close-btn" type="button" aria-label="关闭">&times;</button>
</div>
<div class="drawer-body" id="drawer-body">
<!-- 动态注入内容 -->
</div>
</div>
</div>
<footer class="site-footer">
<p>RailwaySystem SYFJGYD学员管理系统 · 交付候选版 (Final Candidate)</p>
<p style="margin-top: 0.35rem; color: var(--text-muted);">
业务流程与状态机规范 · <a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/handbook/02-business-process.md" target="_blank" rel="noopener noreferrer">查阅业务流程详细手册</a>
</p>
</footer>
<script src="assets/docs.js"></script>
<script>
// 抽屉详情字典库 (据实编写,无扩写)
const nodeDetails = {
resource: {
title: "1. 基础资源录入与治理 (Master Data)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>教务管理员</strong>:在开学或开班前,统一录入师资档案(姓名、职称、联系电话)、学员主档、标准课程库以及校区空间(理论教室、实训演练基地)。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">关键业务规则与约束</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>教师手机号唯一性</strong>:同一手机号不可重复录入,重复时触发业务错误拦截(40001)。</li>
<li><strong>学员脱敏与哈希保护</strong>:学员身份证存储密文与哈希,列表及详情接口仅返回末4位脱敏掩码,避免明文泄露。</li>
<li><strong>空间互斥 XOR 属性</strong>:理论教室与实训场地在底层物理模型中属于独立实体,供后续排课互斥引用。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/teacher
POST /train/student
POST /train/course
POST /train/classroom
POST /train/training-site</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_teacher</code>, <code>train_student</code>, <code>train_course</code>, <code>train_classroom_info</code>, <code>train_training_site</code></p>
</div>
`
},
plan: {
title: "2. 培训班计划拟定 (Training Plan)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>培训主管 / 教务科</strong>:拟定开班方案,录入培训班名称、起止日期、报到截止日期、计划招生人数上限及办班地点。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">状态机与业务约束</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>主键生成</strong>:后端自动生成全局唯一的 32 位 UUID 主键。</li>
<li><strong>初始状态机</strong>:创建成功后,计划状态强制为 <code>0:草稿 (DRAFT)</code>。</li>
<li><strong>时间窗口校验</strong>:起始时间必须小于结束时间,且报到截止时间不得晚于培训起始时间。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/plans
GET /train/plans/{id}
PUT /train/plans/{id}</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_class_plan</code> (字段: <code>plan_id</code>, <code>plan_name</code>, <code>plan_status</code>, <code>start_time</code>, <code>end_time</code>, <code>recruit_num</code>)</p>
</div>
`
},
course: {
title: "3. 计划课程与师资绑定 (Plan Courses)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>教务排课员</strong>:为处于草稿状态的培训班指定本期教学大纲中的课程清单,并指派授课教师。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">关键业务规则</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>关联一致性</strong>:支持批量绑定 <code>cIds</code>,生成 <code>train_plan_course</code> 映射。</li>
<li><strong>学时汇总</strong>:系统根据所选课程的 <code>c_duration</code> 自动累加该班期总课时。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/plans/{id}/courses
GET /train/plans/{id}/courses</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_plan_course</code> (字段: <code>plan_course_id</code>, <code>plan_id</code>, <code>c_id</code>, <code>t_id</code>, <code>remark</code>)</p>
</div>
`
},
enrollment: {
title: "4. 学员入班与招生容量守卫 (Enrollment)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>站段劳资 / 培训管理员</strong>:根据各站段参训名单,将合格在册学员批量或逐一报名单录入培训班。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">状态机与安全拦截规则</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>初始参训状态</strong>:入班成功后,参训状态初始为 <code>0:待报到 (PENDING)</code>。</li>
<li><strong>同一计划重复报名拦截</strong>:严格唯一索引拦截,返回业务码 <code>40030: ENROLLMENT_ALREADY_EXISTS</code>。</li>
<li><strong>计划招生容量超限拦截</strong>:入班人数达到 <code>recruit_num</code> 上限时,拦截返回 <code>40031: ENROLLMENT_CAPACITY_EXCEEDED</code>。</li>
<li><strong>停用学员拦截</strong>:若学员主档状态为 <code>status=0</code>,拒绝入班。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/plans/{id}/enrollments
GET /train/plans/{id}/enrollments
PUT /train/enrollments/{id}/status</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_enrollment</code> (字段: <code>enrollment_id</code>, <code>plan_id</code>, <code>student_id</code>, <code>enrollment_status</code>, <code>accommodation_required</code>)</p>
</div>
`
},
scheduling: {
title: "5. 排课四维冲突预检与保存 (Scheduling)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>教务排课员</strong>:在开课前编制课次(Lesson),安排上课时段、授课教师及教学空间。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">排课预检与硬阻断规则</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>四维冲突预检</strong>:检测教师时段冲突 (TEACHER)、教室占用 (CLASSROOM Warning)、场地占用 (SITE Warning) 及班级课次重叠 (PLAN_OVERLAP Warning)。</li>
<li><strong>Warning 非硬阻断</strong>:教室、场地与班级重叠为 Warning 级别,管理员确认后标准 POST 正常保存,禁止任何旁路 force 参数。</li>
<li><strong>场地 XOR 互斥硬阻断</strong>:<code>venue_type</code> 严格控制理论教室与实训场地二选一,不可同时传或同时为空。</li>
<li><strong>非法时间硬阻断</strong>:起止时间倒挂拦截(40010);已结束计划禁止排课(40012)。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/lessons/check-conflict
POST /train/plans/{id}/lessons
GET /train/plans/{id}/timetable</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_lesson</code> (字段: <code>lesson_id</code>, <code>plan_course_id</code>, <code>venue_type</code>, <code>class_room_id</code>, <code>site_id</code>, <code>start_time</code>, <code>end_time</code>)</p>
</div>
`
},
report: {
title: "6. 现场开班报到确认 (Report Confirm)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>基地教务值班员 / 学员</strong>:学员到达培训基地现场,通过刷卡、人脸或人工核验完成开班报到。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">报到状态联动与防重复机制</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>状态强事务流转</strong>:调用报到确认接口后,生成 <code>train_class_report</code> 记录,同时将学员在 <code>train_enrollment</code> 的状态由 <code>0:待报到</code> 更新为 <code>1:已报到</code>,并写入 <code>join_time</code>。</li>
<li><strong>重复报到拦截</strong>:已报到学员再次报到时直接拦截,返回业务错误码 <code>40050</code>。</li>
<li><strong>报到时限校验</strong>:超过计划设定的 <code>report_deadline</code> 需由管理员特别确认。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/enrollments/{enrollmentId}/report
GET /train/report/list</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_class_report</code> (字段: <code>report_id</code>, <code>enrollment_id</code>, <code>report_time</code>, <code>report_way</code>, <code>verify_note</code>)</p>
</div>
`
},
training: {
title: "7. 正式培训与课表执行 (Training Execution)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>任课教师 / 学员</strong>:依照课表安排,在指定理论教室或实训场地开展教学。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">业务支持特性</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>课表聚合视图</strong>:<code>GET /train/plans/{id}/timetable</code> 统一映射聚合 <code>courseName</code>, <code>teacherName</code>, <code>venueName</code> 及 <code>boardSn</code>。</li>
<li><strong>现场物联呈现</strong>:教室门口电子班牌实时显示当前课程与教师信息。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>GET /train/plans/{id}/timetable</code></pre>
</div>
`
},
attendance: {
title: "8. 课程考勤打卡与审计留痕 (Attendance)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>授课教师 / 随班班主任</strong>:跟踪学员课次到课率,处理签到、签退及异常请假补签。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">考勤幂等性与审计规范</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>签到签退幂等性</strong>:重复请求签到或签退时,保持首次打卡时间 <code>signInTime / signOutTime</code>,杜绝覆盖。</li>
<li><strong>人工代签留痕</strong>:管理员或教师人工修改考勤状态时,系统自动置 <code>manual_flag = 1</code>,记录修改人与修改备注供合规追溯。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/attendance/sign-in
POST /train/attendance/sign-out
PUT /train/attendance/{checkinId}
GET /train/attendance/list</code></pre>
</div>
<div class="drawer-section">
<div class="drawer-section-title">物理数据模型</div>
<p><code>train_student_checkin</code> (字段: <code>checkin_id</code>, <code>enrollment_id</code>, <code>lesson_id</code>, <code>sign_in_time</code>, <code>sign_out_time</code>, <code>manual_flag</code>)</p>
</div>
`
},
completion: {
title: "9. 结训生命周期与退训保护 (Plan Completion)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">核心业务角色与职责</div>
<p><strong>教务科主管</strong>:培训周期结束、考核完毕后,发起班期正式结训动作。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">强事务联动与退训保护不变量</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>计划状态变更</strong>:<code>train_class_plan.plan_status</code> 由 <code>1:进行中</code> 变更为 <code>2:已结束</code>。</li>
<li><strong>在读活动学员自动完成</strong>:状态为 <code>0:待报到</code> 或 <code>1:已报到</code> 的学员,参训状态自动批量流转为 <code>4:已完成</code>,且 <code>complete_time</code> 真实写入。</li>
<li><strong>历史退训学员状态防误覆盖 (核心保护)</strong>:提前申请退训的学员 (<code>enrollmentStatus = 3</code>) 状态严格保持为 3,<code>complete_time</code> 保持为空,严禁被批量完成逻辑覆盖。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口契约</div>
<pre><code>POST /train/plans/{id}/complete</code></pre>
</div>
`
},
dormitory: {
title: "后勤住宿管理支线 (Accommodation)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">业务定位与流程衔接</div>
<p><strong>后勤宿管人员</strong>:作为核心教学的辅助支线,在学员报名时由 <code>accommodationRequired = 1</code> 触发,分配基地宿舍床位,结训时同步退宿交接。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口与数据表</div>
<pre><code>GET /train/dormitory/list
POST /train/dormitory/check-in
POST /train/dormitory/check-out</code></pre>
<p>物理表:<code>train_dormitory_info</code>, <code>train_accommodation</code></p>
</div>
`
},
board: {
title: "电子班牌显示支撑层 (Electronic Board)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物联设备支撑定位</div>
<p><strong>设备管理与现场终端</strong>:作为教学排课的呈现层,将物理班牌与教室绑定,并在课表及前端路由 <code>/equip/board</code> 中输出信息。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">核心验证规则与降级逻辑</div>
<ul style="padding-left: 1.25rem; line-height: 1.8;">
<li><strong>UUID 主键生成</strong>:自动生成 32 位 UUID 主键,默认在线状态 <code>board_status = 0</code>。</li>
<li><strong>教室绑定 0/1/N 映射</strong>:<code>GET /train/classroom/{id}</code> 准确返回绑定的班牌数组。</li>
<li><strong>课表 boardSn 智能降级规则</strong>:
<br>· 教室未绑板:<code>boardSn = null</code>;
<br>· 教室恰好绑 1 块板:<code>boardSn = 班牌序列号</code>;
<br>· 教室绑定 >1 块板:<code>boardSn = null</code> (避免多板歧义降级)。
</li>
<li><strong>逻辑删除保障</strong>:执行删除后,底层置 <code>del_flag = '2'</code>,常规查询接口不可见。</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">技术接口与物理表</div>
<pre><code>POST /train/board
GET /train/board/list
DELETE /train/board/{id}</code></pre>
<p>物理表:<code>train_electronic_board</code> (主键 <code>board_id</code> 32 位 UUID)</p>
</div>
`
}
};
function handleNodeClick(nodeKey) {
const data = nodeDetails[nodeKey];
if (!data) return;
window.openDrawer(data.title, data.html);
}
</script>
</body>
</html>