2026-08-14 23:43:13 +02:00
|
|
|
from datetime import date, datetime, timedelta
|
2026-08-15 01:26:22 +02:00
|
|
|
from flask import Blueprint, flash, make_response, redirect, render_template, request, url_for
|
2026-08-14 23:43:13 +02:00
|
|
|
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
|
2026-08-15 01:26:22 +02:00
|
|
|
from app_new.core.services.planning_service import PlanningService
|
2026-08-14 23:43:13 +02:00
|
|
|
|
|
|
|
|
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()
|
2026-08-15 01:26:22 +02:00
|
|
|
available_minutes = 0
|
|
|
|
|
for offset in range(7):
|
|
|
|
|
hours = PlanningService.get_working_hours(monday + timedelta(days=offset), current_user.id)
|
|
|
|
|
if hours:
|
|
|
|
|
available_minutes += int((datetime.combine(date.min, hours[1]) - datetime.combine(date.min, hours[0])).total_seconds() / 60)
|
|
|
|
|
if hours[2] and hours[3]:
|
|
|
|
|
available_minutes -= int((datetime.combine(date.min, hours[3]) - datetime.combine(date.min, hours[2])).total_seconds() / 60)
|
|
|
|
|
target = round(available_minutes * 0.10)
|
|
|
|
|
logs = PreventionWorkLog.query.filter_by(user_id=current_user.id).order_by(PreventionWorkLog.work_date.desc()).limit(20).all()
|
|
|
|
|
return render_template("prevention/index.html", staff=Staff.query.filter_by(is_active=True).order_by(Staff.last_name).all(), logs=logs, 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, available_minutes=available_minutes)
|
2026-08-14 23:43:13 +02:00
|
|
|
|
|
|
|
|
@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():
|
2026-08-15 01:26:22 +02:00
|
|
|
severity = request.form.get("severity", type=int) or 1
|
|
|
|
|
probability = request.form.get("probability", type=int) or 1
|
|
|
|
|
work_unit = request.form.get("work_unit", "").strip()
|
|
|
|
|
hazard = request.form.get("hazard", "").strip()
|
|
|
|
|
if not work_unit or not hazard or severity not in range(1, 5) or probability not in range(1, 5):
|
|
|
|
|
flash("Unité, danger et cotations de 1 à 4 sont obligatoires.", "danger")
|
|
|
|
|
return redirect(url_for("prevention.index"))
|
|
|
|
|
db.session.add(RiskAssessment(work_unit=work_unit, hazard=hazard, exposed_people=request.form.get("exposed_people"), severity=severity, probability=probability, control_measures=request.form.get("control_measures"), reviewed_on=date.today()))
|
2026-08-14 23:43:13 +02:00
|
|
|
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"))
|
|
|
|
|
|
2026-08-21 12:12:23 +02:00
|
|
|
@prevention_bp.post("/action/<int:id>/status")
|
|
|
|
|
@login_required
|
|
|
|
|
def update_action_status(id):
|
|
|
|
|
action = PreventionAction.query.get_or_404(id)
|
|
|
|
|
allowed = {"a_faire", "validee", "planifiee", "en_cours", "verifiee", "cloturee", "refusee"}
|
|
|
|
|
status = request.form.get("status")
|
|
|
|
|
if status not in allowed:
|
|
|
|
|
flash("Statut de prévention invalide.", "danger")
|
|
|
|
|
else:
|
|
|
|
|
action.status = status
|
|
|
|
|
db.session.commit()
|
|
|
|
|
flash("Action de prévention mise à jour.", "success")
|
|
|
|
|
return redirect(url_for("prevention.index"))
|
|
|
|
|
|
2026-08-14 23:43:13 +02:00
|
|
|
@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"))
|
2026-08-15 01:26:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@prevention_bp.get('/duerp.csv')
|
|
|
|
|
@login_required
|
|
|
|
|
def export_duerp():
|
|
|
|
|
"""Exporte le registre DUERP exploitable dans un tableur."""
|
|
|
|
|
import csv
|
|
|
|
|
import io
|
|
|
|
|
output = io.StringIO()
|
|
|
|
|
writer = csv.writer(output, delimiter=';')
|
|
|
|
|
writer.writerow(['Unité de travail', 'Danger', 'Personnes exposées', 'Gravité', 'Probabilité', 'Criticité', 'Mesures existantes', 'Dernière revue'])
|
|
|
|
|
for risk in RiskAssessment.query.order_by(RiskAssessment.work_unit, RiskAssessment.hazard).all():
|
|
|
|
|
writer.writerow([risk.work_unit, risk.hazard, risk.exposed_people or '', risk.severity,
|
|
|
|
|
risk.probability, (risk.severity or 0) * (risk.probability or 0),
|
|
|
|
|
risk.control_measures or '', risk.reviewed_on.isoformat() if risk.reviewed_on else ''])
|
|
|
|
|
response = make_response('\ufeff' + output.getvalue())
|
|
|
|
|
response.headers['Content-Type'] = 'text/csv; charset=utf-8'
|
|
|
|
|
response.headers['Content-Disposition'] = 'attachment; filename=duerp.csv'
|
|
|
|
|
return response
|