From 1c0304091d5a7dd05859167f5e417cf932c67028 Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 11:19:17 +0800
Subject: [PATCH] 字段信息基础班
---
docs/tms/gen_tms_schema.py | 786 +++++++++++++
docs/tms/gen_xlsx.py | 222 +++
docs/tms/玻思韬TMS-JNPF数据表设计.md | 269 ++++
docs/tms/玻思韬TMS-JNPF数据表字段清单.xlsx | 0
docs/tms/tms_schema_pg.sql | 2171 +++++++++++++++++++++++++++++++++++++
5 files changed, 3,448 insertions(+), 0 deletions(-)
diff --git a/docs/tms/gen_tms_schema.py b/docs/tms/gen_tms_schema.py
new file mode 100644
index 0000000..53900b8
--- /dev/null
+++ b/docs/tms/gen_tms_schema.py
@@ -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姝e父 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, "妗f缂栧彿 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", "缂栫爜锛歰nsite/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", "绛惧埌鏂瑰紡锛欰PP鎵爜 / 寰俊鎵爜 / 鍦ㄧ嚎绛惧埌", [
+ col("mode_code", "varchar(50)", "NOT NULL", "缂栫爜锛歛pp_scan/wx_scan/online"),
+ col("mode_name", "varchar(100)", "NOT NULL", "鍚嶇О"),
+ col("tip_seconds", "int", "NOT NULL DEFAULT 30", "鏈夊煿璁鐐规椂寮哄埗闃呰绉掓暟锛孖T鍙皟"),
+ 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", "缂栫爜锛歲uiz/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", "鍩硅鏁欐潗/浼佷笟鐭ヨ瘑搴撱�傚彲涓婁紶鏈湴鎴栧紩鐢―MS锛堥渶閴存潈锛夈�傛挙閿�鍚庝笉鍙啀寮曠敤銆�", [
+ 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, "鏁欐潗闄勪欢锛圝NPF闄勪欢JSON锛�"),
+ col("dms_file_id", "varchar(50)", None, "寮曠敤鐨凞MS鏂囦欢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姝e父 / 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, "绛旀瑙f瀽/澶囨敞锛岄敊棰樺彲鏄剧ず"),
+ 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瀵笲閿欍��", [
+ 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'", "鏄惁姝g‘绛旀 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, "鐢熸垚鐨勫煿璁换鍔D"),
+ 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閮ㄨ礋璐d汉"),
+ 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锛屽綊妗e洖鍐�"),
+ 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, "浠诲姟绫诲瀷锛歯ew鏂板 / 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", "鍩硅璁板綍銆備换鍔″彂甯冨悗鐢熸垚锛涘叧闂椂闂村埌杈捐繘寰呭綊妗c��", [
+ 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瀹炴搷妗f"),
+ 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, "鍘熻瘯棰業D"),
+ col("question_type", "varchar(20)", None, "棰樺瀷蹇収"),
+ col("stem", "text", None, "棰樺共蹇収"),
+ col("options_json", "text", None, "閫夐」蹇収 JSON"),
+ col("correct_answer", "text", None, "褰撴椂姝g‘绛旀"),
+ 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, "瑙f瀽蹇収"),
+ 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 妗f =====
+add("tms_person_file", "涓汉鍩硅妗f銆傚叆鑱岃嚜鍔ㄥ缓妗o紱涓�浜轰竴妗c��", [
+ col("archive_no", "varchar(50)", "NOT NULL", "妗f缂栧彿 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", "涓汉鍩硅妗f-宸ヤ綔灞ュ巻", [
+ 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", "璧勮川鏂囦欢/鑾峰緱璇佷功銆傝祫璐ㄦ枃浠剁鐞嗕笌妗f瀛愯〃鍏辩敤姝よ〃銆�", [
+ col("archive_id", "varchar(50)", None, "涓汉妗fID锛屾棤澶栭敭瀛楁鏃剁敱涓讳粠鍐欏叆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", "涓汉鍩硅鐩綍锛堝綊妗e揩鐓э級銆傝褰曞綊妗e悗鍐欏叆锛屾弧瓒冲璁°��", [
+ col("archive_id", "varchar(50)", "NOT NULL", "涓汉妗fID"),
+ 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 寤鸿〃鑴氭湰
+-- 閫傜敤锛欽NPF 6.x锛堝瓧娈甸鏍煎榻愮幇鏈� lims_/dms_ 琛級
+-- 鏉ユ簮锛氱幓鎬濋煬TMS鍔熻兘璁捐涔� V1 + 鑰侀浼佺晫闈㈠瓧娈�
+--
+-- 浣跨敤
+-- 1) 鎺ㄨ崘锛欽NPF銆屾暟鎹缓妯°�嶆寜琛ㄦ竻鍗曞缓琛紝鍐嶇粦琛ㄥ崟锛堢郴缁熷瓧娈典細鑷姩甯︼級
+-- 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. 涓汉妗f", ["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)
diff --git a/docs/tms/gen_xlsx.py b/docs/tms/gen_xlsx.py
new file mode 100644
index 0000000..f5c7565
--- /dev/null
+++ b/docs/tms/gen_xlsx.py
@@ -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": ("涓汉鍩硅妗f", "AR+YYYY+4"),
+ "tms_person_work": ("妗f-宸ヤ綔灞ュ巻", ""),
+ "tms_person_cert": ("璧勮川鏂囦欢/璇佷功", ""),
+ "tms_person_train": ("妗f-鍩硅鐩綍蹇収", ""),
+ "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"),
+ ("鍗曟嵁瑙勫垯", "妗f缂栧彿", "AR + YYYY + 4浣嶆祦姘�", "鍩硅甯�/涓汉妗f 渚� 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)
diff --git a/docs/tms/tms_schema_pg.sql b/docs/tms/tms_schema_pg.sql
new file mode 100644
index 0000000..d122c8a
--- /dev/null
+++ b/docs/tms/tms_schema_pg.sql
@@ -0,0 +1,2171 @@
+-- =============================================================================
+-- 鐜绘�濋煬 TMS 鍩硅绠$悊绯荤粺 路 PostgreSQL 18 寤鸿〃鑴氭湰
+-- 閫傜敤锛欽NPF 6.x锛堝瓧娈甸鏍煎榻愮幇鏈� lims_/dms_ 琛級
+-- 鏉ユ簮锛氱幓鎬濋煬TMS鍔熻兘璁捐涔� V1 + 鑰侀浼佺晫闈㈠瓧娈�
+--
+-- 浣跨敤
+-- 1) 鎺ㄨ崘锛欽NPF銆屾暟鎹缓妯°�嶆寜琛ㄦ竻鍗曞缓琛紝鍐嶇粦琛ㄥ崟锛堢郴缁熷瓧娈典細鑷姩甯︼級
+-- 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姝e父 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 '妗f缂栧彿 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姝e父 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姝e父 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姝e父 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姝e父 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 '缂栫爜锛歰nsite/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姝e父 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;
+
+-- 绛惧埌鏂瑰紡锛欰PP鎵爜 / 寰俊鎵爜 / 鍦ㄧ嚎绛惧埌
+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 '绛惧埌鏂瑰紡锛欰PP鎵爜 / 寰俊鎵爜 / 鍦ㄧ嚎绛惧埌';
+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 '缂栫爜锛歛pp_scan/wx_scan/online';
+COMMENT ON COLUMN tms_sign_mode.mode_name IS '鍚嶇О';
+COMMENT ON COLUMN tms_sign_mode.tip_seconds IS '鏈夊煿璁鐐规椂寮哄埗闃呰绉掓暟锛孖T鍙皟';
+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姝e父 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 '缂栫爜锛歲uiz/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姝e父 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姝e父 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;
+
+-- 鍩硅鏁欐潗/浼佷笟鐭ヨ瘑搴撱�傚彲涓婁紶鏈湴鎴栧紩鐢―MS锛堥渶閴存潈锛夈�傛挙閿�鍚庝笉鍙啀寮曠敤銆�
+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 '鍩硅鏁欐潗/浼佷笟鐭ヨ瘑搴撱�傚彲涓婁紶鏈湴鎴栧紩鐢―MS锛堥渶閴存潈锛夈�傛挙閿�鍚庝笉鍙啀寮曠敤銆�';
+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 '鏁欐潗闄勪欢锛圝NPF闄勪欢JSON锛�';
+COMMENT ON COLUMN tms_material.dms_file_id IS '寮曠敤鐨凞MS鏂囦欢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姝e父 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姝e父 / 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姝e父 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 '绛旀瑙f瀽/澶囨敞锛岄敊棰樺彲鏄剧ず';
+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姝e父 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瀵笲閿欍��
+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瀵笲閿欍��';
+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 '鏄惁姝g‘绛旀 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姝e父 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姝e父 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姝e父 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姝e父 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姝e父 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姝e父 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姝e父 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 '鐢熸垚鐨勫煿璁换鍔D';
+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姝e父 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姝e父 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姝e父 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閮ㄨ礋璐d汉';
+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姝e父 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姝e父 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姝e父 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锛屽綊妗e洖鍐�';
+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 '浠诲姟绫诲瀷锛歯ew鏂板 / 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姝e父 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姝e父 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姝e父 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姝e父 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;
+
+-- 鍩硅璁板綍銆備换鍔″彂甯冨悗鐢熸垚锛涘叧闂椂闂村埌杈捐繘寰呭綊妗c��
+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 '鍩硅璁板綍銆備换鍔″彂甯冨悗鐢熸垚锛涘叧闂椂闂村埌杈捐繘寰呭綊妗c��';
+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姝e父 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姝e父 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姝e父 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瀹炴搷妗f';
+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姝e父 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姝e父 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 '鍘熻瘯棰業D';
+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 '褰撴椂姝g‘绛旀';
+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 '瑙f瀽蹇収';
+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姝e父 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姝e父 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姝e父 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姝e父 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. 涓汉妗f
+-- ---------------------------------------------------------------------------
+-- 涓汉鍩硅妗f銆傚叆鑱岃嚜鍔ㄥ缓妗o紱涓�浜轰竴妗c��
+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 '涓汉鍩硅妗f銆傚叆鑱岃嚜鍔ㄥ缓妗o紱涓�浜轰竴妗c��';
+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 '妗f缂栧彿 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姝e父 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;
+
+-- 涓汉鍩硅妗f-宸ヤ綔灞ュ巻
+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 '涓汉鍩硅妗f-宸ヤ綔灞ュ巻';
+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姝e父 1鍒犻櫎';
+CREATE INDEX IF NOT EXISTS idx_tms_pwork_fid ON tms_person_work (f_foreign_id);
+
+-- 璧勮川鏂囦欢/鑾峰緱璇佷功銆傝祫璐ㄦ枃浠剁鐞嗕笌妗f瀛愯〃鍏辩敤姝よ〃銆�
+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 '璧勮川鏂囦欢/鑾峰緱璇佷功銆傝祫璐ㄦ枃浠剁鐞嗕笌妗f瀛愯〃鍏辩敤姝よ〃銆�';
+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 '涓汉妗fID锛屾棤澶栭敭瀛楁鏃剁敱涓讳粠鍐欏叆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姝e父 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);
+
+-- 涓汉鍩硅鐩綍锛堝綊妗e揩鐓э級銆傝褰曞綊妗e悗鍐欏叆锛屾弧瓒冲璁°��
+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 '涓汉鍩硅鐩綍锛堝綊妗e揩鐓э級銆傝褰曞綊妗e悗鍐欏叆锛屾弧瓒冲璁°��';
+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 '涓汉妗fID';
+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姝e父 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姝e父 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姝e父 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姝e父 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姝e父 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姝e父 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姝e父 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;
diff --git "a/docs/tms/\347\216\273\346\200\235\351\237\254TMS-JNPF\346\225\260\346\215\256\350\241\250\345\255\227\346\256\265\346\270\205\345\215\225.xlsx" "b/docs/tms/\347\216\273\346\200\235\351\237\254TMS-JNPF\346\225\260\346\215\256\350\241\250\345\255\227\346\256\265\346\270\205\345\215\225.xlsx"
new file mode 100644
index 0000000..50f2c2b
--- /dev/null
+++ "b/docs/tms/\347\216\273\346\200\235\351\237\254TMS-JNPF\346\225\260\346\215\256\350\241\250\345\255\227\346\256\265\346\270\205\345\215\225.xlsx"
Binary files differ
diff --git "a/docs/tms/\347\216\273\346\200\235\351\237\254TMS-JNPF\346\225\260\346\215\256\350\241\250\350\256\276\350\256\241.md" "b/docs/tms/\347\216\273\346\200\235\351\237\254TMS-JNPF\346\225\260\346\215\256\350\241\250\350\256\276\350\256\241.md"
new file mode 100644
index 0000000..7e093e4
--- /dev/null
+++ "b/docs/tms/\347\216\273\346\200\235\351\237\254TMS-JNPF\346\225\260\346\215\256\350\241\250\350\256\276\350\256\241.md"
@@ -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. 鍩虹妗f琛ㄥ崟 鈫� 棰樺簱璇曞嵎 鈫� 璁″垝 鈫� 浠诲姟/璁板綍 鈫� 妗f
+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 [妗f]
+ PF[tms_person_file 涓汉妗f]
+ 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` |
+
+鑰佺晫闈㈤噷銆屽煿璁换鍔°�嶅拰銆屼釜浜轰换鍔°�嶅瓧娈电爜娣风敤锛孞NPF 蹇呴』鎷嗘垚涓ゅ紶琛ㄣ��
+
+---
+
+## 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 |
+| 妗f缂栧彿 `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`锛團DS 瑕佹眰鎸夌粍鍒垝鍒嗭級銆傚崰鐢ㄤ笉鍗曠嫭瀛樿〃锛屽彂甯�/淇濆瓨浠诲姟鏃剁敤鏃堕棿浜ゅ弶鏍¢獙銆�
+
+鍩硅甯堟寜 `user_id` 鍞竴锛岄噸澶嶅綍鍏ユ彁绀轰笉鎶ラ敊锛涘垪琛ㄧ敤 `biz_status` 绛涖�屾湁鏁堟湡鍐� / 宸叉挙閿�銆嶃��
+
+### 5.2 棰樺簱璇曞嵎
+
+| 椤哄簭 | 琛ㄥ崟 | 琛� | 涓讳粠 |
+|---|---|---|---|
+| 9 | 棰樺簱 | `tms_question_bank` | 鍚� |
+| 10 | 璇曢 | `tms_question` | 閫夐」 `tms_question_option` |
+| 11 | 璇曞嵎 | `tms_paper` | 绔犺妭 + 璇曞嵎璇曢 |
+
+鑰佺晫闈㈠瓧娈靛凡钀藉叆锛氶搴撳悕绉�/鐘舵��/澶囨敞锛涜瘯棰樼被鍨嬨�佹墍灞為搴撱�侀毦搴︺�佺姸鎬侊紙榛樿寮�鏀撅級銆佹潵婧愩�侀骞层�侀�夐」 A-D 鍙鍒狅紱璇曞嵎鍚嶇О銆佺姸鎬併�佽�冭瘯鏃堕暱銆佹垚缁╁叕甯冩椂闂淬�佸悎鏍煎垎鏁般�佽瘯棰樻帓搴忥紙鑷韩椤哄簭/闅忔満锛夈��
+
+**鍏抽敭绾︽潫锛圙MP锛�**锛氭敼璇曢銆佹敼璇曞嵎銆佹敼绛旀锛岄兘涓嶈兘鍔ㄥ凡鑰冭褰曘�傚凡鑰冩暟鎹彧瀛樺湪 `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` | 鏄� |
+
+宀椾綅璁″垝涓昏〃瀵归綈鑰佺晫闈細宀椾綅缂栫爜/鍚嶇О銆佹墍灞為儴闂ㄣ�佽鍒掔粨鏉熸椂闂淬�佸矖浣嶆弿杩般�佸煿璁璞★紱瀛愯〃锛氬煿璁鍒掓椂闂淬�佸煿璁笀銆佸煿璁唴瀹广�佸煿璁柟寮忋�佽鏃躲�佽�冩牳鏂瑰紡銆�
+
+骞村害璁″垝瀵归綈鑰佺晫闈細骞翠唤銆佽鍒掔紪鍙枫�佽鍒掔被鍨嬨�佸埗瀹氭棩鏈�/浜�/閮ㄩ棬銆佺増鏈紱瀛愯〃锛氬煿璁湀浠姐�佸唴瀹广�佹柟寮忋�佸璞°�佽鏃躲�佸煿璁笀銆佽�冩牳鏂瑰紡銆佺姸鎬侊紱琛ㄥ熬澶囨敞銆侀儴闂ㄨ礋璐d汉銆丵A 閮ㄨ礋璐d汉銆�
+
+杞矖锛歚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` 绛涘緟褰掓。 / 鍙綊妗� / 宸插綊妗c��
+
+### 5.5 妗f涓庡叾瀹冩祦绋�
+
+| 椤哄簭 | 琛ㄥ崟 | 琛� |
+|---|---|---|
+| 25 | 涓汉鍩硅妗f | `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` |
+
+涓汉妗f瀵归綈鑰佺晫闈細妗f缂栧彿銆佸缓妗f椂闂淬�佸憳宸ュ鍚嶃�佹墍灞為儴闂ㄣ�佸叆鑱屾椂闂淬�佷富瑕佸矖浣嶃�佸吋鑱屽矖浣嶏紱瀛愯〃宸ヤ綔灞ュ巻锛涙煡鐪嬮〉杩樻湁澶栭儴鍩硅/璇佷功銆佸煿璁洰褰曪紙璁板綍缂栧彿銆佺被鍨嬨�佸唴瀹广�佹柟寮忋�佸煿璁笀銆佽�冩牳鏂瑰紡銆佹槸鍚﹀悎鏍笺�佸紑濮�/缁撴潫锛夈��
+
+鍏ヨ亴鑷姩寤烘。锛氫汉浜嬬敤鎴锋柊澧炴椂鎻� `tms_person_file`銆備竴浜轰竴妗o紝`user_id` 鍞竴銆�
+
+---
+
+## 6. 蹇呴』鍐欏湪浜嬩欢/浠g爜閲岀殑瑙勫垯
+
+杩欎簺涓嶈闈犺〃缁撴瀯纭害鏉燂紝鏀惧彂甯�/淇濆瓨/褰掓。鎸夐挳锛�
+
+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. 瑙掕壊鍙厤鏉冮檺锛屼笉鏂板缓琛�
+
+| 瑙掕壊 | 鏁版嵁鑼冨洿 |
+|---|---|
+| 绯荤粺绠$悊鍛� | 鍩虹璁剧疆 |
+| 鍏徃鍩硅绠$悊鍛� | 鍏ㄥ叕鍙歌鍒�/浠诲姟/妗f |
+| 閮ㄩ棬鍩硅绠$悊鍛� | 鏈儴闂� + `tms_admin_auth` 鎺堟潈閮ㄩ棬锛堝彲鍚笅绾э級 |
+| 鍩硅甯� | 鑷繁鐨勪换鍔℃巿璇俱�佹墦鍒嗐�佸疄鎿嶃�侀槄鍗� |
+| 鏅�氬憳宸� | 浠呬釜浜轰换鍔°�佷釜浜鸿�冭瘯璁板綍銆佷釜浜烘。妗� |
+
+銆屼釜浜哄煿璁。妗堛�嶅彧鑳界湅鑷繁锛涖�屼釜浜哄煿璁。妗堢鐞嗐�嶆寜鎺堟潈鐪嬮儴闂�/鍏徃銆�
+
+---
+
+## 8. 寤鸿涓�鏈熻寖鍥�
+
+鍏堝缓骞惰兘璺戦�氫富閾捐矾鐨勮〃锛�
+
+鍦扮偣銆佸煿璁笀銆佹暀鏉愩�侀搴撱�佽瘯棰樸�佽瘯鍗枫�佸勾搴﹁鍒掋�佸矖浣嶈鍒掋�佸煿璁换鍔°�佷釜浜轰换鍔°�佸煿璁褰曘�佺鍒版槑缁嗐�佺瓟鍗峰揩鐓с�佷釜浜烘。妗堛��
+
+浜屾湡鍐嶉摵锛氬矖浣嶇洰褰曞崌鐗堛�佹彁闂璁俱�佸疄鎿�/鐜板満鎻愰棶銆佽褰曞彉鏇淬�佽祫鏂欏綊妗c�佸鍑�/杞矖/鏂板憳宸�/澶嶅矖娴佺▼銆佸鎵撶増鏈�佹彁閱掕鍒欍��
+
+棰樺簱璇曞嵎寮曟搸涓�鏈熷氨瑕佸揩鐓ц〃锛屽惁鍒欎互鍚庢敼棰樹細姹℃煋宸插綊妗h褰曘��
+
--
Gitblit v1.8.0