Structure le suivi de la prévention et du DUERP
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
This commit is contained in:
parent
b6c94b74d7
commit
ed6675b9d3
10 changed files with 187 additions and 1 deletions
|
|
@ -136,6 +136,8 @@ def create_app(config_name='default'):
|
||||||
|
|
||||||
from .housing import housing_bp
|
from .housing import housing_bp
|
||||||
app.register_blueprint(housing_bp, url_prefix='/housing')
|
app.register_blueprint(housing_bp, url_prefix='/housing')
|
||||||
|
from .prevention import prevention_bp
|
||||||
|
app.register_blueprint(prevention_bp, url_prefix='/prevention')
|
||||||
|
|
||||||
# Blueprints des intégrations
|
# Blueprints des intégrations
|
||||||
from .pronote.routes import pronote_bp
|
from .pronote.routes import pronote_bp
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ PATRIMOINE_BLUEPRINTS = {
|
||||||
PLANNING_BLUEPRINTS = {"planning", "scheduler", "interventions_planning"}
|
PLANNING_BLUEPRINTS = {"planning", "scheduler", "interventions_planning"}
|
||||||
STOCK_BLUEPRINTS = {"parts", "meters"}
|
STOCK_BLUEPRINTS = {"parts", "meters"}
|
||||||
CONTRACT_BLUEPRINTS = {"companies", "contracts", "services"}
|
CONTRACT_BLUEPRINTS = {"companies", "contracts", "services"}
|
||||||
PREVENTION_BLUEPRINTS = {"trainings", "constraints"}
|
PREVENTION_BLUEPRINTS = {"trainings", "constraints", "prevention"}
|
||||||
|
|
||||||
|
|
||||||
def canonical_role(role):
|
def canonical_role(role):
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from .planning import (
|
||||||
)
|
)
|
||||||
from .settings import AppSettings
|
from .settings import AppSettings
|
||||||
from .audit import AuditLog
|
from .audit import AuditLog
|
||||||
|
from .prevention import PreventionWorkLog, StaffAuthorization, RiskAssessment, PreventionAction, SafetyRegisterEntry
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'User', 'Staff',
|
'User', 'Staff',
|
||||||
|
|
@ -31,4 +32,5 @@ __all__ = [
|
||||||
'PreventiveTask', 'PreventiveTaskConsumable', 'ScheduledTask',
|
'PreventiveTask', 'PreventiveTaskConsumable', 'ScheduledTask',
|
||||||
'TechnicianAvailability', 'AdminTask', 'ZoneAccessRule',
|
'TechnicianAvailability', 'AdminTask', 'ZoneAccessRule',
|
||||||
'AppSettings', 'AuditLog',
|
'AppSettings', 'AuditLog',
|
||||||
|
'PreventionWorkLog', 'StaffAuthorization', 'RiskAssessment', 'PreventionAction', 'SafetyRegisterEntry',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
69
app_new/core/models/prevention.py
Normal file
69
app_new/core/models/prevention.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
"""Registre de prévention, DUERP et suivi du temps dédié."""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from app_new.extensions import db
|
||||||
|
|
||||||
|
|
||||||
|
class PreventionWorkLog(db.Model):
|
||||||
|
__tablename__ = "prevention_work_logs"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
|
||||||
|
work_date = db.Column(db.Date, nullable=False, index=True)
|
||||||
|
duration_minutes = db.Column(db.Integer, nullable=False)
|
||||||
|
activity = db.Column(db.String(255), nullable=False)
|
||||||
|
notes = db.Column(db.Text)
|
||||||
|
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
user = db.relationship("User")
|
||||||
|
|
||||||
|
|
||||||
|
class StaffAuthorization(db.Model):
|
||||||
|
__tablename__ = "staff_authorizations"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
staff_id = db.Column(db.Integer, db.ForeignKey("staff.id"), nullable=False, index=True)
|
||||||
|
name = db.Column(db.String(160), nullable=False)
|
||||||
|
issued_on = db.Column(db.Date)
|
||||||
|
expires_on = db.Column(db.Date, index=True)
|
||||||
|
issuer = db.Column(db.String(160))
|
||||||
|
notes = db.Column(db.Text)
|
||||||
|
staff = db.relationship("Staff", backref=db.backref("authorizations", cascade="all, delete-orphan"))
|
||||||
|
|
||||||
|
|
||||||
|
class RiskAssessment(db.Model):
|
||||||
|
__tablename__ = "risk_assessments"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
work_unit = db.Column(db.String(160), nullable=False)
|
||||||
|
hazard = db.Column(db.String(255), nullable=False)
|
||||||
|
exposed_people = db.Column(db.String(255))
|
||||||
|
severity = db.Column(db.Integer, nullable=False, default=1)
|
||||||
|
probability = db.Column(db.Integer, nullable=False, default=1)
|
||||||
|
control_measures = db.Column(db.Text)
|
||||||
|
reviewed_on = db.Column(db.Date)
|
||||||
|
is_active = db.Column(db.Boolean, nullable=False, default=True)
|
||||||
|
@property
|
||||||
|
def score(self): return self.severity * self.probability
|
||||||
|
|
||||||
|
|
||||||
|
class PreventionAction(db.Model):
|
||||||
|
__tablename__ = "prevention_actions"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
title = db.Column(db.String(255), nullable=False)
|
||||||
|
source_type = db.Column(db.String(40), nullable=False, default="observation")
|
||||||
|
risk_id = db.Column(db.Integer, db.ForeignKey("risk_assessments.id"))
|
||||||
|
priority = db.Column(db.String(20), nullable=False, default="normale")
|
||||||
|
owner = db.Column(db.String(160))
|
||||||
|
due_date = db.Column(db.Date, index=True)
|
||||||
|
status = db.Column(db.String(30), nullable=False, default="a_faire")
|
||||||
|
evidence = db.Column(db.Text)
|
||||||
|
risk = db.relationship("RiskAssessment", backref="actions")
|
||||||
|
|
||||||
|
|
||||||
|
class SafetyRegisterEntry(db.Model):
|
||||||
|
__tablename__ = "safety_register_entries"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
entry_date = db.Column(db.Date, nullable=False, index=True)
|
||||||
|
entry_type = db.Column(db.String(50), nullable=False)
|
||||||
|
title = db.Column(db.String(255), nullable=False)
|
||||||
|
details = db.Column(db.Text)
|
||||||
|
location = db.Column(db.String(255))
|
||||||
|
follow_up = db.Column(db.Text)
|
||||||
|
created_by_id = db.Column(db.Integer, db.ForeignKey("users.id"))
|
||||||
|
created_by = db.relationship("User")
|
||||||
2
app_new/prevention/__init__.py
Normal file
2
app_new/prevention/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .routes import prevention_bp
|
||||||
|
__all__ = ["prevention_bp"]
|
||||||
54
app_new/prevention/routes.py
Normal file
54
app_new/prevention/routes.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from sqlalchemy import func
|
||||||
|
from app_new.extensions import db
|
||||||
|
from app_new.core.models.user import Staff
|
||||||
|
from app_new.core.models.prevention import PreventionWorkLog, StaffAuthorization, RiskAssessment, PreventionAction, SafetyRegisterEntry
|
||||||
|
|
||||||
|
prevention_bp = Blueprint("prevention", __name__)
|
||||||
|
def _date(value): return datetime.strptime(value, "%Y-%m-%d").date() if value else None
|
||||||
|
|
||||||
|
@prevention_bp.route("/")
|
||||||
|
@login_required
|
||||||
|
def index():
|
||||||
|
monday = date.today() - timedelta(days=date.today().weekday())
|
||||||
|
minutes = db.session.query(func.coalesce(func.sum(PreventionWorkLog.duration_minutes), 0)).filter(PreventionWorkLog.user_id == current_user.id, PreventionWorkLog.work_date >= monday).scalar()
|
||||||
|
target = 210 # 10 % d'une semaine de 35 h, paramétrable à l'étape suivante.
|
||||||
|
return render_template("prevention/index.html", staff=Staff.query.filter_by(is_active=True).order_by(Staff.last_name).all(), logs=PreventionWorkLog.query.order_by(PreventionWorkLog.work_date.desc()).limit(20).all(), risks=RiskAssessment.query.filter_by(is_active=True).all(), actions=PreventionAction.query.order_by(PreventionAction.due_date).all(), entries=SafetyRegisterEntry.query.order_by(SafetyRegisterEntry.entry_date.desc()).limit(20).all(), minutes=minutes, target=target)
|
||||||
|
|
||||||
|
@prevention_bp.post("/staff")
|
||||||
|
@login_required
|
||||||
|
def add_staff():
|
||||||
|
db.session.add(Staff(first_name=request.form.get("first_name", "").strip(), last_name=request.form.get("last_name", "").strip(), function=request.form.get("function")))
|
||||||
|
db.session.commit(); flash("Agent ajouté.", "success"); return redirect(url_for("prevention.index"))
|
||||||
|
|
||||||
|
@prevention_bp.post("/time")
|
||||||
|
@login_required
|
||||||
|
def add_time():
|
||||||
|
db.session.add(PreventionWorkLog(user_id=current_user.id, work_date=_date(request.form.get("work_date")) or date.today(), duration_minutes=max(1, request.form.get("duration_minutes", type=int) or 1), activity=request.form.get("activity", "").strip(), notes=request.form.get("notes")))
|
||||||
|
db.session.commit(); flash("Temps de prévention enregistré.", "success"); return redirect(url_for("prevention.index"))
|
||||||
|
|
||||||
|
@prevention_bp.post("/authorization")
|
||||||
|
@login_required
|
||||||
|
def add_authorization():
|
||||||
|
db.session.add(StaffAuthorization(staff_id=request.form.get("staff_id", type=int), name=request.form.get("name", "").strip(), issued_on=_date(request.form.get("issued_on")), expires_on=_date(request.form.get("expires_on")), issuer=request.form.get("issuer")))
|
||||||
|
db.session.commit(); flash("Habilitation enregistrée.", "success"); return redirect(url_for("prevention.index"))
|
||||||
|
|
||||||
|
@prevention_bp.post("/risk")
|
||||||
|
@login_required
|
||||||
|
def add_risk():
|
||||||
|
db.session.add(RiskAssessment(work_unit=request.form.get("work_unit", "").strip(), hazard=request.form.get("hazard", "").strip(), exposed_people=request.form.get("exposed_people"), severity=request.form.get("severity", type=int) or 1, probability=request.form.get("probability", type=int) or 1, control_measures=request.form.get("control_measures"), reviewed_on=date.today()))
|
||||||
|
db.session.commit(); flash("Risque ajouté au DUERP.", "success"); return redirect(url_for("prevention.index"))
|
||||||
|
|
||||||
|
@prevention_bp.post("/action")
|
||||||
|
@login_required
|
||||||
|
def add_action():
|
||||||
|
db.session.add(PreventionAction(title=request.form.get("title", "").strip(), source_type=request.form.get("source_type", "observation"), risk_id=request.form.get("risk_id", type=int), priority=request.form.get("priority", "normale"), owner=request.form.get("owner"), due_date=_date(request.form.get("due_date"))))
|
||||||
|
db.session.commit(); flash("Action créée.", "success"); return redirect(url_for("prevention.index"))
|
||||||
|
|
||||||
|
@prevention_bp.post("/register")
|
||||||
|
@login_required
|
||||||
|
def add_register():
|
||||||
|
db.session.add(SafetyRegisterEntry(entry_date=_date(request.form.get("entry_date")) or date.today(), entry_type=request.form.get("entry_type", "observation"), title=request.form.get("title", "").strip(), details=request.form.get("details"), location=request.form.get("location"), created_by_id=current_user.id))
|
||||||
|
db.session.commit(); flash("Entrée ajoutée au registre.", "success"); return redirect(url_for("prevention.index"))
|
||||||
|
|
@ -192,6 +192,7 @@
|
||||||
<li><a class="dropdown-item" href="{{ url_for('planning.admin_tasks') }}"><i class="bi bi-clipboard"></i> Tâches administratives</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('planning.admin_tasks') }}"><i class="bi bi-clipboard"></i> Tâches administratives</a></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('planning.rules') }}"><i class="bi bi-shield-check"></i> Règles d'accès</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('planning.rules') }}"><i class="bi bi-shield-check"></i> Règles d'accès</a></li>
|
||||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('prevention.index') }}"><i class="bi bi-shield-check"></i> Prévention / DUERP</a></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('trainings.index') }}"><i class="bi bi-mortarboard"></i> Formations</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('trainings.index') }}"><i class="bi bi-mortarboard"></i> Formations</a></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('trainings.my_trainings') }}"><i class="bi bi-calendar-check"></i> Mes formations</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('trainings.my_trainings') }}"><i class="bi bi-calendar-check"></i> Mes formations</a></li>
|
||||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||||
|
|
|
||||||
9
app_new/templates/prevention/index.html
Normal file
9
app_new/templates/prevention/index.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.html" %}{% block title %}Prévention — GMAO{% endblock %}{% block content %}
|
||||||
|
<div class="d-flex justify-content-between"><div><h1 class="h3"><i class="bi bi-shield-check"></i> Assistant de prévention</h1><p class="text-muted">Agents, temps dédié, DUERP, habilitations et plan d’actions.</p></div><div class="text-end"><strong>{{ (minutes / 60)|round(1) }} h / {{ (target / 60)|round(1) }} h</strong><div class="progress" style="width:220px"><div class="progress-bar" style="width:{{ [100, minutes * 100 / target]|min }}%">Objectif 10 %</div></div></div></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-lg-6"><div class="card"><div class="card-header">Temps de prévention</div><div class="card-body"><form method="post" action="{{ url_for('prevention.add_time') }}" class="row g-2"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="col-4"><input type="date" name="work_date" class="form-control" required></div><div class="col-3"><input type="number" min="1" name="duration_minutes" class="form-control" placeholder="Minutes" required></div><div class="col"><input name="activity" class="form-control" placeholder="Activité" required></div><div class="col-auto"><button class="btn btn-primary">Ajouter</button></div></form><table class="table table-sm mt-3"><tbody>{% for x in logs %}<tr><td>{{ x.work_date|date_fmt }}</td><td>{{ x.activity }}</td><td>{{ x.duration_minutes }} min</td></tr>{% endfor %}</tbody></table></div></div></div>
|
||||||
|
<div class="col-lg-6"><div class="card"><div class="card-header">Agents et habilitations ({{ staff|length }})</div><div class="card-body"><form method="post" action="{{ url_for('prevention.add_staff') }}" class="row g-2 mb-3"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="col"><input name="first_name" class="form-control" placeholder="Prénom" required></div><div class="col"><input name="last_name" class="form-control" placeholder="Nom" required></div><div class="col"><input name="function" class="form-control" placeholder="Fonction"></div><div class="col-auto"><button class="btn btn-primary">Ajouter</button></div></form><form method="post" action="{{ url_for('prevention.add_authorization') }}" class="row g-2"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="col"><select name="staff_id" class="form-select" required><option value="">Agent…</option>{% for x in staff %}<option value="{{ x.id }}">{{ x.full_name }}</option>{% endfor %}</select></div><div class="col"><input name="name" class="form-control" placeholder="Habilitation / formation" required></div><div class="col"><input type="date" name="expires_on" class="form-control"></div><div class="col-auto"><button class="btn btn-outline-primary">Ajouter</button></div></form></div></div></div>
|
||||||
|
<div class="col-lg-6"><div class="card"><div class="card-header">DUERP — risques</div><div class="card-body"><form method="post" action="{{ url_for('prevention.add_risk') }}" class="row g-2"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="col-5"><input name="work_unit" class="form-control" placeholder="Unité de travail" required></div><div class="col-7"><input name="hazard" class="form-control" placeholder="Danger / situation" required></div><div class="col-3"><input type="number" min="1" max="4" name="severity" class="form-control" placeholder="Gravité"></div><div class="col-3"><input type="number" min="1" max="4" name="probability" class="form-control" placeholder="Probabilité"></div><div class="col"><input name="control_measures" class="form-control" placeholder="Mesures existantes"></div><div class="col-auto"><button class="btn btn-primary">Ajouter</button></div></form><table class="table table-sm mt-3"><tbody>{% for x in risks %}<tr><td>{{ x.work_unit }}</td><td>{{ x.hazard }}</td><td><span class="badge text-bg-{{ 'danger' if x.score >= 9 else 'warning' if x.score >= 4 else 'success' }}">{{ x.score }}</span></td></tr>{% endfor %}</tbody></table></div></div></div>
|
||||||
|
<div class="col-lg-6"><div class="card"><div class="card-header">Plan d’actions</div><div class="card-body"><form method="post" action="{{ url_for('prevention.add_action') }}" class="row g-2"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="col"><input name="title" class="form-control" placeholder="Action" required></div><div class="col-3"><input name="owner" class="form-control" placeholder="Pilote"></div><div class="col-3"><input type="date" name="due_date" class="form-control"></div><div class="col-auto"><button class="btn btn-primary">Ajouter</button></div></form><table class="table table-sm mt-3"><tbody>{% for x in actions %}<tr><td>{{ x.title }}</td><td>{{ x.owner or '—' }}</td><td>{{ x.due_date|date_fmt }}</td><td>{{ x.status }}</td></tr>{% endfor %}</tbody></table></div></div></div>
|
||||||
|
<div class="col-12"><div class="card"><div class="card-header">Registre sécurité / incidents / exercices</div><div class="card-body"><form method="post" action="{{ url_for('prevention.add_register') }}" class="row g-2"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="col-2"><input type="date" name="entry_date" class="form-control" required></div><div class="col-2"><select name="entry_type" class="form-select"><option>observation</option><option>incident</option><option>presque_accident</option><option>exercice</option><option>verification</option></select></div><div class="col"><input name="title" class="form-control" placeholder="Objet" required></div><div class="col"><input name="location" class="form-control" placeholder="Lieu"></div><div class="col-auto"><button class="btn btn-primary">Ajouter</button></div></form><table class="table table-sm mt-3"><tbody>{% for x in entries %}<tr><td>{{ x.entry_date|date_fmt }}</td><td>{{ x.entry_type }}</td><td>{{ x.title }}</td><td>{{ x.location }}</td></tr>{% endfor %}</tbody></table></div></div></div>
|
||||||
|
</div>{% endblock %}
|
||||||
16
migrations/versions/f7a1b2c3d4e5_add_prevention_registers.py
Normal file
16
migrations/versions/f7a1b2c3d4e5_add_prevention_registers.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""Ajoute les registres de prévention et le DUERP.
|
||||||
|
|
||||||
|
Revision ID: f7a1b2c3d4e5
|
||||||
|
Revises: e6f0a1b2c3d4
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
revision="f7a1b2c3d4e5"; down_revision="e6f0a1b2c3d4"; branch_labels=None; depends_on=None
|
||||||
|
def upgrade():
|
||||||
|
op.create_table("prevention_work_logs",sa.Column("id",sa.Integer(),primary_key=True),sa.Column("user_id",sa.Integer(),sa.ForeignKey("users.id"),nullable=False),sa.Column("work_date",sa.Date(),nullable=False),sa.Column("duration_minutes",sa.Integer(),nullable=False),sa.Column("activity",sa.String(255),nullable=False),sa.Column("notes",sa.Text()),sa.Column("created_at",sa.DateTime())); op.create_index("ix_prevention_work_logs_user_id","prevention_work_logs",["user_id"]); op.create_index("ix_prevention_work_logs_work_date","prevention_work_logs",["work_date"])
|
||||||
|
op.create_table("staff_authorizations",sa.Column("id",sa.Integer(),primary_key=True),sa.Column("staff_id",sa.Integer(),sa.ForeignKey("staff.id"),nullable=False),sa.Column("name",sa.String(160),nullable=False),sa.Column("issued_on",sa.Date()),sa.Column("expires_on",sa.Date()),sa.Column("issuer",sa.String(160)),sa.Column("notes",sa.Text())); op.create_index("ix_staff_authorizations_staff_id","staff_authorizations",["staff_id"]); op.create_index("ix_staff_authorizations_expires_on","staff_authorizations",["expires_on"])
|
||||||
|
op.create_table("risk_assessments",sa.Column("id",sa.Integer(),primary_key=True),sa.Column("work_unit",sa.String(160),nullable=False),sa.Column("hazard",sa.String(255),nullable=False),sa.Column("exposed_people",sa.String(255)),sa.Column("severity",sa.Integer(),nullable=False),sa.Column("probability",sa.Integer(),nullable=False),sa.Column("control_measures",sa.Text()),sa.Column("reviewed_on",sa.Date()),sa.Column("is_active",sa.Boolean(),nullable=False))
|
||||||
|
op.create_table("prevention_actions",sa.Column("id",sa.Integer(),primary_key=True),sa.Column("title",sa.String(255),nullable=False),sa.Column("source_type",sa.String(40),nullable=False),sa.Column("risk_id",sa.Integer(),sa.ForeignKey("risk_assessments.id")),sa.Column("priority",sa.String(20),nullable=False),sa.Column("owner",sa.String(160)),sa.Column("due_date",sa.Date()),sa.Column("status",sa.String(30),nullable=False),sa.Column("evidence",sa.Text())); op.create_index("ix_prevention_actions_due_date","prevention_actions",["due_date"])
|
||||||
|
op.create_table("safety_register_entries",sa.Column("id",sa.Integer(),primary_key=True),sa.Column("entry_date",sa.Date(),nullable=False),sa.Column("entry_type",sa.String(50),nullable=False),sa.Column("title",sa.String(255),nullable=False),sa.Column("details",sa.Text()),sa.Column("location",sa.String(255)),sa.Column("follow_up",sa.Text()),sa.Column("created_by_id",sa.Integer(),sa.ForeignKey("users.id"))); op.create_index("ix_safety_register_entries_entry_date","safety_register_entries",["entry_date"])
|
||||||
|
def downgrade():
|
||||||
|
for table in ("safety_register_entries","prevention_actions","risk_assessments","staff_authorizations","prevention_work_logs"): op.drop_table(table)
|
||||||
31
tests/integration/test_prevention.py
Normal file
31
tests/integration/test_prevention.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
from datetime import date
|
||||||
|
from uuid import uuid4
|
||||||
|
from flask_login import login_user
|
||||||
|
from app_new.extensions import db
|
||||||
|
from app_new.core.models.prevention import PreventionAction, PreventionWorkLog, RiskAssessment
|
||||||
|
from app_new.core.models.user import User
|
||||||
|
from app_new.prevention.routes import add_risk, add_time, index
|
||||||
|
|
||||||
|
|
||||||
|
def test_prevention_dashboard_tracks_time_and_duerp(app):
|
||||||
|
with app.app_context():
|
||||||
|
user = User(username=f"prev-{uuid4().hex}", full_name="Prévention", role="assistant_prevention", password_hash="x")
|
||||||
|
db.session.add(user); db.session.commit()
|
||||||
|
with app.test_request_context(method="POST", data={"work_date": date.today().isoformat(), "duration_minutes": "90", "activity": "Visite sécurité"}):
|
||||||
|
login_user(user); assert add_time.__wrapped__().status_code == 302
|
||||||
|
with app.test_request_context(method="POST", data={"work_unit": "Entretien", "hazard": "Chute", "severity": "4", "probability": "3"}):
|
||||||
|
login_user(user); assert add_risk.__wrapped__().status_code == 302
|
||||||
|
assert PreventionWorkLog.query.filter_by(user_id=user.id).one().duration_minutes == 90
|
||||||
|
assert RiskAssessment.query.filter_by(work_unit="Entretien").one().score == 12
|
||||||
|
with app.test_request_context():
|
||||||
|
login_user(user); html = index.__wrapped__()
|
||||||
|
assert "Objectif 10 %" in html and "Visite sécurité" in html
|
||||||
|
|
||||||
|
|
||||||
|
def test_prevention_action_can_reference_a_risk(app):
|
||||||
|
with app.app_context():
|
||||||
|
risk = RiskAssessment(work_unit="Cuisine", hazard="Brûlure", severity=3, probability=2)
|
||||||
|
db.session.add(risk); db.session.flush()
|
||||||
|
action = PreventionAction(title="Installer une protection", risk=risk, owner="Gestionnaire")
|
||||||
|
db.session.add(action); db.session.commit()
|
||||||
|
assert action.risk.score == 6
|
||||||
Loading…
Reference in a new issue