按 2026-11-02 需求补齐计划交付后端能力: - 角色授权:权限目录化,接口统一准入校验,未登记默认拒绝; 管理员判断改为按数据库角色重新判定,防止权限码持有者绕过 - 系统监控:单进程采集资源、请求与异常指标,分钟级聚合, 阈值配置化;敏感配置增加 repr 保护,Redis 密码 URL 转义 - 试用期计提:按回款日判定并写入快照,年度结清与调整单联动; 人员模型补试用期、离职日期与工资版本历史字段 - 计提模板:公式骨架受控,页面维护名称与参数绑定 - T+ 采集:自动化配置表与进度查询 内置角色禁停用保护对齐模块注释;核算规则版本提为常量并 补充批量核算失败堆栈日志。 影响范围:后端全部模块、数据库迁移 V22-V28 验证:uv run ruff check 通过;922 个单元测试全部通过; 集成测试因本机 MySQL 数据卷被其他容器锁定暂未运行
48 lines
17 KiB
Python
48 lines
17 KiB
Python
"""V22 完整角色及个人授权;目录与模板固化于此版本。"""
|
||
from alembic import op
|
||
import sqlalchemy as sa
|
||
|
||
revision: str = "22a3b4c5d6e7"
|
||
down_revision: str | None = "21f2a3b4c5d6"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
DDL = ["CREATE TABLE authorization_settings (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '单例主键ID' AUTO_INCREMENT, \n\tmaker_user_id BIGINT UNSIGNED COMMENT '现任工资制作人账号ID', \n\trevision INTEGER NOT NULL COMMENT '全局授权版本' DEFAULT '1', \n\tupdated_by BIGINT UNSIGNED COMMENT '最近修改人ID', \n\tcreate_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n\tupdate_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n\tCONSTRAINT pk_authorization_settings PRIMARY KEY (id), \n\tCONSTRAINT fk_authorization_settings_maker_user_id FOREIGN KEY(maker_user_id) REFERENCES users (id) ON DELETE RESTRICT, \n\tCONSTRAINT fk_authorization_settings_updated_by FOREIGN KEY(updated_by) REFERENCES users (id) ON DELETE SET NULL\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='授权设置表(单例与并发修改锁)' COLLATE utf8mb4_general_ci;", "CREATE TABLE user_permission_overrides (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n\tuser_id BIGINT UNSIGNED NOT NULL COMMENT '系统用户ID', \n\tpermission_id BIGINT UNSIGNED NOT NULL COMMENT '权限ID', \n\teffect VARCHAR(8) NOT NULL COMMENT '个人覆盖:ALLOW允许 DENY禁止', \n\tcreate_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n\tupdate_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n\tCONSTRAINT pk_user_permission_overrides PRIMARY KEY (id), \n\tCONSTRAINT uk_user_permission_overrides_user_permission UNIQUE (user_id, permission_id), \n\tCONSTRAINT fk_user_permission_overrides_user_id FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE, \n\tCONSTRAINT fk_user_permission_overrides_permission_id FOREIGN KEY(permission_id) REFERENCES permissions (id) ON DELETE RESTRICT\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='用户权限覆盖表(个人禁止优先)' COLLATE utf8mb4_general_ci;", 'CREATE INDEX idx_user_permission_overrides_permission_id ON user_permission_overrides (permission_id);']
|
||
PERMISSIONS = [('dashboard:view', '首页:查看', 1, None, 1), ('system:users:view', '用户管理:查看', 1, None, 2), ('system:users:create', '用户管理:新增', 2, 'system:users:view', 3), ('system:users:update', '用户管理:编辑', 2, 'system:users:view', 4), ('system:users:delete', '用户管理:删除', 2, 'system:users:view', 5), ('system:users:status', '用户管理:变更状态', 2, 'system:users:view', 6), ('system:users:password', '用户管理:重置密码', 2, 'system:users:view', 7), ('system:roles:view', '角色管理:查看', 1, None, 8), ('system:roles:create', '角色管理:新增', 2, 'system:roles:view', 9), ('system:roles:update', '角色管理:编辑', 2, 'system:roles:view', 10), ('system:roles:delete', '角色管理:删除', 2, 'system:roles:view', 11), ('system:roles:status', '角色管理:变更状态', 2, 'system:roles:view', 12), ('system:authorization:view', '权限与工资制作人:查看', 1, None, 13), ('system:authorization:update', '权限与工资制作人:编辑', 2, 'system:authorization:view', 14), ('system:business_roles:view', '岗位角色:查看', 1, None, 15), ('system:business_roles:create', '岗位角色:新增', 2, 'system:business_roles:view', 16), ('system:business_roles:update', '岗位角色:编辑', 2, 'system:business_roles:view', 17), ('system:business_roles:delete', '岗位角色:删除', 2, 'system:business_roles:view', 18), ('system:business_roles:status', '岗位角色:变更状态', 2, 'system:business_roles:view', 19), ('system:params:view', '计提参数:查看', 1, None, 20), ('system:params:update', '计提参数:编辑', 2, 'system:params:view', 21), ('system:params:withdraw', '计提参数:撤回参数', 2, 'system:params:view', 22), ('system:params:export', '计提参数:导出', 2, 'system:params:view', 23), ('system:params:preview', '计提参数:预览', 2, 'system:params:view', 24), ('system:settings:view', '公司信息:查看', 1, None, 25), ('system:settings:update', '公司信息:编辑', 2, 'system:settings:view', 26), ('system:logs:view', '日志查询:查看', 1, None, 27), ('base:personnel:view', '人员管理:查看', 1, None, 28), ('base:personnel:create', '人员管理:新增', 2, 'base:personnel:view', 29), ('base:personnel:update', '人员管理:编辑', 2, 'base:personnel:view', 30), ('base:personnel:delete', '人员管理:删除', 2, 'base:personnel:view', 31), ('base:personnel:status', '人员管理:变更状态', 2, 'base:personnel:view', 32), ('base:personnel:regions', '人员管理:维护区域', 2, 'base:personnel:view', 33), ('base:personnel:plans', '人员管理:维护计划', 2, 'base:personnel:view', 34), ('base:products:view', '产品管理:查看', 1, None, 35), ('base:products:create', '产品管理:新增', 2, 'base:products:view', 36), ('base:products:update', '产品管理:编辑', 2, 'base:products:view', 37), ('base:products:delete', '产品管理:删除', 2, 'base:products:view', 38), ('base:products:status', '产品管理:变更状态', 2, 'base:products:view', 39), ('base:customers:view', '客户管理:查看', 1, None, 40), ('base:customers:create', '客户管理:新增', 2, 'base:customers:view', 41), ('base:customers:update', '客户管理:编辑', 2, 'base:customers:view', 42), ('base:customers:delete', '客户管理:删除', 2, 'base:customers:view', 43), ('base:customers:rating', '客户管理:客户评级', 2, 'base:customers:view', 44), ('base:customers:ownership', '客户管理:变更归属', 2, 'base:customers:view', 45), ('business:businesses:view', '业务管理:查看', 1, None, 46), ('business:businesses:create', '业务管理:新增', 2, 'business:businesses:view', 47), ('business:businesses:update', '业务管理:编辑', 2, 'business:businesses:view', 48), ('business:businesses:delete', '业务管理:删除', 2, 'business:businesses:view', 49), ('business:businesses:status', '业务管理:变更状态', 2, 'business:businesses:view', 50), ('business:businesses:ownership', '业务管理:变更归属', 2, 'business:businesses:view', 51), ('business:shipments:view', '发货管理:查看', 1, None, 52), ('business:shipments:create', '发货管理:新增', 2, 'business:shipments:view', 53), ('business:shipments:update', '发货管理:编辑', 2, 'business:shipments:view', 54), ('business:shipments:delete', '发货管理:删除', 2, 'business:shipments:view', 55), ('business:receipts:view', '回款管理:查看', 1, None, 56), ('business:receipts:create', '回款管理:新增', 2, 'business:receipts:view', 57), ('business:receipts:update', '回款管理:编辑', 2, 'business:receipts:view', 58), ('business:receipts:delete', '回款管理:删除', 2, 'business:receipts:view', 59), ('business:ratings:view', '评级标准:查看', 1, None, 60), ('business:ratings:create', '评级标准:新增', 2, 'business:ratings:view', 61), ('business:ratings:update', '评级标准:编辑', 2, 'business:ratings:view', 62), ('business:ratings:delete', '评级标准:删除', 2, 'business:ratings:view', 63), ('tplus:view', '采集与导入:查看', 1, None, 64), ('tplus:collect', '采集与导入:创建采集', 2, 'tplus:view', 65), ('tplus:retry', '采集与导入:重试采集', 2, 'tplus:view', 66), ('tplus:preview', '采集与导入:预览', 2, 'tplus:view', 67), ('tplus:import', '采集与导入:执行导入', 2, 'tplus:view', 68), ('tplus:facts', '采集与导入:维护补充资料', 2, 'tplus:view', 69), ('tplus:export', '采集与导入:导出', 2, 'tplus:view', 70), ('payroll:view', '正式核算与分配:查看', 1, None, 71), ('payroll:prepare', '正式核算与分配:准备核算资料', 2, 'payroll:view', 72), ('payroll:calculate', '正式核算与分配:核算与重算', 2, 'payroll:view', 73), ('payroll:allocation', '正式核算与分配:修改工资分配', 2, 'payroll:view', 74), ('settlements:view', '月度工资:查看', 1, None, 75), ('settlements:generate', '月度工资:生成工资', 2, 'settlements:view', 76), ('settlements:submit', '月度工资:提交审核', 2, 'settlements:view', 77), ('adjustments:view', '工资调整:查看', 1, None, 78), ('adjustments:create', '工资调整:新增', 2, 'adjustments:view', 79), ('approvals:view', '审核记录:查看', 1, None, 80), ('approvals:finance', '审核记录:财务复核', 2, 'approvals:view', 81), ('approvals:leader', '审核记录:领导审核', 2, 'approvals:view', 82), ('reports:business:view', '业务统计:查看', 1, None, 83), ('reports:business:export', '业务统计:导出', 2, 'reports:business:view', 84), ('reports:personal:view', '个人明细:查看', 1, None, 85), ('reports:personal:export', '个人明细:导出', 2, 'reports:personal:view', 86), ('reports:department:view', '部门汇总:查看', 1, None, 87), ('reports:department:export', '部门汇总:导出', 2, 'reports:department:view', 88), ('reports:payments:view', '回款统计:查看', 1, None, 89), ('reports:payments:export', '回款统计:导出', 2, 'reports:payments:view', 90)]
|
||
DEFAULTS = {'admin': ['adjustments:create', 'adjustments:view', 'approvals:finance', 'approvals:leader', 'approvals:view', 'base:customers:create', 'base:customers:delete', 'base:customers:ownership', 'base:customers:rating', 'base:customers:update', 'base:customers:view', 'base:personnel:create', 'base:personnel:delete', 'base:personnel:plans', 'base:personnel:regions', 'base:personnel:status', 'base:personnel:update', 'base:personnel:view', 'base:products:create', 'base:products:delete', 'base:products:status', 'base:products:update', 'base:products:view', 'business:businesses:create', 'business:businesses:delete', 'business:businesses:ownership', 'business:businesses:status', 'business:businesses:update', 'business:businesses:view', 'business:ratings:create', 'business:ratings:delete', 'business:ratings:update', 'business:ratings:view', 'business:receipts:create', 'business:receipts:delete', 'business:receipts:update', 'business:receipts:view', 'business:shipments:create', 'business:shipments:delete', 'business:shipments:update', 'business:shipments:view', 'dashboard:view', 'payroll:allocation', 'payroll:calculate', 'payroll:prepare', 'payroll:view', 'reports:business:export', 'reports:business:view', 'reports:department:export', 'reports:department:view', 'reports:payments:export', 'reports:payments:view', 'reports:personal:export', 'reports:personal:view', 'settlements:generate', 'settlements:submit', 'settlements:view', 'system:authorization:update', 'system:authorization:view', 'system:business_roles:create', 'system:business_roles:delete', 'system:business_roles:status', 'system:business_roles:update', 'system:business_roles:view', 'system:logs:view', 'system:params:export', 'system:params:preview', 'system:params:update', 'system:params:view', 'system:params:withdraw', 'system:roles:create', 'system:roles:delete', 'system:roles:status', 'system:roles:update', 'system:roles:view', 'system:settings:update', 'system:settings:view', 'system:users:create', 'system:users:delete', 'system:users:password', 'system:users:status', 'system:users:update', 'system:users:view', 'tplus:collect', 'tplus:export', 'tplus:facts', 'tplus:import', 'tplus:preview', 'tplus:retry', 'tplus:view'], 'user': ['base:customers:view', 'base:personnel:view', 'base:products:view', 'business:businesses:view', 'business:ratings:view', 'business:receipts:view', 'business:shipments:view'], 'marketing_ops': ['base:customers:view', 'base:personnel:view', 'base:products:view', 'business:businesses:view', 'business:ratings:view', 'business:receipts:view', 'business:shipments:view'], 'finance': ['adjustments:view', 'approvals:finance', 'approvals:view', 'base:customers:view', 'base:personnel:view', 'base:products:view', 'business:businesses:view', 'business:ratings:view', 'business:receipts:view', 'business:shipments:view', 'dashboard:view', 'payroll:view', 'reports:business:export', 'reports:business:view', 'reports:department:export', 'reports:department:view', 'reports:payments:export', 'reports:payments:view', 'reports:personal:export', 'reports:personal:view', 'settlements:view', 'system:params:preview', 'system:params:view'], 'leader': ['adjustments:view', 'approvals:leader', 'approvals:view', 'base:customers:view', 'base:personnel:view', 'base:products:view', 'business:businesses:view', 'business:ratings:view', 'business:receipts:view', 'business:shipments:view', 'dashboard:view', 'payroll:view', 'reports:business:export', 'reports:business:view', 'reports:department:export', 'reports:department:view', 'reports:payments:export', 'reports:payments:view', 'reports:personal:export', 'reports:personal:view', 'settlements:view', 'system:params:preview', 'system:params:view'], 'payroll_maker': ['adjustments:create', 'adjustments:view', 'approvals:view', 'base:customers:create', 'base:customers:delete', 'base:customers:ownership', 'base:customers:rating', 'base:customers:update', 'base:customers:view', 'base:personnel:create', 'base:personnel:delete', 'base:personnel:plans', 'base:personnel:regions', 'base:personnel:status', 'base:personnel:update', 'base:personnel:view', 'base:products:create', 'base:products:delete', 'base:products:status', 'base:products:update', 'base:products:view', 'business:businesses:create', 'business:businesses:delete', 'business:businesses:ownership', 'business:businesses:status', 'business:businesses:update', 'business:businesses:view', 'business:ratings:create', 'business:ratings:delete', 'business:ratings:update', 'business:ratings:view', 'business:receipts:create', 'business:receipts:delete', 'business:receipts:update', 'business:receipts:view', 'business:shipments:create', 'business:shipments:delete', 'business:shipments:update', 'business:shipments:view', 'payroll:allocation', 'payroll:calculate', 'payroll:prepare', 'payroll:view', 'reports:business:view', 'reports:department:view', 'reports:payments:view', 'reports:personal:view', 'settlements:generate', 'settlements:submit', 'settlements:view', 'tplus:collect', 'tplus:export', 'tplus:facts', 'tplus:import', 'tplus:preview', 'tplus:retry', 'tplus:view']}
|
||
|
||
def upgrade():
|
||
bind=op.get_bind()
|
||
for sql in DDL:
|
||
op.execute(sql)
|
||
for code,name,kind,parent,order in PERMISSIONS:
|
||
bind.execute(sa.text("INSERT INTO permissions (perm_code,perm_name,perm_type,sort_order,status) VALUES (:code,:name,:kind,:order,1)"),dict(code=code,name=name,kind=kind,order=order))
|
||
for code,_,_,parent,_ in PERMISSIONS:
|
||
if parent:
|
||
bind.execute(sa.text("UPDATE permissions p JOIN permissions parent ON parent.perm_code=:parent SET p.parent_id=parent.id WHERE p.perm_code=:code"),dict(parent=parent,code=code))
|
||
bind.execute(sa.text("INSERT INTO roles (role_code,role_name,description,sort_order,status) VALUES ('payroll_maker','工资制作人','指定制作人还需在权限设置中绑定,角色本身不能绕过月份锁',5,1)"))
|
||
for role,codes in DEFAULTS.items():
|
||
for code in codes:
|
||
bind.execute(sa.text("INSERT INTO role_permissions (role_id,permission_id) SELECT r.id,p.id FROM roles r JOIN permissions p ON p.perm_code=:code WHERE r.role_code=:role"),dict(code=code,role=role))
|
||
from app.core.config import get_settings
|
||
maker=get_settings().payroll_maker_user_id
|
||
if maker is not None and not bind.execute(sa.text("SELECT id FROM users WHERE id=:id AND status=1"),dict(id=maker)).scalar():
|
||
raise RuntimeError("旧工资制作人账号无效,请修正配置后重试迁移")
|
||
bind.execute(sa.text("INSERT INTO authorization_settings (id,maker_user_id,revision) VALUES (1,:maker,1)"),dict(maker=maker))
|
||
if maker is not None:
|
||
bind.execute(sa.text("INSERT INTO user_roles (user_id,role_id) SELECT :maker,id FROM roles WHERE role_code='payroll_maker'"),dict(maker=maker))
|
||
|
||
def downgrade():
|
||
bind=op.get_bind()
|
||
if bind.execute(sa.text("SELECT revision FROM authorization_settings WHERE id=1")).scalar()!=1 or bind.execute(sa.text("SELECT COUNT(*) FROM user_permission_overrides")).scalar():
|
||
raise RuntimeError("已发生授权修改,拒绝删除授权依据;请保留数据并回退应用版本")
|
||
op.drop_table('user_permission_overrides')
|
||
op.drop_table('authorization_settings')
|
||
codes=[row[0] for row in PERMISSIONS]
|
||
for code in codes:
|
||
bind.execute(sa.text("DELETE rp FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE p.perm_code=:code"),dict(code=code))
|
||
bind.execute(sa.text("DELETE ur FROM user_roles ur JOIN roles r ON r.id=ur.role_id WHERE r.role_code='payroll_maker'"))
|
||
bind.execute(sa.text("DELETE FROM roles WHERE role_code='payroll_maker'"))
|
||
for code in reversed(codes):
|
||
bind.execute(sa.text("DELETE FROM permissions WHERE perm_code=:code"),dict(code=code))
|