119 lines
9.4 KiB
Python
119 lines
9.4 KiB
Python
"""Sépare le super administrateur du rôle administrateur configurable.
|
||
|
||
La migration ne supprime aucune donnée. Les comptes existants reçoivent
|
||
explicitement ``super_admin`` afin de garantir la continuité d'accès.
|
||
"""
|
||
from alembic import op
|
||
import sqlalchemy as sa
|
||
|
||
revision = "9f4a1b2c3d5e"
|
||
down_revision = "9e39f0a1b2c3"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
PERMISSIONS = {
|
||
"dashboard.view", "intervention.view", "intervention.create", "intervention.edit",
|
||
"intervention.assign", "intervention.change_status", "intervention.close", "intervention.reject",
|
||
"intervention.delete", "intervention.postpone", "intervention.comment", "patrimoine.view",
|
||
"patrimoine.create", "patrimoine.edit", "patrimoine.move", "patrimoine.change_quantity",
|
||
"patrimoine.retire", "patrimoine.delete", "planning.view", "planning.create", "planning.edit",
|
||
"planning.delete", "planning.manage", "stock.view", "stock.edit", "stock.receive", "stock.issue",
|
||
"stock.transfer", "stock.inventory", "stock.adjust", "stock.destroy", "stock.transvasement",
|
||
"stock.dilute", "stock.configure", "contract.view", "contract.manage", "prevention.view",
|
||
"prevention.manage", "export.use", "housing.private", "gmao_config.view", "gmao_config.configure",
|
||
"watchdog_dnd.view", "watchdog_dnd.configure", "integration.ent.view", "integration.ent.configure",
|
||
"integration.outlook.view", "integration.outlook.configure", "integration.pronote.view",
|
||
"integration.pronote.configure", "integration.yeastar.view", "integration.yeastar.configure",
|
||
"user.view", "user.create", "user.edit", "user.delete", "user.manage", "role.view", "role.create",
|
||
"role.edit", "role.archive", "role.manage", "audit.view", "audit.export", "system.view",
|
||
"system.configure", "system.admin",
|
||
}
|
||
|
||
LABELS = {
|
||
"dashboard.view": ("Consulter le tableau de bord", "Permet d'accéder au tableau de bord général de la GMAO."),
|
||
"intervention.view": ("Consulter les interventions", "Permet de consulter la liste et le détail des interventions."),
|
||
"intervention.create": ("Créer une intervention", "Permet d'enregistrer une nouvelle intervention dans la GMAO."),
|
||
"intervention.edit": ("Modifier une intervention", "Permet de modifier une intervention existante."),
|
||
"intervention.assign": ("Affecter une intervention", "Permet d'affecter une intervention à un agent ou une entreprise."),
|
||
"intervention.change_status": ("Changer le statut d'une intervention", "Permet de faire évoluer le statut d'une intervention selon son workflow."),
|
||
"intervention.close": ("Clôturer une intervention", "Permet de clôturer une intervention terminée et d'enregistrer son état final."),
|
||
"intervention.reject": ("Refuser une intervention", "Permet de refuser une demande en enregistrant un motif."),
|
||
"patrimoine.view": ("Consulter le patrimoine", "Permet de consulter les bâtiments, zones, locaux et équipements."),
|
||
"patrimoine.edit": ("Modifier le patrimoine", "Permet de modifier les informations des éléments du patrimoine."),
|
||
"patrimoine.move": ("Déplacer des équipements", "Permet de déplacer des équipements entre les locaux."),
|
||
"stock.view": ("Consulter les stocks", "Permet de consulter les produits, lots, quantités et emplacements."),
|
||
"stock.receive": ("Réceptionner des produits", "Permet d'enregistrer l'entrée en stock de produits et leurs lots."),
|
||
"stock.issue": ("Sortir des produits du stock", "Permet d'enregistrer une sortie de produits vers un agent ou pour utilisation."),
|
||
"stock.transfer": ("Transférer du stock", "Permet de déplacer une quantité entre emplacements."),
|
||
"stock.inventory": ("Réaliser un inventaire", "Permet d'enregistrer un comptage physique et de traiter les écarts."),
|
||
"stock.dilute": ("Préparer une dilution", "Permet d'enregistrer la préparation d'un produit dilué."),
|
||
"system.configure": ("Modifier la configuration générale", "Permet de modifier les paramètres généraux de l'application."),
|
||
"user.manage": ("Gérer les utilisateurs", "Permet de créer, modifier, désactiver et administrer les comptes."),
|
||
"role.manage": ("Gérer les rôles et permissions", "Permet de créer et modifier les rôles et leurs permissions."),
|
||
}
|
||
|
||
|
||
def _label(code):
|
||
if code in LABELS:
|
||
return LABELS[code]
|
||
module, _, action = code.partition(".")
|
||
return (f"{action.replace('_', ' ').capitalize()} — {module.replace('_', ' ').capitalize()}",
|
||
f"Permet d'effectuer l'action « {action.replace('_', ' ')} » dans le domaine concerné.")
|
||
|
||
|
||
def upgrade():
|
||
bind = op.get_bind()
|
||
bind.execute(sa.text("""INSERT INTO roles (slug,name,description,is_system,is_active,created_at,updated_at)
|
||
SELECT 'super_admin','Super administrateur','Rôle système protégé donnant un accès complet à l'application.',1,1,NOW(),NOW()
|
||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM roles WHERE slug='super_admin')"""))
|
||
bind.execute(sa.text("""UPDATE roles SET name='Super administrateur', description='Rôle système protégé donnant un accès complet à l'application.', is_system=1, is_active=1 WHERE slug='super_admin'"""))
|
||
bind.execute(sa.text("""UPDATE roles SET name='Administrateur', description='Rôle système destiné à l’administration courante de la GMAO. Ses permissions sont configurables.' WHERE slug='admin'"""))
|
||
role_text = {
|
||
'responsable_gmao': ('Responsable GMAO', 'Rôle système destiné à la supervision et à la gestion générale de la GMAO.'),
|
||
'technicien': ('Technicien', 'Rôle système destiné aux opérations de maintenance.'),
|
||
'assistant_prevention': ('Assistant de prévention', 'Rôle système donnant accès aux fonctions liées à la prévention.'),
|
||
'demandeur': ('Demandeur', 'Rôle système destiné aux utilisateurs pouvant créer et suivre des demandes.'),
|
||
'lecture': ('Lecture seule', 'Rôle système destiné aux utilisateurs autorisés uniquement à consulter certaines informations.'),
|
||
}
|
||
for slug, (name, description) in role_text.items():
|
||
bind.execute(sa.text("UPDATE roles SET name=:name, description=:description WHERE slug=:slug"), dict(slug=slug, name=name, description=description))
|
||
bind.execute(sa.text("""INSERT INTO permissions (code,name,module,action,description,is_active)
|
||
SELECT '*','Accès complet à l’application','system','all','Accès complet réservé au super administrateur.',1
|
||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM permissions WHERE code='*')"""))
|
||
bind.execute(sa.text("UPDATE permissions SET name='Accès complet à l’application', description='Accès complet réservé au super administrateur.', is_active=1 WHERE code='*'"))
|
||
for code in sorted(PERMISSIONS):
|
||
name, description = _label(code)
|
||
module, _, action = code.partition('.')
|
||
bind.execute(sa.text("""INSERT INTO permissions (code,name,module,action,description,is_active)
|
||
SELECT :code,:name,:module,:action,:description,1 FROM DUAL
|
||
WHERE NOT EXISTS (SELECT 1 FROM permissions WHERE code=:code)"""), dict(code=code, name=name, module=module, action=action, description=description))
|
||
bind.execute(sa.text("UPDATE permissions SET name=:name, description=:description, module=:module, action=:action, is_active=1 WHERE code=:code"), dict(code=code, name=name, module=module, action=action, description=description))
|
||
# Les permissions historiques conservées reçoivent elles aussi un
|
||
# intitulé humain, sans modifier leur code utilisé par le runtime.
|
||
for row in bind.execute(sa.text("SELECT code,module,action FROM permissions WHERE code <> '*'" )).all():
|
||
name, description = _label(row.code)
|
||
bind.execute(sa.text("UPDATE permissions SET name=:name, description=:description WHERE code=:code"), dict(code=row.code, name=name, description=description))
|
||
# Le wildcard ne doit plus appartenir à admin.
|
||
bind.execute(sa.text("DELETE rp FROM role_permissions rp JOIN roles r ON r.id=rp.role_id JOIN permissions p ON p.id=rp.permission_id WHERE r.slug='admin' AND p.code='*'"))
|
||
bind.execute(sa.text("""INSERT INTO role_permissions (role_id,permission_id,effect)
|
||
SELECT r.id,p.id,'allow' FROM roles r JOIN permissions p
|
||
WHERE r.slug='super_admin' AND p.code='*'
|
||
AND NOT EXISTS (SELECT 1 FROM role_permissions rp WHERE rp.role_id=r.id AND rp.permission_id=p.id)"""))
|
||
bind.execute(sa.text("""DELETE rp FROM role_permissions rp JOIN roles r ON r.id=rp.role_id
|
||
JOIN permissions p ON p.id=rp.permission_id
|
||
WHERE r.slug='admin' AND p.code IN ('intervention.manage','patrimoine.manage','stock.manage')"""))
|
||
# Admin reçoit les permissions applicatives explicites (dont les anciennes
|
||
# permissions conservées en base), sans le wildcard.
|
||
bind.execute(sa.text("""INSERT INTO role_permissions (role_id,permission_id,effect)
|
||
SELECT r.id,p.id,'allow' FROM roles r CROSS JOIN permissions p
|
||
WHERE r.slug='admin' AND p.code <> '*' AND p.is_active=1
|
||
AND NOT EXISTS (SELECT 1 FROM role_permissions rp WHERE rp.role_id=r.id AND rp.permission_id=p.id)"""))
|
||
# Continuité d'accès : tous les comptes existants reçoivent le rôle protégé.
|
||
bind.execute(sa.text("""INSERT INTO user_roles (user_id,role_id,assigned_at)
|
||
SELECT u.id,r.id,NOW() FROM users u CROSS JOIN roles r WHERE r.slug='super_admin'
|
||
AND NOT EXISTS (SELECT 1 FROM user_roles ur WHERE ur.user_id=u.id AND ur.role_id=r.id)"""))
|
||
|
||
|
||
def downgrade():
|
||
# Aucun lien utilisateur ni permission ne doit être supprimé par rollback.
|
||
pass
|