agent.develop: 批次1开发基础:数据契约(models JSON)+ahserver/bricks工程脚手架+DDL迁移基座(T01/T02)
This commit is contained in:
parent
043916b54d
commit
d801859c1c
64
docs/02-develop/dev-notes.md
Normal file
64
docs/02-develop/dev-notes.md
Normal file
@ -0,0 +1,64 @@
|
||||
# 一期批次1 组织人事底座(F01~F17)设计定稿 —— agent.develop 开发过程记录
|
||||
|
||||
- 迭代:人事系统-初始迭代(批次1:组织人事底座 + 系统管理与权限基座)
|
||||
- 阶段:agent.develop(设计评审定稿 → 开发基线)
|
||||
- 需求基线:SRS v3.1(`docs/00-requirement/requirement-spec.md`)+ `iteration1-function-detail.md`(F01~F15,81 条)+ `approved-features.md`(FEAT-B1-01~12)
|
||||
- 技术口径:ocai 规范 —— bricks + dspy 前端 / ahserver(Python) 后端 / apppublic/sqlor(MariaDB,库 hrs) / 应用 hr-web
|
||||
- 归档:交付件正本在 `deliverables/agent.develop/`;设计基线文档在 `docs/01-design/`(git 已推送)
|
||||
|
||||
## 1. 阶段目标与结论
|
||||
|
||||
目标:对 agent.design 产出的 4+2 份设计文档完成评审修订与定稿,形成可直接驱动 T01~T27 开发任务的契约基线。
|
||||
|
||||
结论:**设计定稿通过,基线冻结**。设计文档 6 份共 143,521B,与 SRS v3.1 完全对齐:F01~F17(17 功能,90 条验收项)、49 张业务表、112 个业务端点(另有 CRUD 自动生成端点族)、32 条 UI 路由、D1/D3/D4/D9/扫码入职/考勤与绩效桩位降级方案与二期预留均已覆盖。
|
||||
|
||||
## 2. 评审修订记录(与 SRS v3.1 对齐的差异项)
|
||||
|
||||
| # | 评审发现 | 修订动作 | 落点 |
|
||||
|---|---|---|---|
|
||||
| 1 | 批次1范围由原 F01~F15(81 条)扩入 FEAT-B1-10 编制管理、FEAT-B1-11 项目式组织 | 新增 F16 编制管理(6 条)、F17 项目式组织(3 条),验收项合计 81→90 | feature-list.md §1;architecture.md §0 |
|
||||
| 2 | 表数量口径原为 43 张,未含编制/项目式/开放接口预留表 | 扩为 49 张:新增 org_headcount_scheme/headcount_item/headcount_snapshot/org_project/org_project_post/sys_openapp 共 6 张 | database-design.md §4 |
|
||||
| 3 | 技术口径原写"部署端口 8000"类旧值 | 统一 9182(测试+生产)、MariaDB 库 hrs、systemd 管理、mysqldump 每日备份 ≥30 天 | architecture.md §7 |
|
||||
| 4 | 降级项缺设计落点 | 新增降级项设计专章 + 降级桩端点汇总:D1 手工录入、D3 钉钉交接占位、D4 模板文本+esign_stub、D9 站内兜底、扫码入职内网链接/二维码、考勤/绩效桩位 | architecture.md §10;api-design.md §5 |
|
||||
| 5 | F01~F15 缺 FEAT 编号映射 | 建立 F01~F17 ↔ FEAT-B1-01~12 映射表与验收项计数(6+5+8+7+4+4+7+3+6+7+4+4+6+5+5+6+3=90) | feature-list.md §1 |
|
||||
| 6 | API 开放接口(FEAT-B1-01⑦)无规范 | 新增 API 开放接口规范 §9(签名鉴权/sys_openapp/openapi ping 桩) | architecture.md §9 |
|
||||
| 7 | 缺 staff-mgr 迁移方案 | 新增 v1→hrs 迁移策略(映射/解密重加密/幂等/核对报告) | database-design.md §8;architecture.md §6 |
|
||||
| 8 | 任务拆解颗粒度不足 | 重排为 T01~T27(约 92 人日),契约先行、含降级项任务落点表与集成验收标准 | iteration1-task-breakdown.md |
|
||||
|
||||
## 3. 定稿文档清单(git 仓 docs/01-design/,HEAD 与工作区一致)
|
||||
|
||||
| 文档 | 版本 | 字节数 | 关键内容 |
|
||||
|---|---|---|---|
|
||||
| architecture.md | v2.0 | 29,403B | 技术选型/模块边界/依赖/部署(9182/MariaDB hrs/systemd)/安全/NFR响应表/API开放规范§9/降级项专章§10 |
|
||||
| database-design.md | v3.0 | 40,259B | 49表清单/字段/索引/ER/models JSON/DDL等价示例/数据字典/staff-mgr迁移/验收映射 |
|
||||
| api-design.md | v2.0 | 29,356B | 112业务端点请求响应契约/错误码11类/典型调用链/降级桩端点表/安全要点 |
|
||||
| ui-design.md | v3.0 | 23,929B | 菜单路由32条/页面组件树12组/交互时序/设计规范/页面-接口追溯 |
|
||||
| feature-list.md | v2.1 | 7,103B | F01~F17↔FEAT映射/90条验收项计数/功能-模块-表落点/范围外与降级项 |
|
||||
| iteration1-task-breakdown.md | v2.0 | 13,471B | T01~T27任务/依赖/估时(约92人日)/验收对照/集成验收标准 |
|
||||
|
||||
合计 143,521B(`wc -c docs/01-design/*.md` 实测)。
|
||||
|
||||
## 4. git 提交证据(hr-system 仓,远端 git@git.opencomputing.cn:yumoqing/hr-system.git)
|
||||
|
||||
| commit | 说明 |
|
||||
|---|---|
|
||||
| e6b266f / bfc1003 | design: 批次1设计评审定稿初版(后被误截断) |
|
||||
| 857c1fd / 8e0a767 / 5a96df5 / 049e63c | fix(design): 恢复6份设计文档完整正文 143,521B(截断问题闭环,PM 复核通过) |
|
||||
| 043916b | agent.develop: 批次1开发基础——数据契约(models JSON)+ahserver/bricks工程脚手架+DDL迁移基座(T01/T02 准备) |
|
||||
| (本提交) | develop: 批次1设计定稿归档——模块状态置定稿+dev-notes+交付件 |
|
||||
|
||||
验证方式:`git log --oneline` / `git show --stat <commit>` / `git status`(工作区 clean,与 origin/main 一致)。
|
||||
|
||||
## 5. 定稿后变更控制
|
||||
|
||||
1. 设计基线冻结:表结构、端点签名、路由、权限矩阵任何变更须走设计评审(修改 docs/01-design/ 四份文档 + 评审记录)。
|
||||
2. T01~T27 开发任务以 `iteration1-task-breakdown.md` 为准建卡;每任务交付模块仓代码 + 对应验收项自测记录。
|
||||
3. T27 集成验收标准:90 条验收项全部通过;P0(FEAT-B1-01~05)100%;降级项专项验证(D1/D3/D4/D9/扫码登记);性能抽测(列表 ≤500ms、报表 ≤3s、导入 500 行 ≤30s);权限与脱敏抽测(SRS §10)。
|
||||
|
||||
## 6. 遗留与风险
|
||||
|
||||
| 项 | 说明 | 处置 |
|
||||
|---|---|---|
|
||||
| 5 个模块仓库(hr_org/hr_roster/hr_flow/hr_contract/hr_report)尚未建仓 | modules/*.md 中为规划地址,实测 ls-remote 不存在 | T01 任务首项:建仓;以 PM 确认地址为准 |
|
||||
| hr-system 复用现有 hr-system 仓 | sys_ 8 张表模型与端点直接在现仓开发 | 无 |
|
||||
| appbase/rbac 沿用基础包 | 登录/用户/字典/rbac 不自研 | T02 验证加载与四角色初始化 |
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块名称: 合同模块 (hr-contract)
|
||||
|
||||
状态:**迭代1总体设计完成(提交审核)**
|
||||
状态:**一期批次1设计定稿(基线冻结;契约变更须走设计评审)**(agent.develop 定稿记录见 docs/02-develop/dev-notes.md)
|
||||
所属应用:hr-web(Web版人事系统)
|
||||
|
||||
## 1. 模块功能
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块名称: 流程审批模块 (hr-flow)
|
||||
|
||||
状态:**迭代1总体设计完成(提交审核)**
|
||||
状态:**一期批次1设计定稿(基线冻结;契约变更须走设计评审)**(agent.develop 定稿记录见 docs/02-develop/dev-notes.md)
|
||||
所属应用:hr-web(Web版人事系统)
|
||||
|
||||
## 1. 模块功能
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块名称: 组织人事模块 (hr-org)
|
||||
|
||||
状态:**迭代1总体设计完成(提交审核)**
|
||||
状态:**一期批次1设计定稿(基线冻结;契约变更须走设计评审)**(agent.develop 定稿记录见 docs/02-develop/dev-notes.md)
|
||||
所属应用:hr-web(Web版人事系统)
|
||||
|
||||
## 1. 模块功能
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块名称: 报表模块 (hr-report)
|
||||
|
||||
状态:**迭代1总体设计完成(提交审核)**
|
||||
状态:**一期批次1设计定稿(基线冻结;契约变更须走设计评审)**(agent.develop 定稿记录见 docs/02-develop/dev-notes.md)
|
||||
所属应用:hr-web(Web版人事系统)
|
||||
|
||||
## 1. 模块功能
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块名称: 花名册模块 (hr-roster)
|
||||
|
||||
状态:**迭代1总体设计完成(提交审核)**
|
||||
状态:**一期批次1设计定稿(基线冻结;契约变更须走设计评审)**(agent.develop 定稿记录见 docs/02-develop/dev-notes.md)
|
||||
所属应用:hr-web(Web版人事系统)
|
||||
|
||||
## 1. 模块功能
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块名称: 权限日志与通用服务模块 (hr-system)
|
||||
|
||||
状态:**迭代1总体设计完成(提交审核)**
|
||||
状态:**一期批次1设计定稿(基线冻结;契约变更须走设计评审)**(agent.develop 定稿记录见 docs/02-develop/dev-notes.md)
|
||||
所属应用:hr-web(Web版人事系统)
|
||||
|
||||
> 说明:本模块为迭代1规划的系统支撑模块(ocai 口径),取代 v1 同名应用 `apps/hr-system.md`(Java 口径,已废弃)。
|
||||
|
||||
BIN
scripts/__pycache__/apply_ddl.cpython-310.pyc
Normal file
BIN
scripts/__pycache__/apply_ddl.cpython-310.pyc
Normal file
Binary file not shown.
BIN
scripts/__pycache__/gen_models.cpython-310.pyc
Normal file
BIN
scripts/__pycache__/gen_models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
scripts/__pycache__/json2ddl.cpython-310.pyc
Normal file
BIN
scripts/__pycache__/json2ddl.cpython-310.pyc
Normal file
Binary file not shown.
118
scripts/apply_ddl.py
Normal file
118
scripts/apply_ddl.py
Normal file
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""DDL 迁移基座执行器(T01/T02):幂等导入 models JSON 生成的 DDL 至 MariaDB hrs 库。
|
||||
特性:
|
||||
- CREATE TABLE IF NOT EXISTS(可重跑);
|
||||
- 缺索引自动补建(先查 information_schema.STATISTICS);
|
||||
- 迁移台账写入 hrs.schema_migration(version/表数/时间/校验和),重复执行只记一条最新;
|
||||
- 依赖 mysql 客户端或 pymysql,自动探测。
|
||||
用法:
|
||||
python3 scripts/apply_ddl.py [--host 127.0.0.1] [--user test] [--password test123] [--db hrs] [--models models] [--version T01]
|
||||
"""
|
||||
import argparse, glob, hashlib, json, os, shutil, subprocess, sys
|
||||
|
||||
BASE = os.path.join(os.path.dirname(__file__), "..")
|
||||
sys.path.insert(0, os.path.join(BASE, "scripts"))
|
||||
from json2ddl import gen_table_ddl, gen_index_guard_sql # noqa: E402
|
||||
|
||||
def run_sql_cli(sql, args):
|
||||
cmd = ["mysql", f"-h{args.host}", f"-P{args.port}", f"-u{args.user}", f"-p{args.password}", args.db]
|
||||
p = subprocess.run(cmd, input=sql.encode("utf-8"), capture_output=True)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(p.stderr.decode("utf-8", "ignore"))
|
||||
return p.stdout.decode("utf-8", "ignore")
|
||||
|
||||
def exec_sql(sql, args):
|
||||
try:
|
||||
import pymysql
|
||||
conn = pymysql.connect(host=args.host, port=args.port, user=args.user,
|
||||
password=args.password, database=args.db, charset="utf8mb4")
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql)
|
||||
conn.commit()
|
||||
return None
|
||||
finally:
|
||||
conn.close()
|
||||
except ImportError:
|
||||
run_sql_cli(sql, args)
|
||||
return None
|
||||
|
||||
def query_sql(sql, args):
|
||||
if shutil.which("mysql"):
|
||||
out = run_sql_cli(sql + "\n", args)
|
||||
return [ln.split("\t") for ln in out.strip().splitlines() if ln]
|
||||
import pymysql
|
||||
conn = pymysql.connect(host=args.host, port=args.port, user=args.user,
|
||||
password=args.password, database=args.db, charset="utf8mb4")
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql)
|
||||
return [list(r) for r in cur.fetchall()]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--host", default="127.0.0.1")
|
||||
ap.add_argument("--port", default=3306, type=int)
|
||||
ap.add_argument("--user", default="test")
|
||||
ap.add_argument("--password", default="test123")
|
||||
ap.add_argument("--db", default="hrs")
|
||||
ap.add_argument("--models", default=os.path.join(BASE, "models"))
|
||||
ap.add_argument("--version", default="T01")
|
||||
args = ap.parse_args()
|
||||
|
||||
# 0) 迁移台账表
|
||||
exec_sql("""CREATE TABLE IF NOT EXISTS `schema_migration` (
|
||||
`version` VARCHAR(32) NOT NULL COMMENT '迁移版本号',
|
||||
`applied_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '执行时间',
|
||||
`table_count` INT NOT NULL DEFAULT 0 COMMENT '本次管理表数',
|
||||
`checksum` VARCHAR(64) DEFAULT NULL COMMENT 'models JSON 合并校验和',
|
||||
`operator` VARCHAR(64) DEFAULT 'agent.develop' COMMENT '执行人',
|
||||
PRIMARY KEY (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='DDL迁移台账(幂等可重跑)';""", args)
|
||||
|
||||
# 1) 收集全部 models JSON,逐表建表 + 补索引
|
||||
created, index_added, total = [], [], 0
|
||||
h = hashlib.sha256()
|
||||
jobs = []
|
||||
for moddir in sorted(glob.glob(os.path.join(args.models, "*"))):
|
||||
if not os.path.isdir(moddir):
|
||||
continue
|
||||
for p in sorted(glob.glob(os.path.join(moddir, "*.json"))):
|
||||
doc = json.load(open(p, encoding="utf-8"))
|
||||
h.update(open(p, "rb").read())
|
||||
table, ddl = gen_table_ddl(p)
|
||||
jobs.append((os.path.basename(moddir), table, ddl, doc))
|
||||
total += 1
|
||||
|
||||
for mod, table, ddl, doc in jobs:
|
||||
before = query_sql(f"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='{args.db}' AND TABLE_NAME='{table}'", args)
|
||||
n_exist = int(before[0][0]) if before else 0
|
||||
exec_sql(ddl, args)
|
||||
if not n_exist:
|
||||
created.append(table)
|
||||
# 索引补建(幂等)
|
||||
rows = query_sql(f"SELECT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='{args.db}' AND TABLE_NAME='{table}'", args)
|
||||
existing_idx = {r[0] for r in rows}
|
||||
for idx in doc.get("indexes", []):
|
||||
if idx["name"] not in existing_idx:
|
||||
exec_sql(gen_index_guard_sql(table, idx), args)
|
||||
index_added.append(f"{table}.{idx['name']}")
|
||||
|
||||
checksum = h.hexdigest()[:16]
|
||||
# 2) 登记迁移台账(幂等 upsert)
|
||||
exec_sql(f"""INSERT INTO schema_migration (version, table_count, checksum)
|
||||
VALUES ('{args.version}', {total}, '{checksum}')
|
||||
ON DUPLICATE KEY UPDATE applied_at=CURRENT_TIMESTAMP, table_count={total}, checksum='{checksum}';""", args)
|
||||
|
||||
# 3) 校验
|
||||
rows = query_sql(f"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='{args.db}'", args)
|
||||
print(f"[apply_ddl] 管理业务表 {total} 张;新建 {len(created)} 张;补索引 {len(index_added)} 个")
|
||||
print(f"[apply_ddl] 新建表: {', '.join(created) if created else '(无,全部已存在,幂等重跑)'}")
|
||||
print(f"[apply_ddl] schema_migration version={args.version} table_count={total} checksum={checksum}")
|
||||
print(f"[apply_ddl] 库 {args.db} 当前表总数: {rows[0][0]}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
780
scripts/gen_models.py
Normal file
780
scripts/gen_models.py
Normal file
@ -0,0 +1,780 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""T01 数据契约生成器:依据 docs/01-design/database-design.md v3.0 §3/§4/§5/§7
|
||||
生成 49 张表的 models/{module}/{table}.json(database-table-definition-spec 四段式:
|
||||
summary/fields/indexes/codes)。幂等可重跑(覆盖写入)。
|
||||
用法:python3 scripts/gen_models.py [输出根目录]
|
||||
"""
|
||||
import json, os, sys
|
||||
|
||||
def F(name, title, ftype, **kw):
|
||||
d = {"name": name, "title": title, "type": ftype}
|
||||
d.update(kw)
|
||||
return d
|
||||
|
||||
def V(name, title, length, **kw):
|
||||
return F(name, title, "varchar", length=length, **kw)
|
||||
|
||||
def AUDIT():
|
||||
return [
|
||||
V("created_by", "创建人", 32),
|
||||
V("updated_by", "更新人", 32),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
F("updated_at", "更新时间", "timestamp"),
|
||||
]
|
||||
|
||||
def IDX(name, fields, unique=False):
|
||||
d = {"name": name, "fields": fields}
|
||||
if unique:
|
||||
d["unique"] = True
|
||||
return d
|
||||
|
||||
def CODE(name, code):
|
||||
return {"name": name, "source": "appcodes", "code": code}
|
||||
|
||||
# ---------------- hr-org(23 张,前缀 org_) ----------------
|
||||
ORG = {
|
||||
"org_unit": {
|
||||
"title": "组织", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("org_code", "组织编码", 32, notnull=True),
|
||||
V("org_name", "组织名称", 128, notnull=True),
|
||||
V("parent_id", "上级组织(自引用)", 32),
|
||||
V("org_type", "组织类型(字典ORG_TYPE)", 16),
|
||||
V("leader_id", "负责人", 32),
|
||||
F("effective_date", "生效日期", "date"),
|
||||
F("expire_date", "失效日期(空=长期)", "date"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_org_code", ["org_code"], True), IDX("idx_parent_id", ["parent_id"]), IDX("idx_status", ["status"])],
|
||||
"codes": [CODE("org_type", "ORG_TYPE")],
|
||||
},
|
||||
"org_unit_change": {
|
||||
"title": "组织变更时间轴", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("org_id", "组织", 32),
|
||||
V("change_type", "变更类型(add/edit/disable/move/split/merge/delete)", 16),
|
||||
F("before_json", "变更前快照", "text"),
|
||||
F("after_json", "变更后快照", "text"),
|
||||
F("effective_date", "生效日期", "date"),
|
||||
V("operator_id", "操作人", 32),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_org_id", ["org_id"]), IDX("idx_created_at", ["created_at"])],
|
||||
},
|
||||
"org_field_def": {
|
||||
"title": "组织自定义字段定义", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("field_code", "字段编码", 64, notnull=True),
|
||||
V("field_name", "字段名称", 64, notnull=True),
|
||||
V("field_type", "字段类型(text/number/date/select/file)", 16),
|
||||
F("options_json", "选项定义JSON", "text"),
|
||||
F("required", "是否必填(0/1)", "int", default=0),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_field_code", ["field_code"], True)],
|
||||
},
|
||||
"org_field_value": {
|
||||
"title": "组织字段值(EAV)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("org_id", "组织", 32, notnull=True),
|
||||
V("field_id", "字段定义", 32, notnull=True),
|
||||
F("value_text", "文本值", "text"),
|
||||
F("value_date", "日期值", "date"),
|
||||
V("value_file", "附件值(文件ID)", 64),
|
||||
F("updated_at", "更新时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("uk_org_field", ["org_id", "field_id"], True)],
|
||||
},
|
||||
"org_job": {
|
||||
"title": "职务(含F17横向职务)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("job_code", "职务编码", 32, notnull=True),
|
||||
V("job_name", "职务名称", 64, notnull=True),
|
||||
V("job_category", "职务类别(vertical/horizontal,F17)", 16, notnull=True, default="vertical"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_job_code", ["job_code"], True), IDX("idx_job_category", ["job_category"])],
|
||||
"codes": [CODE("job_category", "JOB_CATEGORY")],
|
||||
},
|
||||
"org_position": {
|
||||
"title": "职位", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("position_code", "职位编码", 32, notnull=True),
|
||||
V("position_name", "职位名称", 64, notnull=True),
|
||||
V("job_id", "职务", 32),
|
||||
V("sequence_id", "序列", 32),
|
||||
V("category", "职位类别(字典POS_CATEGORY)", 16),
|
||||
V("dept_ids", "适用部门(多值,逗号分隔)", 512),
|
||||
F("description", "职位描述", "text"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_position_code", ["position_code"], True), IDX("idx_job_id", ["job_id"])],
|
||||
"codes": [CODE("category", "POS_CATEGORY")],
|
||||
},
|
||||
"org_sequence": {
|
||||
"title": "序列", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("seq_category", "序列类别", 32),
|
||||
V("seq_name", "序列名称", 64, notnull=True),
|
||||
V("grade_level_ids", "关联职级类别(多值,逗号分隔)", 512),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [],
|
||||
},
|
||||
"org_grade_level": {
|
||||
"title": "职级", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("level_code", "职级编码", 32, notnull=True),
|
||||
V("level_name", "职级名称", 64, notnull=True),
|
||||
V("level_category", "职级类别", 32),
|
||||
V("start_rank_id", "起始职等", 32),
|
||||
V("end_rank_id", "结束职等", 32),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_level_code", ["level_code"], True)],
|
||||
},
|
||||
"org_grade_rank": {
|
||||
"title": "职等", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("rank_code", "职等编码", 32, notnull=True),
|
||||
V("rank_name", "职等名称", 64, notnull=True),
|
||||
F("rank_level", "职等层级", "int"),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_rank_code", ["rank_code"], True)],
|
||||
},
|
||||
"org_blacklist": {
|
||||
"title": "黑名单", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("name", "姓名", 64),
|
||||
V("id_number_hash", "证件号哈希(确定性,匹配用)", 64),
|
||||
V("id_number_cipher", "证件号密文", 256),
|
||||
V("reason", "拉黑原因", 256),
|
||||
V("source", "来源(leave/manual)", 16),
|
||||
V("source_employee_id", "来源员工档案", 32),
|
||||
V("created_by", "创建人", 32),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_id_number_hash", ["id_number_hash"])],
|
||||
},
|
||||
"org_contract_company": {
|
||||
"title": "合同公司", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("company_code", "公司编码", 32, notnull=True),
|
||||
V("company_name", "公司名称", 128, notnull=True),
|
||||
V("credit_code", "统一社会信用代码", 32),
|
||||
V("legal_person", "法人", 64),
|
||||
V("contact_info", "联系方式", 128),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_company_code", ["company_code"], True)],
|
||||
},
|
||||
"org_entry": {
|
||||
"title": "入职记录", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_id", "员工档案(可空,待入职)", 32),
|
||||
V("name", "姓名", 64, notnull=True),
|
||||
V("id_type", "证件类型(字典ID_TYPE)", 16, default="id_card"),
|
||||
V("id_number_cipher", "证件号密文", 256),
|
||||
V("id_number_hash", "证件号哈希", 64),
|
||||
F("id_valid_from", "证件有效期起", "date"),
|
||||
F("id_valid_to", "证件有效期止(长期=9999-12-31)", "date"),
|
||||
V("id_authority", "证件签发机关", 128),
|
||||
V("id_source", "证件信息来源(字典ID_SOURCE,D1手工录入)", 16, default="manual"),
|
||||
V("employee_type", "员工类型(字典EMP_TYPE)", 16),
|
||||
V("org_id", "入职组织", 32),
|
||||
V("position_id", "职位", 32),
|
||||
V("company_id", "合同公司", 32),
|
||||
V("work_location", "办公地点", 128),
|
||||
F("hire_date", "入职日期", "date"),
|
||||
V("entry_status", "入职状态(字典ENTRY_STATUS)", 24, default="pending_approval"),
|
||||
F("is_rehire", "是否复职(0/1)", "int", default=0),
|
||||
V("register_status", "登记表状态(none/invited/filled)", 16, default="none"),
|
||||
V("register_token", "登记表token(扫码入职降级)", 64),
|
||||
V("register_url", "登记表链接", 256),
|
||||
V("qrcode_file", "登记二维码图片(文件ID)", 64),
|
||||
F("register_expire", "登记链接过期时间", "timestamp"),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_entry_status", ["entry_status"]), IDX("idx_hire_date", ["hire_date"]),
|
||||
IDX("idx_flow_instance_id", ["flow_instance_id"]), IDX("uk_register_token", ["register_token"], True)],
|
||||
"codes": [CODE("id_type", "ID_TYPE"), CODE("id_source", "ID_SOURCE"), CODE("employee_type", "EMP_TYPE"),
|
||||
CODE("entry_status", "ENTRY_STATUS"), CODE("register_status", "REGISTER_STATUS")],
|
||||
},
|
||||
"org_regularization": {
|
||||
"title": "转正记录", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_id", "员工档案", 32),
|
||||
F("probation_end_date", "试用期止(预计转正日)", "date"),
|
||||
F("regular_date", "转正日期", "date"),
|
||||
V("apply_type", "申请方式(self/proxy/manual)", 16),
|
||||
V("status", "状态(pending/approved/rejected/cancelled)", 16, default="pending"),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_employee_id", ["employee_id"]), IDX("idx_regular_date", ["regular_date"])],
|
||||
},
|
||||
"org_transfer": {
|
||||
"title": "调动单(支持批量)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("transfer_type", "调动类型(字典TRANSFER_TYPE)", 24),
|
||||
V("status", "状态(pending/confirmed/cancelled)", 16, default="pending"),
|
||||
F("effective_date", "生效日期", "date"),
|
||||
V("batch_no", "批次号", 32),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_status", ["status"]), IDX("idx_effective_date", ["effective_date"])],
|
||||
"codes": [CODE("transfer_type", "TRANSFER_TYPE")],
|
||||
},
|
||||
"org_transfer_detail": {
|
||||
"title": "调动明细(逐人)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("transfer_id", "调动单", 32),
|
||||
V("employee_id", "员工档案", 32),
|
||||
V("from_org_id", "原组织", 32),
|
||||
V("to_org_id", "新组织", 32),
|
||||
V("from_position_id", "原职位", 32),
|
||||
V("to_position_id", "新职位", 32),
|
||||
V("from_grade_level_id", "原职级", 32),
|
||||
V("to_grade_level_id", "新职级", 32),
|
||||
V("status", "状态(pending/done/cancelled)", 16, default="pending"),
|
||||
],
|
||||
"indexes": [IDX("idx_transfer_id", ["transfer_id"]), IDX("idx_employee_id", ["employee_id"])],
|
||||
},
|
||||
"org_leave": {
|
||||
"title": "离职记录", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_id", "员工档案", 32),
|
||||
V("leave_type", "离职类型(辞职/劝退/合同到期/退休)", 24),
|
||||
V("leave_reason", "离职原因(字典LEAVE_REASON)", 32),
|
||||
F("leave_date", "离职日期", "date"),
|
||||
V("apply_type", "申请方式(self/proxy/manual)", 16),
|
||||
V("status", "状态(pending_leave/left/cancelled)", 16, default="pending_leave"),
|
||||
V("handover_status", "交接状态(pending/done)", 16, default="pending"),
|
||||
V("certificate_file", "离职证明文件ID", 64),
|
||||
F("blacklist_flag", "是否加入黑名单(0/1)", "int", default=0),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_employee_id", ["employee_id"]), IDX("idx_status", ["status"]), IDX("idx_leave_date", ["leave_date"])],
|
||||
"codes": [CODE("leave_reason", "LEAVE_REASON")],
|
||||
},
|
||||
"org_leave_handover": {
|
||||
"title": "离职交接项", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("leave_id", "离职记录", 32),
|
||||
V("item_type", "交接项类型(字典HANDOVER_TYPE,含dingtalk_resource占位D3)", 32),
|
||||
F("item_content", "交接内容", "text"),
|
||||
V("handover_to_id", "交接人", 32),
|
||||
V("status", "状态(pending/done)", 16, default="pending"),
|
||||
F("remark", "备注", "text"),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
F("updated_at", "更新时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_leave_id", ["leave_id"])],
|
||||
"codes": [CODE("item_type", "HANDOVER_TYPE")],
|
||||
},
|
||||
"org_concurrent_post": {
|
||||
"title": "兼岗", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_id", "员工档案", 32),
|
||||
V("org_id", "兼岗组织", 32),
|
||||
V("position_id", "兼岗职位", 32),
|
||||
F("start_date", "开始日期", "date"),
|
||||
F("end_date", "结束日期", "date"),
|
||||
V("status", "状态(awaiting/active/expired)", 16, default="awaiting"),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_employee_id", ["employee_id"]), IDX("idx_status", ["status"])],
|
||||
},
|
||||
"org_headcount_scheme": {
|
||||
"title": "编制方案(F16)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("scheme_name", "方案名称", 128, notnull=True),
|
||||
V("cycle_type", "周期(字典HEADCOUNT_CYCLE: year/quarter/month)", 16, notnull=True),
|
||||
F("cycle_start", "周期起", "date", notnull=True),
|
||||
F("cycle_end", "周期止", "date", notnull=True),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_cycle", ["cycle_start", "cycle_end"]), IDX("idx_status", ["status"])],
|
||||
"codes": [CODE("cycle_type", "HEADCOUNT_CYCLE")],
|
||||
},
|
||||
"headcount_item": {
|
||||
"title": "编制细分项(F16)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("scheme_id", "编制方案", 32, notnull=True),
|
||||
F("scope_json", "占编范围条件(员工类型/状态/职位/职级/自定义字段)", "text", notnull=True),
|
||||
F("seg_json", "细分维度(空=整体)", "text"),
|
||||
F("head_limit", "编制数量", "int", notnull=True, default=0),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_scheme_id", ["scheme_id"])],
|
||||
},
|
||||
"headcount_snapshot": {
|
||||
"title": "编制快照(F16历史回溯/实时计数)", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("scheme_id", "编制方案", 32, notnull=True),
|
||||
V("item_id", "细分项", 32, notnull=True),
|
||||
F("snap_date", "快照日期", "date", notnull=True),
|
||||
F("used_count", "占编人数", "int", notnull=True, default=0),
|
||||
F("over_count", "超编数(0=未超编)", "int", notnull=True, default=0),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("uk_scheme_item_date", ["scheme_id", "item_id", "snap_date"], True), IDX("idx_snap_date", ["snap_date"])],
|
||||
},
|
||||
"org_project": {
|
||||
"title": "项目式组织(F17)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("project_code", "项目编码", 32, notnull=True),
|
||||
V("project_name", "项目名称", 128, notnull=True),
|
||||
V("parent_id", "上级项目(自引用多层级)", 32),
|
||||
V("owner_id", "负责人", 32),
|
||||
F("start_date", "开始日期", "date"),
|
||||
F("end_date", "结束日期", "date"),
|
||||
V("project_status", "项目状态(字典PROJECT_STATUS)", 16, notnull=True, default="active"),
|
||||
F("attrs_json", "项目属性", "text"),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_project_code", ["project_code"], True), IDX("idx_parent_id", ["parent_id"]), IDX("idx_project_status", ["project_status"])],
|
||||
"codes": [CODE("project_status", "PROJECT_STATUS")],
|
||||
},
|
||||
"org_project_post": {
|
||||
"title": "项目任职(F17横向)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("project_id", "项目组织", 32, notnull=True),
|
||||
V("employee_id", "员工", 32, notnull=True),
|
||||
V("job_id", "横向职务(org_job.job_category=horizontal)", 32),
|
||||
F("start_date", "开始日期", "date"),
|
||||
F("end_date", "结束日期", "date"),
|
||||
V("status", "状态 active/expired", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_proj_emp_job", ["project_id", "employee_id", "job_id", "start_date"], True), IDX("idx_employee_id", ["employee_id"])],
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------- hr-roster(7 张,前缀 roster_) ----------------
|
||||
ROSTER = {
|
||||
"roster_field_group": {
|
||||
"title": "花名册字段分组", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("group_code", "分组编码", 32, notnull=True),
|
||||
V("group_name", "分组名称(工作信息/个人信息/绩效结果/培训记录)", 64, notnull=True),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_group_code", ["group_code"], True)],
|
||||
},
|
||||
"roster_field_def": {
|
||||
"title": "花名册字段定义", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("group_id", "字段分组", 32),
|
||||
V("field_code", "字段编码", 64, notnull=True),
|
||||
V("field_name", "字段名称", 64, notnull=True),
|
||||
V("field_type", "字段类型(text/number/date/select/multiselect/file)", 16),
|
||||
F("options_json", "选项定义(字典code或自定义项)", "text"),
|
||||
F("required", "是否必填(0/1)", "int", default=0),
|
||||
F("sensitive", "是否敏感(0/1,驱动脱敏与可见性)", "int", default=0),
|
||||
F("editable_self", "员工自助可编辑(0/1)", "int", default=0),
|
||||
F("need_audit", "自助修改需审核(0/1)", "int", default=0),
|
||||
F("apply_types", "适用员工类型JSON", "text"),
|
||||
F("sort_no", "排序号", "int", default=0),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_field_code", ["field_code"], True), IDX("idx_group_id", ["group_id"])],
|
||||
},
|
||||
"roster_employee": {
|
||||
"title": "员工主档", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_no", "工号", 32, notnull=True),
|
||||
V("name", "姓名", 64, notnull=True),
|
||||
V("gender", "性别(字典GENDER)", 16),
|
||||
V("id_type", "证件类型", 16, default="id_card"),
|
||||
V("id_number_cipher", "证件号密文(敏感)", 256, sensitive=True),
|
||||
V("id_number_hash", "证件号哈希(确定性,唯一/黑名单匹配)", 64, sensitive=True),
|
||||
F("id_valid_from", "证件有效期起", "date"),
|
||||
F("id_valid_to", "证件有效期止(长期=9999-12-31)", "date"),
|
||||
V("id_authority", "证件签发机关", 128),
|
||||
V("id_source", "证件信息来源(字典ID_SOURCE)", 16, default="manual"),
|
||||
F("birthday", "出生日期", "date"),
|
||||
V("phone", "手机号(明文存储,按角色脱敏)", 20, sensitive=True),
|
||||
V("email", "邮箱", 128),
|
||||
V("org_id", "组织", 32),
|
||||
V("position_id", "职位", 32),
|
||||
V("job_id", "职务", 32),
|
||||
V("grade_level_id", "职级", 32),
|
||||
V("grade_rank_id", "职等", 32),
|
||||
V("sequence_id", "序列", 32),
|
||||
V("employee_type", "员工类型(字典EMP_TYPE)", 16),
|
||||
V("employee_status", "员工状态(字典EMP_STATUS)", 16),
|
||||
F("hire_date", "入职日期", "date"),
|
||||
F("probation_end_date", "试用期止(预计转正日)", "date"),
|
||||
F("regular_date", "转正日期", "date"),
|
||||
F("leave_date", "离职日期", "date"),
|
||||
V("company_id", "合同公司", 32),
|
||||
V("work_location", "办公地点", 128),
|
||||
V("direct_leader_id", "直属主管", 32),
|
||||
V("prev_employee_id", "复职关联原档案ID", 32),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_employee_no", ["employee_no"], True), IDX("uk_id_number_hash", ["id_number_hash"], True),
|
||||
IDX("idx_org_id", ["org_id"]), IDX("idx_status", ["employee_status"]), IDX("idx_hire_date", ["hire_date"]),
|
||||
IDX("idx_probation_end", ["probation_end_date"]), IDX("idx_direct_leader", ["direct_leader_id"]),
|
||||
IDX("idx_prev_employee", ["prev_employee_id"])],
|
||||
"codes": [CODE("gender", "GENDER"), CODE("employee_type", "EMP_TYPE"), CODE("employee_status", "EMP_STATUS"),
|
||||
CODE("id_source", "ID_SOURCE"), CODE("id_type", "ID_TYPE")],
|
||||
},
|
||||
"roster_field_value": {
|
||||
"title": "花名册字段值(EAV)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_id", "员工档案", 32, notnull=True),
|
||||
V("field_id", "字段定义", 32, notnull=True),
|
||||
F("value_text", "文本值", "text"),
|
||||
F("value_number", "数值", "decimal", precision=18, scale=4),
|
||||
F("value_date", "日期值", "date"),
|
||||
V("value_file", "附件值(文件ID)", 64),
|
||||
V("updated_by", "更新人", 32),
|
||||
F("updated_at", "更新时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("uk_employee_field", ["employee_id", "field_id"], True), IDX("idx_field_id", ["field_id"])],
|
||||
},
|
||||
"roster_empno_rule": {
|
||||
"title": "工号规则", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("rule_name", "规则名称", 64, notnull=True),
|
||||
V("prefix", "工号前缀", 16),
|
||||
V("match_field", "匹配字段(如company_id)", 32),
|
||||
V("match_value", "匹配值", 64),
|
||||
F("seq_length", "流水号长度", "int", default=4),
|
||||
F("current_seq", "当前流水号", "int", default=0),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [],
|
||||
},
|
||||
"roster_timeline": {
|
||||
"title": "员工时间轴", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_id", "员工档案", 32),
|
||||
V("event_type", "事件类型(entry/regular/transfer/leave/concurrent/contract/field_change/headcount/project/migrate)", 24),
|
||||
F("event_date", "事件日期", "date"),
|
||||
V("title", "标题", 128),
|
||||
F("content_json", "内容JSON", "text"),
|
||||
V("source_type", "来源类型", 32),
|
||||
V("source_id", "来源ID", 32),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_emp_date", ["employee_id", "event_date"])],
|
||||
},
|
||||
"roster_type_rule": {
|
||||
"title": "员工类型字段规则", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("employee_type", "员工类型(字典EMP_TYPE)", 16, notnull=True),
|
||||
F("field_rules_json", "按类型必填/隐藏字段规则", "text"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_employee_type", ["employee_type"], True)],
|
||||
"codes": [CODE("employee_type", "EMP_TYPE")],
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------- hr-flow(7 张,前缀 flow_) ----------------
|
||||
FLOW = {
|
||||
"flow_form_def": {
|
||||
"title": "表单定义", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("form_code", "表单编码", 64, notnull=True),
|
||||
V("form_name", "表单名称", 128, notnull=True),
|
||||
V("biz_type", "业务类型(字典FLOW_BIZ_TYPE,含attendance_*桩位)", 32),
|
||||
F("fields_json", "字段定义(引用roster_field_def或自定义项)", "text"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_form_code", ["form_code"], True)],
|
||||
"codes": [CODE("biz_type", "FLOW_BIZ_TYPE")],
|
||||
},
|
||||
"flow_def": {
|
||||
"title": "流程定义", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("flow_code", "流程编码", 64, notnull=True),
|
||||
V("flow_name", "流程名称", 128, notnull=True),
|
||||
V("form_id", "表单定义", 32),
|
||||
V("biz_type", "业务类型(字典FLOW_BIZ_TYPE)", 32),
|
||||
F("match_cond_json", "匹配条件(员工类型/部门)", "text"),
|
||||
F("version", "版本", "int", default=1),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_flow_code", ["flow_code"], True), IDX("idx_biz_type", ["biz_type"])],
|
||||
"codes": [CODE("biz_type", "FLOW_BIZ_TYPE")],
|
||||
},
|
||||
"flow_node_def": {
|
||||
"title": "流程节点", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("flow_id", "流程定义", 32),
|
||||
F("node_seq", "节点序号", "int", default=1),
|
||||
V("node_name", "节点名称", 64),
|
||||
V("approver_type", "审批人类型(user/role/leader/admin/flow_role)", 16),
|
||||
V("approver_value", "审批人取值", 256),
|
||||
V("pass_rule", "通过规则(any/all)", 8, default="all"),
|
||||
F("field_perm_json", "节点字段可读写权限", "text"),
|
||||
V("cc_to", "抄送对象", 512),
|
||||
],
|
||||
"indexes": [IDX("idx_flow_id", ["flow_id"])],
|
||||
},
|
||||
"flow_role": {
|
||||
"title": "审批角色", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("role_name", "角色名称", 64, notnull=True),
|
||||
F("flow_scope_json", "可用流程范围", "text"),
|
||||
F("op_perm_json", "操作权限", "text"),
|
||||
F("data_scope_json", "数据查看范围", "text"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [],
|
||||
},
|
||||
"flow_instance": {
|
||||
"title": "流程实例", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("inst_no", "审批编号", 32, notnull=True),
|
||||
V("flow_id", "流程定义", 32, notnull=True),
|
||||
V("form_id", "表单定义", 32, notnull=True),
|
||||
V("biz_type", "业务类型(字典FLOW_BIZ_TYPE)", 32, notnull=True),
|
||||
V("biz_id", "业务单据ID", 32),
|
||||
V("title", "标题", 256),
|
||||
V("initiator_id", "发起人", 32, notnull=True),
|
||||
V("inst_status", "实例状态(字典INST_STATUS)", 16, notnull=True, default="running"),
|
||||
F("submit_data_json", "提交表单数据快照", "text"),
|
||||
F("start_time", "发起时间", "timestamp"),
|
||||
F("finish_time", "结束时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("uk_inst_no", ["inst_no"], True), IDX("idx_biz", ["biz_type", "biz_id"]),
|
||||
IDX("idx_initiator", ["initiator_id"]), IDX("idx_status", ["inst_status"])],
|
||||
"codes": [CODE("biz_type", "FLOW_BIZ_TYPE"), CODE("inst_status", "INST_STATUS")],
|
||||
},
|
||||
"flow_task": {
|
||||
"title": "审批任务(待办/已办)", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("instance_id", "流程实例", 32),
|
||||
V("node_id", "节点定义", 32),
|
||||
V("node_name", "节点名称", 64),
|
||||
V("approver_id", "审批人", 32),
|
||||
V("task_status", "任务状态(pending/approved/rejected/forwarded/cancelled)", 16, default="pending"),
|
||||
F("comment", "审批意见", "text"),
|
||||
F("handle_time", "处理时间", "timestamp"),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_approver_status", ["approver_id", "task_status"]), IDX("idx_instance", ["instance_id"])],
|
||||
},
|
||||
"flow_op_log": {
|
||||
"title": "流转日志", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("instance_id", "流程实例", 32),
|
||||
V("op_type", "操作类型(submit/approve/reject/forward/cancel/withdraw)", 16),
|
||||
V("operator_id", "操作人", 32),
|
||||
F("comment", "意见", "text"),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_instance", ["instance_id"])],
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------- hr-contract(4 张,前缀 contract_) ----------------
|
||||
CONTRACT = {
|
||||
"contract_type": {
|
||||
"title": "合同类型", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("type_code", "类型编码", 32, notnull=True),
|
||||
V("type_name", "类型名称(劳动合同/保密协议/竞业协议…)", 64, notnull=True),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_type_code", ["type_code"], True)],
|
||||
},
|
||||
"contract_template": {
|
||||
"title": "合同模板", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("template_name", "模板名称", 128, notnull=True),
|
||||
V("type_id", "合同类型", 32),
|
||||
V("file_path", "模板文件(文件ID)", 256),
|
||||
F("field_marks_json", "占位符映射", "text"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [],
|
||||
},
|
||||
"contract_info": {
|
||||
"title": "合同台账", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("contract_no", "合同编号", 64, notnull=True),
|
||||
V("employee_id", "员工档案", 32),
|
||||
V("type_id", "合同类型", 32),
|
||||
V("company_id", "签约公司(org_contract_company)", 32),
|
||||
V("template_id", "合同模板", 32),
|
||||
F("start_date", "合同起始日", "date"),
|
||||
F("end_date", "合同到期日", "date"),
|
||||
F("sign_date", "签订日期", "date"),
|
||||
V("contract_status", "合同状态(字典CONTRACT_STATUS)", 16, default="active"),
|
||||
V("file_path", "合同文件(文件ID)", 256),
|
||||
V("text_file_path", "模板生成合同文本(D4)", 256),
|
||||
V("flow_instance_id", "流程实例", 32),
|
||||
V("remind_rule_id", "到期提醒规则", 32),
|
||||
V("esign_status", "电子签状态(字典ESIGN_STATUS,D4预留)", 16, default="not_enabled"),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_employee", ["employee_id"]), IDX("idx_end_date", ["end_date"]), IDX("idx_status", ["contract_status"])],
|
||||
"codes": [CODE("contract_status", "CONTRACT_STATUS"), CODE("esign_status", "ESIGN_STATUS")],
|
||||
},
|
||||
"contract_remind_rule": {
|
||||
"title": "合同到期提醒规则", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("rule_name", "规则名称", 64, notnull=True),
|
||||
F("days_before", "提前天数", "int", default=30),
|
||||
V("target_role", "提醒对象(self/leader/hr)", 16, default="hr"),
|
||||
F("content_template", "提醒内容模板", "text"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [],
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------- hr-system(8 张,前缀 sys_) ----------------
|
||||
SYS = {
|
||||
"sys_data_scope": {
|
||||
"title": "数据范围(F11)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("admin_user_id", "管理员用户", 32),
|
||||
V("scope_type", "范围类型(org/field)", 16),
|
||||
F("org_ids_json", "组织维度(含子树)", "text"),
|
||||
F("field_ids_json", "花名册字段维度", "text"),
|
||||
F("remark", "备注", "text"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_admin_user", ["admin_user_id"])],
|
||||
},
|
||||
"sys_audit_log": {
|
||||
"title": "操作日志(只增,F12)", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("module", "模块(hr-org/hr-roster/...)", 32, notnull=True),
|
||||
V("target_type", "对象类型(roster_employee/org_unit/...)", 32, notnull=True),
|
||||
V("target_id", "对象ID", 32, notnull=True),
|
||||
V("operation", "操作类型(字典AUDIT_OP)", 16, notnull=True),
|
||||
V("operator_id", "操作人", 32, notnull=True),
|
||||
V("operator_name", "操作人姓名", 64, default=""),
|
||||
F("before_json", "变更前快照", "text"),
|
||||
F("after_json", "变更后快照", "text"),
|
||||
V("request_ip", "请求IP", 64),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_target", ["target_type", "target_id"]), IDX("idx_operator", ["operator_id"]),
|
||||
IDX("idx_created_at", ["created_at"]), IDX("idx_module_op", ["module", "operation"])],
|
||||
"codes": [CODE("operation", "AUDIT_OP")],
|
||||
},
|
||||
"sys_message": {
|
||||
"title": "站内消息(F15)", "log": True, "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("receiver_id", "接收人", 32),
|
||||
V("msg_type", "消息类型(todo/approval/remind/care/notice)", 16),
|
||||
V("title", "标题", 256),
|
||||
F("content", "内容", "text"),
|
||||
V("biz_type", "业务类型", 32),
|
||||
V("biz_id", "业务ID", 32),
|
||||
V("send_channel", "发送渠道(字典MSG_CHANNEL,一期仅site;D9)", 16, default="site"),
|
||||
F("is_read", "是否已读(0/1)", "int", default=0),
|
||||
F("created_at", "创建时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("idx_receiver_read", ["receiver_id", "is_read"]), IDX("idx_created_at", ["created_at"])],
|
||||
"codes": [CODE("send_channel", "MSG_CHANNEL")],
|
||||
},
|
||||
"sys_remind_rule": {
|
||||
"title": "通用提醒规则(F15)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("scene_code", "提醒场景(字典REMIND_SCENE,≥6类)", 32, notnull=True),
|
||||
V("rule_name", "规则名称", 64, notnull=True),
|
||||
F("days_before", "提前天数", "int", default=7),
|
||||
F("content_template", "提醒内容模板", "text"),
|
||||
V("target_role", "提醒对象(admin/employee/leader)", 16, default="admin"),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_scene_rule", ["scene_code", "rule_name"], True)],
|
||||
"codes": [CODE("scene_code", "REMIND_SCENE")],
|
||||
},
|
||||
"sys_announcement": {
|
||||
"title": "企业公告(F15)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("title", "标题", 256, notnull=True),
|
||||
F("content", "内容", "text"),
|
||||
F("publish_scope_json", "发布范围", "text"),
|
||||
F("publish_time", "发布时间", "timestamp"),
|
||||
V("status", "状态(draft/published/offline)", 16, default="draft"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_status", ["status"])],
|
||||
},
|
||||
"sys_policy": {
|
||||
"title": "企业政策(F15)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("title", "标题", 256, notnull=True),
|
||||
V("file_path", "政策文件(文件ID)", 256),
|
||||
F("view_scope_json", "查阅范围", "text"),
|
||||
F("downloadable", "允许下载(0/1)", "int", default=0),
|
||||
F("publish_time", "发布时间", "timestamp"),
|
||||
V("status", "状态(draft/published/offline)", 16, default="draft"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("idx_status", ["status"])],
|
||||
},
|
||||
"sys_care_config": {
|
||||
"title": "关怀配置(生日/周年,F15)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("care_type", "关怀类型(birthday/anniversary)", 16, notnull=True),
|
||||
F("template", "文案模板", "text"),
|
||||
F("enable", "启用(0/1)", "int", default=1),
|
||||
V("push_target", "推送对象(employee/leader)", 16, default="employee"),
|
||||
V("updated_by", "更新人", 32),
|
||||
F("updated_at", "更新时间", "timestamp"),
|
||||
],
|
||||
"indexes": [IDX("uk_care_type", ["care_type"], True)],
|
||||
},
|
||||
"sys_openapp": {
|
||||
"title": "API开放应用(一期预留,FEAT-B1-01⑦)", "fields": [
|
||||
V("id", "主键", 32, notnull=True),
|
||||
V("app_key", "应用Key", 64, notnull=True),
|
||||
V("app_secret_cipher", "应用密钥密文", 256),
|
||||
V("app_name", "应用名称", 128),
|
||||
F("scope_json", "授权资源范围", "text"),
|
||||
F("rate_limit", "限流(req/s,默认10)", "int", default=10),
|
||||
V("status", "状态 active/inactive", 16, notnull=True, default="active"),
|
||||
] + AUDIT(),
|
||||
"indexes": [IDX("uk_app_key", ["app_key"], True)],
|
||||
},
|
||||
}
|
||||
|
||||
MODULES = [("hr_org", ORG), ("hr_roster", ROSTER), ("hr_flow", FLOW), ("hr_contract", CONTRACT), ("hr_system", SYS)]
|
||||
|
||||
def main():
|
||||
root = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(__file__), "..", "models")
|
||||
total = 0
|
||||
for mod, tables in MODULES:
|
||||
mdir = os.path.join(root, mod)
|
||||
os.makedirs(mdir, exist_ok=True)
|
||||
for tname, spec in tables.items():
|
||||
doc = {
|
||||
"summary": [{"name": tname, "title": spec["title"], "pk": ["id"],
|
||||
"charset": "utf8mb4", "collate": "utf8mb4_unicode_ci"}],
|
||||
"fields": spec["fields"],
|
||||
"indexes": spec.get("indexes", []),
|
||||
"codes": spec.get("codes", []),
|
||||
}
|
||||
path = os.path.join(mdir, tname + ".json")
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(doc, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
total += 1
|
||||
print(f"{mod}: {len(tables)} 张 -> {mdir}")
|
||||
print(f"共生成 models JSON: {total} 张表")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
89
scripts/json2ddl.py
Normal file
89
scripts/json2ddl.py
Normal file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""json2ddl 等价实现(ocai database-table-definition-spec):
|
||||
读取 models/{module}/*.json(summary/fields/indexes/codes),生成幂等 MySQL/MariaDB DDL。
|
||||
幂等性:CREATE TABLE IF NOT EXISTS;索引先查 information_schema 再按需补建(可重跑)。
|
||||
用法:python3 scripts/json2ddl.py <models根目录> [输出sql文件]
|
||||
"""
|
||||
import json, os, sys, glob
|
||||
|
||||
TYPE_MAP = {
|
||||
"varchar": lambda f: f"VARCHAR({f.get('length', 64)})",
|
||||
"text": lambda f: "TEXT",
|
||||
"int": lambda f: "INT",
|
||||
"date": lambda f: "DATE",
|
||||
"timestamp": lambda f: "TIMESTAMP NULL DEFAULT NULL",
|
||||
"decimal": lambda f: f"DECIMAL({f.get('precision', 18)},{f.get('scale', 4)})",
|
||||
}
|
||||
|
||||
def render_field(f):
|
||||
t = f["type"]
|
||||
if t not in TYPE_MAP:
|
||||
raise ValueError(f"未知字段类型 {t} ({f.get('name')})")
|
||||
col = f"`{f['name']}` {TYPE_MAP[t](f)}"
|
||||
if f.get("notnull"):
|
||||
col += " NOT NULL"
|
||||
if "default" in f and t != "timestamp":
|
||||
dv = f["default"]
|
||||
col += f" DEFAULT '{dv}'" if isinstance(dv, str) else f" DEFAULT {dv}"
|
||||
col += f" COMMENT '{f['title']}'"
|
||||
return col
|
||||
|
||||
def render_index_sql(table, idx):
|
||||
kind = "UNIQUE KEY" if idx.get("unique") else "KEY"
|
||||
cols = ",".join(f"`{c}`" for c in idx["fields"])
|
||||
return f" {kind} `{idx['name']}` ({cols})"
|
||||
|
||||
def gen_table_ddl(path):
|
||||
doc = json.load(open(path, encoding="utf-8"))
|
||||
s = doc["summary"][0]
|
||||
table = s["name"]
|
||||
lines = [render_field(f) for f in doc["fields"]]
|
||||
pk = ",".join(f"`{c}`" for c in s.get("pk", ["id"]))
|
||||
lines.append(f" PRIMARY KEY ({pk})")
|
||||
for idx in doc.get("indexes", []):
|
||||
lines.append(render_index_sql(table, idx))
|
||||
charset = s.get("charset", "utf8mb4")
|
||||
collate = s.get("collate", "utf8mb4_unicode_ci")
|
||||
title = s.get("title", table)
|
||||
return table, (f"CREATE TABLE IF NOT EXISTS `{table}` (\n" + ",\n".join(lines) +
|
||||
f"\n) ENGINE=InnoDB DEFAULT CHARSET={charset} COLLATE={collate} COMMENT='{title}';")
|
||||
|
||||
def gen_index_guard_sql(table, idx):
|
||||
"""运行时补索引(建表时已含;此处供重跑补漏,apply_ddl.py 使用)"""
|
||||
kind = "UNIQUE INDEX" if idx.get("unique") else "INDEX"
|
||||
cols = ",".join(f"`{c}`" for c in idx["fields"])
|
||||
return f"ALTER TABLE `{table}` ADD {kind} `{idx['name']}` ({cols});"
|
||||
|
||||
def main():
|
||||
src = sys.argv[1] if len(sys.argv) > 1 else "models"
|
||||
out = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
parts = ["-- 人事系统 hr-web 批次1 DDL(json2ddl 生成,幂等可重跑)",
|
||||
"-- 来源:models/*.json(database-design.md v3.0,49 表);目标库:hrs(MariaDB)",
|
||||
"-- 执行:mysql -utest -ptest123 hrs < mysql.ddl.sql 或 python3 scripts/apply_ddl.py",
|
||||
"SET NAMES utf8mb4;", ""]
|
||||
n = 0
|
||||
for moddir in sorted(glob.glob(os.path.join(src, "*"))):
|
||||
if not os.path.isdir(moddir):
|
||||
continue
|
||||
files = sorted(glob.glob(os.path.join(moddir, "*.json")))
|
||||
if not files:
|
||||
continue
|
||||
parts.append(f"-- ===== {os.path.basename(moddir)}({len(files)} 张)=====")
|
||||
for p in files:
|
||||
table, ddl = gen_table_ddl(p)
|
||||
parts.append(f"-- {table}")
|
||||
parts.append(ddl)
|
||||
parts.append("")
|
||||
n += 1
|
||||
parts.append(f"-- 共 {n} 张表")
|
||||
sql = "\n".join(parts) + "\n"
|
||||
if out:
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
f.write(sql)
|
||||
print(f"生成 {out}:{n} 张表")
|
||||
else:
|
||||
sys.stdout.write(sql)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user