刘光辉
10 小时以前 1c0304091d5a7dd05859167f5e417cf932c67028
字段信息基础班
5个文件已添加
3448 ■■■■■ 已修改文件
docs/tms/gen_tms_schema.py 786 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/tms/gen_xlsx.py 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/tms/tms_schema_pg.sql 2171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/tms/玻思韬TMS-JNPF数据表字段清单.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/tms/玻思韬TMS-JNPF数据表设计.md 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/tms/gen_tms_schema.py
New file
@@ -0,0 +1,786 @@
# -*- coding: utf-8 -*-
from pathlib import Path
SYS = [
    ("f_id", "varchar(50)", "NOT NULL", "主键"),
    ("f_tenant_id", "varchar(50)", None, "租户"),
]
SYS_AUDIT = [
    ("f_creator_user_id", "varchar(50)", None, "创建人"),
    ("f_creator_time", "timestamp", None, "创建时间"),
    ("f_last_modify_user_id", "varchar(50)", None, "修改人"),
    ("f_last_modify_time", "timestamp", None, "修改时间"),
    ("f_delete_mark", "int", "NOT NULL DEFAULT 0", "删除标记 0正常 1删除"),
    ("f_delete_user_id", "varchar(50)", None, "删除人"),
    ("f_delete_time", "timestamp", None, "删除时间"),
]
SYS_FLOW = [
    ("f_flow_id", "varchar(50)", None, "流程模板ID"),
    ("f_flow_task_id", "varchar(50)", None, "流程任务ID"),
    ("f_flow_state", "int", None, "流程状态"),
]
CHILD_FK = ("f_foreign_id", "varchar(50)", "NOT NULL", "主表f_id")
def col(name, typ, extra=None, cmt=""):
    return (name, typ, extra, cmt)
tables = []
def add(name, comment, cols, indexes=None, child=False, flow=False, delete_user=True):
    fields = list(SYS)
    if child:
        fields.append(CHILD_FK)
    fields.extend(cols)
    if flow:
        fields.extend(SYS_FLOW)
    audit = list(SYS_AUDIT)
    if not delete_user:
        audit = [x for x in audit if x[0] not in ("f_delete_user_id", "f_delete_time")]
    fields.extend(audit)
    tables.append({
        "name": name,
        "comment": comment,
        "cols": fields,
        "indexes": indexes or [],
        "child": child,
    })
# ===== 1 åŸºç¡€è®¾ç½® =====
add("tms_place", "培训地点。在线学习任务地点可空;占用按小时与未取消任务时间交叉校验。", [
    col("place_no", "varchar(50)", "NOT NULL", "地点编号,单据规则 A+YYYYMM+4,如 A2026090001"),
    col("place_name", "varchar(200)", "NOT NULL", "地点名称"),
    col("place_group", "varchar(50)", None, "组别/分区,字典:办公区/生产区/A区/B区"),
    col("enabled", "varchar(20)", "NOT NULL DEFAULT '1'", "1启用 0停用"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[
    ("uk", "uk_tms_place_no", ["place_no"], True),
    ("idx", "idx_tms_place_group", ["place_group", "enabled"], False),
])
add("tms_trainer", "培训师档案,关联系统用户;重复录入按user_id识别;账号注销后自动废弃。", [
    col("archive_no", "varchar(50)", None, "档案编号 AR+YYYY+4,可与个人培训档案共用"),
    col("user_id", "varchar(50)", "NOT NULL", "系统用户ID"),
    col("trainer_name", "varchar(100)", None, "姓名(冗余,便于列表)"),
    col("dept_id", "varchar(50)", None, "部门"),
    col("post_id", "varchar(50)", None, "岗位"),
    col("school", "varchar(200)", None, "毕业院校"),
    col("education", "varchar(50)", None, "学历"),
    col("major", "varchar(100)", None, "专业"),
    col("tech_title", "varchar(100)", None, "技术职称"),
    col("hire_date", "date", None, "入职时间"),
    col("license", "varchar(200)", None, "执业证书"),
    col("work_years", "varchar(50)", None, "工作年限"),
    col("trainer_level", "varchar(50)", None, "培训师级别"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'valid'", "valid有效期内 / revoked已撤销"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[
    ("uk", "uk_tms_trainer_user", ["user_id"], True),
    ("uk", "uk_tms_trainer_no", ["archive_no"], True),
    ("idx", "idx_tms_trainer_status", ["biz_status"], False),
])
add("tms_trainer_work", "培训师个人工作经历子表", [
    col("sort_no", "int", None, "序号"),
    col("hire_date", "date", None, "入职时间"),
    col("leave_date", "date", None, "离职时间"),
    col("company_name", "varchar(200)", None, "公司名称"),
    col("post_name", "varchar(100)", None, "任职岗位"),
    col("leave_reason", "varchar(200)", None, "离职原因"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_trainer_work_fid", ["f_foreign_id"], False)])
add("tms_trainer_exp", "培训师个人培训经历子表", [
    col("sort_no", "int", None, "序号"),
    col("train_date", "date", None, "培训时间"),
    col("org_name", "varchar(200)", None, "培训机构"),
    col("cert_name", "varchar(200)", None, "培训证书"),
    col("cert_file", "text", None, "证书附件"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_trainer_exp_fid", ["f_foreign_id"], False)])
add("tms_admin_auth", "培训管理员跨部门授权。到期前一个月提醒管理员/培训师。", [
    col("admin_user_id", "varchar(50)", "NOT NULL", "培训管理员"),
    col("scope_dept_id", "text", "NOT NULL", "管理范围,组织多选"),
    col("include_child", "varchar(10)", "NOT NULL DEFAULT '1'", "是否包含下级部门 1是 0否"),
    col("registrar_id", "varchar(50)", None, "登记人"),
    col("register_date", "date", None, "登记日期"),
    col("expire_date", "date", None, "授权到期日,可空表示长期"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'valid'", "valid有效 / revoked废弃"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("idx", "idx_tms_admin_auth_user", ["admin_user_id", "biz_status"], False)])
add("tms_train_mode", "培训方式配置(含签到规则,不仅是字典)", [
    col("mode_code", "varchar(50)", "NOT NULL", "编码:onsite/practice/online"),
    col("mode_name", "varchar(100)", "NOT NULL", "名称:集中授课/操作授课/在线学习"),
    col("sign_rule", "varchar(20)", "NOT NULL DEFAULT 'both'", "scan仅扫码 / both扫码+在线"),
    col("allow_guest_sign", "varchar(10)", "NOT NULL DEFAULT '0'", "允许名单外签到,集中授课=1"),
    col("same_day_required", "varchar(10)", "NOT NULL DEFAULT '0'", "开始结束必须同一天,集中/操作=1"),
    col("place_required", "varchar(10)", "NOT NULL DEFAULT '1'", "地点必填,在线学习=0"),
    col("sort_no", "int", None, "排序"),
    col("enabled", "varchar(10)", "NOT NULL DEFAULT '1'", "启用"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("uk", "uk_tms_train_mode_code", ["mode_code"], True)])
add("tms_sign_mode", "签到方式:APP扫码 / å¾®ä¿¡æ‰«ç  / åœ¨çº¿ç­¾åˆ°", [
    col("mode_code", "varchar(50)", "NOT NULL", "编码:app_scan/wx_scan/online"),
    col("mode_name", "varchar(100)", "NOT NULL", "名称"),
    col("tip_seconds", "int", "NOT NULL DEFAULT 30", "有培训要点时强制阅读秒数,IT可调"),
    col("match_roster", "varchar(10)", "NOT NULL DEFAULT '1'", "是否必须匹配培训对象名单"),
    col("enabled", "varchar(10)", "NOT NULL DEFAULT '1'", "启用"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("uk", "uk_tms_sign_mode_code", ["mode_code"], True)])
add("tms_eval_mode", "培训效果评估方式:提问 / çŽ°åœºæ“ä½œ / åœ¨çº¿è€ƒè¯• / æ— éœ€è€ƒæ ¸", [
    col("mode_code", "varchar(50)", "NOT NULL", "编码:quiz/practice/exam/none"),
    col("mode_name", "varchar(100)", "NOT NULL", "名称"),
    col("need_paper", "varchar(10)", "NOT NULL DEFAULT '0'", "是否需要试卷"),
    col("need_quiz", "varchar(10)", "NOT NULL DEFAULT '0'", "是否需要提问预设"),
    col("need_practice", "varchar(10)", "NOT NULL DEFAULT '0'", "是否需要实操评分"),
    col("enabled", "varchar(10)", "NOT NULL DEFAULT '1'", "启用"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("uk", "uk_tms_eval_mode_code", ["mode_code"], True)])
add("tms_course", "培训课程主数据,计划/任务可引用", [
    col("course_no", "varchar(50)", None, "课程编号"),
    col("course_name", "varchar(200)", "NOT NULL", "课程名称"),
    col("category", "varchar(50)", None, "课程分类"),
    col("train_mode", "varchar(50)", None, "培训方式编码"),
    col("eval_mode", "varchar(50)", None, "考核方式编码"),
    col("paper_id", "varchar(50)", None, "关联试卷"),
    col("hours", "numeric(8,1)", None, "学时"),
    col("enabled", "varchar(10)", "NOT NULL DEFAULT '1'", "启用"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("uk", "uk_tms_course_no", ["course_no"], True)])
add("tms_material", "培训教材/企业知识库。可上传本地或引用DMS(需鉴权)。撤销后不可再引用。", [
    col("material_no", "varchar(50)", "NOT NULL", "教材编号 TM+YYYYMM+4"),
    col("material_name", "varchar(500)", "NOT NULL", "教材名称"),
    col("file_type", "varchar(50)", None, "Word/PPT/Excel/PDF/视频/音频"),
    col("apply_time", "date", None, "申请时间"),
    col("applicant_id", "varchar(50)", None, "上传申请人"),
    col("apply_dept_id", "varchar(50)", None, "申请人部门"),
    col("file_json", "text", None, "教材附件(JNPF附件JSON)"),
    col("dms_file_id", "varchar(50)", None, "引用的DMS文件ID"),
    col("quote_count", "int", "NOT NULL DEFAULT 0", "被任务引用次数"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'valid'", "valid有效 / revoked已撤销"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[
    ("uk", "uk_tms_material_no", ["material_no"], True),
    ("idx", "idx_tms_material_type", ["file_type", "biz_status"], False),
])
# ===== 2 è€ƒè¯•引擎 =====
add("tms_question_bank", "题库", [
    col("bank_name", "varchar(200)", "NOT NULL", "题库名称"),
    col("question_count", "int", "NOT NULL DEFAULT 0", "试题数量(维护时回写)"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'normal'", "normal正常 / invalid失效"),
    col("remark", "text", None, "备注"),
], indexes=[("idx", "idx_tms_qbank_status", ["biz_status"], False)])
add("tms_question", "试题。修改不影响已完成考试快照。", [
    col("question_no", "varchar(50)", None, "试题编号,可流水"),
    col("bank_id", "varchar(50)", "NOT NULL", "所属题库 tms_question_bank.f_id"),
    col("question_type", "varchar(20)", "NOT NULL", "single单选 / multi多选 / judge判断 / blank填空 / essay问答"),
    col("difficulty", "varchar(20)", None, "easy/normal/hard,界面默认一般"),
    col("source_type", "varchar(50)", None, "试题来源,默认自命题"),
    col("stem", "text", "NOT NULL", "题干,富文本"),
    col("analysis", "text", None, "答案解析/备注,错题可显示"),
    col("admin_user_id", "varchar(50)", None, "管理员"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'open'", "open开放 / closed不开放 / invalid废弃"),
], indexes=[
    ("idx", "idx_tms_question_bank", ["bank_id", "question_type", "biz_status"], False),
])
add("tms_question_option", "试题选项。单选单选中,多选可多选中,判断可做成A对B错。", [
    col("sort_no", "int", None, "序号"),
    col("option_label", "varchar(10)", None, "A/B/C/D..."),
    col("option_content", "text", None, "选项内容"),
    col("is_correct", "varchar(10)", "NOT NULL DEFAULT '0'", "是否正确答案 1是 0否"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_qopt_fid", ["f_foreign_id"], False)])
add("tms_paper", "试卷。失效试卷不得再用于新任务;改卷不影响已考记录。", [
    col("paper_name", "varchar(300)", "NOT NULL", "试卷名称"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'open'", "open开放 / closed不开放 / invalid失效"),
    col("duration_min", "int", "NOT NULL DEFAULT 60", "考试时长(分钟)"),
    col("exam_start", "timestamp", None, "试卷级开考时间,可空,任务上再设"),
    col("exam_end", "timestamp", None, "试卷级结考时间,可空"),
    col("score_publish_time", "timestamp", None, "成绩公布时间,空=立即公布"),
    col("pass_score", "numeric(8,1)", None, "合格分数"),
    col("total_score", "numeric(8,1)", None, "试卷总分"),
    col("sort_mode", "varchar(20)", "NOT NULL DEFAULT 'paper'", "paper试卷自身顺序 / random随机"),
    col("has_subjective", "varchar(10)", "NOT NULL DEFAULT '0'", "是否含主观题,组卷时回写"),
    col("remark", "text", None, "备注"),
], indexes=[("idx", "idx_tms_paper_status", ["biz_status"], False)])
add("tms_paper_section", "试卷章节,可按题型归类", [
    col("section_name", "varchar(100)", None, "章节名,如 ä¸€ã€å•选题"),
    col("question_type", "varchar(20)", None, "默认题型"),
    col("sort_no", "int", None, "排序"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_psec_fid", ["f_foreign_id"], False)])
add("tms_paper_question", "试卷组成试题(当前版本)。已考试卷走 tms_exam_item å¿«ç…§ã€‚", [
    col("section_id", "varchar(50)", None, "章节ID"),
    col("question_id", "varchar(50)", "NOT NULL", "试题ID"),
    col("score", "numeric(8,1)", None, "本题分值"),
    col("sort_no", "int", None, "自定义排序"),
], child=True, delete_user=False, indexes=[
    ("idx", "idx_tms_pq_fid", ["f_foreign_id"], False),
    ("idx", "idx_tms_pq_qid", ["question_id"], False),
])
# ===== 3 å²—位/年度计划 =====
add("tms_post_catalog", "岗位培训目录。支持升版、废弃。审批通过后可整单复制。", [
    col("catalog_no", "varchar(50)", None, "目录编号"),
    col("post_id", "varchar(50)", "NOT NULL", "岗位"),
    col("post_name", "varchar(100)", None, "岗位名称冗余"),
    col("dept_id", "varchar(50)", None, "所属部门"),
    col("version_no", "int", "NOT NULL DEFAULT 1", "版本号,升版+1"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'valid'", "valid有效 / revoked废弃"),
    col("remark", "varchar(500)", None, "备注"),
], flow=True, indexes=[
    ("idx", "idx_tms_pcat_post", ["post_id", "biz_status"], False),
])
add("tms_post_catalog_item", "岗位培训目录内容行,支持回车多行、手动排序", [
    col("sort_no", "int", None, "顺序"),
    col("subject", "varchar(500)", None, "培训内容"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("hours", "numeric(8,1)", None, "课时"),
    col("eval_mode", "varchar(50)", None, "考核方式"),
    col("material_id", "varchar(50)", None, "教材"),
    col("course_id", "varchar(50)", None, "课程"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_pcati_fid", ["f_foreign_id"], False)])
add("tms_post_plan", "岗位培训计划。由目录推送,可生成个人岗位培训和培训任务。", [
    col("plan_no", "varchar(50)", "NOT NULL", "计划编号 JP+YYYYMM+4,如 JP2026090012"),
    col("post_id", "varchar(50)", None, "岗位编码/ID"),
    col("post_name", "varchar(100)", None, "岗位名称"),
    col("dept_id", "varchar(50)", None, "所属部门"),
    col("plan_end_time", "date", None, "计划结束时间,带入个人岗位培训"),
    col("post_desc", "text", None, "岗位描述"),
    col("trainees", "text", None, "培训对象,用户多选"),
    col("catalog_id", "varchar(50)", None, "来源目录"),
    col("publish_status", "varchar(20)", "NOT NULL DEFAULT 'draft'", "draft未发布 / published已发布"),
    col("remark", "varchar(500)", None, "备注"),
], flow=True, indexes=[
    ("uk", "uk_tms_pplan_no", ["plan_no"], True),
    ("idx", "idx_tms_pplan_post", ["post_id", "publish_status"], False),
])
add("tms_post_plan_item", "岗位培训计划明细", [
    col("plan_time", "date", None, "培训计划时间"),
    col("teacher_id", "varchar(50)", None, "培训师"),
    col("subject", "varchar(500)", None, "培训内容"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("hours", "numeric(8,1)", None, "培训课时"),
    col("eval_mode", "varchar(50)", None, "考核方式"),
    col("material_id", "varchar(50)", None, "教材"),
    col("task_id", "varchar(50)", None, "生成的培训任务ID"),
    col("line_status", "varchar(20)", None, "pending待发布 / published已发布 / done已完成"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_pplani_fid", ["f_foreign_id"], False)])
add("tms_person_post", "个人岗位培训清单。转岗时子表标记已培训过则不生成任务。", [
    col("plan_id", "varchar(50)", None, "岗位培训计划ID"),
    col("catalog_id", "varchar(50)", None, "目录ID"),
    col("user_id", "varchar(50)", "NOT NULL", "培训对象"),
    col("post_id", "varchar(50)", None, "岗位"),
    col("plan_end_time", "date", None, "计划结束时间(由计划带出)"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'doing'", "doing执行中 / done完成 / returned退回"),
    col("person_remark", "varchar(500)", None, "个人备注"),
    col("audit_remark", "varchar(500)", None, "审核备注"),
    col("approve_remark", "varchar(500)", None, "审批备注"),
], flow=True, indexes=[
    ("idx", "idx_tms_ppost_user", ["user_id", "biz_status"], False),
    ("idx", "idx_tms_ppost_plan", ["plan_id"], False),
])
add("tms_person_post_item", "个人岗位培训明细", [
    col("subject", "varchar(500)", None, "培训内容"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("hours", "numeric(8,1)", None, "课时"),
    col("eval_mode", "varchar(50)", None, "考核方式"),
    col("already_trained", "varchar(10)", "NOT NULL DEFAULT '0'", "转岗:是否已培训过,是则不生成任务"),
    col("task_id", "varchar(50)", None, "生成的培训任务"),
    col("line_status", "varchar(20)", None, "状态"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_pposti_fid", ["f_foreign_id"], False)])
add("tms_annual_plan", "年度培训计划(公司/部门)。办结后生成培训任务。流程:部门负责人→QA负责人。", [
    col("plan_year", "int", "NOT NULL", "年份,制定时默认当前年+1"),
    col("plan_no", "varchar(50)", "NOT NULL", "计划编号,如 20260001"),
    col("plan_type", "varchar(20)", None, "company公司级 / dept部门级"),
    col("author_id", "varchar(50)", None, "制定人"),
    col("author_dept_id", "varchar(50)", None, "制定部门"),
    col("author_date", "date", None, "制定日期"),
    col("version_no", "int", "NOT NULL DEFAULT 1", "版本"),
    col("dept_leader_id", "varchar(50)", None, "部门负责人(签核展示)"),
    col("qa_leader_id", "varchar(50)", None, "QA部负责人"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'draft'", "draft草稿 / approving审批中 / approved已批准"),
    col("remark", "text", None, "备注"),
], flow=True, indexes=[
    ("uk", "uk_tms_aplan_no", ["plan_no"], True),
    ("idx", "idx_tms_aplan_year", ["plan_year", "plan_type", "author_dept_id"], False),
])
add("tms_annual_plan_item", "年度培训计划明细", [
    col("train_month", "varchar(6)", None, "培训月份 YYYYMM,如 202609"),
    col("subject", "varchar(500)", None, "培训内容"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("trainees", "text", None, "培训对象"),
    col("hours", "numeric(8,1)", None, "培训课时"),
    col("teacher_id", "varchar(50)", None, "培训师"),
    col("eval_mode", "varchar(50)", None, "考核方式"),
    col("course_id", "varchar(50)", None, "培训课程"),
    col("line_status", "varchar(20)", "NOT NULL DEFAULT 'pending'", "pending待执行 / tasked已生成任务 / done已完成 / delayed已延期"),
    col("task_id", "varchar(50)", None, "生成的培训任务"),
], child=True, delete_user=False, indexes=[
    ("idx", "idx_tms_aplani_fid", ["f_foreign_id"], False),
    ("idx", "idx_tms_aplani_month", ["train_month"], False),
])
add("tms_annual_delay", "年度培训计划延期申请", [
    col("plan_id", "varchar(50)", "NOT NULL", "年度计划ID"),
    col("item_id", "varchar(50)", None, "计划明细ID"),
    col("old_month", "varchar(6)", None, "原月份"),
    col("new_month", "varchar(6)", None, "延期至月份"),
    col("reason", "text", None, "延期原因"),
    col("biz_status", "varchar(20)", None, "审批状态"),
], flow=True, indexes=[("idx", "idx_tms_adelay_plan", ["plan_id"], False)])
# ===== 4 åŸ¹è®­ä»»åŠ¡ / è®°å½• =====
add("tms_task", "培训任务(核心)。来源:岗位计划/年度计划/文件生效/临时/外出/再维护。", [
    col("task_no", "varchar(50)", "NOT NULL", "任务编号 TT+YYYYMM+4,如 TT2026090618"),
    col("category", "varchar(50)", None, "培训分类:临时培训/岗位培训计划/年度培训计划/文件生效培训/外出培训/再维护"),
    col("train_type", "varchar(50)", None, "培训类型(列表字段,可与分类并存)"),
    col("source_type", "varchar(50)", None, "来源类型编码"),
    col("source_plan_no", "varchar(50)", None, "来源计划编号"),
    col("source_id", "varchar(50)", None, "来源主表ID"),
    col("source_item_id", "varchar(50)", None, "来源明细ID,归档回写"),
    col("subject", "text", None, "培训主题"),
    col("key_points", "text", None, "培训要点,签到前强制阅读"),
    col("material_id", "varchar(50)", None, "培训教材"),
    col("material_no", "varchar(50)", None, "教材编号冗余"),
    col("place_id", "varchar(50)", None, "培训地点"),
    col("place_no", "varchar(50)", None, "地点编号冗余"),
    col("place_name", "varchar(200)", None, "地点名称冗余"),
    col("start_time", "timestamp", None, "培训开始时间"),
    col("end_time", "timestamp", None, "培训结束时间"),
    col("close_time", "timestamp", None, "任务关闭时间,到达后记录进待归档"),
    col("paper_id", "varchar(50)", None, "试卷"),
    col("paper_name", "varchar(300)", None, "试卷名称冗余"),
    col("exam_start", "timestamp", None, "考试开始时间"),
    col("exam_end", "timestamp", None, "考试结束时间"),
    col("teacher_id", "text", None, "培训师,可多选"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("eval_mode", "varchar(50)", None, "培训效果评估方式/考核方式"),
    col("trainees", "text", None, "培训对象,用户多选;发布时拆个人任务"),
    col("task_kind", "varchar(20)", None, "任务类型:new新增 / continueç»§ç»­(再维护)"),
    col("prev_task_no", "varchar(50)", None, "上一次培训任务编号"),
    col("publish_status", "varchar(20)", "NOT NULL DEFAULT 'draft'", "draft未发布 / published已发布 / cancelled已取消 / done已完成"),
    col("publish_time", "timestamp", None, "发布时间"),
    col("retake_limit", "int", "NOT NULL DEFAULT 1", "不合格可重考次数"),
    col("pass_score", "numeric(8,1)", None, "通过分数,空则用试卷"),
    col("quiz_ratio", "int", None, "提问比例0-100,自动算应提问人数"),
    col("sign_rate", "int", None, "签到率%,取整舍小数"),
    col("pass_rate", "int", None, "合格率%,取整舍小数"),
    col("unfinished_rate", "int", None, "未完成率%"),
    col("overdue_remind", "varchar(10)", "NOT NULL DEFAULT '0'", "是否逾期提醒部门领导"),
    col("invite_flag", "varchar(10)", "NOT NULL DEFAULT '0'", "是否发送培训邀请"),
    col("dms_file_id", "varchar(50)", None, "文件生效自动引入的附件"),
    col("remark", "varchar(500)", None, "备注"),
], flow=True, indexes=[
    ("uk", "uk_tms_task_no", ["task_no"], True),
    ("idx", "idx_tms_task_status", ["publish_status", "start_time"], False),
    ("idx", "idx_tms_task_place", ["place_id", "start_time", "end_time"], False),
    ("idx", "idx_tms_task_source", ["source_type", "source_id"], False),
    ("idx", "idx_tms_task_teacher", ["teacher_id"], False),
])
add("tms_task_file", "培训任务教材/附件清单,发布后同步到培训记录", [
    col("file_no", "varchar(50)", None, "编号"),
    col("file_name", "varchar(300)", None, "文件名称"),
    col("material_id", "varchar(50)", None, "来源教材"),
    col("file_json", "text", None, "附件"),
    col("can_download", "varchar(10)", "NOT NULL DEFAULT '0'", "引用教材默认不可下载打印"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_tfile_fid", ["f_foreign_id"], False)])
add("tms_task_quiz", "培训任务提问预设问题和答案,签到后弹出", [
    col("sort_no", "int", None, "序号"),
    col("question", "text", None, "问题"),
    col("answer", "text", None, "参考答案"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_tquiz_fid", ["f_foreign_id"], False)])
add("tms_person_task", "个人培训任务。发布时按人拆行;学员端自学/签到/考试入口。", [
    col("task_id", "varchar(50)", "NOT NULL", "培训任务ID"),
    col("task_no", "varchar(50)", None, "任务编号冗余"),
    col("user_id", "varchar(50)", "NOT NULL", "培训对象"),
    col("dept_id", "varchar(50)", None, "当时部门快照"),
    col("hours", "numeric(8,1)", None, "学时"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'todo'", "todo未完成 / signed已签到 / done已完成 / expired已过期 / cancelled已取消"),
    col("sign_time", "timestamp", None, "签到时间"),
    col("sign_mode", "varchar(20)", None, "签到方式"),
    col("learn_seconds", "int", None, "在线学习时长秒"),
    col("exam_score", "numeric(8,1)", None, "考试成绩"),
    col("pass_flag", "varchar(20)", None, "pass合格 / fail不合格 / na无需考核"),
    col("guest_flag", "varchar(10)", "NOT NULL DEFAULT '0'", "是否名单外签到"),
], indexes=[
    ("idx", "idx_tms_ptask_user", ["user_id", "biz_status"], False),
    ("idx", "idx_tms_ptask_task", ["task_id"], False),
    ("uk", "uk_tms_ptask_task_user", ["task_id", "user_id"], True),
])
add("tms_record", "培训记录。任务发布后生成;关闭时间到达进待归档。", [
    col("record_no", "varchar(50)", None, "记录编号,默认同任务编号"),
    col("task_id", "varchar(50)", "NOT NULL", "培训任务ID"),
    col("task_no", "varchar(50)", None, "任务编号"),
    col("category", "varchar(50)", None, "培训分类"),
    col("subject", "text", None, "培训主题"),
    col("teacher_id", "text", None, "培训师"),
    col("train_type", "varchar(50)", None, "培训类型"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("eval_mode", "varchar(50)", None, "考核方式"),
    col("start_time", "timestamp", None, "培训开始时间,套打模板按此索引版本"),
    col("end_time", "timestamp", None, "培训结束时间"),
    col("close_time", "timestamp", None, "关闭时间"),
    col("archive_status", "varchar(20)", "NOT NULL DEFAULT 'pending'", "pending待归档 / ready可归档 / archived已归档 / invalid无效"),
    col("archive_time", "timestamp", None, "归档时间"),
    col("archiver_id", "varchar(50)", None, "归档人"),
    col("sign_rate", "int", None, "参与/签到率% å–整舍小数"),
    col("pass_rate", "int", None, "合格率%"),
    col("lecture_file", "text", None, "培训讲义附件"),
    col("paper_sign_file", "text", None, "纸质签到表附件"),
    col("print_tpl_id", "varchar(50)", None, "打印模板版本"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[
    ("uk", "uk_tms_record_task", ["task_id"], True),
    ("idx", "idx_tms_record_arch", ["archive_status", "start_time"], False),
])
add("tms_record_sign", "培训记录签到/结果明细(一人一行)", [
    col("user_id", "varchar(50)", "NOT NULL", "培训对象"),
    col("user_name", "varchar(100)", None, "姓名快照"),
    col("dept_id", "varchar(50)", None, "部门快照"),
    col("sign_time", "timestamp", None, "签到时间"),
    col("sign_mode", "varchar(20)", None, "签到方式"),
    col("guest_flag", "varchar(10)", "NOT NULL DEFAULT '0'", "名单外签到"),
    col("result_flag", "varchar(20)", None, "pass合格 / fail不合格 / absent未参加 / na无需考核"),
    col("exam_score", "numeric(8,1)", None, "考试成绩"),
    col("practice_score", "numeric(8,1)", None, "实操成绩"),
    col("quiz_score", "numeric(8,1)", None, "提问成绩"),
    col("exam_id", "varchar(50)", None, "答卷ID"),
    col("judge_user_id", "varchar(50)", None, "结果判定人(培训师)"),
    col("judge_time", "timestamp", None, "判定时间"),
], child=True, delete_user=False, indexes=[
    ("idx", "idx_tms_rsign_fid", ["f_foreign_id"], False),
    ("idx", "idx_tms_rsign_user", ["user_id"], False),
])
add("tms_record_change", "培训记录变更:改结束时间、增加培训人员。培训师发起→部门领导(领导即培训师则跳过)。", [
    col("record_id", "varchar(50)", "NOT NULL", "培训记录ID"),
    col("task_id", "varchar(50)", None, "任务ID"),
    col("change_type", "varchar(20)", None, "end_time改结束时间 / add_user增加人员"),
    col("old_end_time", "timestamp", None, "原结束时间"),
    col("new_end_time", "timestamp", None, "新结束时间"),
    col("add_users", "text", None, "新增人员"),
    col("reason", "text", None, "变更原因"),
    col("biz_status", "varchar(20)", None, "审批状态"),
], flow=True, indexes=[("idx", "idx_tms_rchg_rec", ["record_id"], False)])
add("tms_archive_file", "培训资料归档(电子/纸质/实操记录双结合)", [
    col("record_id", "varchar(50)", "NOT NULL", "培训记录ID"),
    col("task_id", "varchar(50)", None, "任务ID"),
    col("archive_type", "varchar(20)", None, "electronic电子 / paper纸质 / practice实操档案"),
    col("file_json", "text", None, "归档资料"),
    col("archive_time", "timestamp", None, "归档时间"),
    col("archiver_id", "varchar(50)", None, "归档人"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("idx", "idx_tms_afile_rec", ["record_id"], False)])
# ===== 5 è€ƒè¯•执行 =====
add("tms_exam", "在线考试答卷。中途退出可续考;提交后客观题自动评分,有主观题进阅卷。", [
    col("task_id", "varchar(50)", "NOT NULL", "培训任务"),
    col("person_task_id", "varchar(50)", None, "个人任务"),
    col("record_id", "varchar(50)", None, "培训记录"),
    col("paper_id", "varchar(50)", None, "当时试卷ID"),
    col("paper_name", "varchar(300)", None, "试卷名称快照"),
    col("user_id", "varchar(50)", "NOT NULL", "考生"),
    col("attempt_no", "int", "NOT NULL DEFAULT 1", "第几次考试"),
    col("start_time", "timestamp", None, "开始作答"),
    col("submit_time", "timestamp", None, "交卷时间"),
    col("duration_min", "int", None, "当时时长快照"),
    col("objective_score", "numeric(8,1)", None, "客观题得分"),
    col("subjective_score", "numeric(8,1)", None, "主观题得分"),
    col("total_score", "numeric(8,1)", None, "总分"),
    col("pass_score", "numeric(8,1)", None, "当时合格分快照"),
    col("pass_flag", "varchar(20)", None, "pass/fail"),
    col("grade_status", "varchar(20)", "NOT NULL DEFAULT 'auto'", "auto无需阅卷 / pending待批改 / graded已批改"),
    col("grader_id", "varchar(50)", None, "阅卷老师"),
    col("grade_time", "timestamp", None, "阅卷时间"),
    col("exam_status", "varchar(20)", "NOT NULL DEFAULT 'doing'", "doing考试中 / submitted已交卷 / cancelled已取消"),
], indexes=[
    ("idx", "idx_tms_exam_task_user", ["task_id", "user_id", "attempt_no"], False),
    ("idx", "idx_tms_exam_grade", ["grade_status"], False),
    ("idx", "idx_tms_exam_user", ["user_id"], False),
])
add("tms_exam_item", "答卷试题快照:题目/选项/答案/分数保留考试当时状态,后续改题改卷不影响。", [
    col("question_id", "varchar(50)", None, "原试题ID"),
    col("question_type", "varchar(20)", None, "题型快照"),
    col("stem", "text", None, "题干快照"),
    col("options_json", "text", None, "选项快照 JSON"),
    col("correct_answer", "text", None, "当时正确答案"),
    col("user_answer", "text", None, "考生作答"),
    col("score", "numeric(8,1)", None, "本题满分"),
    col("got_score", "numeric(8,1)", None, "得分"),
    col("is_subjective", "varchar(10)", "NOT NULL DEFAULT '0'", "是否主观题"),
    col("analysis", "text", None, "解析快照"),
    col("sort_no", "int", None, "试卷内顺序"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_eitem_fid", ["f_foreign_id"], False)])
add("tms_practice", "实操考核主表,保存后回写培训记录", [
    col("task_id", "varchar(50)", "NOT NULL", "培训任务"),
    col("record_id", "varchar(50)", None, "培训记录"),
    col("teacher_id", "varchar(50)", None, "考核人"),
    col("practice_time", "timestamp", None, "考核时间"),
    col("remark", "varchar(500)", None, "备注"),
], indexes=[("idx", "idx_tms_prac_task", ["task_id"], False)])
add("tms_practice_item", "实操考核明细", [
    col("user_id", "varchar(50)", "NOT NULL", "培训对象"),
    col("content", "text", None, "考核内容"),
    col("score", "numeric(8,1)", None, "分数"),
    col("result_flag", "varchar(20)", None, "合格/不合格"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_praci_fid", ["f_foreign_id"], False)])
add("tms_quiz_score", "现场提问考核打分,保存后回写培训记录", [
    col("task_id", "varchar(50)", "NOT NULL", "培训任务"),
    col("record_id", "varchar(50)", None, "培训记录"),
    col("user_id", "varchar(50)", "NOT NULL", "培训对象"),
    col("question", "text", None, "提问内容"),
    col("answer", "text", None, "回答"),
    col("score", "numeric(8,1)", None, "分数"),
    col("teacher_id", "varchar(50)", None, "提问人"),
    col("quiz_time", "timestamp", None, "时间"),
], indexes=[
    ("idx", "idx_tms_qscore_task", ["task_id"], False),
    ("idx", "idx_tms_qscore_user", ["user_id"], False),
])
# ===== 6 æ¡£æ¡ˆ =====
add("tms_person_file", "个人培训档案。入职自动建档;一人一档。", [
    col("archive_no", "varchar(50)", "NOT NULL", "档案编号 AR+YYYY+4"),
    col("archive_date", "date", None, "建档时间"),
    col("user_id", "varchar(50)", "NOT NULL", "员工"),
    col("user_name", "varchar(100)", None, "姓名冗余"),
    col("dept_id", "varchar(50)", None, "所属部门"),
    col("hire_date", "date", None, "入职时间"),
    col("leave_date", "date", None, "离职时间"),
    col("main_post_id", "varchar(50)", None, "主要岗位"),
    col("part_post_id", "varchar(50)", None, "兼职岗位"),
    col("biz_status", "varchar(20)", "NOT NULL DEFAULT 'valid'", "valid在职 / left离职"),
], indexes=[
    ("uk", "uk_tms_pfile_user", ["user_id"], True),
    ("uk", "uk_tms_pfile_no", ["archive_no"], True),
])
add("tms_person_work", "个人培训档案-工作履历", [
    col("company_name", "varchar(200)", None, "公司名称"),
    col("post_name", "varchar(100)", None, "任职岗位"),
    col("hire_date", "date", None, "入职时间"),
    col("leave_date", "date", None, "离职时间"),
], child=True, delete_user=False, indexes=[("idx", "idx_tms_pwork_fid", ["f_foreign_id"], False)])
add("tms_person_cert", "资质文件/获得证书。资质文件管理与档案子表共用此表。", [
    col("archive_id", "varchar(50)", None, "个人档案ID,无外键字段时由主从写入f_foreign_id"),
    col("user_id", "varchar(50)", None, "员工,列表模块用"),
    col("cert_no", "varchar(100)", None, "证书编号"),
    col("cert_name", "varchar(200)", None, "证书名称"),
    col("gain_date", "date", None, "获得时间"),
    col("expire_date", "date", None, "有效期至"),
    col("cert_file", "text", None, "证书附件"),
    col("cert_status", "varchar(20)", None, "valid有效 / near近效期 / invalid无效"),
], child=True, indexes=[
    ("idx", "idx_tms_pcert_fid", ["f_foreign_id"], False),
    ("idx", "idx_tms_pcert_user", ["user_id"], False),
])
add("tms_person_train", "个人培训目录(归档快照)。记录归档后写入,满足审计。", [
    col("archive_id", "varchar(50)", "NOT NULL", "个人档案ID"),
    col("user_id", "varchar(50)", "NOT NULL", "员工"),
    col("record_id", "varchar(50)", None, "培训记录ID"),
    col("record_no", "varchar(50)", None, "培训记录编号"),
    col("category", "varchar(50)", None, "培训类型/分类"),
    col("subject", "text", None, "培训内容"),
    col("train_mode", "varchar(50)", None, "培训方式"),
    col("teacher_id", "text", None, "培训师"),
    col("eval_mode", "varchar(50)", None, "考核方式"),
    col("pass_flag", "varchar(20)", None, "是否合格"),
    col("start_time", "timestamp", None, "培训开始时间"),
    col("end_time", "timestamp", None, "培训结束时间"),
    col("hours", "numeric(8,1)", None, "学时"),
], indexes=[
    ("idx", "idx_tms_ptrain_user", ["user_id"], False),
    ("idx", "idx_tms_ptrain_arch", ["archive_id"], False),
    ("idx", "idx_tms_ptrain_rec", ["record_id"], False),
])
# ===== 7 å…¶ä»–流程 =====
add("tms_out_train", "外出培训", [
    col("apply_no", "varchar(50)", None, "申请单号"),
    col("applicant_id", "varchar(50)", None, "申请人"),
    col("apply_dept_id", "varchar(50)", None, "申请部门"),
    col("org_name", "varchar(200)", None, "外部机构"),
    col("subject", "varchar(500)", None, "培训主题"),
    col("start_time", "timestamp", None, "开始"),
    col("end_time", "timestamp", None, "结束"),
    col("hours", "numeric(8,1)", None, "学时"),
    col("file_json", "text", None, "资料附件"),
    col("biz_status", "varchar(20)", None, "审批状态"),
], flow=True, indexes=[("uk", "uk_tms_out_no", ["apply_no"], True)])
add("tms_transfer_eval", "转岗人员考核鉴定", [
    col("user_id", "varchar(50)", "NOT NULL", "转岗人员"),
    col("from_post_id", "varchar(50)", None, "原岗位"),
    col("to_post_id", "varchar(50)", None, "新岗位"),
    col("eval_result", "varchar(50)", None, "鉴定结论"),
    col("remark", "text", None, "说明"),
    col("biz_status", "varchar(20)", None, "审批状态"),
], flow=True, indexes=[("idx", "idx_tms_teval_user", ["user_id"], False)])
add("tms_newcomer_eval", "新员工培训效果考评", [
    col("user_id", "varchar(50)", "NOT NULL", "新员工"),
    col("eval_result", "varchar(50)", None, "考评结论"),
    col("remark", "text", None, "说明"),
    col("biz_status", "varchar(20)", None, "审批状态"),
], flow=True, indexes=[("idx", "idx_tms_neval_user", ["user_id"], False)])
add("tms_return_eval", "复岗员工培训效果考评", [
    col("user_id", "varchar(50)", "NOT NULL", "复岗员工"),
    col("eval_result", "varchar(50)", None, "考评结论"),
    col("remark", "text", None, "说明"),
    col("biz_status", "varchar(20)", None, "审批状态"),
], flow=True, indexes=[("idx", "idx_tms_reval_user", ["user_id"], False)])
add("tms_print_template", "培训记录套打模板版本。按培训开始时间匹配生效版本。", [
    col("tpl_code", "varchar(50)", "NOT NULL", "模板编码,如 TRAIN_RECORD"),
    col("version_no", "int", "NOT NULL", "版本号"),
    col("effective_from", "date", "NOT NULL", "生效起始(按培训开始时间索引)"),
    col("effective_to", "date", None, "生效截止,空=当前"),
    col("file_json", "text", None, "模板文件"),
    col("enabled", "varchar(10)", "NOT NULL DEFAULT '1'", "启用"),
], indexes=[("idx", "idx_tms_ptpl_code", ["tpl_code", "effective_from"], False)])
add("tms_remind_rule", "提醒规则:计划到期前一个月、授权到期、个人岗位培训时限等", [
    col("rule_code", "varchar(50)", "NOT NULL", "规则编码"),
    col("rule_name", "varchar(100)", None, "名称"),
    col("biz_type", "varchar(50)", None, "annual_plan/admin_auth/person_post/task_overdue"),
    col("days_before", "int", "NOT NULL DEFAULT 30", "提前天数"),
    col("notify_roles", "varchar(200)", None, "通知角色:部门培训管理员,培训师,部门领导"),
    col("enabled", "varchar(10)", "NOT NULL DEFAULT '1'", "启用"),
], indexes=[("uk", "uk_tms_remind_code", ["rule_code"], True)])
HEADER = '''-- =============================================================================
-- çŽ»æ€éŸ¬ TMS åŸ¹è®­ç®¡ç†ç³»ç»Ÿ Â· PostgreSQL 18 å»ºè¡¨è„šæœ¬
-- é€‚用:JNPF 6.x(字段风格对齐现有 lims_/dms_ è¡¨ï¼‰
-- æ¥æºï¼šçŽ»æ€éŸ¬TMS功能设计书 V1 + è€é£žä¼ç•Œé¢å­—段
--
-- ä½¿ç”¨
--   1) æŽ¨èï¼šJNPF「数据建模」按表清单建表,再绑表单(系统字段会自动带)
--   2) æˆ–先执行本脚本,再到数据建模「同步数据表」
--
-- çº¦å®š
--   f_id ä¸»é”® varchar(50);子表 f_foreign_id = ä¸»è¡¨ f_id
--   æµç¨‹è¡¨å•含 f_flow_id / f_flow_task_id / f_flow_state
--   ç”¨æˆ·/组织/岗位存 JNPF ID;多选 text;附件 text(JSON)
--   ä¸šåŠ¡çŠ¶æ€å­˜ç¼–ç ï¼Œé€‰é¡¹ç”¨ JNPF å­—å…¸
-- =============================================================================
BEGIN;
'''
FOOTER = '''
-- =============================================================================
-- é¢„置数据(培训方式 / ç­¾åˆ°æ–¹å¼ / è¯„估方式 / æé†’规则)
-- =============================================================================
INSERT INTO tms_train_mode (f_id, mode_code, mode_name, sign_rule, allow_guest_sign, same_day_required, place_required, sort_no, enabled, f_delete_mark)
VALUES
 ('tms_mode_onsite',   'onsite',   '集中授课', 'scan', '1', '1', '1', 1, '1', 0),
 ('tms_mode_practice', 'practice', '操作授课', 'scan', '0', '1', '1', 2, '1', 0),
 ('tms_mode_online',   'online',   '在线学习', 'both', '0', '0', '0', 3, '1', 0)
ON CONFLICT (f_id) DO NOTHING;
INSERT INTO tms_sign_mode (f_id, mode_code, mode_name, tip_seconds, match_roster, enabled, f_delete_mark)
VALUES
 ('tms_sign_app',    'app_scan', 'APP扫码',  30, '1', '1', 0),
 ('tms_sign_wx',     'wx_scan',  '微信扫码', 30, '1', '1', 0),
 ('tms_sign_online', 'online',   '在线签到', 30, '1', '1', 0)
ON CONFLICT (f_id) DO NOTHING;
INSERT INTO tms_eval_mode (f_id, mode_code, mode_name, need_paper, need_quiz, need_practice, enabled, f_delete_mark)
VALUES
 ('tms_eval_quiz',     'quiz',     '提问',     '0', '1', '0', '1', 0),
 ('tms_eval_practice', 'practice', '现场操作', '0', '0', '1', '1', 0),
 ('tms_eval_exam',     'exam',     '在线考试', '1', '0', '0', '1', 0),
 ('tms_eval_none',     'none',     '无需考核', '0', '0', '0', '1', 0)
ON CONFLICT (f_id) DO NOTHING;
INSERT INTO tms_remind_rule (f_id, rule_code, rule_name, biz_type, days_before, notify_roles, enabled, f_delete_mark)
VALUES
 ('tms_rm_annual', 'annual_due', '年度计划到期前一个月', 'annual_plan', 30, '部门培训管理员,培训师', '1', 0),
 ('tms_rm_auth',   'auth_due',   '管理员授权到期前一个月', 'admin_auth', 30, '培训管理员,培训师', '1', 0),
 ('tms_rm_overdue','task_overdue','培训逾期提醒部门领导', 'task_overdue', 0, '部门领导', '1', 0)
ON CONFLICT (f_id) DO NOTHING;
COMMIT;
'''
def sql_type_line(c):
    name, typ, extra, _ = c
    extra_s = f" {extra}" if extra else ""
    return f"    {name:<24} {typ}{extra_s}"
def emit_table(t):
    lines = []
    lines.append(f"-- {t['comment']}")
    lines.append(f"CREATE TABLE IF NOT EXISTS {t['name']} (")
    col_lines = []
    for c in t["cols"]:
        col_lines.append(sql_type_line(c))
    col_lines.append(f"    CONSTRAINT pk_{t['name']} PRIMARY KEY (f_id)")
    lines.append(",\n".join(col_lines))
    lines.append(");")
    lines.append(f"COMMENT ON TABLE {t['name']} IS '{t['comment'].replace(chr(39), chr(39)*2)}';")
    for c in t["cols"]:
        if c[3]:
            cmt = c[3].replace("'", "''")
            lines.append(f"COMMENT ON COLUMN {t['name']}.{c[0]} IS '{cmt}';")
    for kind, iname, cols, uniqueish in t["indexes"]:
        colsql = ", ".join(cols)
        if kind == "uk":
            # unique among not-deleted; nullable columns allowed multiple NULL in PG unique, extra WHERE keeps it simple
            lines.append(f"CREATE UNIQUE INDEX IF NOT EXISTS {iname} ON {t['name']} ({colsql}) WHERE f_delete_mark = 0;")
        else:
            lines.append(f"CREATE INDEX IF NOT EXISTS {iname} ON {t['name']} ({colsql});")
    lines.append("")
    return "\n".join(lines)
out = [HEADER]
groups = [
    ("1. åŸºç¡€è®¾ç½®", ["tms_place","tms_trainer","tms_trainer_work","tms_trainer_exp","tms_admin_auth","tms_train_mode","tms_sign_mode","tms_eval_mode","tms_course","tms_material"]),
    ("2. é¢˜åº“试卷", ["tms_question_bank","tms_question","tms_question_option","tms_paper","tms_paper_section","tms_paper_question"]),
    ("3. å²—位/年度计划", ["tms_post_catalog","tms_post_catalog_item","tms_post_plan","tms_post_plan_item","tms_person_post","tms_person_post_item","tms_annual_plan","tms_annual_plan_item","tms_annual_delay"]),
    ("4. åŸ¹è®­ä»»åŠ¡ä¸Žè®°å½•", ["tms_task","tms_task_file","tms_task_quiz","tms_person_task","tms_record","tms_record_sign","tms_record_change","tms_archive_file"]),
    ("5. è€ƒè¯•与考核", ["tms_exam","tms_exam_item","tms_practice","tms_practice_item","tms_quiz_score"]),
    ("6. ä¸ªäººæ¡£æ¡ˆ", ["tms_person_file","tms_person_work","tms_person_cert","tms_person_train"]),
    ("7. å…¶ä»–流程与配置", ["tms_out_train","tms_transfer_eval","tms_newcomer_eval","tms_return_eval","tms_print_template","tms_remind_rule"]),
]
by_name = {t["name"]: t for t in tables}
for title, names in groups:
    out.append(f"-- ---------------------------------------------------------------------------")
    out.append(f"-- {title}")
    out.append(f"-- ---------------------------------------------------------------------------")
    for n in names:
        out.append(emit_table(by_name[n]))
out.append(FOOTER)
sql_path = Path(r"D:/qiansheng/demo/flowApi/docs/tms/tms_schema_pg.sql")
sql_path.write_text("\n".join(out), encoding="utf-8")
print("tables", len(tables), "bytes", sql_path.stat().st_size)
docs/tms/gen_xlsx.py
New file
@@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
from pathlib import Path
import re
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
sql = Path(r"D:/qiansheng/demo/flowApi/docs/tms/tms_schema_pg.sql").read_text(encoding="utf-8")
tables = []
current = None
block = None
create_map = {}
for line in sql.splitlines():
    m = re.match(r"CREATE TABLE IF NOT EXISTS (\w+)", line)
    if m:
        current = {"name": m.group(1), "comment": "", "cols": []}
        tables.append(current)
        block = [line]
        continue
    if block is not None:
        block.append(line)
        if line.startswith(");"):
            cols = []
            for ln in block[1:]:
                if "CONSTRAINT" in ln or ln.strip().startswith(");") or not ln.strip():
                    continue
                ln2 = ln.strip().rstrip(",")
                parts = ln2.split()
                if len(parts) >= 2:
                    cols.append((parts[0], " ".join(parts[1:])))
            create_map[current["name"]] = cols
            block = None
        continue
    if current is None:
        continue
    m = re.match(r"COMMENT ON TABLE (\w+) IS '(.+)';", line)
    if m and current["name"] == m.group(1):
        current["comment"] = m.group(2)
        continue
    m = re.match(r"COMMENT ON COLUMN (\w+)\.(\w+) IS '(.+)';", line)
    if m and current["name"] == m.group(1):
        current["cols"].append((m.group(2), m.group(3)))
FORMS = {
    "tms_place": ("培训地点", "A+YYYYMM+4"),
    "tms_trainer": ("培训师档案", "AR+YYYY+4"),
    "tms_trainer_work": ("培训师-工作经历", ""),
    "tms_trainer_exp": ("培训师-培训经历", ""),
    "tms_admin_auth": ("培训管理员授权", ""),
    "tms_train_mode": ("培训方式", ""),
    "tms_sign_mode": ("签到方式", ""),
    "tms_eval_mode": ("培训效果评估方式", ""),
    "tms_course": ("培训课程", "KC+YYYYMM+4"),
    "tms_material": ("培训教材", "TM+YYYYMM+4"),
    "tms_question_bank": ("题库", ""),
    "tms_question": ("试题", ""),
    "tms_question_option": ("试题选项", ""),
    "tms_paper": ("试卷", ""),
    "tms_paper_section": ("试卷章节", ""),
    "tms_paper_question": ("试卷试题", ""),
    "tms_post_catalog": ("岗位培训目录", ""),
    "tms_post_catalog_item": ("岗位目录明细", ""),
    "tms_post_plan": ("岗位培训计划", "JP+YYYYMM+4"),
    "tms_post_plan_item": ("岗位计划明细", ""),
    "tms_person_post": ("个人岗位培训", ""),
    "tms_person_post_item": ("个人岗位培训明细", ""),
    "tms_annual_plan": ("年度培训计划", "YYYY+4位流水"),
    "tms_annual_plan_item": ("年度计划明细", ""),
    "tms_annual_delay": ("年度计划延期", ""),
    "tms_task": ("培训任务", "TT+YYYYMM+4"),
    "tms_task_file": ("任务教材附件", ""),
    "tms_task_quiz": ("任务提问预设", ""),
    "tms_person_task": ("个人培训任务", ""),
    "tms_record": ("培训记录", "默认同任务编号"),
    "tms_record_sign": ("签到/结果明细", ""),
    "tms_record_change": ("培训记录变更", ""),
    "tms_archive_file": ("培训资料归档", ""),
    "tms_exam": ("在线考试答卷", ""),
    "tms_exam_item": ("答卷试题快照", ""),
    "tms_practice": ("实操考核", ""),
    "tms_practice_item": ("实操考核明细", ""),
    "tms_quiz_score": ("现场提问考核", ""),
    "tms_person_file": ("个人培训档案", "AR+YYYY+4"),
    "tms_person_work": ("档案-工作履历", ""),
    "tms_person_cert": ("资质文件/证书", ""),
    "tms_person_train": ("档案-培训目录快照", ""),
    "tms_out_train": ("外出培训", "WC+YYYYMM+4"),
    "tms_transfer_eval": ("转岗考核鉴定", ""),
    "tms_newcomer_eval": ("新员工培训效果考评", ""),
    "tms_return_eval": ("复岗培训效果考评", ""),
    "tms_print_template": ("套打模板版本", ""),
    "tms_remind_rule": ("提醒规则", ""),
}
CTRL = {
    "user_id": "用户",
    "teacher_id": "用户(可多选)",
    "trainees": "用户多选",
    "admin_user_id": "用户",
    "applicant_id": "用户",
    "author_id": "用户",
    "registrar_id": "用户",
    "grader_id": "用户",
    "archiver_id": "用户",
    "dept_id": "组织",
    "apply_dept_id": "组织",
    "author_dept_id": "组织",
    "scope_dept_id": "组织多选",
    "post_id": "岗位",
    "main_post_id": "岗位",
    "part_post_id": "岗位",
    "file_json": "附件",
    "cert_file": "附件",
    "lecture_file": "附件",
    "paper_sign_file": "附件",
    "stem": "富文本",
    "key_points": "多行",
    "subject": "多行",
    "start_time": "日期时间",
    "end_time": "日期时间",
    "close_time": "日期时间",
    "exam_start": "日期时间",
    "exam_end": "日期时间",
    "hire_date": "日期",
    "enabled": "下拉/开关",
    "biz_status": "下拉-字典",
    "publish_status": "下拉-字典",
    "train_mode": "下拉/关联培训方式",
    "eval_mode": "下拉/关联评估方式",
    "place_id": "关联表单-培训地点",
    "material_id": "关联表单-培训教材",
    "paper_id": "关联表单-试卷",
    "bank_id": "关联表单-题库",
    "course_id": "关联表单-课程",
    "task_id": "关联表单-培训任务",
    "record_id": "关联表单-培训记录",
}
def suggest_ctrl(name, typ):
    if name in CTRL:
        return CTRL[name]
    if name.startswith("f_"):
        return "系统字段"
    if "timestamp" in typ:
        return "日期时间"
    if typ.startswith("date"):
        return "日期"
    if "numeric" in typ or typ.startswith("int"):
        return "数字"
    if name.endswith("_id"):
        return "关联/用户/组织"
    if "text" in typ:
        return "多行/附件/JSON"
    return "单行/下拉"
wb = Workbook()
ws = wb.active
ws.title = "表清单"
ws.append(["序号", "表名", "中文名", "表说明", "主从", "流程", "单据规则"])
for i, t in enumerate(tables, 1):
    child = any(c[0] == "f_foreign_id" for c in t["cols"])
    flow = any(c[0] == "f_flow_id" for c in t["cols"])
    form_name, rule = FORMS.get(t["name"], (t["comment"][:20], ""))
    ws.append([i, t["name"], form_name, t["comment"], "子表" if child else "主表", "是" if flow else "否", rule])
ws2 = wb.create_sheet("字段明细")
ws2.append(["表名", "中文名", "字段名", "类型", "说明", "JNPF建议控件"])
for t in tables:
    comments = {n: c for n, c in t["cols"]}
    form_name = FORMS.get(t["name"], (t["comment"][:20], ""))[0]
    for n, typ in create_map.get(t["name"], []):
        ws2.append([t["name"], form_name, n, typ, comments.get(n, ""), suggest_ctrl(n, typ)])
ws3 = wb.create_sheet("字典与单据规则")
ws3.append(["类型", "名称", "选项或规则", "用途"])
for row in [
    ("字典", "任务发布状态", "draft未发布 / published已发布 / cancelled已取消 / done已完成", "tms_task.publish_status"),
    ("字典", "培训分类", "临时培训 / å²—位培训计划 / å¹´åº¦åŸ¹è®­è®¡åˆ’ / æ–‡ä»¶ç”Ÿæ•ˆåŸ¹è®­ / å¤–出培训 / å†ç»´æŠ¤", "tms_task.category"),
    ("字典", "任务类型", "new新增 / continueç»§ç»­", "tms_task.task_kind"),
    ("字典", "归档状态", "pending待归档 / ready可归档 / archived已归档 / invalid无效", "tms_record.archive_status"),
    ("字典", "个人任务状态", "todo未完成 / signed已签到 / done已完成 / expired已过期 / cancelled已取消", "tms_person_task.biz_status"),
    ("字典", "是否合格", "pass合格 / fail不合格 / absent未参加 / na无需考核", "签到结果/考试"),
    ("字典", "题型", "single单选 / multi多选 / judge判断 / blank填空 / essay问答", "试题"),
    ("字典", "试题试卷状态", "open开放 / closed不开放 / invalid废弃", "题库试卷"),
    ("字典", "难度", "easy简单 / normal一般 / hard困难", "试题"),
    ("字典", "阅卷状态", "auto无需阅卷 / pending待批改 / graded已批改", "tms_exam.grade_status"),
    ("字典", "有效撤销", "valid有效期内 / revoked已撤销", "培训师/授权/教材"),
    ("字典", "计划行状态", "pending待执行 / tasked已生成任务 / done已完成 / delayed已延期", "年度计划明细"),
    ("字典", "教材类型", "Word / PPT / Excel / PDF / è§†é¢‘ / éŸ³é¢‘", "教材"),
    ("单据规则", "地点编号", "A + YYYYMM + 4位流水", "tms_place.place_no ä¾‹ A2026090001"),
    ("单据规则", "档案编号", "AR + YYYY + 4位流水", "培训师/个人档案 ä¾‹ AR20260028"),
    ("单据规则", "教材编号", "TM + YYYYMM + 4位流水", "tms_material ä¾‹ TM2026090001"),
    ("单据规则", "岗位计划编号", "JP + YYYYMM + 4位流水", "tms_post_plan ä¾‹ JP2026090012"),
    ("单据规则", "年度计划编号", "年份4位 + 4位流水", "tms_annual_plan ä¾‹ 20260001"),
    ("单据规则", "培训任务编号", "TT + YYYYMM + 4位流水", "tms_task ä¾‹ TT2026090618"),
]:
    ws3.append(list(row))
header_fill = PatternFill("solid", fgColor="1F4E79")
header_font = Font(color="FFFFFF", bold=True)
thin = Border(*(Side(style="thin", color="D0D0D0"),) * 4)
for sheet in wb.worksheets:
    for cell in sheet[1]:
        cell.fill = header_fill
        cell.font = header_font
        cell.alignment = Alignment(horizontal="center")
    sheet.freeze_panes = "A2"
    sheet.auto_filter.ref = sheet.dimensions
    widths = {}
    for row in sheet.iter_rows(min_row=1, max_row=min(sheet.max_row, 80)):
        for c in row:
            c.border = thin
            c.alignment = Alignment(wrap_text=True, vertical="center")
            widths[c.column] = max(widths.get(c.column, 10), min(len(str(c.value or "")) + 4, 42))
    for col, w in widths.items():
        sheet.column_dimensions[get_column_letter(col)].width = w
xlsx = Path(r"D:/qiansheng/demo/flowApi/docs/tms/玻思韬TMS-JNPF数据表字段清单.xlsx")
wb.save(xlsx)
print("tables", len(tables), "xlsx", xlsx.stat().st_size)
docs/tms/tms_schema_pg.sql
New file
@@ -0,0 +1,2171 @@
-- =============================================================================
-- çŽ»æ€éŸ¬ TMS åŸ¹è®­ç®¡ç†ç³»ç»Ÿ Â· PostgreSQL 18 å»ºè¡¨è„šæœ¬
-- é€‚用:JNPF 6.x(字段风格对齐现有 lims_/dms_ è¡¨ï¼‰
-- æ¥æºï¼šçŽ»æ€éŸ¬TMS功能设计书 V1 + è€é£žä¼ç•Œé¢å­—段
--
-- ä½¿ç”¨
--   1) æŽ¨èï¼šJNPF「数据建模」按表清单建表,再绑表单(系统字段会自动带)
--   2) æˆ–先执行本脚本,再到数据建模「同步数据表」
--
-- çº¦å®š
--   f_id ä¸»é”® varchar(50);子表 f_foreign_id = ä¸»è¡¨ f_id
--   æµç¨‹è¡¨å•含 f_flow_id / f_flow_task_id / f_flow_state
--   ç”¨æˆ·/组织/岗位存 JNPF ID;多选 text;附件 text(JSON)
--   ä¸šåŠ¡çŠ¶æ€å­˜ç¼–ç ï¼Œé€‰é¡¹ç”¨ JNPF å­—å…¸
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. åŸºç¡€è®¾ç½®
-- ---------------------------------------------------------------------------
-- åŸ¹è®­åœ°ç‚¹ã€‚在线学习任务地点可空;占用按小时与未取消任务时间交叉校验。
CREATE TABLE IF NOT EXISTS tms_place (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    place_no                 varchar(50) NOT NULL,
    place_name               varchar(200) NOT NULL,
    place_group              varchar(50),
    enabled                  varchar(20) NOT NULL DEFAULT '1',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_place PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_place IS '培训地点。在线学习任务地点可空;占用按小时与未取消任务时间交叉校验。';
COMMENT ON COLUMN tms_place.f_id IS '主键';
COMMENT ON COLUMN tms_place.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_place.place_no IS '地点编号,单据规则 A+YYYYMM+4,如 A2026090001';
COMMENT ON COLUMN tms_place.place_name IS '地点名称';
COMMENT ON COLUMN tms_place.place_group IS '组别/分区,字典:办公区/生产区/A区/B区';
COMMENT ON COLUMN tms_place.enabled IS '1启用 0停用';
COMMENT ON COLUMN tms_place.remark IS '备注';
COMMENT ON COLUMN tms_place.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_place.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_place.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_place.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_place.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_place.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_place.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_place_no ON tms_place (place_no) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_place_group ON tms_place (place_group, enabled);
-- åŸ¹è®­å¸ˆæ¡£æ¡ˆï¼Œå…³è”系统用户;重复录入按user_id识别;账号注销后自动废弃。
CREATE TABLE IF NOT EXISTS tms_trainer (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    archive_no               varchar(50),
    user_id                  varchar(50) NOT NULL,
    trainer_name             varchar(100),
    dept_id                  varchar(50),
    post_id                  varchar(50),
    school                   varchar(200),
    education                varchar(50),
    major                    varchar(100),
    tech_title               varchar(100),
    hire_date                date,
    license                  varchar(200),
    work_years               varchar(50),
    trainer_level            varchar(50),
    biz_status               varchar(20) NOT NULL DEFAULT 'valid',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_trainer PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_trainer IS '培训师档案,关联系统用户;重复录入按user_id识别;账号注销后自动废弃。';
COMMENT ON COLUMN tms_trainer.f_id IS '主键';
COMMENT ON COLUMN tms_trainer.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_trainer.archive_no IS '档案编号 AR+YYYY+4,可与个人培训档案共用';
COMMENT ON COLUMN tms_trainer.user_id IS '系统用户ID';
COMMENT ON COLUMN tms_trainer.trainer_name IS '姓名(冗余,便于列表)';
COMMENT ON COLUMN tms_trainer.dept_id IS '部门';
COMMENT ON COLUMN tms_trainer.post_id IS '岗位';
COMMENT ON COLUMN tms_trainer.school IS '毕业院校';
COMMENT ON COLUMN tms_trainer.education IS '学历';
COMMENT ON COLUMN tms_trainer.major IS '专业';
COMMENT ON COLUMN tms_trainer.tech_title IS '技术职称';
COMMENT ON COLUMN tms_trainer.hire_date IS '入职时间';
COMMENT ON COLUMN tms_trainer.license IS '执业证书';
COMMENT ON COLUMN tms_trainer.work_years IS '工作年限';
COMMENT ON COLUMN tms_trainer.trainer_level IS '培训师级别';
COMMENT ON COLUMN tms_trainer.biz_status IS 'valid有效期内 / revoked已撤销';
COMMENT ON COLUMN tms_trainer.remark IS '备注';
COMMENT ON COLUMN tms_trainer.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_trainer.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_trainer.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_trainer.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_trainer.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_trainer.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_trainer.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_trainer_user ON tms_trainer (user_id) WHERE f_delete_mark = 0;
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_trainer_no ON tms_trainer (archive_no) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_trainer_status ON tms_trainer (biz_status);
-- åŸ¹è®­å¸ˆä¸ªäººå·¥ä½œç»åŽ†å­è¡¨
CREATE TABLE IF NOT EXISTS tms_trainer_work (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    sort_no                  int,
    hire_date                date,
    leave_date               date,
    company_name             varchar(200),
    post_name                varchar(100),
    leave_reason             varchar(200),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_trainer_work PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_trainer_work IS '培训师个人工作经历子表';
COMMENT ON COLUMN tms_trainer_work.f_id IS '主键';
COMMENT ON COLUMN tms_trainer_work.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_trainer_work.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_trainer_work.sort_no IS '序号';
COMMENT ON COLUMN tms_trainer_work.hire_date IS '入职时间';
COMMENT ON COLUMN tms_trainer_work.leave_date IS '离职时间';
COMMENT ON COLUMN tms_trainer_work.company_name IS '公司名称';
COMMENT ON COLUMN tms_trainer_work.post_name IS '任职岗位';
COMMENT ON COLUMN tms_trainer_work.leave_reason IS '离职原因';
COMMENT ON COLUMN tms_trainer_work.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_trainer_work.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_trainer_work.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_trainer_work.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_trainer_work.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_trainer_work_fid ON tms_trainer_work (f_foreign_id);
-- åŸ¹è®­å¸ˆä¸ªäººåŸ¹è®­ç»åŽ†å­è¡¨
CREATE TABLE IF NOT EXISTS tms_trainer_exp (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    sort_no                  int,
    train_date               date,
    org_name                 varchar(200),
    cert_name                varchar(200),
    cert_file                text,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_trainer_exp PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_trainer_exp IS '培训师个人培训经历子表';
COMMENT ON COLUMN tms_trainer_exp.f_id IS '主键';
COMMENT ON COLUMN tms_trainer_exp.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_trainer_exp.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_trainer_exp.sort_no IS '序号';
COMMENT ON COLUMN tms_trainer_exp.train_date IS '培训时间';
COMMENT ON COLUMN tms_trainer_exp.org_name IS '培训机构';
COMMENT ON COLUMN tms_trainer_exp.cert_name IS '培训证书';
COMMENT ON COLUMN tms_trainer_exp.cert_file IS '证书附件';
COMMENT ON COLUMN tms_trainer_exp.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_trainer_exp.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_trainer_exp.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_trainer_exp.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_trainer_exp.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_trainer_exp_fid ON tms_trainer_exp (f_foreign_id);
-- åŸ¹è®­ç®¡ç†å‘˜è·¨éƒ¨é—¨æŽˆæƒã€‚到期前一个月提醒管理员/培训师。
CREATE TABLE IF NOT EXISTS tms_admin_auth (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    admin_user_id            varchar(50) NOT NULL,
    scope_dept_id            text NOT NULL,
    include_child            varchar(10) NOT NULL DEFAULT '1',
    registrar_id             varchar(50),
    register_date            date,
    expire_date              date,
    biz_status               varchar(20) NOT NULL DEFAULT 'valid',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_admin_auth PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_admin_auth IS '培训管理员跨部门授权。到期前一个月提醒管理员/培训师。';
COMMENT ON COLUMN tms_admin_auth.f_id IS '主键';
COMMENT ON COLUMN tms_admin_auth.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_admin_auth.admin_user_id IS '培训管理员';
COMMENT ON COLUMN tms_admin_auth.scope_dept_id IS '管理范围,组织多选';
COMMENT ON COLUMN tms_admin_auth.include_child IS '是否包含下级部门 1是 0否';
COMMENT ON COLUMN tms_admin_auth.registrar_id IS '登记人';
COMMENT ON COLUMN tms_admin_auth.register_date IS '登记日期';
COMMENT ON COLUMN tms_admin_auth.expire_date IS '授权到期日,可空表示长期';
COMMENT ON COLUMN tms_admin_auth.biz_status IS 'valid有效 / revoked废弃';
COMMENT ON COLUMN tms_admin_auth.remark IS '备注';
COMMENT ON COLUMN tms_admin_auth.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_admin_auth.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_admin_auth.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_admin_auth.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_admin_auth.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_admin_auth.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_admin_auth.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_admin_auth_user ON tms_admin_auth (admin_user_id, biz_status);
-- åŸ¹è®­æ–¹å¼é…ç½®ï¼ˆå«ç­¾åˆ°è§„则,不仅是字典)
CREATE TABLE IF NOT EXISTS tms_train_mode (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    mode_code                varchar(50) NOT NULL,
    mode_name                varchar(100) NOT NULL,
    sign_rule                varchar(20) NOT NULL DEFAULT 'both',
    allow_guest_sign         varchar(10) NOT NULL DEFAULT '0',
    same_day_required        varchar(10) NOT NULL DEFAULT '0',
    place_required           varchar(10) NOT NULL DEFAULT '1',
    sort_no                  int,
    enabled                  varchar(10) NOT NULL DEFAULT '1',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_train_mode PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_train_mode IS '培训方式配置(含签到规则,不仅是字典)';
COMMENT ON COLUMN tms_train_mode.f_id IS '主键';
COMMENT ON COLUMN tms_train_mode.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_train_mode.mode_code IS '编码:onsite/practice/online';
COMMENT ON COLUMN tms_train_mode.mode_name IS '名称:集中授课/操作授课/在线学习';
COMMENT ON COLUMN tms_train_mode.sign_rule IS 'scan仅扫码 / both扫码+在线';
COMMENT ON COLUMN tms_train_mode.allow_guest_sign IS '允许名单外签到,集中授课=1';
COMMENT ON COLUMN tms_train_mode.same_day_required IS '开始结束必须同一天,集中/操作=1';
COMMENT ON COLUMN tms_train_mode.place_required IS '地点必填,在线学习=0';
COMMENT ON COLUMN tms_train_mode.sort_no IS '排序';
COMMENT ON COLUMN tms_train_mode.enabled IS '启用';
COMMENT ON COLUMN tms_train_mode.remark IS '备注';
COMMENT ON COLUMN tms_train_mode.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_train_mode.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_train_mode.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_train_mode.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_train_mode.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_train_mode.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_train_mode.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_train_mode_code ON tms_train_mode (mode_code) WHERE f_delete_mark = 0;
-- ç­¾åˆ°æ–¹å¼ï¼šAPP扫码 / å¾®ä¿¡æ‰«ç  / åœ¨çº¿ç­¾åˆ°
CREATE TABLE IF NOT EXISTS tms_sign_mode (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    mode_code                varchar(50) NOT NULL,
    mode_name                varchar(100) NOT NULL,
    tip_seconds              int NOT NULL DEFAULT 30,
    match_roster             varchar(10) NOT NULL DEFAULT '1',
    enabled                  varchar(10) NOT NULL DEFAULT '1',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_sign_mode PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_sign_mode IS '签到方式:APP扫码 / å¾®ä¿¡æ‰«ç  / åœ¨çº¿ç­¾åˆ°';
COMMENT ON COLUMN tms_sign_mode.f_id IS '主键';
COMMENT ON COLUMN tms_sign_mode.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_sign_mode.mode_code IS '编码:app_scan/wx_scan/online';
COMMENT ON COLUMN tms_sign_mode.mode_name IS '名称';
COMMENT ON COLUMN tms_sign_mode.tip_seconds IS '有培训要点时强制阅读秒数,IT可调';
COMMENT ON COLUMN tms_sign_mode.match_roster IS '是否必须匹配培训对象名单';
COMMENT ON COLUMN tms_sign_mode.enabled IS '启用';
COMMENT ON COLUMN tms_sign_mode.remark IS '备注';
COMMENT ON COLUMN tms_sign_mode.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_sign_mode.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_sign_mode.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_sign_mode.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_sign_mode.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_sign_mode.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_sign_mode.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_sign_mode_code ON tms_sign_mode (mode_code) WHERE f_delete_mark = 0;
-- åŸ¹è®­æ•ˆæžœè¯„估方式:提问 / çŽ°åœºæ“ä½œ / åœ¨çº¿è€ƒè¯• / æ— éœ€è€ƒæ ¸
CREATE TABLE IF NOT EXISTS tms_eval_mode (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    mode_code                varchar(50) NOT NULL,
    mode_name                varchar(100) NOT NULL,
    need_paper               varchar(10) NOT NULL DEFAULT '0',
    need_quiz                varchar(10) NOT NULL DEFAULT '0',
    need_practice            varchar(10) NOT NULL DEFAULT '0',
    enabled                  varchar(10) NOT NULL DEFAULT '1',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_eval_mode PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_eval_mode IS '培训效果评估方式:提问 / çŽ°åœºæ“ä½œ / åœ¨çº¿è€ƒè¯• / æ— éœ€è€ƒæ ¸';
COMMENT ON COLUMN tms_eval_mode.f_id IS '主键';
COMMENT ON COLUMN tms_eval_mode.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_eval_mode.mode_code IS '编码:quiz/practice/exam/none';
COMMENT ON COLUMN tms_eval_mode.mode_name IS '名称';
COMMENT ON COLUMN tms_eval_mode.need_paper IS '是否需要试卷';
COMMENT ON COLUMN tms_eval_mode.need_quiz IS '是否需要提问预设';
COMMENT ON COLUMN tms_eval_mode.need_practice IS '是否需要实操评分';
COMMENT ON COLUMN tms_eval_mode.enabled IS '启用';
COMMENT ON COLUMN tms_eval_mode.remark IS '备注';
COMMENT ON COLUMN tms_eval_mode.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_eval_mode.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_eval_mode.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_eval_mode.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_eval_mode.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_eval_mode.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_eval_mode.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_eval_mode_code ON tms_eval_mode (mode_code) WHERE f_delete_mark = 0;
-- åŸ¹è®­è¯¾ç¨‹ä¸»æ•°æ®ï¼Œè®¡åˆ’/任务可引用
CREATE TABLE IF NOT EXISTS tms_course (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    course_no                varchar(50),
    course_name              varchar(200) NOT NULL,
    category                 varchar(50),
    train_mode               varchar(50),
    eval_mode                varchar(50),
    paper_id                 varchar(50),
    hours                    numeric(8,1),
    enabled                  varchar(10) NOT NULL DEFAULT '1',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_course PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_course IS '培训课程主数据,计划/任务可引用';
COMMENT ON COLUMN tms_course.f_id IS '主键';
COMMENT ON COLUMN tms_course.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_course.course_no IS '课程编号';
COMMENT ON COLUMN tms_course.course_name IS '课程名称';
COMMENT ON COLUMN tms_course.category IS '课程分类';
COMMENT ON COLUMN tms_course.train_mode IS '培训方式编码';
COMMENT ON COLUMN tms_course.eval_mode IS '考核方式编码';
COMMENT ON COLUMN tms_course.paper_id IS '关联试卷';
COMMENT ON COLUMN tms_course.hours IS '学时';
COMMENT ON COLUMN tms_course.enabled IS '启用';
COMMENT ON COLUMN tms_course.remark IS '备注';
COMMENT ON COLUMN tms_course.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_course.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_course.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_course.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_course.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_course.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_course.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_course_no ON tms_course (course_no) WHERE f_delete_mark = 0;
-- åŸ¹è®­æ•™æ/企业知识库。可上传本地或引用DMS(需鉴权)。撤销后不可再引用。
CREATE TABLE IF NOT EXISTS tms_material (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    material_no              varchar(50) NOT NULL,
    material_name            varchar(500) NOT NULL,
    file_type                varchar(50),
    apply_time               date,
    applicant_id             varchar(50),
    apply_dept_id            varchar(50),
    file_json                text,
    dms_file_id              varchar(50),
    quote_count              int NOT NULL DEFAULT 0,
    biz_status               varchar(20) NOT NULL DEFAULT 'valid',
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_material PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_material IS '培训教材/企业知识库。可上传本地或引用DMS(需鉴权)。撤销后不可再引用。';
COMMENT ON COLUMN tms_material.f_id IS '主键';
COMMENT ON COLUMN tms_material.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_material.material_no IS '教材编号 TM+YYYYMM+4';
COMMENT ON COLUMN tms_material.material_name IS '教材名称';
COMMENT ON COLUMN tms_material.file_type IS 'Word/PPT/Excel/PDF/视频/音频';
COMMENT ON COLUMN tms_material.apply_time IS '申请时间';
COMMENT ON COLUMN tms_material.applicant_id IS '上传申请人';
COMMENT ON COLUMN tms_material.apply_dept_id IS '申请人部门';
COMMENT ON COLUMN tms_material.file_json IS '教材附件(JNPF附件JSON)';
COMMENT ON COLUMN tms_material.dms_file_id IS '引用的DMS文件ID';
COMMENT ON COLUMN tms_material.quote_count IS '被任务引用次数';
COMMENT ON COLUMN tms_material.biz_status IS 'valid有效 / revoked已撤销';
COMMENT ON COLUMN tms_material.remark IS '备注';
COMMENT ON COLUMN tms_material.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_material.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_material.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_material.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_material.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_material.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_material.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_material_no ON tms_material (material_no) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_material_type ON tms_material (file_type, biz_status);
-- ---------------------------------------------------------------------------
-- 2. é¢˜åº“试卷
-- ---------------------------------------------------------------------------
-- é¢˜åº“
CREATE TABLE IF NOT EXISTS tms_question_bank (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    bank_name                varchar(200) NOT NULL,
    question_count           int NOT NULL DEFAULT 0,
    biz_status               varchar(20) NOT NULL DEFAULT 'normal',
    remark                   text,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_question_bank PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_question_bank IS '题库';
COMMENT ON COLUMN tms_question_bank.f_id IS '主键';
COMMENT ON COLUMN tms_question_bank.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_question_bank.bank_name IS '题库名称';
COMMENT ON COLUMN tms_question_bank.question_count IS '试题数量(维护时回写)';
COMMENT ON COLUMN tms_question_bank.biz_status IS 'normal正常 / invalid失效';
COMMENT ON COLUMN tms_question_bank.remark IS '备注';
COMMENT ON COLUMN tms_question_bank.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_question_bank.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_question_bank.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_question_bank.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_question_bank.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_question_bank.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_question_bank.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_qbank_status ON tms_question_bank (biz_status);
-- è¯•题。修改不影响已完成考试快照。
CREATE TABLE IF NOT EXISTS tms_question (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    question_no              varchar(50),
    bank_id                  varchar(50) NOT NULL,
    question_type            varchar(20) NOT NULL,
    difficulty               varchar(20),
    source_type              varchar(50),
    stem                     text NOT NULL,
    analysis                 text,
    admin_user_id            varchar(50),
    biz_status               varchar(20) NOT NULL DEFAULT 'open',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_question PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_question IS '试题。修改不影响已完成考试快照。';
COMMENT ON COLUMN tms_question.f_id IS '主键';
COMMENT ON COLUMN tms_question.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_question.question_no IS '试题编号,可流水';
COMMENT ON COLUMN tms_question.bank_id IS '所属题库 tms_question_bank.f_id';
COMMENT ON COLUMN tms_question.question_type IS 'single单选 / multi多选 / judge判断 / blank填空 / essay问答';
COMMENT ON COLUMN tms_question.difficulty IS 'easy/normal/hard,界面默认一般';
COMMENT ON COLUMN tms_question.source_type IS '试题来源,默认自命题';
COMMENT ON COLUMN tms_question.stem IS '题干,富文本';
COMMENT ON COLUMN tms_question.analysis IS '答案解析/备注,错题可显示';
COMMENT ON COLUMN tms_question.admin_user_id IS '管理员';
COMMENT ON COLUMN tms_question.biz_status IS 'open开放 / closed不开放 / invalid废弃';
COMMENT ON COLUMN tms_question.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_question.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_question.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_question.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_question.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_question.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_question.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_question_bank ON tms_question (bank_id, question_type, biz_status);
-- è¯•题选项。单选单选中,多选可多选中,判断可做成A对B错。
CREATE TABLE IF NOT EXISTS tms_question_option (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    sort_no                  int,
    option_label             varchar(10),
    option_content           text,
    is_correct               varchar(10) NOT NULL DEFAULT '0',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_question_option PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_question_option IS '试题选项。单选单选中,多选可多选中,判断可做成A对B错。';
COMMENT ON COLUMN tms_question_option.f_id IS '主键';
COMMENT ON COLUMN tms_question_option.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_question_option.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_question_option.sort_no IS '序号';
COMMENT ON COLUMN tms_question_option.option_label IS 'A/B/C/D...';
COMMENT ON COLUMN tms_question_option.option_content IS '选项内容';
COMMENT ON COLUMN tms_question_option.is_correct IS '是否正确答案 1是 0否';
COMMENT ON COLUMN tms_question_option.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_question_option.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_question_option.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_question_option.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_question_option.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_qopt_fid ON tms_question_option (f_foreign_id);
-- è¯•卷。失效试卷不得再用于新任务;改卷不影响已考记录。
CREATE TABLE IF NOT EXISTS tms_paper (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    paper_name               varchar(300) NOT NULL,
    biz_status               varchar(20) NOT NULL DEFAULT 'open',
    duration_min             int NOT NULL DEFAULT 60,
    exam_start               timestamp,
    exam_end                 timestamp,
    score_publish_time       timestamp,
    pass_score               numeric(8,1),
    total_score              numeric(8,1),
    sort_mode                varchar(20) NOT NULL DEFAULT 'paper',
    has_subjective           varchar(10) NOT NULL DEFAULT '0',
    remark                   text,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_paper PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_paper IS '试卷。失效试卷不得再用于新任务;改卷不影响已考记录。';
COMMENT ON COLUMN tms_paper.f_id IS '主键';
COMMENT ON COLUMN tms_paper.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_paper.paper_name IS '试卷名称';
COMMENT ON COLUMN tms_paper.biz_status IS 'open开放 / closed不开放 / invalid失效';
COMMENT ON COLUMN tms_paper.duration_min IS '考试时长(分钟)';
COMMENT ON COLUMN tms_paper.exam_start IS '试卷级开考时间,可空,任务上再设';
COMMENT ON COLUMN tms_paper.exam_end IS '试卷级结考时间,可空';
COMMENT ON COLUMN tms_paper.score_publish_time IS '成绩公布时间,空=立即公布';
COMMENT ON COLUMN tms_paper.pass_score IS '合格分数';
COMMENT ON COLUMN tms_paper.total_score IS '试卷总分';
COMMENT ON COLUMN tms_paper.sort_mode IS 'paper试卷自身顺序 / random随机';
COMMENT ON COLUMN tms_paper.has_subjective IS '是否含主观题,组卷时回写';
COMMENT ON COLUMN tms_paper.remark IS '备注';
COMMENT ON COLUMN tms_paper.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_paper.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_paper.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_paper.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_paper.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_paper.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_paper.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_paper_status ON tms_paper (biz_status);
-- è¯•卷章节,可按题型归类
CREATE TABLE IF NOT EXISTS tms_paper_section (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    section_name             varchar(100),
    question_type            varchar(20),
    sort_no                  int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_paper_section PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_paper_section IS '试卷章节,可按题型归类';
COMMENT ON COLUMN tms_paper_section.f_id IS '主键';
COMMENT ON COLUMN tms_paper_section.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_paper_section.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_paper_section.section_name IS '章节名,如 ä¸€ã€å•选题';
COMMENT ON COLUMN tms_paper_section.question_type IS '默认题型';
COMMENT ON COLUMN tms_paper_section.sort_no IS '排序';
COMMENT ON COLUMN tms_paper_section.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_paper_section.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_paper_section.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_paper_section.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_paper_section.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_psec_fid ON tms_paper_section (f_foreign_id);
-- è¯•卷组成试题(当前版本)。已考试卷走 tms_exam_item å¿«ç…§ã€‚
CREATE TABLE IF NOT EXISTS tms_paper_question (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    section_id               varchar(50),
    question_id              varchar(50) NOT NULL,
    score                    numeric(8,1),
    sort_no                  int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_paper_question PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_paper_question IS '试卷组成试题(当前版本)。已考试卷走 tms_exam_item å¿«ç…§ã€‚';
COMMENT ON COLUMN tms_paper_question.f_id IS '主键';
COMMENT ON COLUMN tms_paper_question.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_paper_question.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_paper_question.section_id IS '章节ID';
COMMENT ON COLUMN tms_paper_question.question_id IS '试题ID';
COMMENT ON COLUMN tms_paper_question.score IS '本题分值';
COMMENT ON COLUMN tms_paper_question.sort_no IS '自定义排序';
COMMENT ON COLUMN tms_paper_question.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_paper_question.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_paper_question.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_paper_question.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_paper_question.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_pq_fid ON tms_paper_question (f_foreign_id);
CREATE INDEX IF NOT EXISTS idx_tms_pq_qid ON tms_paper_question (question_id);
-- ---------------------------------------------------------------------------
-- 3. å²—位/年度计划
-- ---------------------------------------------------------------------------
-- å²—位培训目录。支持升版、废弃。审批通过后可整单复制。
CREATE TABLE IF NOT EXISTS tms_post_catalog (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    catalog_no               varchar(50),
    post_id                  varchar(50) NOT NULL,
    post_name                varchar(100),
    dept_id                  varchar(50),
    version_no               int NOT NULL DEFAULT 1,
    biz_status               varchar(20) NOT NULL DEFAULT 'valid',
    remark                   varchar(500),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_post_catalog PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_post_catalog IS '岗位培训目录。支持升版、废弃。审批通过后可整单复制。';
COMMENT ON COLUMN tms_post_catalog.f_id IS '主键';
COMMENT ON COLUMN tms_post_catalog.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_post_catalog.catalog_no IS '目录编号';
COMMENT ON COLUMN tms_post_catalog.post_id IS '岗位';
COMMENT ON COLUMN tms_post_catalog.post_name IS '岗位名称冗余';
COMMENT ON COLUMN tms_post_catalog.dept_id IS '所属部门';
COMMENT ON COLUMN tms_post_catalog.version_no IS '版本号,升版+1';
COMMENT ON COLUMN tms_post_catalog.biz_status IS 'valid有效 / revoked废弃';
COMMENT ON COLUMN tms_post_catalog.remark IS '备注';
COMMENT ON COLUMN tms_post_catalog.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_post_catalog.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_post_catalog.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_post_catalog.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_post_catalog.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_post_catalog.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_post_catalog.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_post_catalog.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_post_catalog.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_post_catalog.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_pcat_post ON tms_post_catalog (post_id, biz_status);
-- å²—位培训目录内容行,支持回车多行、手动排序
CREATE TABLE IF NOT EXISTS tms_post_catalog_item (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    sort_no                  int,
    subject                  varchar(500),
    train_mode               varchar(50),
    hours                    numeric(8,1),
    eval_mode                varchar(50),
    material_id              varchar(50),
    course_id                varchar(50),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_post_catalog_item PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_post_catalog_item IS '岗位培训目录内容行,支持回车多行、手动排序';
COMMENT ON COLUMN tms_post_catalog_item.f_id IS '主键';
COMMENT ON COLUMN tms_post_catalog_item.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_post_catalog_item.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_post_catalog_item.sort_no IS '顺序';
COMMENT ON COLUMN tms_post_catalog_item.subject IS '培训内容';
COMMENT ON COLUMN tms_post_catalog_item.train_mode IS '培训方式';
COMMENT ON COLUMN tms_post_catalog_item.hours IS '课时';
COMMENT ON COLUMN tms_post_catalog_item.eval_mode IS '考核方式';
COMMENT ON COLUMN tms_post_catalog_item.material_id IS '教材';
COMMENT ON COLUMN tms_post_catalog_item.course_id IS '课程';
COMMENT ON COLUMN tms_post_catalog_item.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_post_catalog_item.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_post_catalog_item.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_post_catalog_item.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_post_catalog_item.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_pcati_fid ON tms_post_catalog_item (f_foreign_id);
-- å²—位培训计划。由目录推送,可生成个人岗位培训和培训任务。
CREATE TABLE IF NOT EXISTS tms_post_plan (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    plan_no                  varchar(50) NOT NULL,
    post_id                  varchar(50),
    post_name                varchar(100),
    dept_id                  varchar(50),
    plan_end_time            date,
    post_desc                text,
    trainees                 text,
    catalog_id               varchar(50),
    publish_status           varchar(20) NOT NULL DEFAULT 'draft',
    remark                   varchar(500),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_post_plan PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_post_plan IS '岗位培训计划。由目录推送,可生成个人岗位培训和培训任务。';
COMMENT ON COLUMN tms_post_plan.f_id IS '主键';
COMMENT ON COLUMN tms_post_plan.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_post_plan.plan_no IS '计划编号 JP+YYYYMM+4,如 JP2026090012';
COMMENT ON COLUMN tms_post_plan.post_id IS '岗位编码/ID';
COMMENT ON COLUMN tms_post_plan.post_name IS '岗位名称';
COMMENT ON COLUMN tms_post_plan.dept_id IS '所属部门';
COMMENT ON COLUMN tms_post_plan.plan_end_time IS '计划结束时间,带入个人岗位培训';
COMMENT ON COLUMN tms_post_plan.post_desc IS '岗位描述';
COMMENT ON COLUMN tms_post_plan.trainees IS '培训对象,用户多选';
COMMENT ON COLUMN tms_post_plan.catalog_id IS '来源目录';
COMMENT ON COLUMN tms_post_plan.publish_status IS 'draft未发布 / published已发布';
COMMENT ON COLUMN tms_post_plan.remark IS '备注';
COMMENT ON COLUMN tms_post_plan.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_post_plan.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_post_plan.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_post_plan.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_post_plan.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_post_plan.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_post_plan.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_post_plan.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_post_plan.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_post_plan.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_pplan_no ON tms_post_plan (plan_no) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_pplan_post ON tms_post_plan (post_id, publish_status);
-- å²—位培训计划明细
CREATE TABLE IF NOT EXISTS tms_post_plan_item (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    plan_time                date,
    teacher_id               varchar(50),
    subject                  varchar(500),
    train_mode               varchar(50),
    hours                    numeric(8,1),
    eval_mode                varchar(50),
    material_id              varchar(50),
    task_id                  varchar(50),
    line_status              varchar(20),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_post_plan_item PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_post_plan_item IS '岗位培训计划明细';
COMMENT ON COLUMN tms_post_plan_item.f_id IS '主键';
COMMENT ON COLUMN tms_post_plan_item.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_post_plan_item.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_post_plan_item.plan_time IS '培训计划时间';
COMMENT ON COLUMN tms_post_plan_item.teacher_id IS '培训师';
COMMENT ON COLUMN tms_post_plan_item.subject IS '培训内容';
COMMENT ON COLUMN tms_post_plan_item.train_mode IS '培训方式';
COMMENT ON COLUMN tms_post_plan_item.hours IS '培训课时';
COMMENT ON COLUMN tms_post_plan_item.eval_mode IS '考核方式';
COMMENT ON COLUMN tms_post_plan_item.material_id IS '教材';
COMMENT ON COLUMN tms_post_plan_item.task_id IS '生成的培训任务ID';
COMMENT ON COLUMN tms_post_plan_item.line_status IS 'pending待发布 / published已发布 / done已完成';
COMMENT ON COLUMN tms_post_plan_item.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_post_plan_item.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_post_plan_item.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_post_plan_item.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_post_plan_item.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_pplani_fid ON tms_post_plan_item (f_foreign_id);
-- ä¸ªäººå²—位培训清单。转岗时子表标记已培训过则不生成任务。
CREATE TABLE IF NOT EXISTS tms_person_post (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    plan_id                  varchar(50),
    catalog_id               varchar(50),
    user_id                  varchar(50) NOT NULL,
    post_id                  varchar(50),
    plan_end_time            date,
    biz_status               varchar(20) NOT NULL DEFAULT 'doing',
    person_remark            varchar(500),
    audit_remark             varchar(500),
    approve_remark           varchar(500),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_person_post PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_post IS '个人岗位培训清单。转岗时子表标记已培训过则不生成任务。';
COMMENT ON COLUMN tms_person_post.f_id IS '主键';
COMMENT ON COLUMN tms_person_post.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_post.plan_id IS '岗位培训计划ID';
COMMENT ON COLUMN tms_person_post.catalog_id IS '目录ID';
COMMENT ON COLUMN tms_person_post.user_id IS '培训对象';
COMMENT ON COLUMN tms_person_post.post_id IS '岗位';
COMMENT ON COLUMN tms_person_post.plan_end_time IS '计划结束时间(由计划带出)';
COMMENT ON COLUMN tms_person_post.biz_status IS 'doing执行中 / done完成 / returned退回';
COMMENT ON COLUMN tms_person_post.person_remark IS '个人备注';
COMMENT ON COLUMN tms_person_post.audit_remark IS '审核备注';
COMMENT ON COLUMN tms_person_post.approve_remark IS '审批备注';
COMMENT ON COLUMN tms_person_post.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_person_post.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_person_post.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_person_post.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_post.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_post.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_post.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_post.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_person_post.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_person_post.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_ppost_user ON tms_person_post (user_id, biz_status);
CREATE INDEX IF NOT EXISTS idx_tms_ppost_plan ON tms_person_post (plan_id);
-- ä¸ªäººå²—位培训明细
CREATE TABLE IF NOT EXISTS tms_person_post_item (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    subject                  varchar(500),
    train_mode               varchar(50),
    hours                    numeric(8,1),
    eval_mode                varchar(50),
    already_trained          varchar(10) NOT NULL DEFAULT '0',
    task_id                  varchar(50),
    line_status              varchar(20),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_person_post_item PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_post_item IS '个人岗位培训明细';
COMMENT ON COLUMN tms_person_post_item.f_id IS '主键';
COMMENT ON COLUMN tms_person_post_item.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_post_item.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_person_post_item.subject IS '培训内容';
COMMENT ON COLUMN tms_person_post_item.train_mode IS '培训方式';
COMMENT ON COLUMN tms_person_post_item.hours IS '课时';
COMMENT ON COLUMN tms_person_post_item.eval_mode IS '考核方式';
COMMENT ON COLUMN tms_person_post_item.already_trained IS '转岗:是否已培训过,是则不生成任务';
COMMENT ON COLUMN tms_person_post_item.task_id IS '生成的培训任务';
COMMENT ON COLUMN tms_person_post_item.line_status IS '状态';
COMMENT ON COLUMN tms_person_post_item.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_post_item.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_post_item.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_post_item.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_post_item.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_pposti_fid ON tms_person_post_item (f_foreign_id);
-- å¹´åº¦åŸ¹è®­è®¡åˆ’(公司/部门)。办结后生成培训任务。流程:部门负责人→QA负责人。
CREATE TABLE IF NOT EXISTS tms_annual_plan (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    plan_year                int NOT NULL,
    plan_no                  varchar(50) NOT NULL,
    plan_type                varchar(20),
    author_id                varchar(50),
    author_dept_id           varchar(50),
    author_date              date,
    version_no               int NOT NULL DEFAULT 1,
    dept_leader_id           varchar(50),
    qa_leader_id             varchar(50),
    biz_status               varchar(20) NOT NULL DEFAULT 'draft',
    remark                   text,
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_annual_plan PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_annual_plan IS '年度培训计划(公司/部门)。办结后生成培训任务。流程:部门负责人→QA负责人。';
COMMENT ON COLUMN tms_annual_plan.f_id IS '主键';
COMMENT ON COLUMN tms_annual_plan.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_annual_plan.plan_year IS '年份,制定时默认当前年+1';
COMMENT ON COLUMN tms_annual_plan.plan_no IS '计划编号,如 20260001';
COMMENT ON COLUMN tms_annual_plan.plan_type IS 'company公司级 / dept部门级';
COMMENT ON COLUMN tms_annual_plan.author_id IS '制定人';
COMMENT ON COLUMN tms_annual_plan.author_dept_id IS '制定部门';
COMMENT ON COLUMN tms_annual_plan.author_date IS '制定日期';
COMMENT ON COLUMN tms_annual_plan.version_no IS '版本';
COMMENT ON COLUMN tms_annual_plan.dept_leader_id IS '部门负责人(签核展示)';
COMMENT ON COLUMN tms_annual_plan.qa_leader_id IS 'QA部负责人';
COMMENT ON COLUMN tms_annual_plan.biz_status IS 'draft草稿 / approving审批中 / approved已批准';
COMMENT ON COLUMN tms_annual_plan.remark IS '备注';
COMMENT ON COLUMN tms_annual_plan.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_annual_plan.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_annual_plan.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_annual_plan.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_annual_plan.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_annual_plan.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_annual_plan.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_annual_plan.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_annual_plan.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_annual_plan.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_aplan_no ON tms_annual_plan (plan_no) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_aplan_year ON tms_annual_plan (plan_year, plan_type, author_dept_id);
-- å¹´åº¦åŸ¹è®­è®¡åˆ’明细
CREATE TABLE IF NOT EXISTS tms_annual_plan_item (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    train_month              varchar(6),
    subject                  varchar(500),
    train_mode               varchar(50),
    trainees                 text,
    hours                    numeric(8,1),
    teacher_id               varchar(50),
    eval_mode                varchar(50),
    course_id                varchar(50),
    line_status              varchar(20) NOT NULL DEFAULT 'pending',
    task_id                  varchar(50),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_annual_plan_item PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_annual_plan_item IS '年度培训计划明细';
COMMENT ON COLUMN tms_annual_plan_item.f_id IS '主键';
COMMENT ON COLUMN tms_annual_plan_item.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_annual_plan_item.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_annual_plan_item.train_month IS '培训月份 YYYYMM,如 202609';
COMMENT ON COLUMN tms_annual_plan_item.subject IS '培训内容';
COMMENT ON COLUMN tms_annual_plan_item.train_mode IS '培训方式';
COMMENT ON COLUMN tms_annual_plan_item.trainees IS '培训对象';
COMMENT ON COLUMN tms_annual_plan_item.hours IS '培训课时';
COMMENT ON COLUMN tms_annual_plan_item.teacher_id IS '培训师';
COMMENT ON COLUMN tms_annual_plan_item.eval_mode IS '考核方式';
COMMENT ON COLUMN tms_annual_plan_item.course_id IS '培训课程';
COMMENT ON COLUMN tms_annual_plan_item.line_status IS 'pending待执行 / tasked已生成任务 / done已完成 / delayed已延期';
COMMENT ON COLUMN tms_annual_plan_item.task_id IS '生成的培训任务';
COMMENT ON COLUMN tms_annual_plan_item.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_annual_plan_item.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_annual_plan_item.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_annual_plan_item.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_annual_plan_item.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_aplani_fid ON tms_annual_plan_item (f_foreign_id);
CREATE INDEX IF NOT EXISTS idx_tms_aplani_month ON tms_annual_plan_item (train_month);
-- å¹´åº¦åŸ¹è®­è®¡åˆ’延期申请
CREATE TABLE IF NOT EXISTS tms_annual_delay (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    plan_id                  varchar(50) NOT NULL,
    item_id                  varchar(50),
    old_month                varchar(6),
    new_month                varchar(6),
    reason                   text,
    biz_status               varchar(20),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_annual_delay PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_annual_delay IS '年度培训计划延期申请';
COMMENT ON COLUMN tms_annual_delay.f_id IS '主键';
COMMENT ON COLUMN tms_annual_delay.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_annual_delay.plan_id IS '年度计划ID';
COMMENT ON COLUMN tms_annual_delay.item_id IS '计划明细ID';
COMMENT ON COLUMN tms_annual_delay.old_month IS '原月份';
COMMENT ON COLUMN tms_annual_delay.new_month IS '延期至月份';
COMMENT ON COLUMN tms_annual_delay.reason IS '延期原因';
COMMENT ON COLUMN tms_annual_delay.biz_status IS '审批状态';
COMMENT ON COLUMN tms_annual_delay.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_annual_delay.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_annual_delay.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_annual_delay.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_annual_delay.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_annual_delay.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_annual_delay.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_annual_delay.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_annual_delay.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_annual_delay.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_adelay_plan ON tms_annual_delay (plan_id);
-- ---------------------------------------------------------------------------
-- 4. åŸ¹è®­ä»»åŠ¡ä¸Žè®°å½•
-- ---------------------------------------------------------------------------
-- åŸ¹è®­ä»»åŠ¡ï¼ˆæ ¸å¿ƒï¼‰ã€‚æ¥æºï¼šå²—ä½è®¡åˆ’/年度计划/文件生效/临时/外出/再维护。
CREATE TABLE IF NOT EXISTS tms_task (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    task_no                  varchar(50) NOT NULL,
    category                 varchar(50),
    train_type               varchar(50),
    source_type              varchar(50),
    source_plan_no           varchar(50),
    source_id                varchar(50),
    source_item_id           varchar(50),
    subject                  text,
    key_points               text,
    material_id              varchar(50),
    material_no              varchar(50),
    place_id                 varchar(50),
    place_no                 varchar(50),
    place_name               varchar(200),
    start_time               timestamp,
    end_time                 timestamp,
    close_time               timestamp,
    paper_id                 varchar(50),
    paper_name               varchar(300),
    exam_start               timestamp,
    exam_end                 timestamp,
    teacher_id               text,
    train_mode               varchar(50),
    eval_mode                varchar(50),
    trainees                 text,
    task_kind                varchar(20),
    prev_task_no             varchar(50),
    publish_status           varchar(20) NOT NULL DEFAULT 'draft',
    publish_time             timestamp,
    retake_limit             int NOT NULL DEFAULT 1,
    pass_score               numeric(8,1),
    quiz_ratio               int,
    sign_rate                int,
    pass_rate                int,
    unfinished_rate          int,
    overdue_remind           varchar(10) NOT NULL DEFAULT '0',
    invite_flag              varchar(10) NOT NULL DEFAULT '0',
    dms_file_id              varchar(50),
    remark                   varchar(500),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_task PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_task IS '培训任务(核心)。来源:岗位计划/年度计划/文件生效/临时/外出/再维护。';
COMMENT ON COLUMN tms_task.f_id IS '主键';
COMMENT ON COLUMN tms_task.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_task.task_no IS '任务编号 TT+YYYYMM+4,如 TT2026090618';
COMMENT ON COLUMN tms_task.category IS '培训分类:临时培训/岗位培训计划/年度培训计划/文件生效培训/外出培训/再维护';
COMMENT ON COLUMN tms_task.train_type IS '培训类型(列表字段,可与分类并存)';
COMMENT ON COLUMN tms_task.source_type IS '来源类型编码';
COMMENT ON COLUMN tms_task.source_plan_no IS '来源计划编号';
COMMENT ON COLUMN tms_task.source_id IS '来源主表ID';
COMMENT ON COLUMN tms_task.source_item_id IS '来源明细ID,归档回写';
COMMENT ON COLUMN tms_task.subject IS '培训主题';
COMMENT ON COLUMN tms_task.key_points IS '培训要点,签到前强制阅读';
COMMENT ON COLUMN tms_task.material_id IS '培训教材';
COMMENT ON COLUMN tms_task.material_no IS '教材编号冗余';
COMMENT ON COLUMN tms_task.place_id IS '培训地点';
COMMENT ON COLUMN tms_task.place_no IS '地点编号冗余';
COMMENT ON COLUMN tms_task.place_name IS '地点名称冗余';
COMMENT ON COLUMN tms_task.start_time IS '培训开始时间';
COMMENT ON COLUMN tms_task.end_time IS '培训结束时间';
COMMENT ON COLUMN tms_task.close_time IS '任务关闭时间,到达后记录进待归档';
COMMENT ON COLUMN tms_task.paper_id IS '试卷';
COMMENT ON COLUMN tms_task.paper_name IS '试卷名称冗余';
COMMENT ON COLUMN tms_task.exam_start IS '考试开始时间';
COMMENT ON COLUMN tms_task.exam_end IS '考试结束时间';
COMMENT ON COLUMN tms_task.teacher_id IS '培训师,可多选';
COMMENT ON COLUMN tms_task.train_mode IS '培训方式';
COMMENT ON COLUMN tms_task.eval_mode IS '培训效果评估方式/考核方式';
COMMENT ON COLUMN tms_task.trainees IS '培训对象,用户多选;发布时拆个人任务';
COMMENT ON COLUMN tms_task.task_kind IS '任务类型:new新增 / continueç»§ç»­(再维护)';
COMMENT ON COLUMN tms_task.prev_task_no IS '上一次培训任务编号';
COMMENT ON COLUMN tms_task.publish_status IS 'draft未发布 / published已发布 / cancelled已取消 / done已完成';
COMMENT ON COLUMN tms_task.publish_time IS '发布时间';
COMMENT ON COLUMN tms_task.retake_limit IS '不合格可重考次数';
COMMENT ON COLUMN tms_task.pass_score IS '通过分数,空则用试卷';
COMMENT ON COLUMN tms_task.quiz_ratio IS '提问比例0-100,自动算应提问人数';
COMMENT ON COLUMN tms_task.sign_rate IS '签到率%,取整舍小数';
COMMENT ON COLUMN tms_task.pass_rate IS '合格率%,取整舍小数';
COMMENT ON COLUMN tms_task.unfinished_rate IS '未完成率%';
COMMENT ON COLUMN tms_task.overdue_remind IS '是否逾期提醒部门领导';
COMMENT ON COLUMN tms_task.invite_flag IS '是否发送培训邀请';
COMMENT ON COLUMN tms_task.dms_file_id IS '文件生效自动引入的附件';
COMMENT ON COLUMN tms_task.remark IS '备注';
COMMENT ON COLUMN tms_task.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_task.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_task.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_task.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_task.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_task.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_task.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_task.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_task.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_task.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_task_no ON tms_task (task_no) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_task_status ON tms_task (publish_status, start_time);
CREATE INDEX IF NOT EXISTS idx_tms_task_place ON tms_task (place_id, start_time, end_time);
CREATE INDEX IF NOT EXISTS idx_tms_task_source ON tms_task (source_type, source_id);
CREATE INDEX IF NOT EXISTS idx_tms_task_teacher ON tms_task (teacher_id);
-- åŸ¹è®­ä»»åŠ¡æ•™æ/附件清单,发布后同步到培训记录
CREATE TABLE IF NOT EXISTS tms_task_file (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    file_no                  varchar(50),
    file_name                varchar(300),
    material_id              varchar(50),
    file_json                text,
    can_download             varchar(10) NOT NULL DEFAULT '0',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_task_file PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_task_file IS '培训任务教材/附件清单,发布后同步到培训记录';
COMMENT ON COLUMN tms_task_file.f_id IS '主键';
COMMENT ON COLUMN tms_task_file.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_task_file.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_task_file.file_no IS '编号';
COMMENT ON COLUMN tms_task_file.file_name IS '文件名称';
COMMENT ON COLUMN tms_task_file.material_id IS '来源教材';
COMMENT ON COLUMN tms_task_file.file_json IS '附件';
COMMENT ON COLUMN tms_task_file.can_download IS '引用教材默认不可下载打印';
COMMENT ON COLUMN tms_task_file.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_task_file.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_task_file.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_task_file.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_task_file.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_tfile_fid ON tms_task_file (f_foreign_id);
-- åŸ¹è®­ä»»åŠ¡æé—®é¢„è®¾é—®é¢˜å’Œç­”æ¡ˆï¼Œç­¾åˆ°åŽå¼¹å‡º
CREATE TABLE IF NOT EXISTS tms_task_quiz (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    sort_no                  int,
    question                 text,
    answer                   text,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_task_quiz PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_task_quiz IS '培训任务提问预设问题和答案,签到后弹出';
COMMENT ON COLUMN tms_task_quiz.f_id IS '主键';
COMMENT ON COLUMN tms_task_quiz.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_task_quiz.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_task_quiz.sort_no IS '序号';
COMMENT ON COLUMN tms_task_quiz.question IS '问题';
COMMENT ON COLUMN tms_task_quiz.answer IS '参考答案';
COMMENT ON COLUMN tms_task_quiz.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_task_quiz.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_task_quiz.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_task_quiz.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_task_quiz.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_tquiz_fid ON tms_task_quiz (f_foreign_id);
-- ä¸ªäººåŸ¹è®­ä»»åŠ¡ã€‚å‘å¸ƒæ—¶æŒ‰äººæ‹†è¡Œï¼›å­¦å‘˜ç«¯è‡ªå­¦/签到/考试入口。
CREATE TABLE IF NOT EXISTS tms_person_task (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    task_id                  varchar(50) NOT NULL,
    task_no                  varchar(50),
    user_id                  varchar(50) NOT NULL,
    dept_id                  varchar(50),
    hours                    numeric(8,1),
    biz_status               varchar(20) NOT NULL DEFAULT 'todo',
    sign_time                timestamp,
    sign_mode                varchar(20),
    learn_seconds            int,
    exam_score               numeric(8,1),
    pass_flag                varchar(20),
    guest_flag               varchar(10) NOT NULL DEFAULT '0',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_person_task PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_task IS '个人培训任务。发布时按人拆行;学员端自学/签到/考试入口。';
COMMENT ON COLUMN tms_person_task.f_id IS '主键';
COMMENT ON COLUMN tms_person_task.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_task.task_id IS '培训任务ID';
COMMENT ON COLUMN tms_person_task.task_no IS '任务编号冗余';
COMMENT ON COLUMN tms_person_task.user_id IS '培训对象';
COMMENT ON COLUMN tms_person_task.dept_id IS '当时部门快照';
COMMENT ON COLUMN tms_person_task.hours IS '学时';
COMMENT ON COLUMN tms_person_task.biz_status IS 'todo未完成 / signed已签到 / done已完成 / expired已过期 / cancelled已取消';
COMMENT ON COLUMN tms_person_task.sign_time IS '签到时间';
COMMENT ON COLUMN tms_person_task.sign_mode IS '签到方式';
COMMENT ON COLUMN tms_person_task.learn_seconds IS '在线学习时长秒';
COMMENT ON COLUMN tms_person_task.exam_score IS '考试成绩';
COMMENT ON COLUMN tms_person_task.pass_flag IS 'pass合格 / fail不合格 / na无需考核';
COMMENT ON COLUMN tms_person_task.guest_flag IS '是否名单外签到';
COMMENT ON COLUMN tms_person_task.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_task.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_task.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_task.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_task.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_person_task.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_person_task.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_ptask_user ON tms_person_task (user_id, biz_status);
CREATE INDEX IF NOT EXISTS idx_tms_ptask_task ON tms_person_task (task_id);
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_ptask_task_user ON tms_person_task (task_id, user_id) WHERE f_delete_mark = 0;
-- åŸ¹è®­è®°å½•。任务发布后生成;关闭时间到达进待归档。
CREATE TABLE IF NOT EXISTS tms_record (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    record_no                varchar(50),
    task_id                  varchar(50) NOT NULL,
    task_no                  varchar(50),
    category                 varchar(50),
    subject                  text,
    teacher_id               text,
    train_type               varchar(50),
    train_mode               varchar(50),
    eval_mode                varchar(50),
    start_time               timestamp,
    end_time                 timestamp,
    close_time               timestamp,
    archive_status           varchar(20) NOT NULL DEFAULT 'pending',
    archive_time             timestamp,
    archiver_id              varchar(50),
    sign_rate                int,
    pass_rate                int,
    lecture_file             text,
    paper_sign_file          text,
    print_tpl_id             varchar(50),
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_record PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_record IS '培训记录。任务发布后生成;关闭时间到达进待归档。';
COMMENT ON COLUMN tms_record.f_id IS '主键';
COMMENT ON COLUMN tms_record.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_record.record_no IS '记录编号,默认同任务编号';
COMMENT ON COLUMN tms_record.task_id IS '培训任务ID';
COMMENT ON COLUMN tms_record.task_no IS '任务编号';
COMMENT ON COLUMN tms_record.category IS '培训分类';
COMMENT ON COLUMN tms_record.subject IS '培训主题';
COMMENT ON COLUMN tms_record.teacher_id IS '培训师';
COMMENT ON COLUMN tms_record.train_type IS '培训类型';
COMMENT ON COLUMN tms_record.train_mode IS '培训方式';
COMMENT ON COLUMN tms_record.eval_mode IS '考核方式';
COMMENT ON COLUMN tms_record.start_time IS '培训开始时间,套打模板按此索引版本';
COMMENT ON COLUMN tms_record.end_time IS '培训结束时间';
COMMENT ON COLUMN tms_record.close_time IS '关闭时间';
COMMENT ON COLUMN tms_record.archive_status IS 'pending待归档 / ready可归档 / archived已归档 / invalid无效';
COMMENT ON COLUMN tms_record.archive_time IS '归档时间';
COMMENT ON COLUMN tms_record.archiver_id IS '归档人';
COMMENT ON COLUMN tms_record.sign_rate IS '参与/签到率% å–整舍小数';
COMMENT ON COLUMN tms_record.pass_rate IS '合格率%';
COMMENT ON COLUMN tms_record.lecture_file IS '培训讲义附件';
COMMENT ON COLUMN tms_record.paper_sign_file IS '纸质签到表附件';
COMMENT ON COLUMN tms_record.print_tpl_id IS '打印模板版本';
COMMENT ON COLUMN tms_record.remark IS '备注';
COMMENT ON COLUMN tms_record.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_record.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_record.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_record.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_record.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_record.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_record.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_record_task ON tms_record (task_id) WHERE f_delete_mark = 0;
CREATE INDEX IF NOT EXISTS idx_tms_record_arch ON tms_record (archive_status, start_time);
-- åŸ¹è®­è®°å½•签到/结果明细(一人一行)
CREATE TABLE IF NOT EXISTS tms_record_sign (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    user_id                  varchar(50) NOT NULL,
    user_name                varchar(100),
    dept_id                  varchar(50),
    sign_time                timestamp,
    sign_mode                varchar(20),
    guest_flag               varchar(10) NOT NULL DEFAULT '0',
    result_flag              varchar(20),
    exam_score               numeric(8,1),
    practice_score           numeric(8,1),
    quiz_score               numeric(8,1),
    exam_id                  varchar(50),
    judge_user_id            varchar(50),
    judge_time               timestamp,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_record_sign PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_record_sign IS '培训记录签到/结果明细(一人一行)';
COMMENT ON COLUMN tms_record_sign.f_id IS '主键';
COMMENT ON COLUMN tms_record_sign.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_record_sign.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_record_sign.user_id IS '培训对象';
COMMENT ON COLUMN tms_record_sign.user_name IS '姓名快照';
COMMENT ON COLUMN tms_record_sign.dept_id IS '部门快照';
COMMENT ON COLUMN tms_record_sign.sign_time IS '签到时间';
COMMENT ON COLUMN tms_record_sign.sign_mode IS '签到方式';
COMMENT ON COLUMN tms_record_sign.guest_flag IS '名单外签到';
COMMENT ON COLUMN tms_record_sign.result_flag IS 'pass合格 / fail不合格 / absent未参加 / na无需考核';
COMMENT ON COLUMN tms_record_sign.exam_score IS '考试成绩';
COMMENT ON COLUMN tms_record_sign.practice_score IS '实操成绩';
COMMENT ON COLUMN tms_record_sign.quiz_score IS '提问成绩';
COMMENT ON COLUMN tms_record_sign.exam_id IS '答卷ID';
COMMENT ON COLUMN tms_record_sign.judge_user_id IS '结果判定人(培训师)';
COMMENT ON COLUMN tms_record_sign.judge_time IS '判定时间';
COMMENT ON COLUMN tms_record_sign.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_record_sign.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_record_sign.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_record_sign.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_record_sign.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_rsign_fid ON tms_record_sign (f_foreign_id);
CREATE INDEX IF NOT EXISTS idx_tms_rsign_user ON tms_record_sign (user_id);
-- åŸ¹è®­è®°å½•变更:改结束时间、增加培训人员。培训师发起→部门领导(领导即培训师则跳过)。
CREATE TABLE IF NOT EXISTS tms_record_change (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    record_id                varchar(50) NOT NULL,
    task_id                  varchar(50),
    change_type              varchar(20),
    old_end_time             timestamp,
    new_end_time             timestamp,
    add_users                text,
    reason                   text,
    biz_status               varchar(20),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_record_change PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_record_change IS '培训记录变更:改结束时间、增加培训人员。培训师发起→部门领导(领导即培训师则跳过)。';
COMMENT ON COLUMN tms_record_change.f_id IS '主键';
COMMENT ON COLUMN tms_record_change.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_record_change.record_id IS '培训记录ID';
COMMENT ON COLUMN tms_record_change.task_id IS '任务ID';
COMMENT ON COLUMN tms_record_change.change_type IS 'end_time改结束时间 / add_user增加人员';
COMMENT ON COLUMN tms_record_change.old_end_time IS '原结束时间';
COMMENT ON COLUMN tms_record_change.new_end_time IS '新结束时间';
COMMENT ON COLUMN tms_record_change.add_users IS '新增人员';
COMMENT ON COLUMN tms_record_change.reason IS '变更原因';
COMMENT ON COLUMN tms_record_change.biz_status IS '审批状态';
COMMENT ON COLUMN tms_record_change.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_record_change.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_record_change.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_record_change.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_record_change.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_record_change.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_record_change.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_record_change.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_record_change.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_record_change.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_rchg_rec ON tms_record_change (record_id);
-- åŸ¹è®­èµ„料归档(电子/纸质/实操记录双结合)
CREATE TABLE IF NOT EXISTS tms_archive_file (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    record_id                varchar(50) NOT NULL,
    task_id                  varchar(50),
    archive_type             varchar(20),
    file_json                text,
    archive_time             timestamp,
    archiver_id              varchar(50),
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_archive_file PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_archive_file IS '培训资料归档(电子/纸质/实操记录双结合)';
COMMENT ON COLUMN tms_archive_file.f_id IS '主键';
COMMENT ON COLUMN tms_archive_file.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_archive_file.record_id IS '培训记录ID';
COMMENT ON COLUMN tms_archive_file.task_id IS '任务ID';
COMMENT ON COLUMN tms_archive_file.archive_type IS 'electronic电子 / paper纸质 / practice实操档案';
COMMENT ON COLUMN tms_archive_file.file_json IS '归档资料';
COMMENT ON COLUMN tms_archive_file.archive_time IS '归档时间';
COMMENT ON COLUMN tms_archive_file.archiver_id IS '归档人';
COMMENT ON COLUMN tms_archive_file.remark IS '备注';
COMMENT ON COLUMN tms_archive_file.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_archive_file.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_archive_file.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_archive_file.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_archive_file.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_archive_file.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_archive_file.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_afile_rec ON tms_archive_file (record_id);
-- ---------------------------------------------------------------------------
-- 5. è€ƒè¯•与考核
-- ---------------------------------------------------------------------------
-- åœ¨çº¿è€ƒè¯•答卷。中途退出可续考;提交后客观题自动评分,有主观题进阅卷。
CREATE TABLE IF NOT EXISTS tms_exam (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    task_id                  varchar(50) NOT NULL,
    person_task_id           varchar(50),
    record_id                varchar(50),
    paper_id                 varchar(50),
    paper_name               varchar(300),
    user_id                  varchar(50) NOT NULL,
    attempt_no               int NOT NULL DEFAULT 1,
    start_time               timestamp,
    submit_time              timestamp,
    duration_min             int,
    objective_score          numeric(8,1),
    subjective_score         numeric(8,1),
    total_score              numeric(8,1),
    pass_score               numeric(8,1),
    pass_flag                varchar(20),
    grade_status             varchar(20) NOT NULL DEFAULT 'auto',
    grader_id                varchar(50),
    grade_time               timestamp,
    exam_status              varchar(20) NOT NULL DEFAULT 'doing',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_exam PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_exam IS '在线考试答卷。中途退出可续考;提交后客观题自动评分,有主观题进阅卷。';
COMMENT ON COLUMN tms_exam.f_id IS '主键';
COMMENT ON COLUMN tms_exam.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_exam.task_id IS '培训任务';
COMMENT ON COLUMN tms_exam.person_task_id IS '个人任务';
COMMENT ON COLUMN tms_exam.record_id IS '培训记录';
COMMENT ON COLUMN tms_exam.paper_id IS '当时试卷ID';
COMMENT ON COLUMN tms_exam.paper_name IS '试卷名称快照';
COMMENT ON COLUMN tms_exam.user_id IS '考生';
COMMENT ON COLUMN tms_exam.attempt_no IS '第几次考试';
COMMENT ON COLUMN tms_exam.start_time IS '开始作答';
COMMENT ON COLUMN tms_exam.submit_time IS '交卷时间';
COMMENT ON COLUMN tms_exam.duration_min IS '当时时长快照';
COMMENT ON COLUMN tms_exam.objective_score IS '客观题得分';
COMMENT ON COLUMN tms_exam.subjective_score IS '主观题得分';
COMMENT ON COLUMN tms_exam.total_score IS '总分';
COMMENT ON COLUMN tms_exam.pass_score IS '当时合格分快照';
COMMENT ON COLUMN tms_exam.pass_flag IS 'pass/fail';
COMMENT ON COLUMN tms_exam.grade_status IS 'auto无需阅卷 / pending待批改 / graded已批改';
COMMENT ON COLUMN tms_exam.grader_id IS '阅卷老师';
COMMENT ON COLUMN tms_exam.grade_time IS '阅卷时间';
COMMENT ON COLUMN tms_exam.exam_status IS 'doing考试中 / submitted已交卷 / cancelled已取消';
COMMENT ON COLUMN tms_exam.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_exam.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_exam.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_exam.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_exam.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_exam.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_exam.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_exam_task_user ON tms_exam (task_id, user_id, attempt_no);
CREATE INDEX IF NOT EXISTS idx_tms_exam_grade ON tms_exam (grade_status);
CREATE INDEX IF NOT EXISTS idx_tms_exam_user ON tms_exam (user_id);
-- ç­”卷试题快照:题目/选项/答案/分数保留考试当时状态,后续改题改卷不影响。
CREATE TABLE IF NOT EXISTS tms_exam_item (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    question_id              varchar(50),
    question_type            varchar(20),
    stem                     text,
    options_json             text,
    correct_answer           text,
    user_answer              text,
    score                    numeric(8,1),
    got_score                numeric(8,1),
    is_subjective            varchar(10) NOT NULL DEFAULT '0',
    analysis                 text,
    sort_no                  int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_exam_item PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_exam_item IS '答卷试题快照:题目/选项/答案/分数保留考试当时状态,后续改题改卷不影响。';
COMMENT ON COLUMN tms_exam_item.f_id IS '主键';
COMMENT ON COLUMN tms_exam_item.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_exam_item.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_exam_item.question_id IS '原试题ID';
COMMENT ON COLUMN tms_exam_item.question_type IS '题型快照';
COMMENT ON COLUMN tms_exam_item.stem IS '题干快照';
COMMENT ON COLUMN tms_exam_item.options_json IS '选项快照 JSON';
COMMENT ON COLUMN tms_exam_item.correct_answer IS '当时正确答案';
COMMENT ON COLUMN tms_exam_item.user_answer IS '考生作答';
COMMENT ON COLUMN tms_exam_item.score IS '本题满分';
COMMENT ON COLUMN tms_exam_item.got_score IS '得分';
COMMENT ON COLUMN tms_exam_item.is_subjective IS '是否主观题';
COMMENT ON COLUMN tms_exam_item.analysis IS '解析快照';
COMMENT ON COLUMN tms_exam_item.sort_no IS '试卷内顺序';
COMMENT ON COLUMN tms_exam_item.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_exam_item.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_exam_item.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_exam_item.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_exam_item.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_eitem_fid ON tms_exam_item (f_foreign_id);
-- å®žæ“è€ƒæ ¸ä¸»è¡¨ï¼Œä¿å­˜åŽå›žå†™åŸ¹è®­è®°å½•
CREATE TABLE IF NOT EXISTS tms_practice (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    task_id                  varchar(50) NOT NULL,
    record_id                varchar(50),
    teacher_id               varchar(50),
    practice_time            timestamp,
    remark                   varchar(500),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_practice PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_practice IS '实操考核主表,保存后回写培训记录';
COMMENT ON COLUMN tms_practice.f_id IS '主键';
COMMENT ON COLUMN tms_practice.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_practice.task_id IS '培训任务';
COMMENT ON COLUMN tms_practice.record_id IS '培训记录';
COMMENT ON COLUMN tms_practice.teacher_id IS '考核人';
COMMENT ON COLUMN tms_practice.practice_time IS '考核时间';
COMMENT ON COLUMN tms_practice.remark IS '备注';
COMMENT ON COLUMN tms_practice.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_practice.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_practice.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_practice.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_practice.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_practice.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_practice.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_prac_task ON tms_practice (task_id);
-- å®žæ“è€ƒæ ¸æ˜Žç»†
CREATE TABLE IF NOT EXISTS tms_practice_item (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    user_id                  varchar(50) NOT NULL,
    content                  text,
    score                    numeric(8,1),
    result_flag              varchar(20),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_practice_item PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_practice_item IS '实操考核明细';
COMMENT ON COLUMN tms_practice_item.f_id IS '主键';
COMMENT ON COLUMN tms_practice_item.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_practice_item.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_practice_item.user_id IS '培训对象';
COMMENT ON COLUMN tms_practice_item.content IS '考核内容';
COMMENT ON COLUMN tms_practice_item.score IS '分数';
COMMENT ON COLUMN tms_practice_item.result_flag IS '合格/不合格';
COMMENT ON COLUMN tms_practice_item.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_practice_item.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_practice_item.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_practice_item.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_practice_item.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_praci_fid ON tms_practice_item (f_foreign_id);
-- çŽ°åœºæé—®è€ƒæ ¸æ‰“åˆ†ï¼Œä¿å­˜åŽå›žå†™åŸ¹è®­è®°å½•
CREATE TABLE IF NOT EXISTS tms_quiz_score (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    task_id                  varchar(50) NOT NULL,
    record_id                varchar(50),
    user_id                  varchar(50) NOT NULL,
    question                 text,
    answer                   text,
    score                    numeric(8,1),
    teacher_id               varchar(50),
    quiz_time                timestamp,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_quiz_score PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_quiz_score IS '现场提问考核打分,保存后回写培训记录';
COMMENT ON COLUMN tms_quiz_score.f_id IS '主键';
COMMENT ON COLUMN tms_quiz_score.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_quiz_score.task_id IS '培训任务';
COMMENT ON COLUMN tms_quiz_score.record_id IS '培训记录';
COMMENT ON COLUMN tms_quiz_score.user_id IS '培训对象';
COMMENT ON COLUMN tms_quiz_score.question IS '提问内容';
COMMENT ON COLUMN tms_quiz_score.answer IS '回答';
COMMENT ON COLUMN tms_quiz_score.score IS '分数';
COMMENT ON COLUMN tms_quiz_score.teacher_id IS '提问人';
COMMENT ON COLUMN tms_quiz_score.quiz_time IS '时间';
COMMENT ON COLUMN tms_quiz_score.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_quiz_score.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_quiz_score.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_quiz_score.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_quiz_score.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_quiz_score.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_quiz_score.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_qscore_task ON tms_quiz_score (task_id);
CREATE INDEX IF NOT EXISTS idx_tms_qscore_user ON tms_quiz_score (user_id);
-- ---------------------------------------------------------------------------
-- 6. ä¸ªäººæ¡£æ¡ˆ
-- ---------------------------------------------------------------------------
-- ä¸ªäººåŸ¹è®­æ¡£æ¡ˆã€‚入职自动建档;一人一档。
CREATE TABLE IF NOT EXISTS tms_person_file (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    archive_no               varchar(50) NOT NULL,
    archive_date             date,
    user_id                  varchar(50) NOT NULL,
    user_name                varchar(100),
    dept_id                  varchar(50),
    hire_date                date,
    leave_date               date,
    main_post_id             varchar(50),
    part_post_id             varchar(50),
    biz_status               varchar(20) NOT NULL DEFAULT 'valid',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_person_file PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_file IS '个人培训档案。入职自动建档;一人一档。';
COMMENT ON COLUMN tms_person_file.f_id IS '主键';
COMMENT ON COLUMN tms_person_file.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_file.archive_no IS '档案编号 AR+YYYY+4';
COMMENT ON COLUMN tms_person_file.archive_date IS '建档时间';
COMMENT ON COLUMN tms_person_file.user_id IS '员工';
COMMENT ON COLUMN tms_person_file.user_name IS '姓名冗余';
COMMENT ON COLUMN tms_person_file.dept_id IS '所属部门';
COMMENT ON COLUMN tms_person_file.hire_date IS '入职时间';
COMMENT ON COLUMN tms_person_file.leave_date IS '离职时间';
COMMENT ON COLUMN tms_person_file.main_post_id IS '主要岗位';
COMMENT ON COLUMN tms_person_file.part_post_id IS '兼职岗位';
COMMENT ON COLUMN tms_person_file.biz_status IS 'valid在职 / left离职';
COMMENT ON COLUMN tms_person_file.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_file.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_file.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_file.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_file.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_person_file.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_person_file.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_pfile_user ON tms_person_file (user_id) WHERE f_delete_mark = 0;
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_pfile_no ON tms_person_file (archive_no) WHERE f_delete_mark = 0;
-- ä¸ªäººåŸ¹è®­æ¡£æ¡ˆ-工作履历
CREATE TABLE IF NOT EXISTS tms_person_work (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    company_name             varchar(200),
    post_name                varchar(100),
    hire_date                date,
    leave_date               date,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    CONSTRAINT pk_tms_person_work PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_work IS '个人培训档案-工作履历';
COMMENT ON COLUMN tms_person_work.f_id IS '主键';
COMMENT ON COLUMN tms_person_work.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_work.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_person_work.company_name IS '公司名称';
COMMENT ON COLUMN tms_person_work.post_name IS '任职岗位';
COMMENT ON COLUMN tms_person_work.hire_date IS '入职时间';
COMMENT ON COLUMN tms_person_work.leave_date IS '离职时间';
COMMENT ON COLUMN tms_person_work.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_work.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_work.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_work.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_work.f_delete_mark IS '删除标记 0正常 1删除';
CREATE INDEX IF NOT EXISTS idx_tms_pwork_fid ON tms_person_work (f_foreign_id);
-- èµ„质文件/获得证书。资质文件管理与档案子表共用此表。
CREATE TABLE IF NOT EXISTS tms_person_cert (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    f_foreign_id             varchar(50) NOT NULL,
    archive_id               varchar(50),
    user_id                  varchar(50),
    cert_no                  varchar(100),
    cert_name                varchar(200),
    gain_date                date,
    expire_date              date,
    cert_file                text,
    cert_status              varchar(20),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_person_cert PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_cert IS '资质文件/获得证书。资质文件管理与档案子表共用此表。';
COMMENT ON COLUMN tms_person_cert.f_id IS '主键';
COMMENT ON COLUMN tms_person_cert.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_cert.f_foreign_id IS '主表f_id';
COMMENT ON COLUMN tms_person_cert.archive_id IS '个人档案ID,无外键字段时由主从写入f_foreign_id';
COMMENT ON COLUMN tms_person_cert.user_id IS '员工,列表模块用';
COMMENT ON COLUMN tms_person_cert.cert_no IS '证书编号';
COMMENT ON COLUMN tms_person_cert.cert_name IS '证书名称';
COMMENT ON COLUMN tms_person_cert.gain_date IS '获得时间';
COMMENT ON COLUMN tms_person_cert.expire_date IS '有效期至';
COMMENT ON COLUMN tms_person_cert.cert_file IS '证书附件';
COMMENT ON COLUMN tms_person_cert.cert_status IS 'valid有效 / near近效期 / invalid无效';
COMMENT ON COLUMN tms_person_cert.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_cert.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_cert.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_cert.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_cert.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_person_cert.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_person_cert.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_pcert_fid ON tms_person_cert (f_foreign_id);
CREATE INDEX IF NOT EXISTS idx_tms_pcert_user ON tms_person_cert (user_id);
-- ä¸ªäººåŸ¹è®­ç›®å½•(归档快照)。记录归档后写入,满足审计。
CREATE TABLE IF NOT EXISTS tms_person_train (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    archive_id               varchar(50) NOT NULL,
    user_id                  varchar(50) NOT NULL,
    record_id                varchar(50),
    record_no                varchar(50),
    category                 varchar(50),
    subject                  text,
    train_mode               varchar(50),
    teacher_id               text,
    eval_mode                varchar(50),
    pass_flag                varchar(20),
    start_time               timestamp,
    end_time                 timestamp,
    hours                    numeric(8,1),
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_person_train PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_person_train IS '个人培训目录(归档快照)。记录归档后写入,满足审计。';
COMMENT ON COLUMN tms_person_train.f_id IS '主键';
COMMENT ON COLUMN tms_person_train.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_person_train.archive_id IS '个人档案ID';
COMMENT ON COLUMN tms_person_train.user_id IS '员工';
COMMENT ON COLUMN tms_person_train.record_id IS '培训记录ID';
COMMENT ON COLUMN tms_person_train.record_no IS '培训记录编号';
COMMENT ON COLUMN tms_person_train.category IS '培训类型/分类';
COMMENT ON COLUMN tms_person_train.subject IS '培训内容';
COMMENT ON COLUMN tms_person_train.train_mode IS '培训方式';
COMMENT ON COLUMN tms_person_train.teacher_id IS '培训师';
COMMENT ON COLUMN tms_person_train.eval_mode IS '考核方式';
COMMENT ON COLUMN tms_person_train.pass_flag IS '是否合格';
COMMENT ON COLUMN tms_person_train.start_time IS '培训开始时间';
COMMENT ON COLUMN tms_person_train.end_time IS '培训结束时间';
COMMENT ON COLUMN tms_person_train.hours IS '学时';
COMMENT ON COLUMN tms_person_train.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_person_train.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_person_train.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_person_train.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_person_train.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_person_train.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_person_train.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_ptrain_user ON tms_person_train (user_id);
CREATE INDEX IF NOT EXISTS idx_tms_ptrain_arch ON tms_person_train (archive_id);
CREATE INDEX IF NOT EXISTS idx_tms_ptrain_rec ON tms_person_train (record_id);
-- ---------------------------------------------------------------------------
-- 7. å…¶ä»–流程与配置
-- ---------------------------------------------------------------------------
-- å¤–出培训
CREATE TABLE IF NOT EXISTS tms_out_train (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    apply_no                 varchar(50),
    applicant_id             varchar(50),
    apply_dept_id            varchar(50),
    org_name                 varchar(200),
    subject                  varchar(500),
    start_time               timestamp,
    end_time                 timestamp,
    hours                    numeric(8,1),
    file_json                text,
    biz_status               varchar(20),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_out_train PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_out_train IS '外出培训';
COMMENT ON COLUMN tms_out_train.f_id IS '主键';
COMMENT ON COLUMN tms_out_train.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_out_train.apply_no IS '申请单号';
COMMENT ON COLUMN tms_out_train.applicant_id IS '申请人';
COMMENT ON COLUMN tms_out_train.apply_dept_id IS '申请部门';
COMMENT ON COLUMN tms_out_train.org_name IS '外部机构';
COMMENT ON COLUMN tms_out_train.subject IS '培训主题';
COMMENT ON COLUMN tms_out_train.start_time IS '开始';
COMMENT ON COLUMN tms_out_train.end_time IS '结束';
COMMENT ON COLUMN tms_out_train.hours IS '学时';
COMMENT ON COLUMN tms_out_train.file_json IS '资料附件';
COMMENT ON COLUMN tms_out_train.biz_status IS '审批状态';
COMMENT ON COLUMN tms_out_train.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_out_train.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_out_train.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_out_train.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_out_train.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_out_train.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_out_train.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_out_train.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_out_train.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_out_train.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_out_no ON tms_out_train (apply_no) WHERE f_delete_mark = 0;
-- è½¬å²—人员考核鉴定
CREATE TABLE IF NOT EXISTS tms_transfer_eval (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    user_id                  varchar(50) NOT NULL,
    from_post_id             varchar(50),
    to_post_id               varchar(50),
    eval_result              varchar(50),
    remark                   text,
    biz_status               varchar(20),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_transfer_eval PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_transfer_eval IS '转岗人员考核鉴定';
COMMENT ON COLUMN tms_transfer_eval.f_id IS '主键';
COMMENT ON COLUMN tms_transfer_eval.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_transfer_eval.user_id IS '转岗人员';
COMMENT ON COLUMN tms_transfer_eval.from_post_id IS '原岗位';
COMMENT ON COLUMN tms_transfer_eval.to_post_id IS '新岗位';
COMMENT ON COLUMN tms_transfer_eval.eval_result IS '鉴定结论';
COMMENT ON COLUMN tms_transfer_eval.remark IS '说明';
COMMENT ON COLUMN tms_transfer_eval.biz_status IS '审批状态';
COMMENT ON COLUMN tms_transfer_eval.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_transfer_eval.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_transfer_eval.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_transfer_eval.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_transfer_eval.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_transfer_eval.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_transfer_eval.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_transfer_eval.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_transfer_eval.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_transfer_eval.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_teval_user ON tms_transfer_eval (user_id);
-- æ–°å‘˜å·¥åŸ¹è®­æ•ˆæžœè€ƒè¯„
CREATE TABLE IF NOT EXISTS tms_newcomer_eval (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    user_id                  varchar(50) NOT NULL,
    eval_result              varchar(50),
    remark                   text,
    biz_status               varchar(20),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_newcomer_eval PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_newcomer_eval IS '新员工培训效果考评';
COMMENT ON COLUMN tms_newcomer_eval.f_id IS '主键';
COMMENT ON COLUMN tms_newcomer_eval.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_newcomer_eval.user_id IS '新员工';
COMMENT ON COLUMN tms_newcomer_eval.eval_result IS '考评结论';
COMMENT ON COLUMN tms_newcomer_eval.remark IS '说明';
COMMENT ON COLUMN tms_newcomer_eval.biz_status IS '审批状态';
COMMENT ON COLUMN tms_newcomer_eval.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_newcomer_eval.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_newcomer_eval.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_newcomer_eval.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_newcomer_eval.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_newcomer_eval.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_newcomer_eval.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_newcomer_eval.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_newcomer_eval.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_newcomer_eval.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_neval_user ON tms_newcomer_eval (user_id);
-- å¤å²—员工培训效果考评
CREATE TABLE IF NOT EXISTS tms_return_eval (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    user_id                  varchar(50) NOT NULL,
    eval_result              varchar(50),
    remark                   text,
    biz_status               varchar(20),
    f_flow_id                varchar(50),
    f_flow_task_id           varchar(50),
    f_flow_state             int,
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_return_eval PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_return_eval IS '复岗员工培训效果考评';
COMMENT ON COLUMN tms_return_eval.f_id IS '主键';
COMMENT ON COLUMN tms_return_eval.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_return_eval.user_id IS '复岗员工';
COMMENT ON COLUMN tms_return_eval.eval_result IS '考评结论';
COMMENT ON COLUMN tms_return_eval.remark IS '说明';
COMMENT ON COLUMN tms_return_eval.biz_status IS '审批状态';
COMMENT ON COLUMN tms_return_eval.f_flow_id IS '流程模板ID';
COMMENT ON COLUMN tms_return_eval.f_flow_task_id IS '流程任务ID';
COMMENT ON COLUMN tms_return_eval.f_flow_state IS '流程状态';
COMMENT ON COLUMN tms_return_eval.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_return_eval.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_return_eval.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_return_eval.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_return_eval.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_return_eval.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_return_eval.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_reval_user ON tms_return_eval (user_id);
-- åŸ¹è®­è®°å½•套打模板版本。按培训开始时间匹配生效版本。
CREATE TABLE IF NOT EXISTS tms_print_template (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    tpl_code                 varchar(50) NOT NULL,
    version_no               int NOT NULL,
    effective_from           date NOT NULL,
    effective_to             date,
    file_json                text,
    enabled                  varchar(10) NOT NULL DEFAULT '1',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_print_template PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_print_template IS '培训记录套打模板版本。按培训开始时间匹配生效版本。';
COMMENT ON COLUMN tms_print_template.f_id IS '主键';
COMMENT ON COLUMN tms_print_template.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_print_template.tpl_code IS '模板编码,如 TRAIN_RECORD';
COMMENT ON COLUMN tms_print_template.version_no IS '版本号';
COMMENT ON COLUMN tms_print_template.effective_from IS '生效起始(按培训开始时间索引)';
COMMENT ON COLUMN tms_print_template.effective_to IS '生效截止,空=当前';
COMMENT ON COLUMN tms_print_template.file_json IS '模板文件';
COMMENT ON COLUMN tms_print_template.enabled IS '启用';
COMMENT ON COLUMN tms_print_template.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_print_template.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_print_template.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_print_template.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_print_template.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_print_template.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_print_template.f_delete_time IS '删除时间';
CREATE INDEX IF NOT EXISTS idx_tms_ptpl_code ON tms_print_template (tpl_code, effective_from);
-- æé†’规则:计划到期前一个月、授权到期、个人岗位培训时限等
CREATE TABLE IF NOT EXISTS tms_remind_rule (
    f_id                     varchar(50) NOT NULL,
    f_tenant_id              varchar(50),
    rule_code                varchar(50) NOT NULL,
    rule_name                varchar(100),
    biz_type                 varchar(50),
    days_before              int NOT NULL DEFAULT 30,
    notify_roles             varchar(200),
    enabled                  varchar(10) NOT NULL DEFAULT '1',
    f_creator_user_id        varchar(50),
    f_creator_time           timestamp,
    f_last_modify_user_id    varchar(50),
    f_last_modify_time       timestamp,
    f_delete_mark            int NOT NULL DEFAULT 0,
    f_delete_user_id         varchar(50),
    f_delete_time            timestamp,
    CONSTRAINT pk_tms_remind_rule PRIMARY KEY (f_id)
);
COMMENT ON TABLE tms_remind_rule IS '提醒规则:计划到期前一个月、授权到期、个人岗位培训时限等';
COMMENT ON COLUMN tms_remind_rule.f_id IS '主键';
COMMENT ON COLUMN tms_remind_rule.f_tenant_id IS '租户';
COMMENT ON COLUMN tms_remind_rule.rule_code IS '规则编码';
COMMENT ON COLUMN tms_remind_rule.rule_name IS '名称';
COMMENT ON COLUMN tms_remind_rule.biz_type IS 'annual_plan/admin_auth/person_post/task_overdue';
COMMENT ON COLUMN tms_remind_rule.days_before IS '提前天数';
COMMENT ON COLUMN tms_remind_rule.notify_roles IS '通知角色:部门培训管理员,培训师,部门领导';
COMMENT ON COLUMN tms_remind_rule.enabled IS '启用';
COMMENT ON COLUMN tms_remind_rule.f_creator_user_id IS '创建人';
COMMENT ON COLUMN tms_remind_rule.f_creator_time IS '创建时间';
COMMENT ON COLUMN tms_remind_rule.f_last_modify_user_id IS '修改人';
COMMENT ON COLUMN tms_remind_rule.f_last_modify_time IS '修改时间';
COMMENT ON COLUMN tms_remind_rule.f_delete_mark IS '删除标记 0正常 1删除';
COMMENT ON COLUMN tms_remind_rule.f_delete_user_id IS '删除人';
COMMENT ON COLUMN tms_remind_rule.f_delete_time IS '删除时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_tms_remind_code ON tms_remind_rule (rule_code) WHERE f_delete_mark = 0;
-- =============================================================================
-- é¢„置数据(培训方式 / ç­¾åˆ°æ–¹å¼ / è¯„估方式 / æé†’规则)
-- =============================================================================
INSERT INTO tms_train_mode (f_id, mode_code, mode_name, sign_rule, allow_guest_sign, same_day_required, place_required, sort_no, enabled, f_delete_mark)
VALUES
 ('tms_mode_onsite',   'onsite',   '集中授课', 'scan', '1', '1', '1', 1, '1', 0),
 ('tms_mode_practice', 'practice', '操作授课', 'scan', '0', '1', '1', 2, '1', 0),
 ('tms_mode_online',   'online',   '在线学习', 'both', '0', '0', '0', 3, '1', 0)
ON CONFLICT (f_id) DO NOTHING;
INSERT INTO tms_sign_mode (f_id, mode_code, mode_name, tip_seconds, match_roster, enabled, f_delete_mark)
VALUES
 ('tms_sign_app',    'app_scan', 'APP扫码',  30, '1', '1', 0),
 ('tms_sign_wx',     'wx_scan',  '微信扫码', 30, '1', '1', 0),
 ('tms_sign_online', 'online',   '在线签到', 30, '1', '1', 0)
ON CONFLICT (f_id) DO NOTHING;
INSERT INTO tms_eval_mode (f_id, mode_code, mode_name, need_paper, need_quiz, need_practice, enabled, f_delete_mark)
VALUES
 ('tms_eval_quiz',     'quiz',     '提问',     '0', '1', '0', '1', 0),
 ('tms_eval_practice', 'practice', '现场操作', '0', '0', '1', '1', 0),
 ('tms_eval_exam',     'exam',     '在线考试', '1', '0', '0', '1', 0),
 ('tms_eval_none',     'none',     '无需考核', '0', '0', '0', '1', 0)
ON CONFLICT (f_id) DO NOTHING;
INSERT INTO tms_remind_rule (f_id, rule_code, rule_name, biz_type, days_before, notify_roles, enabled, f_delete_mark)
VALUES
 ('tms_rm_annual', 'annual_due', '年度计划到期前一个月', 'annual_plan', 30, '部门培训管理员,培训师', '1', 0),
 ('tms_rm_auth',   'auth_due',   '管理员授权到期前一个月', 'admin_auth', 30, '培训管理员,培训师', '1', 0),
 ('tms_rm_overdue','task_overdue','培训逾期提醒部门领导', 'task_overdue', 0, '部门领导', '1', 0)
ON CONFLICT (f_id) DO NOTHING;
COMMIT;
docs/tms/²£Ë¼èºTMS-JNPFÊý¾Ý±í×Ö¶ÎÇåµ¥.xlsx
Binary files differ
docs/tms/²£Ë¼èºTMS-JNPFÊý¾Ý±íÉè¼Æ.md
New file
@@ -0,0 +1,269 @@
# çŽ»æ€éŸ¬ TMS Â· JNPF / PostgreSQL æ•°æ®è¡¨è®¾è®¡
依据《玻思韬TMS功能设计书 V1》和老飞企界面字段设计,给 JNPF 6.x + PostgreSQL 18 ç”¨ã€‚
风格对齐现有 `lims_*` / `dms_*`:主键 `f_id`,子表 `f_foreign_id`,流程 `f_flow_id`,业务字段英文蛇形、不加 `f_`。
交付物:
- å»ºè¡¨è„šæœ¬ï¼š`docs/tms/tms_schema_pg.sql`(48 å¼ è¡¨ + é¢„置数据)
- å­—段清单:`docs/tms/玻思韬TMS-JNPF数据表字段清单.xlsx`
---
## 1. æ€Žä¹ˆåœ¨ JNPF å»º
推荐顺序:
1. å…ˆé…å­—典和单据规则(第 3、4 èŠ‚ï¼‰
2. æ•°æ®å»ºæ¨¡æŒ‰ç¬¬ 2 èŠ‚é¡ºåºå»ºè¡¨ï¼ˆæˆ–å…ˆè·‘ SQL,再「同步数据表」)
3. åŸºç¡€æ¡£æ¡ˆè¡¨å• â†’ é¢˜åº“试卷 â†’ è®¡åˆ’ â†’ ä»»åŠ¡/记录 â†’ æ¡£æ¡ˆ
4. ç”¨æˆ·ã€ç»„织、岗位、附件一律用平台控件,不要自建用户表
5. åŸ¹è®­å¯¹è±¡ã€åŸ¹è®­å¸ˆå¤šé€‰å­˜ç”¨æˆ· ID;发布时再按人拆 `tms_person_task`
系统字段每张表都有,设计器会带出,不必在业务字段里重复:
| å­—段 | ç”¨é€” |
|---|---|
| `f_id` | ä¸»é”® |
| `f_tenant_id` | ç§Ÿæˆ· |
| `f_foreign_id` | å­è¡¨æŒ‡å‘主表 |
| `f_flow_id` / `f_flow_task_id` / `f_flow_state` | æµç¨‹è¡¨å• |
| `f_creator_user_id` / `f_creator_time` | åˆ›å»º |
| `f_last_modify_user_id` / `f_last_modify_time` | ä¿®æ”¹ |
| `f_delete_mark` | é€»è¾‘删除 |
---
## 2. è¡¨å…³ç³»ï¼ˆä¸»é“¾è·¯ï¼‰
```mermaid
flowchart LR
  subgraph base [基础]
    P[tms_place åœ°ç‚¹]
    T[tms_trainer åŸ¹è®­å¸ˆ]
    M[tms_material æ•™æ]
    C[tms_course è¯¾ç¨‹]
    A[tms_admin_auth æŽˆæƒ]
  end
  subgraph exameng [考试引擎]
    QB[tms_question_bank é¢˜åº“]
    Q[tms_question è¯•题]
    PAP[tms_paper è¯•卷]
  end
  subgraph plan [计划]
    PC[tms_post_catalog å²—位目录]
    PP[tms_post_plan å²—位计划]
    PPP[tms_person_post ä¸ªäººå²—位培训]
    AP[tms_annual_plan å¹´åº¦è®¡åˆ’]
  end
  subgraph exec [执行]
    TK[tms_task åŸ¹è®­ä»»åŠ¡]
    PT[tms_person_task ä¸ªäººä»»åŠ¡]
    RC[tms_record åŸ¹è®­è®°å½•]
    EX[tms_exam ç­”卷]
  end
  subgraph file [档案]
    PF[tms_person_file ä¸ªäººæ¡£æ¡ˆ]
    CERT[tms_person_cert èµ„质证书]
    DIR[tms_person_train åŸ¹è®­ç›®å½•]
  end
  QB --> Q --> PAP
  PC --> PP --> PPP
  PP --> TK
  AP --> TK
  M --> TK
  P --> TK
  T --> TK
  PAP --> TK
  C --> AP
  TK --> PT
  TK --> RC
  TK --> EX
  PT --> EX
  RC --> DIR
  PF --> CERT
  PF --> DIR
```
主键怎么串:
| ä»Ž | åˆ° | å­—段 |
|---|---|---|
| å¹´è®¡åˆ’明细 / å²—计划明细 | ä»»åŠ¡ | `source_id` + `source_item_id` |
| ä»»åŠ¡ | ä¸ªäººä»»åŠ¡ | `task_id` |
| ä»»åŠ¡ | åŸ¹è®­è®°å½• | `task_id`(一对一) |
| ä»»åŠ¡ | ç­”卷 | `task_id` + `user_id` + `attempt_no` |
| è®°å½•签到子表 | è®°å½• | `f_foreign_id` |
| è®°å½•å½’æ¡£ | ä¸ªäººåŸ¹è®­ç›®å½• | `record_id` + `user_id` |
老界面里「培训任务」和「个人任务」字段码混用,JNPF å¿…须拆成两张表。
---
## 3. å…ˆé…å­—å…¸
| å­—å…¸ | é€‰é¡¹ |
|---|---|
| ä»»åŠ¡å‘å¸ƒçŠ¶æ€ | draft æœªå‘布 / published å·²å‘布 / cancelled å·²å–消 / done å·²å®Œæˆ |
| åŸ¹è®­åˆ†ç±» | ä¸´æ—¶åŸ¹è®­ / å²—位培训计划 / å¹´åº¦åŸ¹è®­è®¡åˆ’ / æ–‡ä»¶ç”Ÿæ•ˆåŸ¹è®­ / å¤–出培训 / å†ç»´æŠ¤ |
| ä»»åŠ¡ç±»åž‹ | new æ–°å¢ž / continue ç»§ç»­ï¼ˆå†ç»´æŠ¤ï¼‰ |
| å½’档状态 | pending å¾…å½’æ¡£ / ready å¯å½’æ¡£ / archived å·²å½’æ¡£ / invalid æ— æ•ˆ |
| ä¸ªäººä»»åŠ¡çŠ¶æ€ | todo æœªå®Œæˆ / signed å·²ç­¾åˆ° / done å·²å®Œæˆ / expired å·²è¿‡æœŸ / cancelled å·²å–消 |
| æ˜¯å¦åˆæ ¼ | pass åˆæ ¼ / fail ä¸åˆæ ¼ / absent æœªå‚加 / na æ— éœ€è€ƒæ ¸ |
| é¢˜åž‹ | single å•选 / multi å¤šé€‰ / judge åˆ¤æ–­ / blank å¡«ç©º / essay é—®ç­” |
| è¯•题试卷状态 | open å¼€æ”¾ / closed ä¸å¼€æ”¾ / invalid åºŸå¼ƒ |
| éš¾åº¦ | easy ç®€å• / normal ä¸€èˆ¬ / hard å›°éš¾ |
| é˜…卷状态 | auto æ— éœ€é˜…卷 / pending å¾…批改 / graded å·²æ‰¹æ”¹ |
| æœ‰æ•ˆæ’¤é”€ | valid æœ‰æ•ˆæœŸå†… / revoked å·²æ’¤é”€ |
| è®¡åˆ’行状态 | pending å¾…执行 / tasked å·²ç”Ÿæˆä»»åŠ¡ / done å·²å®Œæˆ / delayed å·²å»¶æœŸ |
| æ•™æç±»åž‹ | Word / PPT / Excel / PDF / è§†é¢‘ / éŸ³é¢‘ |
培训方式、签到方式、评估方式做成**业务表**(带规则),不要只做字典:
- é›†ä¸­æŽˆè¯¾ï¼šä»…扫码、允许名单外签到、开始结束同一天、地点必填
- æ“ä½œæŽˆè¯¾ï¼šä»…扫码、不允许名单外、同一天、地点必填
- åœ¨çº¿å­¦ä¹ ï¼šæ‰«ç +在线、地点不必填、扫码必须匹配名单
---
## 4. å•据规则(对齐老编号)
| å¯¹è±¡ | è§„则 | è€ç•Œé¢ç¤ºä¾‹ |
|---|---|---|
| åœ°ç‚¹ç¼–号 `place_no` | `A` + YYYYMM + 4 ä½ | A2026090001 |
| æ¡£æ¡ˆç¼–号 `archive_no` | `AR` + YYYY + 4 ä½ | AR20260028 |
| æ•™æç¼–号 `material_no` | `TM` + YYYYMM + 4 ä½ | TM2026090001 |
| å²—位计划 `plan_no` | `JP` + YYYYMM + 4 ä½ | JP2026090012 |
| å¹´åº¦è®¡åˆ’ `plan_no` | å¹´ä»½ 4 ä½ + 4 ä½æµæ°´ | 20260001 |
| åŸ¹è®­ä»»åŠ¡ `task_no` | `TT` + YYYYMM + 4 ä½ | TT2026090618 |
培训记录编号默认等于任务编号(老档案培训目录里就是 TT å·ï¼‰ã€‚
---
## 5. è¡¨å•与表(搭建顺序)
### 5.1 åŸºç¡€è®¾ç½®
| é¡ºåº | è¡¨å• | è¡¨ | ä¸»ä»Ž | æµç¨‹ |
|---|---|---|---|---|
| 1 | åŸ¹è®­åœ°ç‚¹ | `tms_place` | å¦ | å¦ |
| 2 | åŸ¹è®­å¸ˆæ¡£æ¡ˆ | `tms_trainer` | å·¥ä½œç»åކ / åŸ¹è®­ç»åކ | å¦ |
| 3 | åŸ¹è®­ç®¡ç†å‘˜æŽˆæƒ | `tms_admin_auth` | å¦ | å¦ |
| 4 | åŸ¹è®­æ–¹å¼ | `tms_train_mode` | å¦ | å¦ |
| 5 | ç­¾åˆ°æ–¹å¼ | `tms_sign_mode` | å¦ | å¦ |
| 6 | è¯„估方式 | `tms_eval_mode` | å¦ | å¦ |
| 7 | åŸ¹è®­è¯¾ç¨‹ | `tms_course` | å¦ | å¦ |
| 8 | åŸ¹è®­æ•™æ | `tms_material` | å¦ | å¦ |
地点表比老界面多了 `place_group`(FDS è¦æ±‚按组别划分)。占用不单独存表,发布/保存任务时用时间交叉校验。
培训师按 `user_id` å”¯ä¸€ï¼Œé‡å¤å½•入提示不报错;列表用 `biz_status` ç­›ã€Œæœ‰æ•ˆæœŸå†… / å·²æ’¤é”€ã€ã€‚
### 5.2 é¢˜åº“试卷
| é¡ºåº | è¡¨å• | è¡¨ | ä¸»ä»Ž |
|---|---|---|---|
| 9 | é¢˜åº“ | `tms_question_bank` | å¦ |
| 10 | è¯•题 | `tms_question` | é€‰é¡¹ `tms_question_option` |
| 11 | è¯•卷 | `tms_paper` | ç« èŠ‚ + è¯•卷试题 |
老界面字段已落入:题库名称/状态/备注;试题类型、所属题库、难度、状态(默认开放)、来源、题干、选项 A-D å¯å¢žåˆ ï¼›è¯•卷名称、状态、考试时长、成绩公布时间、合格分数、试题排序(自身顺序/随机)。
**关键约束(GMP)**:改试题、改试卷、改答案,都不能动已考记录。已考数据只存在 `tms_exam` + `tms_exam_item` å¿«ç…§é‡Œã€‚
### 5.3 å²—位 / å¹´åº¦è®¡åˆ’
| é¡ºåº | è¡¨å• | è¡¨ | æµç¨‹ |
|---|---|---|---|
| 12 | å²—位培训目录 | `tms_post_catalog` + item | å¯ |
| 13 | å²—位培训计划 | `tms_post_plan` + item | å¯ |
| 14 | ä¸ªäººå²—位培训 | `tms_person_post` + item | å¯é€€å›ž |
| 15 | å¹´åº¦åŸ¹è®­è®¡åˆ’ | `tms_annual_plan` + item | éƒ¨é—¨è´Ÿè´£äºº â†’ QA |
| 16 | å¹´åº¦è®¡åˆ’延期 | `tms_annual_delay` | æ˜¯ |
岗位计划主表对齐老界面:岗位编码/名称、所属部门、计划结束时间、岗位描述、培训对象;子表:培训计划时间、培训师、培训内容、培训方式、课时、考核方式。
年度计划对齐老界面:年份、计划编号、计划类型、制定日期/人/部门、版本;子表:培训月份、内容、方式、对象、课时、培训师、考核方式、状态;表尾备注、部门负责人、QA éƒ¨è´Ÿè´£äººã€‚
转岗:`tms_person_post_item.already_trained=1` æ—¶ä¸ç”Ÿæˆå¯¹åº”培训任务。
### 5.4 ä»»åŠ¡ / è®°å½• / è€ƒè¯•
| é¡ºåº | è¡¨å• | è¡¨ | è¯´æ˜Ž |
|---|---|---|---|
| 17 | åŸ¹è®­ä»»åŠ¡ | `tms_task` + æ•™æ + æé—®é¢„设 | æ ¸å¿ƒï¼›è·¨æœˆ/临时/在岗走流程 |
| 18 | ä¸ªäººåŸ¹è®­ä»»åŠ¡ | `tms_person_task` | å‘布时按人生成,学员入口 |
| 19 | åŸ¹è®­è®°å½• | `tms_record` + ç­¾åˆ°æ˜Žç»† | å‘布时生成;关闭时间到 â†’ å¾…å½’æ¡£ |
| 20 | åŸ¹è®­è®°å½•变更 | `tms_record_change` | æ”¹ç»“束时间 / åŠ äºº |
| 21 | åŸ¹è®­èµ„料归档 | `tms_archive_file` | ç”µå­/纸质/实操 |
| 22 | åœ¨çº¿è€ƒè¯• | `tms_exam` + å¿«ç…§ | åˆ—表/阅卷/个人考试记录共用 |
| 23 | å®žæ“è€ƒæ ¸ | `tms_practice` + item | å›žå†™è®°å½• |
| 24 | çŽ°åœºæé—®è€ƒæ ¸ | `tms_quiz_score` | å›žå†™è®°å½• |
培训任务主表对齐老界面:
编号、培训分类、来源计划编号、培训主题、培训要点、教材编号/附件、地点编号/名称、开始/结束、试卷编号/名称、开考/结考、培训师、任务关闭时间、培训方式、评估方式、培训对象。
列表另有:是否已发布、任务类型(新增/继续)、上一次任务编号。这些都在 `tms_task`。
记录台账不必再建模,用 `tms_record.archive_status` ç­›å¾…å½’æ¡£ / å¯å½’æ¡£ / å·²å½’档。
### 5.5 æ¡£æ¡ˆä¸Žå…¶å®ƒæµç¨‹
| é¡ºåº | è¡¨å• | è¡¨ |
|---|---|---|
| 25 | ä¸ªäººåŸ¹è®­æ¡£æ¡ˆ | `tms_person_file` + å±¥åކ + è¯ä¹¦ + åŸ¹è®­ç›®å½• |
| 26 | èµ„质文件管理 | å¤ç”¨ `tms_person_file` åˆ—表,证书子表 `tms_person_cert` |
| 27 | å¤–出培训 | `tms_out_train` |
| 28 | è½¬å²— / æ–°å‘˜å·¥ / å¤å²—考评 | `tms_transfer_eval` / `tms_newcomer_eval` / `tms_return_eval` |
| 29 | å¥—打模板版本 | `tms_print_template`(按培训开始时间匹配版本) |
| 30 | æé†’规则 | `tms_remind_rule` |
个人档案对齐老界面:档案编号、建档时间、员工姓名、所属部门、入职时间、主要岗位、兼职岗位;子表工作履历;查看页还有外部培训/证书、培训目录(记录编号、类型、内容、方式、培训师、考核方式、是否合格、开始/结束)。
入职自动建档:人事用户新增时插 `tms_person_file`。一人一档,`user_id` å”¯ä¸€ã€‚
---
## 6. å¿…须写在事件/代码里的规则
这些不要靠表结构硬约束,放发布/保存/归档按钮:
1. **时间**:`start_time <= end_time <= close_time`;考试时间不得早于培训开始;集中/操作授课开始结束必须同一天。
2. **地点占用**:同一 `place_id`,未取消任务的 `[start,end)` æŒ‰å°æ—¶ä¸å¾—交叉。
3. **培训师冲突**:集中/操作授课下,同一培训师同一时段不能在两个任务。
4. **有人签到不能取消**;误取消可恢复(`cancelled â†’ draft/published`)。
5. **发布**:拆个人任务;生成培训记录;考核方式=在线考试则生成 `tms_exam` å¾…考行;同步教材到记录。
6. **关闭时间到达** â†’ è®°å½• `archive_status=pending`。
7. **归档**:计算签到率/合格率(取整舍小数);未完成人员生成再维护任务(`task_kind=continue`,`prev_task_no` æŒ‡å‘原任务);写入 `tms_person_train`。
8. **无效任务**:无人完成时可打 `invalid`,并自动生成新任务。
9. **提问人数**:`ceil(签到人数 * quiz_ratio / 100)`,取整。
10. **授权/计划到期前 30 å¤©** æŒ‰ `tms_remind_rule` æŽ¨é€ç»™éƒ¨é—¨åŸ¹è®­ç®¡ç†å‘˜å’ŒåŸ¹è®­å¸ˆã€‚
---
## 7. è§’色只配权限,不新建表
| è§’色 | æ•°æ®èŒƒå›´ |
|---|---|
| ç³»ç»Ÿç®¡ç†å‘˜ | åŸºç¡€è®¾ç½® |
| å…¬å¸åŸ¹è®­ç®¡ç†å‘˜ | å…¨å…¬å¸è®¡åˆ’/任务/档案 |
| éƒ¨é—¨åŸ¹è®­ç®¡ç†å‘˜ | æœ¬éƒ¨é—¨ + `tms_admin_auth` æŽˆæƒéƒ¨é—¨ï¼ˆå¯å«ä¸‹çº§ï¼‰ |
| åŸ¹è®­å¸ˆ | è‡ªå·±çš„任务授课、打分、实操、阅卷 |
| æ™®é€šå‘˜å·¥ | ä»…个人任务、个人考试记录、个人档案 |
「个人培训档案」只能看自己;「个人培训档案管理」按授权看部门/公司。
---
## 8. å»ºè®®ä¸€æœŸèŒƒå›´
先建并能跑通主链路的表:
地点、培训师、教材、题库、试题、试卷、年度计划、岗位计划、培训任务、个人任务、培训记录、签到明细、答卷快照、个人档案。
二期再铺:岗位目录升版、提问预设、实操/现场提问、记录变更、资料归档、外出/转岗/新员工/复岗流程、套打版本、提醒规则。
题库试卷引擎一期就要快照表,否则以后改题会污染已归档记录。